1. 程式人生 > >RecyclerView常用功能全面總結

RecyclerView常用功能全面總結

簡介

RecyclerView可以說是做Android應用開發使用最廣的幾個控制元件之一。它是在Android 5.0版本引入進來的,位於support-v7包中,可以通過在build.gradle中新增如下程式碼將它引入到專案中:

 implementation 'com.android.support:recyclerview-v7:27.1.1'

和ListView的比較

在RecyclerView出現之前Android中的複用列表大多通過ListView實現的,RecyclerView並不會完全替代ListView,至少現在來看ListView並沒有被廢棄掉。RecyclerView和ListView互有優勢。

ListView的優點:
  • 可以直接通過addHeaderView(), addFooterView()新增頭檢視和尾檢視。
  • 直接提供了setOnItemClickListener()和setOnItemLongClickListener()設定點選事件和長按事件。
  • “android:divider”設定自定義分割線。
RecyclerView的優點
  • 提供了多種LayoutManager,可輕鬆實現多種樣式的佈局
  • 支援區域性重新整理
  • 已經實現了View的複用,不需要類似if(convertView == null)的實現,而且回收機制更加完善
  • 容易實現新增item、刪除item的動畫效果
  • 容易實現拖拽、側滑刪除等功能

簡單使用

RecylerView的使用幾個必須的步驟如下:
- 建立Adapter,並設定給RecylerView
- 為RecylerView設定LayoutManager
這兩個是RecylerView使用的必須步驟,可以看出
RecylerView和ListView的使用方法相比只是多了一步設定LayoutManager。

通過程式碼來看Adapter的具體實現:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private
Context context; private List<String> mDatas; public MyAdapter(Context context , List<String> mDatas){ this.context = context; this.mDatas = mDatas; } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout,parent,false)); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { holder.textView.setText("第"+position); } @Override public int getItemCount() { return mDatas.size(); } public class MyViewHolder extends RecyclerView.ViewHolder{ private TextView textView; public MyViewHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.textview); } } }

將Adapter和LayoutManager設定給RecylerView:

        myAdapter = new MyAdapter(this,datas);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        mRl.setLayoutManager(linearLayoutManager);
        mRl.setAdapter(myAdapter);

這樣一個最基本的RecyclerView就實現了。

GridLayoutManager,StaggeredGridLayoutManager

在前面基本使用的程式碼中,給RecyclerView設定的是LinearLayoutManager,其實RecyclerView還提供了另外兩種佈局管理器,這就是GridLayoutManager和StaggeredGridLayoutManager,通過它們可以很簡單的分別實現表格佈局和瀑布流佈局。

通過GridLayoutManager可以讓RecyclerVie實現GridView的效果。GridLayoutManager的使用方式如下:

//4可以理解為一行顯示4列
GridLayoutManager linearLayoutManager = new GridLayoutManager(this,4);
mRl.setLayoutManager(linearLayoutManager);

通過StaggeredGridLayoutManager可以讓RecyclerVie實現瀑布流的效果。StaggeredGridLayoutManager的使用方式如下:

  StaggeredGridLayoutManager linearLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);

當然也可以根據需求自定義LayoutManager.通過原始碼就可以可以看到GridLayoutManager其實也是繼承了LinearLayoutManager,相當於自定義一個LinearLayoutManager,把一行一列,變為一行多列。

ItemDecoration

ItemDecoration其實翻譯過來就是item裝飾品,所以它不僅僅是用來繪製分割線,它可以繪製出各式各樣的的itemview的裝飾介面。只是由於RecyclerView中並沒有提供類似android:divider的方法來實現分割線,所以往往通過ItemDecoration來實現各種分割線效果。
ItemDecoration是一個抽象類,主要有下面3個方法:

    //用於撐開整個itemview
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
    //用於繪製具體的分隔線形狀,在itemview前面繪製,有可能被itemview覆蓋
    public void onDraw(Canvas c, RecyclerView parent, State state) 
    //它是在itemview之後繪製,可以覆蓋itemview的內容
    public void onDrawOver(Canvas c, RecyclerView parent, State state) 

通過程式碼來實現一條綠色的分割線:

    public class MyItemDecoration extends RecyclerView.ItemDecoration{
        private int mDiverHeight;
        private Paint mPaint;

        public MyItemDecoration(){
            mPaint = new Paint();
            mPaint.setColor(Color.GREEN);
        }

        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);

            int childCount = parent.getChildCount();

            for (int i = 0; i < childCount; i++) {
                View view = parent.getChildAt(i);

                if (parent.getChildAdapterPosition(view) !=0){
                    float top = view.getTop()-mDiverHeight;
                    float left = parent.getPaddingLeft();
                    float right = parent.getWidth()-parent.getPaddingRight();
                    float bottom = view.getTop();

                    c.drawRect(left,top,right,bottom,mPaint);
                }
            }

        }


        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
