RecyclerView和ListView一樣,都是做列表顯示View子項的控件,它比ListView更高效和自由。
解析RecycleView,Recycle View,意思就是該控件只管回收和顯示View子項,而對于如何顯示,顯示什么,它是不關心的,這給開發過程帶來了極大的便利,比如ListView只能作為單列的列表顯示,GridView將一個界面表格化,通常情況下GridView通過強制View子項的寬度來顯示,在橫豎屏切換時的效果很差。
而RecycleView可以實現:
ListView的功能
GridView的功能
橫向ListView的功能
橫向ScrollView的功能
瀑布流的功能
添加和刪除View子項
這些功能,非常強大,可以看出,它幾乎可以替代所有的動態布局控件。
這么強大的動態布局控件,得益于它的高度解耦,同樣,眾所周知,高度解耦,就意味著復雜度提升,相較于ListView、GridView等控件,RecycleView才實現過程是相對較復雜的。
RecyclerView的適配器需要繼承自RecyclerView.Adapter,在該適配器將要面向ViewHolder,也就是說,它內部已經實現了緩存復用。
實現LIstView功能
注意,RecyclerView是谷歌在support-v7包中添加的新組件,在開發的時候,請添加support.v7包以及recyclerview-v7包,在module的Project Structure中的Dependencies部分添加如下圖所示:
首先創建RecyclerViewDemo1,活動:RecyclerViewDemo.java,layout:activiy_main.xml。
簡單通過RecyclerView實現ListView的功能,那么首先就要理解ListView的實現過程。ListView是通過列表的的形式依次顯示一行行的數據,從編程的角度出發,就是一個LinnerLayout布局,方向為縱向,依次添加進數據,后通過Scrolling實現滾動屏幕。
再來對比RecyclerView,屏幕滾動部分,在RecyclerView中已經被實現好了,不需要關心,需要關心的就是布局,以及方向。
Java Code
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//設置RecyclerView的布局管理器,為了模仿ListView,
//這里設置為Vertical的LinnerLayout
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(OrientationHelper.VERTICAL);
recyclerView.setLayoutManager(manager);
上述代碼第5行,定義了一個LinearLayoutManager,傳入上下文為活動的this,第6行將布局管理器的方向設置為VERTICAL,第7行,將這個布局管理器傳遞給RecyclerView。
對比ListView,這個時候我們只是擁有了一個ListView實例,數據源,適配器,以及View子項都還沒有說明。
接下來,創建一個View子項,這個View子項可以顯示一張圖片,以及一段文字,可以使用一個TextView,當然為了自由度,將聲明兩個控件,一個TextView、一個ImageView,分別用來顯示文字和圖片。
子View還是通過xml文件來定義。
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行,分別定義了一個ImageView和一個TextView,第4行,將打下限定在了wrap_content,View子項的大小可控。
四大組成部分中,已經實現了兩部分,接下來是數據源,因為是一個Demo,這里數據源我們使用一個包含一個String和一個圖片Id的HashMap來實現。
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實例有了,數據源有了,View子項有了,只剩下適配器將這些東西串聯起來了。
RecyclerView的Adapter,通常使用的是自定義的,這是由RecyclerView高度解耦,高度自由決定的,高度解耦意味著編碼復雜度上升。
在創建Adapter的時候,繼承自RecyclerView.Adapter,不過在這之前,請先建立一個自定義的ViewHolder類,這是因為,在RecyclerView的編碼過程中,緩存部分的工作已經交由RecyclerView做好了,那么緩存什么,View子項是什么,需要程序員告知它。
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;
//自定義構造器,傳入上下文和數據源
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子項
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) {
//對View子項賦值
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子項真正建立連接
public MyViewHolder(View itemView) {
super(itemView);
img = (ImageView) itemView.findViewById(R.id.img_item);
textView = (TextView) itemView.findViewById(R.id.txv_item);
}
}
}
四大組成部分已經編寫完成,接下來,
myAdapter = new MyAdapter(this, imgHash);
recyclerView.setAdapter(myAdapter);
完成適配器配置。
來看一下現象如下圖所示:
和ListView的效果很像,已經將數據完整的顯示在活動中,但是仔細看,會發現,View子項和View子項之間沒有明顯的分隔。并沒有ListView中的分隔的效果。
對于RecyclerView來說,添加分隔線也是耦合事件,所以,當想要分隔線的時候,需要自己添加。需要添加recyclerView.addItemDecoration()方法添加分隔線。該方法的參數需要一個RecycleView.ItemDecoration實例,該類是一個abstract的虛類,要實現該類需要做自定義操作,谷歌并沒有提供默認的實現。
創建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;
//構造器,從參數離別中拿出要繪制的Drawable
public MyItemDecoration(Context context, int orientation) {
//參數列表
final TypedArray a = context.obtainStyledAttributes(ATTRS);
//參數列表0項
mDivider = a.getDrawable(0);
//使用完參數列表之后一定要做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子項
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子項
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));帶活動的onCreate方法中。
再看展示的效果圖,如下圖:
可以看到,已經有分隔線了,這個分隔線是我們在ATTRS中放入的第0個Drawable的id,我們知道它是來自android.R的,即系統集成的,使用該分隔線的一個好處就是,我們可以對其進行自定義。
在res下的drawable目錄下創建一個shap,來設置一個漸變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中添加一個android:listDivider子項,為listDivider賦值為定義好的drawable。
效果圖如下:
這樣的效果已經和ListView很像了,但是,在點擊對應的View子項的時候,會發現,是沒有效果的,這是因為RecyclerView中,并沒有和ListView一樣的OnItemClickLisener接口。
還是因為RecyclerView高度解耦的緣故,而自定義監聽將更加自由。在Adapter中添加一個接口,用以監聽按鈕事件。
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;
}
});
}
}
在調用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));
}
});
到此,點擊事件也已經注冊到活動中,點擊每個View子項都已經有相應。
但是,還有一個問題,點擊事件發生的時候,View子項沒有任何反應,位View子項添加一個background,在Drawable里邊頂一個selector。就可以實現點擊事件發生時改變背景顏色的功能。
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>
至此,一個仿照的ListView就被實現出來了,而且更具靈活性,也更生動。
在靈活性上,一個很重要的體現就是,可以任意的增加和刪除View子項。在上述的代碼中其實已經可以看見一部分了,在長按的監聽事件中,長按發生時,會將對應的View子項從列表中刪除。
Java Code
public void removeData(int position){
nameList.remove(position);
imgList.remove(position);
notifyItemRemoved(position);
notifyDataSetChanged();//必須添加
}
notifyItemRemoved方法即為刪除方法。同樣的,還有一個為notifyItemInser(int position)的方法,用以插入一個View子項。View子項也是可以移動的,方法為notifyItemMove(int fromPosition, int toPosition)。