Android小白之RecyclerView的基本用法與進階

前言

recyclerView是Android 5.0 materials design 中的組件之一,是一個用來取代listView的SDK,它的靈活性與可替代性比listview更好,原理和listView相似,都是僅僅維護少量的view來加載展示大量的數(shù)據(jù)集。下面就開始對它進行介紹。介紹之前,先將實現(xiàn)效果展示出來:

GIF.gif

使用recyclerView的好處:

recyclerView封裝了viewHolder的回收復用機制,也就是說recyclerView標準化了viewHolder,編寫Adapter面向的是viewHolder而不再是view了,復用的邏輯被封裝了,寫起來也就更方便了。recyclerView提供了一種插拔式的體驗,高度的解耦,異常的靈活,針對一個Item的顯示RecylerView專門抽取出了相應(yīng)的類,來控制Item的顯示,使其的擴展性非常強。它還可以實現(xiàn)多種效果,例如你想要橫向或者縱向的控制滑動列表,可以通過LinearLayoutManager這個類去控制,類似于GridView的展示效果可以用GridLayoutManager這個類,瀑布流的效果可以通過staggeredGridLayoutManager實現(xiàn),另外你想控制Item的分隔線,可以通過繼承RecylerView的ItemDecoration這個類,控制Item增刪的動畫,可以通過ItemAnimator這個類進行控制,然后針對自己的業(yè)務(wù)需求去抒寫代碼。

recyclerView的簡單使用:

  1. 添加依賴:

      dependencies {
        ...
        compile 'com.android.support:recyclerview-v7:21.0.+'
      }
    

2.在xml布局文件中創(chuàng)建一個RecyclerView的布局和item的布局:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/my_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"/>

    </RelativeLayout>

    創(chuàng)建item布局,通過使用CardView進行包裹達到更好的界面效果展示:

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="10dp"
        android:layout_margin="5dp"
        android:foreground="?android:attr/selectableItemBackground"
        app:cardElevation="5dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="5dp">

            <ImageView
                android:id="@+id/item_image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@mipmap/ic_launcher" />

            <TextView
                android:id="@+id/item_textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginLeft="10dp"
                android:text="標題"
                android:textSize="18sp" />

        </LinearLayout>
    </android.support.v7.widget.CardView>
  1. 編寫代碼邏輯,在MainActivity中找到recyclerView,設(shè)置他的顯示樣式,并為他設(shè)置Adapter.

     mRecyclerView = (RecyclerView) findViewById(R.id.recycle_view);
     //創(chuàng)建默認的線性LinearLayoutManager
     LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
     //gridLayoutManager 多列表
     GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3);
     //StaggeredGridLayoutManager 瀑布流
     StaggeredGridLayoutManager staggeredGridLayoutManager = new
             StaggeredGridLayoutManager(5,StaggeredGridLayoutManager.HORIZONTAL);
     mRecyclerView.setLayoutManager(mLayoutManager);
     //確定每個item的高度都是固定的,提高性能
     mRecyclerView.setHasFixedSize(true);
     //設(shè)置適配器
     mAdapter = new MyAdapter(this);
     mRecyclerView.setAdapter(mAdapter);
     //也可以設(shè)置分割線
     recyclerView.addItemDecoration(new DividerGridItemDecoration(this ));
     //設(shè)置增加或刪除條目的動畫
     recyclerView.setItemAnimator( new DefaultItemAnimator());
    
  2. 為recyclerView編寫Adapter:

    /**
     * 創(chuàng)建adapter需要實現(xiàn)三個方法:
     * 1. onCreateViewHolder():這個方法主要生成為每個Item inflater出一個View,但是該方法返回的是一個ViewHolder。該方法把View直接封裝在ViewHolder中,然后我們面向的是ViewHolder這個實例,當然這個ViewHolder需要我們自己去編寫,直接省去了當初的convertView.setTag(holder)和convertView.getTag()這些繁瑣的步驟。
     * 2. onBindViewHolder():這個方法主要用于適配渲染數(shù)據(jù)到View中。將界面與數(shù)據(jù)進行綁定,方法提供給你了一個viewHolder,而不是原來的convertView。
     * 3. getItemCount():這個方法就類似于BaseAdapter的getCount方法了,即總共有多少個條目。
     */

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    
        private View view;
      
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            view = View.inflate(parent.getContext(), R.layout.item_adapter, null);
            return new MyViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.setData(position);
        }
    
        @Override
        public int getItemCount() {
            return 1000;
        }
    
        /**
         * 設(shè)置viewHolder
         */
    
        public class MyViewHolder extends RecyclerView.ViewHolder {
    
            TextView tvName;
            ImageView ivImage;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                //初始化控件
                tvName = (TextView) view.findViewById(R.id.item_textview);
                ivImage = (ImageView) view.findViewById(R.id.item_image);
            }
        }
    }
  1. 這樣一個通過RecyclerView來實現(xiàn)界面的數(shù)據(jù)加載就實現(xiàn)了。

