揭開RecyclerView的神秘面紗(三):操作數(shù)據(jù)及添加分割線

前言

上一篇文章揭開RecyclerView的神秘面紗(二):處理RecyclerView的點擊事件中,筆者主要講述了處理RecyclerView點擊事件的幾種方法,處理點擊事件是為了實現(xiàn)與用戶的交互,那么實現(xiàn)什么交互呢?對于一個視圖數(shù)據(jù)來說,無非有如下可能:添加數(shù)據(jù)和刪除數(shù)據(jù)和修改數(shù)據(jù),接下來將講述怎么操作數(shù)據(jù)和添加分割線。本系列所有源碼看下方下載地址。

操作數(shù)據(jù)

首先我們看看官方提供了什么API給我們調用,我們看RecyclerView.Adapter中提供的四個方法如下:

//該方法用于當增加一個數(shù)據(jù)的時候,position表示新增數(shù)據(jù)顯示的位置
final void notifyItemInserted(int position)

//該方法用于刪除一個數(shù)據(jù)的時候,position表示數(shù)據(jù)刪除的位置
final void notifyItemRemoved(int position)

//該方法表示所在position對應的item位置不會改變,但是該item內容發(fā)生變化
final void notifyItemChanged(int position)

//該方法一般用于:適配器之前裝載的數(shù)據(jù)大部分已經過時了,需要重新更新數(shù)據(jù)
//調用該方法的時候,recyclerView會重新計算子item及所有子item重新布局
//出于效率考慮,官方建議用更加精確的方法(比如上面三個方法)來取代這個方法
final void notifyDataSetChanged()

為了直觀地看到各個方法的用法,我們對原有代碼進行修改,看看具體的運行結果如何。
首先對MyAdapter.java修改,新增三個方法:

    //移除數(shù)據(jù)
    public void removeData(int position) {
        mDataSet.remove(position);
        notifyItemRemoved(position);
    }
    //新增數(shù)據(jù)
    public void addData(int position){
        mDataSet.add(position,"Add One");
        notifyItemInserted(position);
    }
    //更改某個位置的數(shù)據(jù)
    public void changeData(int position){
        mDataSet.set(position,"Item has changed"+ count++);
        notifyItemChanged(position);
    }

接下來我們在MainActivity.java中調用這三個方法:

mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener2(this, mRecyclerView,
    new RecyclerViewClickListener2.OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            Toast.makeText(MainActivity.this, "Click " + mData.get(position), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onItemLongClick(View view, int position) {
            //長按某個item后,將移除這個item
            mAdapter.removeData(position);
        }
    }));

...

toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
    @Override
    public boolean onMenuItemClick(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_search:
                Toast.makeText(MainActivity.this, "Search !", Toast.LENGTH_LONG).show();
                break;
            case R.id.action_add:
                mAdapter.addData(1);
                break;
            case R.id.action_change:
                mAdapter.changeData(2);
                break;
        }
        return true;
    }
});

接下來,我們看看選擇不同選項時的運行結果是怎樣的:


長按刪除

點擊增加

點擊更改

可以看出,數(shù)據(jù)都被正確地增加、刪除、修改成功了。我們再仔細觀察一下,每一個Item變化的時候都不是瞬間變化的,它都會有一個動畫效果,使得用戶體驗很好,其實這里面使用了RecyclerView默認提供的動畫效果:

//這一句不是必要的,因為RecyclerView會默認使用
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

還記得在第一節(jié)的時候提到過RecyclerView.ItemAnimator這個抽象類,它是用于控制Item的動畫效果的,而DefaultItemAnimator()正是它的一個實現(xiàn)類。這也說明了,我們可以自定義實現(xiàn)很多特效動畫。至于它的各種動畫效果,可以參考GitHub上的開源項目:RecyclerViewItemAnimators,這里就不展開來講了。

添加分割線

細心的同學會發(fā)現(xiàn),筆者的RecyclerView每個Item之間是用magin屬性來分開的,那么RecycleView有沒有像ListView那樣可以直接在xml屬性中添加android:divider呢?答案是沒有的,可能是考慮到RecycleView靈活多變的特點,官方沒有添加這個屬性,不過我們還是有辦法可以手動去添加。在第一節(jié)有提到過: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);
    }
}

省略了三個官方不提倡的用法,這里簡單說明一下以上各個方法的作用:
onDraw和onDrawOver,顯然,這兩個方法是用于繪制的,那么繪制分割線的邏輯可以放在這里面,它們二者的具體區(qū)別是:onDraw是在item view繪制之前調用,而onDrawOver是在item view繪制之后調用,因此我們一般選擇重寫其中一個方法即可。getItemOffsets,這個方法是告訴RecyclerView在繪制完一個item view的時候,應該留下多少空位,以便于繪制分割線。
既然知道了這三個方法的作用,那么我們來寫一個實現(xiàn)類,新建DividerItemDecoration(注:該類參考自Android官方):

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    //使用系統(tǒng)自帶的listDivider
    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){
        //使用TypeArray加載該系統(tǒng)資源
        final TypedArray ta = context.obtainStyledAttributes(ATTRS);
        mDivider = ta.getDrawable(0);
        //緩存
        ta.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){
        //獲取分割線的左邊距,即RecyclerView的padding值
        final int left = parent.getPaddingLeft();
        //分割線右邊距
        final int right = parent.getWidth() - parent.getPaddingRight();
        final int childCount = parent.getChildCount();
        //遍歷所有item view,為它們的下方繪制分割線
        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);
        }
    }

    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, View view, RecyclerView parent, RecyclerView.State state) {
        if(mOrientation == VERTICAL_LIST){
            //設置偏移的高度是mDivider.getIntrinsicHeight,該高度正是分割線的高度
            outRect.set(0,0,0,mDivider.getIntrinsicHeight());
        }else{
            outRect.set(0,0,mDivider.getIntrinsicWidth(),0);
        }
    }
}

接著在MainActivity.java添加如下代碼:

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

最后,我們把之前在itemlayout.xml添加的margin值去掉,那么我們看看運行結果:


分割線

可以看出,出現(xiàn)了淺灰色的分割線。這里面用的是系統(tǒng)自帶的分割線樣式,我們完全可以使用自定義樣式來取代系統(tǒng)的分割線,關于繪制分割線的更多知識,比如說網格布局的分割線的繪制,可以參考如下的博客:

張鴻洋的博客:Android RecyclerView 使用完全解析 體驗藝術般的控件

關于RecyclerView的用法講解至此,縱觀整個使用流程,其靈活性、可定制性還是很高的,特別是結合另一個控件CardView,能使得它看起來更加優(yōu)雅,有興趣的可以去了解一下二者的結合使用。

源碼下載地址:
http://download.csdn.net/detail/a553181867/9518275

更多閱讀:
揭開RecyclerView的神秘面紗(一):RecyclerView的基本使用
揭開RecyclerView的神秘面紗(二):處理RecyclerView的點擊事件

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容