RecyclerView 詳解



概述
      
      
      
              
RecyclerView出現已經有一段時間了,相信大家肯定不陌生了,大家可以通過導入support-v7對其進行使用。 
據官方的介紹,該控件用於在有限的窗口中展示大量數據集,其實這樣功能的控件我們並不陌生,例如:ListView、GridView。
那么有了ListView、GridView為什么還需要RecyclerView這樣的控件呢?
整體上看RecyclerView架構,提供了一種插拔式的體驗,高度的解耦,異常的靈活,通過設置它提供的不同LayoutManager,ItemDecoration , ItemAnimator實現令人瞠目的效果。
  • 你想要控制其顯示的方式,請通過布局管理器LayoutManager
  • 你想要控制Item間的間隔(可繪制),請通過ItemDecoration
  • 你想要控制Item增刪的動畫,請通過ItemAnimator
  • 你想要控制點擊、長按事件,請自己寫!

RecyclerView基本的使用代碼:
     mRecyclerView = findView(R.id.id_recyclerview);
     mRecyclerView.setLayoutManager(layout);//設置布局管理器
     mRecyclerView.setAdapter(adapter)//設置adapter
     mRecyclerView.setItemAnimator(new DefaultItemAnimator());//設置Item增加、移除動畫
     mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.HORIZONTAL_LIST));//添加分割線  

相比較於ListView的代碼,ListView可能只需要去設置一個adapter就能正常使用了。而RecyclerView基本需要上面一系列的步驟,那么為什么會添加這么多的步驟呢?
那么就必須解釋下RecyclerView的這個名字了,從它類名上看,RecyclerView代表的意義是,我只管Recycler View,也就是說RecyclerView只管回收與復用View,其他的你可以自己去設置。可以看出其高度的解耦,給予你充分的定制自由(所以你才可以輕松的通過這個控件實現ListView,GirdView,瀑布流等效果)。

分割線 ItemDecoration
      
      
      
              
RecyclerView並沒有支持divider這樣的屬性,你可以給Item的布局去設置margin、background等方式來間接添加分割線,但這種方式不夠優雅,我們的分割線可以在代碼中通過以下方法添加。
     mRecyclerView.addItemDecoration() 
該方法的參數為RecyclerView.ItemDecoration,該類為抽象類,官方目前並沒有提供默認的實現類。 
public static abstract class ItemDecoration {
    public void onDraw(Canvas c, RecyclerView parent, State state) {
        onDraw(c, parent);
    }
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        onDrawOver(c, parent);
    }
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);
    }
    @Deprecated
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        outRect.set(0, 0, 0, 0);
    }
}

若我們調用mRecyclerView.addItemDecoration()方法添加分割線,則RecyclerView在繪制的時候,會 繪制 Decoration ,即調用該類的onDraw和onDrawOver方法:
  • onDraw 方法在drawChildren之前
  • onDrawOver在drawChildren之后,一般我們選擇復寫其中一個即可。
  • getItemOffsets 可以通過outRect.set()為每個Item設置一定的偏移量,主要用於繪制Decorator。

代碼 Activity
      
      
      
              