RecyclerView的進階

給RecyclerView的item添加點擊事件:

1.由于RecyclerView沒有提供直接添加item點擊事件的方法,因此需要通過自定義接口實現(xiàn)item的點擊事件。

/**
 * 自定義接口,給item設(shè)置點擊事件
 */

//聲明接口的變量
private onRecycleViewItemClickListener mOnItemClickListener = null;

public interface onRecycleViewItemClickListener {

    void onItemClick(View view, int position);
}

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

2.在MyAdapter類中,通過實現(xiàn)view的onClickListener方法,重寫它的onClick()方法;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> implements View.OnClickListener {

....

@Override
public void onClick(View view) {
    
    //在該處先判空。
    if (mOnItemClickListener != null) {
        //通過getTag的方式獲取到position
        mOnItemClickListener.onItemClick(view, (Integer) view.getTag());
    }
}

3.在Adapter的實現(xiàn)方法:onCreateViewHolder()中給view添加點擊事件,onBindViewHolder()通過setTag的方式設(shè)置item的position。

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    view = View.inflate(parent.getContext(), R.layout.item_adapter, null);

    //給新創(chuàng)建的view注冊點擊事件
    view.setOnClickListener(this);

    return new MyViewHolder(view);
}

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    holder.setData(position);
    //將position保存在viewItem的tag中,以便在點擊的時候獲取。
    holder.itemView.setTag(position);
}

4.在MainActivity中,通過給adapter設(shè)置暴露出來的setOnItemClickListener()方法,來響應(yīng)點擊事件。

 //給item設(shè)置點擊事件
    mAdapter.setOnItemClickListener(new MyAdapter.onRecycleViewItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            Toast.makeText(MainActivity.this,"第"+position+"個item被點擊了",Toast.LENGTH_SHORT).show();
        }
    });

給recyclerView的item添加分割線:

recyclerView有提供添加分割線的方法:通過addItemDecoration()實現(xiàn)。

    //添加分割線
    mRecyclerView.addItemDecoration(new RecycleViewItemDecoration());

2.自定義類RecycleViewItemDecoration繼承RecyclerView.ItemDecoration,并實現(xiàn)它的兩個方法getItemOffsets()和onDraw()方法。

/**
 * 設(shè)置item的分割線
 */
private class RecycleViewItemDecoration extends RecyclerView.ItemDecoration {

    private Paint mPaint;
    public RecycleViewItemDecoration() {
        //構(gòu)建畫筆
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.GRAY);
    }

    /**
     * 實現(xiàn)兩個方法
     * 確定分割線的位置
     * @param outRect
     * @param view
     * @param parent
     * @param state
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //先確定位置,在每個item的底部留出5px的位置
        int position = parent.getChildAdapterPosition(view);
        //判斷當前item不是第0個位置,就在item的頂部繪制分割線
        if(position != 0) {
            outRect.top = 5;
        }
    }

    /**
     * 繪制分割線,用Canvas在每一個item的頭部繪制
     * @param c
     * @param parent
     * @param state
     */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

        int childCount = parent.getChildCount();

        //指定繪畫的矩形區(qū)域
        Rect rect = new Rect();
        rect.left = parent.getPaddingLeft();
        rect.right = parent.getWidth() - parent.getPaddingRight();
        for (int i = 1; i < childCount; i++) {
            //分割線的底部是itemView的頂部
            rect.bottom = parent.getChildAt(i).getTop();
            rect.top = rect.bottom - 5;
            c.drawRect(rect,mPaint);
        }
    }
}

給RecyclerView添加頭部和底部:

listview添加頭部和底部有具體的方法實現(xiàn),通過addHeaderView()和addFooterView()兩個方法就能實現(xiàn)。而recyclerView沒有提供具體的方法實現(xiàn),需要自定義來實現(xiàn)。通過對listview添加頭部的方法addHeaderView()的源碼進行分析,發(fā)現(xiàn)它是在對Adapter進行判空處理之后,判斷當前的listview是否已經(jīng)添加Adapter,然后在原來的adapter的基礎(chǔ)上進行了一層包裝,其實listview本身也是不支持添加頭部和底部的,只是系統(tǒng)為我們設(shè)定好了。最主要是需要 清楚HeaderViewListAdapter是如何實現(xiàn)的。