//
//            int childCount = parent.getChildCount();
//
//            for (int i = 0; i < childCount; i++) {
//                View view = parent.getChildAt(i);
//
//                if (parent.getChildAdapterPosition(view) !=0){
//                    float top = view.getTop()-mDiverHeight;
//                    float left = parent.getPaddingLeft();
//                    float right = parent.getWidth()-parent.getPaddingRight();
//                    float bottom = view.getTop();
//
//                    c.drawRect(left,top,right,bottom,mPaint);
//                }
//            }
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            if (parent.getChildLayoutPosition(view) != 0){
                outRect.top = 10;
                mDiverHeight = 40;
            }
        }
    }

分析一下上面的程式碼,
1. 首先在getItemOffsets中利用 outRect.top在itemview的頂部撐開了10px的高度,將mDiverHeight的值設定為40px.
2. onDraw方法中具體繪製分隔線樣式
3. onDrawOver方法中同樣的程式碼繪製分割線

分別看下在不同方法中繪製出來的效果:


圖一,雖然mDiverHeight的高度設定為40px,但並沒有顯示那麼高,因為在getItemOffsets方法中只將itemview撐開了10px,進一步驗證了onDraw繪製的分隔線有可能被itemview覆蓋。

圖二,mDiverHeight的高度設定為40px,實際顯示也是40px,雖然在getItemOffsets方法中只將itemview撐開了10px,但是分隔線覆蓋掉了itemview部分介面,也驗證了onDrawOver繪製的分隔線會覆蓋itemview。

總結,在熟悉了onDraw和onDrawOver方法之後可以利用它們繪製出多種多樣的itemview裝飾介面。

ItemAnimator

通過ItemAnimator可以設定Item新增、刪除、更新的動畫,即使沒有設定它也會看到動畫,因為RecyclerView設定了預設動畫DefaultItemAnimator,通過程式碼來看下DefaultItemAnimator的效果:

 DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator();
 defaultItemAnimator.setAddDuration(1000);//設定時間長一點,容易看出效果
 mRl.setItemAnimator(defaultItemAnimator);
需要注意的是這裡改變資料重新整理並不是呼叫的notifyDataSetChanged()方法,新增時呼叫notifyItemInserted(1),刪除時呼叫notifyItemRemoved(1)。 從效果可以看出預設動畫的效果是: item新增時:漸變色由0-1顯現 item刪除時:漸變色由1-0消失 只有DefaultItemAnimator往往不能滿足需求,這時就需要自定義ItemAnimator,雖然動畫效果需要自定義,但是基本都是在add和remove時展示,所以只需要在DefaultItemAnimator的基礎上做修改就行了,下面實現一個在預設動畫的基礎上再增加一個平移動畫。Copy DefaultItemAnimator全部程式碼至MyItemAnimator,只需要修改新增部分: MyItemAnimator.java
 @Override
    public boolean animateAdd(final RecyclerView.ViewHolder holder) {
        resetAnimation(holder);
        holder.itemView.setAlpha(0);
        mPendingAdditions.add(holder);

        //新增平移動畫,預設將itemview左移自身寬度
        ViewCompat.setTranslationX(holder.itemView,-holder.itemView.getWidth());
        mPendingAdditions.add(holder);
        return true;
    }

    void animateAddImpl(final RecyclerView.ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimator animation = view.animate();
        mAddAnimations.add(holder);
        //新增translationX(0),將itemview平移出來
        animation.alpha(1).translationX(0).setDuration(getAddDuration())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                        dispatchAddStarting(holder);
                    }

                    @Override
                    public void onAnimationCancel(Animator animator) {
                        ViewCompat.setAlpha(view, 1);
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        animation.setListener(null);
                        dispatchAddFinished(holder);
                        mAddAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();

    }

效果如下:

一個簡單的自定義ItemAnimator就這樣實現了。

Item側滑和拖拽

RecyclerView的item側滑功能主要是通過ItemTouchHelper實現的,它是一個支援RecyclerView滑動刪除和拖拽的實用工具類,使用也很簡單:

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallBack);
itemTouchHelper.attachToRecyclerView(mRl);

所以只需要關注這裡的mCallBack,看下它的實現:

public ItemTouchHelper.Callback mCallBack  = new ItemTouchHelper.Callback() {
    /**
     * 指定可以拖拽(drag)和滑動(swipe)的方向
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return 0;
    }

    /**
     * 拖拽回撥
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    /**
     * 滑動回撥
     * @param viewHolder
     * @param direction
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

    }
};

但是在實際使用的時候更多的是通過SimpleCallback,它是繼承至ItemTouchHelper.Callback,通過程式碼來實現一個簡單的帶有側滑和拖拽的效果:

ItemTouchHelper.SimpleCallback mCallback =
    new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP |
            ItemTouchHelper.DOWN | ItemTouchHelper.LEFT |ItemTouchHelper.RIGHT,
            ItemTouchHelper.START | ItemTouchHelper.END) {

        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            int fromPosition = viewHolder.getAdapterPosition();//當前ViewHolder的position
            int toPosition = target.getAdapterPosition();//目標ViewHolder的position

            //交換位置
            if (fromPosition < toPosition) {
                for (int i = fromPosition; i < toPosition; i++) {
                    Collections.swap(datas, i, i + 1);
                }
            } else {
                for (int i = fromPosition; i > toPosition; i--) {
                    Collections.swap(datas, i, i - 1);
                }
            }
            myAdapter.notifyItemMoved(fromPosition, toPosition);
            return true;
        }


        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            int position = viewHolder.getAdapterPosition();
            datas.remove(position);//刪除資料
            myAdapter.notifyItemRemoved(position); 
        }


        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                //滑動時改變Item的透明度
                final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
                viewHolder.itemView.setAlpha(alpha);
                viewHolder.itemView.setTranslationX(dX);
            }
        }
    };

然後看下效果圖:

可以看到最基本的拖拽和側滑都已經實現了。

多種型別佈局

RecyclerView的常用功能裡面還有就是多種狀態佈局,也就是用一個RecyclerView實現有多種狀態的佈局,比如給RecyclerView增加一個Header和Footer
然後中間是常規資料,主要通過Adapter的getItemViewType方法來區分不同的佈局。修改Adapter的程式碼如下:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context context;
    private List<String> mDatas;

    public static final int ITME_TYPE_HEADER = 1;
    public static final int ITME_TYPE_CONTENT = 2;
    public static final int ITME_TYPE_BOTTOM = 3;

    private int mHeaderCount = 1;
    private int mBottmoCount = 1;

    public MyAdapter(Context context , List<String> mDatas){

        this.context = context;
        this.mDatas = mDatas;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        switch (viewType){
            case ITME_TYPE_HEADER:
                return new HeadViewHolder(LayoutInflater.from(context).inflate(R.layout.item_header,parent,false));
            case ITME_TYPE_CONTENT:
                return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout,parent,false));
            case ITME_TYPE_BOTTOM:
                return new BottomViewHolder(LayoutInflater.from(context).inflate(R.layout.item_header,parent,false));
        }
        return null;

    }

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

        if (holder instanceof  HeadViewHolder){
            ((HeadViewHolder) holder).textView_header.setText("我是頭");
        }else if (holder instanceof  MyViewHolder){
            //注意這裡要減去頭的數量
            ((MyViewHolder) holder).textView.setText("第"+(position-mHeaderCount));
        }else if (holder instanceof BottomViewHolder){
            ((BottomViewHolder) holder).textView_bottom.setText("我是尾");
        }
    }


    /**
     * 要加上頭尾
     * @return
     */
    @Override
    public int getItemCount() {
        return mDatas.size()+mHeaderCount+mBottmoCount;
    }

    /**
     * 根據position來區分佈局型別
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        if (position<mHeaderCount){
            return ITME_TYPE_HEADER;
        }else if(position>= mHeaderCount+mDatas.size()){
            return ITME_TYPE_BOTTOM;
        }else{
            return ITME_TYPE_CONTENT;
        }
    }

    public class MyViewHolder extends RecyclerView.ViewHolder{

        private TextView textView;
        public MyViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.textview);

        }
    }

    //頭部ViewHolder
    public static class HeadViewHolder extends RecyclerView.ViewHolder {
        private TextView textView_header;
        public HeadViewHolder(View itemView) {
            super(itemView);
            textView_header = itemView.findViewById(R.id.textView1);
        }
    }

    //尾部ViewHodler
    public static class BottomViewHolder extends RecyclerView.ViewHolder {
        private TextView textView_bottom;
        public BottomViewHolder(View itemView) {
            super(itemView);
            textView_bottom = itemView.findViewById(R.id.textView1);
        }
    }
}

效果如圖:

總結

RecyclerView真的很強大,很好用。