public class MainActivity extends ActionBarActivity implements MyOnItemClickLitener {
    private RecyclerView mRecyclerView;
    private List<String> mDatas;//數據
    private List<Integer> mHeights;//高度
    private MyRecyclerViewAdapter mRecyclerViewAdapter;//適配器
    private MyStaggeredAdapter mStaggeredAdapter;
    private ItemDecoration decoration1;//分割線
    private ItemDecoration decoration2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mRecyclerView = new android.support.v7.widget.RecyclerView(this);
        setContentView(mRecyclerView);
        initData();
        initAdapter();
        initRecylerView();
    }
    protected void initData() {
        mDatas = new ArrayList<String>();
        for (int i = 'A'; i < 'z'; i++) {
            mDatas.add("" + (char) i);
        }
        mHeights = new ArrayList<Integer>();
        for (int i = 0; i < mDatas.size(); i++) {
            mHeights.add((int) (100 + Math.random() * 500));
        }
    }
    private void initAdapter() {
        mRecyclerViewAdapter = new MyRecyclerViewAdapter(thismDatas);
        mStaggeredAdapter = new MyStaggeredAdapter(thismDatasmHeights);
        mRecyclerViewAdapter.setOnItemClickLitener(this);
        mStaggeredAdapter.setOnItemClickLitener(this);
    }
    private void initRecylerView() {
        decoration1 = new DividerItemDecoration(this, 0);
        decoration2 = new DividerItemDecoration(this, 1);
        mRecyclerView.setPadding(10, 10, 10, 10);
        mRecyclerView.setAdapter(mRecyclerViewAdapter);//設置adapter
        mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));//設置布局管理器
        mRecyclerView.addItemDecoration(decoration1);//添加一個分割線
        mRecyclerView.addItemDecoration(decoration2);//還可以再添加一個分割線
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());//設置Item增加、移除動畫。github上有很多動畫效果,如RecyclerViewItemAnimators
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return super.onCreateOptionsMenu(menu);
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        mRecyclerView.removeItemDecoration(decoration1);//移除分割線,即使沒有顯示也可以移除
        mRecyclerView.removeItemDecoration(decoration2);
        switch (item.getItemId()) {
        case R.id.add:
            mRecyclerViewAdapter.addData(new Random().nextInt(5));
            break;
        case R.id.delete:
            mRecyclerViewAdapter.removeData(new Random().nextInt(5));
            break;
        //通過RecyclerView去實現ListView、GridView、瀑布流的效果基本上沒有什么區別,僅僅通過設置不同的LayoutManager即可實現
        case R.id.listview:
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
            break;
        case R.id.gridview:
            mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));
            break;
        case R.id.staggeredHorizontalGridView://Staggered:錯列的,叉排的。
            mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.HORIZONTAL));//5行
            break;
        case R.id.staggeredVerticalGridview:
            mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));//3列
            break;
        case R.id.staggeredAdapter:
            mRecyclerView.setAdapter(mStaggeredAdapter);
            break;
        case R.id.recyclerViewAdapter:
            mRecyclerView.setAdapter(mRecyclerViewAdapter);
            break;
        }
        return true;
    }
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this, position + " 被點擊了", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this, position + "被長按了", Toast.LENGTH_SHORT).show();
    }
}

固定寬高的Adapter
       
       
       
               
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {
    private Context context;
    private List<String> mDatas;
    private MyOnItemClickLitener mOnItemClickLitener;
    public void setOnItemClickLitener(MyOnItemClickLitener mOnItemClickLitener) {
        this.mOnItemClickLitener = mOnItemClickLitener;
    }
    public MyRecyclerViewAdapter(Context context, List<String> datas) {
        this.context = context;
        mDatas = datas;
    }
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item, parent, false));
    }
    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        holder.tv.setText(mDatas.get(position));
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.tv.getLayoutParams();
        lp.setMargins(5, 5, 5, 5);//設置邊距
        holder.tv.setLayoutParams(lp);
        // 如果設置了回調,則設置點擊事件
        if (mOnItemClickLitener != null) {
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.onItemClick(holder.itemView, pos);
                }
            });
            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.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");
        notifyItemInserted(position);//更新數據集,注意不是用adapter.notifyDataSetChanged(),否則沒有動畫效果
    }
    /**移除並更新數據,同時具有動畫效果*/
    public void removeData(int position) {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }
    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tv;
        public MyViewHolder(View view) {
            super(view);
            tv = (TextView) view.findViewById(R.id.id_num);
        }
    }
}

隨機寬高的Adapter
       
       
       
               
/**和MyAdapter唯一的區別就是:在代碼中動態設置了TextView的高度(寬度)*/
public class MyStaggeredAdapter extends RecyclerView.Adapter<MyStaggeredAdapter.MyViewHolder> {
    private Context context;
    private List<String> mDatas;
    private List<Integer> mHeights;//高度
    private MyOnItemClickLitener mOnItemClickLitener;
    public void setOnItemClickLitener(MyOnItemClickLitener mOnItemClickLitener) {
        this.mOnItemClickLitener = mOnItemClickLitener;
    }
    public MyStaggeredAdapter(Context context, List<String> datas, List<Integer> heights) {
        this.context = context;
        this.mDatas = datas;
        this.mHeights = heights;
    }
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item, parent, false));
    }
    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.tv.getLayoutParams();
        lp.setMargins(5, 5, 5, 5);
        //橫向時,item的寬度需要設置;縱向時,item的高度需要設置
        lp.height = mHeights.get(position);//******************************************************************************************唯一的區別在這里!
        lp.width = mHeights.get(position);//*******************************************************************************************唯一的區別在這里!
        holder.tv.setLayoutParams(lp);
        holder.tv.setText(mDatas.get(position));
        // 如果設置了回調,則設置點擊事件
        if (mOnItemClickLitener != null) {
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.onItemClick(holder.itemView, pos);
                }
            });
            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.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 RecyclerView.ViewHolder {
        TextView tv;
        public MyViewHolder(View view) {
            super(view);
            tv = (TextView) view.findViewById(R.id.id_num);
        }
    }
}