public void addHeaderView(View v, Object data, boolean isSelectable) {
     //第一步 數(shù)據(jù)的初始化和賦值 并添加到頭部視圖list中
    final FixedViewInfo info = new FixedViewInfo();
    info.view = v;
    info.data = data;
    info.isSelectable = isSelectable;
    //添加頭部集合
    mHeaderViewInfos.add(info);
    mAreAllItemsSelectable &= isSelectable;

    // Wrap the adapter if it wasn't already wrapped.
    if (mAdapter != null) {
        //第二步 關(guān)鍵點
        //給mAdapter賦值成HeaderViewListAdapter對象 并把head、foot視圖列表和原來的adapter傳進去 
        //所以這里就是關(guān)鍵 從這里進入看HeaderViewListAdapter類的源碼
        if (!(mAdapter instanceof HeaderViewListAdapter)) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
        }
        //第三步 被觀察者發(fā)出修改通知
        // In the case of re-adding a header view, or adding one later on,
        // we need to notify the observer.
        if (mDataSetObserver != null) {
            mDataSetObserver.onChanged();
        }
    }
}

接下來,就自己實現(xiàn)一下,實現(xiàn)的基本原理是通過getItemViewType()方法返回不同的類型來添加頭部和底部。
貼上實現(xiàn)該功能的整段代碼,對上面的代碼塊有部分改動:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {

    private View view;
    private Context mContext;
    private LayoutInflater mLayoutInflater;
    //item類型
    public static final int ITEM_TYPE_HEADER = 0;
    public static final int ITEM_TYPE_CONTENT = 1;
    public static final int ITEM_TYPE_BOTTOM = 2;
    //頭部View個數(shù)
    private int mHeaderCount = 1;
    //底部View個數(shù)
    private int mBottomCount = 1;

    int icons[] = {R.drawable.g1, R.drawable.g2, R.drawable.g3, R.drawable.g4, R.drawable.g5, R.drawable.g6, R.drawable.g7, R.drawable.g9,
            R.drawable.g10, R.drawable.g11, R.drawable.g12, R.drawable.g13, R.drawable.g14, R.drawable.g15, R.drawable.g16, R.drawable.g17, R.drawable.g18, R.drawable.g19,
            R.drawable.g20, R.drawable.g21, R.drawable.g22, R.drawable.g23, R.drawable.g24, R.drawable.g25, R.drawable.g26, R.drawable.g27, R.drawable.g28, R.drawable.g29,};

    String names[] = {"瀏覽器", "輸入法", "健康", "效率", "教育", "理財",
            "閱讀", "個性化", "購物", "資訊", "生活", "工具", "出行", "通訊", "拍照", "社交",
            "影音", "安全", "休閑", "棋牌", "益智", "射擊", "體育", "兒童", "網(wǎng)游", "角色", "策略",
            "經(jīng)營", "競速"};

    public MyAdapter(Context context) {
        this.mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        if (viewType == ITEM_TYPE_HEADER) {
            return new HeaderViewHolder(mLayoutInflater.inflate(R.layout.rv_headerview, parent, false));
        } else if (viewType == mHeaderCount) {
            view = mLayoutInflater.inflate(R.layout.item_adapter, parent, false);
            view.setOnClickListener(this);
            return new MyViewHolder(view);
        } else if (viewType == ITEM_TYPE_BOTTOM) {
            return new BottomViewHolder(mLayoutInflater.inflate(R.layout.rv_footerview, parent, false));
        }
        return null;

    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if (holder instanceof HeaderViewHolder) {

        } else if (holder instanceof MyViewHolder) {
            ((MyViewHolder) holder).setData(position);
            //將position保存在viewItem的tag中,以便在點擊的時候獲取。
            holder.itemView.setTag(position);
        } else if (holder instanceof BottomViewHolder) {

        }
    }

    @Override
    public int getItemCount() {
        return getContentItemCount() + mHeaderCount + mBottomCount;
    }

    //填充內(nèi)容的長度
    public int getContentItemCount() {
        return names.length;
    }

    //判斷當前item類型
    @Override
    public int getItemViewType(int position) {
        int dataItemCount = getContentItemCount();
        if (mHeaderCount != 0 && position < mHeaderCount) {
            //頭部View
            return ITEM_TYPE_HEADER;
        } else if (mBottomCount != 0 && position >= (mHeaderCount + dataItemCount)) {
            //底部View
            return ITEM_TYPE_BOTTOM;
        } else {
            //內(nèi)容View
            return ITEM_TYPE_CONTENT;
        }
    }

    /**
     * 這兩個方法是用來判斷當前item是頭部還是底部,在使用gridLayoutManager的時候調(diào)用
     * @param position
     * @return
     */
    public boolean isHeaderView(int position) {
        return mHeaderCount != 0 && position < mHeaderCount;
    }

    public boolean isBottomView(int position) {
        return mBottomCount != 0 && position >= (mHeaderCount + getContentItemCount());
    }

    /**
     * 自定義接口,給item設(shè)置點擊事件
     */

    //聲明接口的變量
    private onRecycleViewItemClickListener mOnItemClickListener = null;

    public interface onRecycleViewItemClickListener {

        void onItemClick(View view, int position);
    }

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

    @Override
    public void onClick(View view) {

        if (mOnItemClickListener != null) {
            //通過getTag的方式獲取到position
            mOnItemClickListener.onItemClick(view, (Integer) view.getTag());
        }
    }

    /**
     * 設(shè)置viewHolder
     */
    
    //中間內(nèi)容的viewHolder
    public class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tvName;
        ImageView ivImage;

        public MyViewHolder(View itemView) {
            super(itemView);
            //初始化控件
            tvName = (TextView) view.findViewById(R.id.item_textview);
            ivImage = (ImageView) view.findViewById(R.id.item_image);
        }

        public void setData(int position) {
            //給控件賦值
            tvName.setText(names[position % names.length]);
            ivImage.setImageDrawable(mContext.getResources().getDrawable(icons[position % icons.length]));
        }
    }

    //頭部的ViewHolder
    public static class HeaderViewHolder extends RecyclerView.ViewHolder {
        public HeaderViewHolder(View itemView) {
            super(itemView);
        }
    }

    //底部的ViewHolder
    public static class BottomViewHolder extends RecyclerView.ViewHolder {
        public BottomViewHolder(View itemView) {
            super(itemView);
        }
    }
}

