當(dāng)前位置:首頁(yè) > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > RecyclerView 學(xué)習(xí)筆記(一)---實(shí)現(xiàn)ListView
RecyclerView和ListView一樣,都是做列表顯示View子項(xiàng)的控件,它比ListView更高效和自由。
解析RecycleView,Recycle View,意思就是該控件只管回收和顯示View子項(xiàng),而對(duì)于如何顯示,顯示什么,它是不關(guān)心的,這給開(kāi)發(fā)過(guò)程帶來(lái)了極大的便利,比如ListView只能作為單列的列表顯示,GridView將一個(gè)界面表格化,通常情況下GridView通過(guò)強(qiáng)制View子項(xiàng)的寬度來(lái)顯示,在橫豎屏切換時(shí)的效果很差。
而RecycleView可以實(shí)現(xiàn):
ListView的功能
GridView的功能
橫向ListView的功能
橫向ScrollView的功能
瀑布流的功能
添加和刪除View子項(xiàng)
這些功能,非常強(qiáng)大,可以看出,它幾乎可以替代所有的動(dòng)態(tài)布局控件。
這么強(qiáng)大的動(dòng)態(tài)布局控件,得益于它的高度解耦,同樣,眾所周知,高度解耦,就意味著復(fù)雜度提升,相較于ListView、GridView等控件,RecycleView才實(shí)現(xiàn)過(guò)程是相對(duì)較復(fù)雜的。
RecyclerView的適配器需要繼承自RecyclerView.Adapter,在該適配器將要面向ViewHolder,也就是說(shuō),它內(nèi)部已經(jīng)實(shí)現(xiàn)了緩存復(fù)用。
實(shí)現(xiàn)LIstView功能
注意,RecyclerView是谷歌在support-v7包中添加的新組件,在開(kāi)發(fā)的時(shí)候,請(qǐng)?zhí)砑觭upport.v7包以及recyclerview-v7包,在module的Project Structure中的Dependencies部分添加如下圖所示:
首先創(chuàng)建RecyclerViewDemo1,活動(dòng):RecyclerViewDemo.java,layout:activiy_main.xml。
簡(jiǎn)單通過(guò)RecyclerView實(shí)現(xiàn)ListView的功能,那么首先就要理解ListView的實(shí)現(xiàn)過(guò)程。ListView是通過(guò)列表的的形式依次顯示一行行的數(shù)據(jù),從編程的角度出發(fā),就是一個(gè)LinnerLayout布局,方向?yàn)榭v向,依次添加進(jìn)數(shù)據(jù),后通過(guò)Scrolling實(shí)現(xiàn)滾動(dòng)屏幕。
再來(lái)對(duì)比RecyclerView,屏幕滾動(dòng)部分,在RecyclerView中已經(jīng)被實(shí)現(xiàn)好了,不需要關(guān)心,需要關(guān)心的就是布局,以及方向。
Java Code
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//設(shè)置RecyclerView的布局管理器,為了模仿ListView,
//這里設(shè)置為Vertical的LinnerLayout
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(OrientationHelper.VERTICAL);
recyclerView.setLayoutManager(manager);
上述代碼第5行,定義了一個(gè)LinearLayoutManager,傳入上下文為活動(dòng)的this,第6行將布局管理器的方向設(shè)置為VERTICAL,第7行,將這個(gè)布局管理器傳遞給RecyclerView。
對(duì)比ListView,這個(gè)時(shí)候我們只是擁有了一個(gè)ListView實(shí)例,數(shù)據(jù)源,適配器,以及View子項(xiàng)都還沒(méi)有說(shuō)明。
接下來(lái),創(chuàng)建一個(gè)View子項(xiàng),這個(gè)View子項(xiàng)可以顯示一張圖片,以及一段文字,可以使用一個(gè)TextView,當(dāng)然為了自由度,將聲明兩個(gè)控件,一個(gè)TextView、一個(gè)ImageView,分別用來(lái)顯示文字和圖片。
子View還是通過(guò)xml文件來(lái)定義。
XML Code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="//schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg">
<ImageView
android:id="@+id/img_item"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/txv_item"
android:gravity="center_vertical|center_horizontal"
android:textSize="21sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
上述代碼第7行和第11行,分別定義了一個(gè)ImageView和一個(gè)TextView,第4行,將打下限定在了wrap_content,View子項(xiàng)的大小可控。
四大組成部分中,已經(jīng)實(shí)現(xiàn)了兩部分,接下來(lái)是數(shù)據(jù)源,因?yàn)槭且粋(gè)Demo,這里數(shù)據(jù)源我們使用一個(gè)包含一個(gè)String和一個(gè)圖片Id的HashMap來(lái)實(shí)現(xiàn)。
Java Code
public HashMap
{
HashMap
imgHash.put("0000", R.drawable.zero);
imgHash.put("1111", R.drawable.one);
imgHash.put("2222", R.drawable.two);
imgHash.put("3333", R.drawable.three);
imgHash.put("4444", R.drawable.four);
imgHash.put("5555", R.drawable.five);
imgHash.put("6666", R.drawable.six);
imgHash.put("7777", R.drawable.seven);
imgHash.put("8888", R.drawable.eight);
imgHash.put("9999", R.drawable.nine);
imgHash.put("aaaa", R.drawable.a);
imgHash.put("bbbb", R.drawable.b);
imgHash.put("cccc", R.drawable.c);
imgHash.put("dddd", R.drawable.d);
imgHash.put("eeee", R.drawable.e);
imgHash.put("ffff", R.drawable.f);
return imgHash;
}
RecyclerView實(shí)例有了,數(shù)據(jù)源有了,View子項(xiàng)有了,只剩下適配器將這些東西串聯(lián)起來(lái)了。
RecyclerView的Adapter,通常使用的是自定義的,這是由RecyclerView高度解耦,高度自由決定的,高度解耦意味著編碼復(fù)雜度上升。
在創(chuàng)建Adapter的時(shí)候,繼承自RecyclerView.Adapter,不過(guò)在這之前,請(qǐng)先建立一個(gè)自定義的ViewHolder類,這是因?yàn)椋赗ecyclerView的編碼過(guò)程中,緩存部分的工作已經(jīng)交由RecyclerView做好了,那么緩存什么,View子項(xiàng)是什么,需要程序員告知它。
Java Code
//自定義的ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder{
public ImageView img;
public TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.img_item);
textView = (TextView) itemView.findViewById(R.id.txv_item);
}
}
編寫自定義適配器。
Java Code
public class MyAdapter extends RecyclerView.Adapter
private Context context;
private List
private List
private LayoutInflater inflater;
private OnItemClickListener onItemClickListener;
//自定義構(gòu)造器,傳入上下文和數(shù)據(jù)源
public MyAdapter(Context context, HashMap
this.context = context;
imgList = new ArrayList<>();
nameList = new ArrayList<>();
for (Object o : imgHash.entrySet()){
Map.Entry entry = (Map.Entry) o;
String name = (String) entry.getKey();
int img = (int) entry.getValue();
nameList.add(name);
imgList.add(nameList.indexOf(name), img);
}
inflater = LayoutInflater.from(context);
}
//面向ViewHolder編程
@Override
public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
//View子項(xiàng)
View view = inflater.inflate(R.layout.item_recycler, viewGroup, false);
return new MyViewHolder(view);
}
//面向ViewHolder編程
@Override
public void onBindViewHolder(MyViewHolder myViewHolder,
@SuppressLint("RecyclerView") final int i) {
//對(duì)View子項(xiàng)賦值
myViewHolder.img.setImageResource(imgList.get(i));
myViewHolder.textView.setText(nameList.get(i));
}
@Override
public int getItemCount() {
return nameList.size();
}
//自定義的ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder{
public ImageView img;
public TextView textView;
//和View子項(xiàng)真正建立連接
public MyViewHolder(View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.img_item);
textView = (TextView) itemView.findViewById(R.id.txv_item);
}
}
}
四大組成部分已經(jīng)編寫完成,接下來(lái),
myAdapter = new MyAdapter(this, imgHash);
recyclerView.setAdapter(myAdapter);
完成適配器配置。
來(lái)看一下現(xiàn)象如下圖所示:
和ListView的效果很像,已經(jīng)將數(shù)據(jù)完整的顯示在活動(dòng)中,但是仔細(xì)看,會(huì)發(fā)現(xiàn),View子項(xiàng)和View子項(xiàng)之間沒(méi)有明顯的分隔。并沒(méi)有ListView中的分隔的效果。
對(duì)于RecyclerView來(lái)說(shuō),添加分隔線也是耦合事件,所以,當(dāng)想要分隔線的時(shí)候,需要自己添加。需要添加recyclerView.addItemDecoration()方法添加分隔線。該方法的參數(shù)需要一個(gè)RecycleView.ItemDecoration實(shí)例,該類是一個(gè)abstract的虛類,要實(shí)現(xiàn)該類需要做自定義操作,谷歌并沒(méi)有提供默認(rèn)的實(shí)現(xiàn)。
創(chuàng)建MyItemDecoration繼承自RecyclerView.ItemDecoration,觀察該類,需要重寫其onDraw方法,getItemOffsets方法。
Java Code
package com.hqyj.dev.recyclerviewdemo1;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by jiyangkang on 2016/7/15 0015.
*/
public class MyItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
private static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
private static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
//構(gòu)造器,從參數(shù)離別中拿出要繪制的Drawable
public MyItemDecoration(Context context, int orientation) {
//參數(shù)列表
final TypedArray a = context.obtainStyledAttributes(ATTRS);
//參數(shù)列表0項(xiàng)
mDivider = a.getDrawable(0);
//使用完參數(shù)列表之后一定要做recycle()
a.recycle();
try {
setmOrientation(orientation);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//選擇繪制什么樣的分隔線
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == VERTICAL_LIST) {
drawVertiacl(c, parent, state);
} else {
drawHorizontal(c, parent, state);
}
}
public void setmOrientation(int orientation) throws IllegalAccessException {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalAccessException("invalid orientation");
}
mOrientation = orientation;
}
//縱向排列的View子項(xiàng)
public void drawVertiacl(Canvas c, RecyclerView parent, RecyclerView.State state) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
//橫向排列的View子項(xiàng)
public void drawHorizontal(Canvas c, RecyclerView parent, RecyclerView.State state) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
自定義ItemDecoration寫好厚,就可以將其添加到RecyclerView中了。
添加recyclerView.addItemDecoration(new MyItemDecoration(this, LinearLayoutManager.VERTICAL));帶活動(dòng)的onCreate方法中。
再看展示的效果圖,如下圖:
可以看到,已經(jīng)有分隔線了,這個(gè)分隔線是我們?cè)贏TTRS中放入的第0個(gè)Drawable的id,我們知道它是來(lái)自android.R的,即系統(tǒng)集成的,使用該分隔線的一個(gè)好處就是,我們可以對(duì)其進(jìn)行自定義。
在res下的drawable目錄下創(chuàng)建一個(gè)shap,來(lái)設(shè)置一個(gè)漸變drawable.
XML Code
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="//schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:centerColor="#ff00ff00"
android:startColor="#ff0000ff"
android:endColor="#ffff0000"/>
<size android:height="2dp"/>
</shape>
然后在styles.xml中為工程的AppTheme中添加一個(gè)android:listDivider子項(xiàng),為listDivider賦值為定義好的drawable。
效果圖如下:
這樣的效果已經(jīng)和ListView很像了,但是,在點(diǎn)擊對(duì)應(yīng)的View子項(xiàng)的時(shí)候,會(huì)發(fā)現(xiàn),是沒(méi)有效果的,這是因?yàn)镽ecyclerView中,并沒(méi)有和ListView一樣的OnItemClickLisener接口。
還是因?yàn)镽ecyclerView高度解耦的緣故,而自定義監(jiān)聽(tīng)將更加自由。在Adapter中添加一個(gè)接口,用以監(jiān)聽(tīng)按鈕事件。
Java Code
public interface OnItemClickListener
{
void onClick(int position, String name, int imgSource);
void onLongClick(int position, String name, int imgSource);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener)
{
this.onItemClickListener = onItemClickListener;
}
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, @SuppressLint("RecyclerView") final int i)
{
myViewHolder.img.setImageResource(imgList.get(i));
myViewHolder.textView.setText(nameList.get(i));
if (onItemClickListener != null)
{
myViewHolder.itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
onItemClickListener.onClick(i, nameList.get(i), imgList.get(i));
}
});
myViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View view)
{
//onItemClickListener.onLongClick(i, nameList.get(i), imgList.get(i));
//removeData(i);
return false;
}
});
}
}
在調(diào)用Adapter的部分添加
Java Code
myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener()
{
@SuppressLint("DefaultLocale")
@Override
public void onClick(int position, String name, int imgSource)
{
textView.setText(String.format("%d is clicked, %s is contained", position, name));
}
@SuppressLint("DefaultLocale")
@Override
public void onLongClick(int position, String name, int imgSource)
{
textView.setText(String.format("%d is Long clicked, %s is contained", position, name));
}
});
到此,點(diǎn)擊事件也已經(jīng)注冊(cè)到活動(dòng)中,點(diǎn)擊每個(gè)View子項(xiàng)都已經(jīng)有相應(yīng)。
但是,還有一個(gè)問(wèn)題,點(diǎn)擊事件發(fā)生的時(shí)候,View子項(xiàng)沒(méi)有任何反應(yīng),位View子項(xiàng)添加一個(gè)background,在Drawable里邊頂一個(gè)selector。就可以實(shí)現(xiàn)點(diǎn)擊事件發(fā)生時(shí)改變背景顏色的功能。
XML Code
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="//schemas.android.com/apk/res/android" >
<item android:state_pressed="true"
android:drawable="@color/colorBg_press"/>
<item android:drawable="@color/colorBg_default"/>
</selector>
至此,一個(gè)仿照的ListView就被實(shí)現(xiàn)出來(lái)了,而且更具靈活性,也更生動(dòng)。
在靈活性上,一個(gè)很重要的體現(xiàn)就是,可以任意的增加和刪除View子項(xiàng)。在上述的代碼中其實(shí)已經(jīng)可以看見(jiàn)一部分了,在長(zhǎng)按的監(jiān)聽(tīng)事件中,長(zhǎng)按發(fā)生時(shí),會(huì)將對(duì)應(yīng)的View子項(xiàng)從列表中刪除。
Java Code
public void removeData(int position){
nameList.remove(position);
imgList.remove(position);
notifyItemRemoved(position);
notifyDataSetChanged();//必須添加
}
notifyItemRemoved方法即為刪除方法。同樣的,還有一個(gè)為notifyItemInser(int position)的方法,用以插入一個(gè)View子項(xiàng)。View子項(xiàng)也是可以移動(dòng)的,方法為notifyItemMove(int fromPosition, int toPosition)。