1. 程式人生 > >RecyclerView 教程,全面瞭解各種使用方法

RecyclerView 教程,全面瞭解各種使用方法

RecyclerView 在2014年就已經出來了,15年的時候有了解一下,但是專案中一直沒用上,最近看到,發現RecyclerView 出現了很多拓展,它的出現就是為了代替ListView、GridView。所以介紹一下RecyclerView該如何使用,及梳理一下這些拓展應該怎麼用,是個什麼效果。

RecyclerView

RecyclerView 比 ListView 更高階且更具靈活性。 它是一個用於顯示龐大資料集的容器,可通過保持有限數量的檢視進行非常有效的滾動操作。 如果您有資料集合,其中的元素將因使用者操作或網路事件而在執行時發生改變,請使用 RecyclerView 。

從它類名上看,RecyclerView代表的意義是,我只管Recycler View,也就是說RecyclerView只管回收與複用View,其他的你可以自己去設定。可以看出其高度的解耦,給予你充分的定製自由(所以你才可以輕鬆的通過這個控制元件實現ListView,GirdView,瀑布流等效果)。

在ListView中 改變列表某一個item資料,然後重新整理列表,會回到最頂部,而RecyclerView可以保持原來滑動的位置不變。

要實現一個RecyclerView,會接觸到它的幾個小夥伴,其中1、2是必須的。剩下的3、4、5三項,可以讓RecyclerView更好看、效果更好。

  1. 想要控制其item們的排列方式,請使用佈局管理器LayoutManager

  2. 如果要建立一個介面卡,請使用RecyclerView.Adapter

  3. 想要控制Item間的間隔,請使用RecyclerView.ItemDecoration

  4. 想要控制Item增刪的動畫,請使用RecyclerView.ItemAnimator

  5. CardView 擴充套件 FrameLayout 類並讓您能夠顯示卡片內的資訊,這些資訊在整個平臺中擁有一致的呈現方式。CardView 小部件可擁有陰影和圓角。

如果要使用 RecyclerView 小部件,必須指定一個Adapter和一個LayoutManager。

在下面會詳解這些類的使用

這裡寫圖片描述

非常簡單的一個示例

這裡先給出一個簡單的例子,感受一下怎麼樣使用RecyclerView

1、首先要用這個控制元件,你需要在gradle檔案中新增包的引用

compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'

2、 然後是在XML檔案用使用RecyclerView

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true"/>

3、在Activity中設定它

public class MainActivity extends ActionBarActivity {
    @InjectView(R.id.recycler_view)
    RecyclerView mRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//這裡用線性顯示 類似於listview
//        mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//這裡用線性宮格顯示 類似於grid view
//        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//這裡用線性宮格顯示 類似於瀑布流
        mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

4、Adapter 的Item的xml程式碼,使用CardView

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:id="@+id/cv_item"
    card_view:cardCornerRadius="4dp">

    <TextView
        android:id="@+id/text_view"
        android:padding="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</android.support.v7.widget.CardView>

5、然後是介面卡程式碼

public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {
    private final LayoutInflater mLayoutInflater;
    private final Context mContext;
    private String[] mTitles;

    public NormalRecyclerViewAdapter(Context context) {
        mTitles = context.getResources().getStringArray(R.array.titles);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
    }

    @Override
    public void onBindViewHolder(NormalTextViewHolder holder, int position) {
        holder.mTextView.setText(mTitles[position]);
    }

    @Override
    public int getItemCount() {
        return mTitles == null ? 0 : mTitles.length;
    }

    public static class NormalTextViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;

        NormalTextViewHolder(View view) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }
}

至此一個簡單的RecyclerView就這樣很乖巧的趴在螢幕上,有圖有真相:

LinearLayoutManager樣式:
這裡寫圖片描述

GridLayoutManager樣式:

這裡寫圖片描述

下面來圍繞這個示例,對RecyclerView的其他小夥伴進行介紹:

二、LayoutManager

佈局管理器,通過設定不同的佈局管理器,來控制這些Item的排列方式。

RecyclerView提供的佈局管理器:

  • LinearLayoutManager 以垂直或水平滾動列表方式顯示專案。
  • GridLayoutManager 在網格中顯示專案。
  • StaggeredGridLayoutManager 在分散對齊網格中顯示專案。

使用這個函式來設定mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

三、Adapter

給recycleView提供資料的類,使用方法如下

 mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));

可以看到資料介面卡與BaseAdapter比較發生了相當大的變化,主要有3個方法:

getItemCount() 獲取總的條目數
onCreateViewHolder() 建立ViewHolder
onBindViewHolder() 將資料繫結至ViewHolder

可見,RecyclerView對ViewHolder也進行了一定的封裝,但是如果你仔細觀察,你會發出一個疑問,ListView裡面有個getView,它返回View為Item的佈局,那麼RecyclerView這個Item的樣子在哪控制?

其實是這樣的,我們建立的ViewHolder必須繼承RecyclerView.ViewHolder,這個RecyclerView.ViewHolder構造時必須傳入一個View,這個View相當於我們ListView getView中的convertView (即:inflate的item佈局需要傳入)。

還有一點,ListView中convertView是複用的,在RecyclerView中,是把ViewHolder類作為快取的單位了,然後convertView作為ViewHolder的成員變數保持在ViewHolder中,也就是說,假設螢幕顯示10個條目,則會建立10個ViewHolder快取起來,每次複用的是ViewHolder,所以他把getView這個方法變為了onCreateViewHolder。

可以對Adapter進行一些個性化操作,實現不同的功能,下面給出兩個示例:

瀑布式佈局

ok,接下來準備看大招,如果讓你去實現個瀑布流,最起碼不是那麼隨意就可以實現的吧?但是,如果使用RecyclerView,分分鐘的事。
那麼如何實現?還是使用StaggeredGridLayoutManage,只需要在Adapter的onBindViewHolder()為我們的item設定個隨機的高度,下面僅給出Adapter的程式碼(下面會給出全部工程程式碼):

public class WaterpallStaggeredAdapter extends
        RecyclerView.Adapter<WaterpallStaggeredAdapter.MyViewHolder> {

    private List<String> mDatas;
    private LayoutInflater mInflater;

    private List<Integer> mHeights;

    public interface OnItemClickListener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    private OnItemClickListener mOnItemClickListener;

    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }

    public WaterpallStaggeredAdapter(Context context, List<String> datas) {
        mInflater = LayoutInflater.from(context);
        mDatas = datas;

        mHeights = new ArrayList<Integer>();
        for (int i = 0; i < mDatas.size(); i++) {
            mHeights.add((int) (100 + Math.random() * 300));
        }
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyViewHolder holder = new MyViewHolder(mInflater.inflate(
                R.layout.item_staggered_home, parent, false));
        return holder;
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        LayoutParams lp = holder.tv.getLayoutParams();
        lp.height = mHeights.get(position);

        holder.tv.setLayoutParams(lp);
        holder.tv.setText(mDatas.get(position));

        // 如果設定了回撥,則設定點選事件
        if (mOnItemClickListener != null) {
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemClick(holder.itemView, pos);
                }
            });

            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemLongClick(holder.itemView, pos);
                    removeData(pos);
                    return false;
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    public void addData(int position) {
        mDatas.add(position, "Insert One");
        mHeights.add((int) (100 + Math.random() * 300));
        notifyItemInserted(position);
    }

    public void removeData(int position) {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }

    class MyViewHolder extends ViewHolder {

        TextView tv;

        public MyViewHolder(View view) {
            super(view);
            tv = (TextView) view.findViewById(R.id.id_num);

        }
    }
}

看下效果圖:

這裡寫圖片描述

CursorAdapter(使用LoaderManager和CursorLoader實現)

如果你用RecyclerView,你會發現CursorAdapter這個類沒有了,既然沒有了,那我們就自己仿照著ListView的CursorAdapter類來實現,具體的程式碼沒什麼大的出入,無非就是註冊兩個觀察者去監聽資料庫資料的變化。

在recycleView中實現CursorAdapter,有兩個地方需要注意一下,一個就是hasStableIds() 這個方法RecyclerView.Adapter中不能複寫父類的方法,需要在初始化的時候呼叫setHasStableIds(true); 來完成相同功能,第二個就是notifyDataSetInvalidated() 這個方法沒有,統一修改成notifyDataSetChanged() 方法即可。

關於RecyclerView的Adapter 各種解決方案

四、ItemDecoration 新增分割線

使用方法:

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));

ItemDecoration類很好的實現了RecyclerView新增分割線(當使用LayoutManager為LinearLayoutManager時)。 該實現類可以看到通過讀取系統主題中的 Android.R.attr.listDivider作為Item間的分割線,並且支援橫向和縱向。

該分割線是系統預設的,你可以在theme.xml中找到該屬性的使用情況。

在styles.xml找使用的android:listDivider的xml——shape_divider,你也可以對其進行自定義

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="android:listDivider">@drawable/shape_divider</item>
    </style>

</resources>

下面給出DividerItemDecoration的程式碼

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    private Drawable mDivider;
    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        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);
            android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
            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);
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        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, int itemPosition, RecyclerView parent) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}

來看看使用分割線和沒有使用分割線的區別:

這裡寫圖片描述這裡寫圖片描述

五、ItemAnimator

刪除和新增的動畫效果

ItemAnimator也是一個抽象類,好在系統為我們提供了一種預設的實現類

藉助預設的實現,當Item新增和移除的時候,新增動畫效果很簡單:

// 設定item動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

系統為我們提供了一個預設的實現,我們為我們的瀑布流新增以上一行程式碼,效果為:

這裡寫圖片描述

如果是GridLayoutManager呢?動畫效果為:
這裡寫圖片描述

六、CardView

使用方法:在item的xml佈局檔案中,直接使用CardView即可。

CardView 擴充套件 FrameLayout 類並讓您能夠顯示卡片內的資訊,這些資訊在整個平臺中擁有一致的呈現方式。CardView 小部件可擁有陰影和圓角。

如果要使用陰影建立卡片,請使用 card_view:cardElevation 屬性。CardView 在 Android 5.0(API 級別 21)及更高版本中使用真實高度與動態陰影,而在早期的 Android 版本中則返回程式設計陰影實現。

使用這些屬性自定義 CardView 小部件的外觀:

  • 如果要在您的佈局中設定圓角半徑,請使用 card_view:cardCornerRadius 屬性。
  • 如果要在您的程式碼中設定圓角半徑,請使用 CardView.setRadius 方法。
  • 如果要設定卡片的背景顏色,請使用 card_view:cardBackgroundColor 屬性。

CardView示例圖:
這裡寫圖片描述

使用CardView很簡單,只需要在xml,建立CardView佈局,見上面示例的第4點,然後在adapter中使用這個xml即可。

原始碼:

參考:
上面圖片都是來自,參考部落格中,給出的程式碼實現和圖片有些許差異。

關注我的公眾號,輕鬆瞭解和學習更多技術
這裡寫圖片描述