在activity當中如果RecyclerView使用Grid類型列表在設(shè)置Adapter后需要調(diào)用這個方法mLayoutManager.setSpanSizeLookup(),根據(jù)當前Item類型來判斷占據(jù)的橫向格數(shù)。

    /**
     * 如果RecyclerView使用Grid類型列表在設(shè)置Adapter后需要調(diào)用這個方法,
     * 根據(jù)當前Item類型來判斷占據(jù)的橫向格數(shù)
     */
    mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            return (mAdapter.isHeaderView(position) ||
                    mAdapter.isBottomView(position)) ? mLayoutManager.getSpanCount() : 1;
        }
    });

RecyclerView實現(xiàn)列表的上拉加載和下拉刷新:

要實現(xiàn)列表的下拉刷新可以使用Android提供的swipeRefreshLayout來實現(xiàn)下拉刷新或者上拉加載,也可以自定義上拉加載和下拉刷新,
使用swipeRefreshLayout來進行控件的下拉刷新。需要在布局文件中對RecyclerView進行包裹。

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/srf_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycle_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical">

        </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>

接下來就是activity中的代碼實現(xiàn),需要先找到控件,然后刷新的監(jiān)聽方法,該處使用了handler來模擬網(wǎng)絡(luò)請求操作。

//利用swipeRefreshLayout設(shè)置下拉刷新
private void setPullToRefresh() {
    //設(shè)置圈圈的顏色
    mRefreshLayout.setColorSchemeColors(Color.RED,Color.BLUE,Color.BLACK);
    //設(shè)置圈圈的顏色,里面放的是顏色的資源值
    // refreshLayout.setColorSchemeColors(R.color.cardview_dark_background,R.color.cardview_light_background);

    mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            Toast.makeText(MainActivity.this,"正在拼命加載中...",Toast.LENGTH_SHORT).show();

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //停止刷新
                    mRefreshLayout.setRefreshing(false);
                }
            },3000);
        }
    });
}   

SwipeRefreshLayout里面需要注意的Api:
1)setOnRefreshListener(OnRefreshListener listener) 設(shè)置下拉監(jiān)聽,當用戶下拉的時候會去執(zhí)行回調(diào)
2)setColorSchemeColors(int... colors) 設(shè)置 進度條的顏色變化,最多可以設(shè)置4種顏色
3)setProgressViewOffset(boolean scale, int start, int end) 調(diào)整進度條距離屏幕頂部的距離
4)setRefreshing(boolean refreshing) 設(shè)置SwipeRefreshLayout當前是否處于刷新狀態(tài),一般是在請求數(shù)據(jù)的時候設(shè)置為true,在數(shù)據(jù)被加載到View中后,設(shè)置為false。

總結(jié):

到此關(guān)于RecyclerView的一些常見使用方式就介紹完了,總的來說RecyclerView的使用比起listview還是要便捷的多。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容