點擊事件回調接口
       
       
       
               
/**系統沒有提供ClickListener和LongClickListener,我們自己通過接口回調處理 */
public interface MyOnItemClickLitener {
    void onItemClick(View view, int position);
    void onItemLongClick(View view, int position);
}

分割線示例
       
       
       
               
/**zhy寫的分割線,Item如果為最后一列則右邊無間隔線,如果為最后一行則底部無分割線*/
public class MyGridItemDecoration extends RecyclerView.ItemDecoration {
    private Drawable mDivider;
    public MyGridItemDecoration(Context context) {
        //通過讀取系統主題中的 Android.R.attr.listDivider屬性,將其作為Item間的分割線, <item name="android:listDivider">@drawable/divider_bg</item>  
        TypedArray typedArray = context.obtainStyledAttributes(new int[] { android.R.attr.listDivider });
        mDivider = typedArray.getDrawable(0);
        typedArray.recycle();//回收TypedArray,以便后面重用。This TypedArray should be recycled after use with recycle()
    }
    @Override
    /**判斷如果是最后一行,則不需要繪制底部;如果是最后一列,則不需要繪制右邊,整個判斷也考慮到了StaggeredGridLayoutManager的橫向和縱向*/
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        int spanCount = getSpanCount(parent);
        int itemCount = parent.getAdapter().getItemCount();
        //使用outRect設置繪制的范圍。一般如果僅僅是希望有空隙,還是去設置item的margin方便
        if (isLastRaw(parent, itemPosition, spanCount, itemCount)) {// 如果是最后一行,則不需要繪制底部
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else if (isLastColum(parent, itemPosition, spanCount, itemCount)) {// 如果是最后一列,則不需要繪制右邊
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
        }
    }
    @Override
    public void onDraw(Canvas c, RecyclerView parent, State state) {
        drawHorizontal(c, parent);
        drawVertical(c, parent);
    }
    //******************************************************************************************
    public void drawHorizontal(Canvas c, RecyclerView parent) {
        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.getLeft() - params.leftMargin;
            final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }
    public void drawVertical(Canvas c, RecyclerView parent) {
        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.getTop() - params.topMargin;
            final int bottom = child.getBottom() + params.bottomMargin;
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }
    //******************************************************************************************
    /**獲取RecyclerView有多少列*/
    private int getSpanCount(RecyclerView parent) {
        int spanCount = -1; // 列數
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        else if (layoutManager instanceof StaggeredGridLayoutManager) spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
        return spanCount;
    }
    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) {
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            childCount = childCount - childCount % spanCount;
            if (pos >= childCount) // 如果是最后一行,則不需要繪制底部
            return true;
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
            // StaggeredGridLayoutManager 且縱向滾動
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount) return true;// 如果是最后一行,則不需要繪制底部
            } else { // StaggeredGridLayoutManager 且橫向滾動
                if ((pos + 1) % spanCount == 0) return true;// 如果是最后一行,則不需要繪制底部
            }
        }
        return false;
    }
    private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) {
        LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            if ((pos + 1) % spanCount == 0) {// 如果是最后一列,則不需要繪制右邊
                return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL) {
                if ((pos + 1) % spanCount == 0) {// 如果是最后一列,則不需要繪制右邊
                    return true;
                }
            } else {
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount) // 如果是最后一列,則不需要繪制右邊
                return true;
            }
        }
        return false;
    }
}





附件列表

     


    免責聲明!

    本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



     
    粵ICP備18138465號   © 2018-2025 CODEPRJ.COM