理解RecyclerView—RecyclerView教程(二)

這個世界充滿假象,唯有痛楚不會說謊。                 ——摔跤吧,爸爸

一、概述

  我們在上一篇文章RecyclerView(一)中對RecyclerView詳細介紹了,包含了基本用法,各種佈局管理器的使用和實現不同類型item,那麼下面我們會繼續對RecyclerView的添加頭尾佈局、添加分割線、添加動畫、添加拖拽和刪除功能進行講解。(源碼在文章最後給出)

二、ItemDecoration分割線

  通常我們添加分割線都是在item的佈局裏面添加,這樣相對簡單,但是有可能增加布局的層級,性能不好而且不太優雅,RecyclerView提供了添加分割線的API:

DividerItemDecoration decoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(decoration);

DividerItemDecoration只有一個構造方法,它是繼承自RecyclerView.ItemDecoration類,是官方提供的一個分割線實現類,個人也可以根據需要自定義。

  • DividerItemDecoration(Context context, int orientation)   orientation表示分割線的方向,這個需要和RecyclerView的線性方向一致,VERTICAL表示垂直方向,HORIZONTAL表示水平方向。
  • setDrawable(Drawable drawable)    上面的分割線默認是灰色的,通過這個方法可以根據你的需要設置不同的顏色,注意drawable不能爲null。

通過setDrawable()改變不同的顏色:

decoration.setDrawable(ContextCompat.getDrawable(this, R.drawable.divider_item_decoration));

我們將第一篇中的線性佈局例子,更改一下,加入分割線,如下左圖爲分割線原始顏色,右圖爲自定義drawable顏色:

divider_item_decoration.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" >
        <stroke android:color="#2196F3" android:width="1dp"/><!--stroke 的 width 是線條的寬度-->
        <size android:height="2dp"/><!--size 的 height 是整個drawable的高度-->

        <!--注意:如果size高度與筆劃寬度相同或小於筆劃寬度,則不會顯示線條-->
</shape>

完整代碼:ItemDecorationActivity.java

public class ItemDecorationActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        //創建佈局管理器-線性佈局
        LinearLayoutManager manager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(manager);

        //設置分割線
        DividerItemDecoration decoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
        //decoration.setDrawable(ContextCompat.getDrawable(this, R.drawable.divider_item_decoration));//自定義顏色
        recyclerView.addItemDecoration(decoration);

        //設置數據
        List<String> stringList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            stringList.add("第 " + i + " 個item");
        }

        ItemDecorationAdapter adapter = new ItemDecorationAdapter(this, stringList);
        recyclerView.setAdapter(adapter);
    }
}

三、ItemAnimator動畫

  RecyclerView能通過setItemAnimator(ItemAnimator animator)來設置item增加、移出、改變時具有的動畫效果,官方提供了默認的動畫類DefaultItemAnimator,如果沒有特殊需求使用這個動畫即可,如果需要自定義則可以繼承RecyclerView.ItemAnimator實現自定義效果。設置動畫API:

 recyclerView.setItemAnimator(new DefaultItemAnimator());

它只是在item添加、移出和改變時產生動畫效果,那麼我們就在item點擊事件中這三種動作模擬一下,在Adapter中設置三種類型,根據不同的類型進行不同的操作:
ItemAnimatorAdapter.java

public class ItemAnimatorAdapter extends RecyclerView.Adapter {
    private Context mContext;
    private List<String> mData;

    private int mItem_Animator_type = 0;
    public static final int ITEM_ADD = 0;//增加item
    public static final int ITEM_REMOVE = 1;//移出item
    public static final int ITEM_NOTIFY = 2;//改變item

    public ItemDecorationAdapter(Context context, List<String> stringList) {
        this.mContext = context;
        this.mData = stringList;
    }

    //用於得到ViewHolder,通過ViewHolder承載視圖中的元素,會爲每一個item inflate出一個view,封裝到ViewHolder中
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (mContext != null) {
            return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_decoration, parent, false));
        }
        //這裏注意,返回的holder不能爲null,否則會報錯
        return null;
    }

    //將指定位置的數據和視圖綁定起來,適配渲染數據到View中,因爲這裏ViewHolder,裏面包含了item的View
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        ViewHolder viewHolder = (ViewHolder) holder;
        final TextView tv_name = viewHolder.mTv_name;
        tv_name.setText(mData.get(position));
        tv_name.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, tv_name.getText().toString(), Toast.LENGTH_SHORT).show();
                setItemAnimator(position);
            }
        });
    }
    /**
     * 設置item的增加,刪除,改變的動畫
     * @param position
     */
    private void setItemAnimator(int position) {
        if (mData == null || mData.isEmpty()) return;

        switch (mItem_Animator_type) {
            case ITEM_ADD://增加
                mData.add(position, "new item " + position);
                notifyItemInserted(position);//增加動畫使用notifyItemInserted()更新數據,否則沒有效果
                break;
            case ITEM_REMOVE://移出
                mData.remove(position);
                notifyItemRemoved(position);//增加動畫使用notifyItemRemoved()更新數據,否則沒有效果
                break;
            case ITEM_NOTIFY://改變
                mData.remove(position);
                mData.add(position, "change item " + position);
                notifyItemChanged(position);
                break;
        }
    }

    //指定RecyclerView有多少個Item
    @Override
    public int getItemCount() {
        return mData.size();
    }

    //創建ViewHolder
    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView mTv_name;

        ViewHolder(@NonNull View itemView) {
            super(itemView);
            mTv_name = itemView.findViewById(R.id.tv_name);
        }
    }
    /**
     * 設置類型
     * @param item_Animator_type
     */
    public void setItem_Animator_type(int item_Animator_type) {
        mItem_Animator_type = item_Animator_type;
    }
}

Activity的全部代碼:ItemAnimatorActivity.java

public class ItemAnimatorActivity extends AppCompatActivity implements View.OnClickListener {
    private ItemDecorationAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_decoration_animator_recyclerview);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        findViewById(R.id.btn_animator_add).setOnClickListener(this);
        findViewById(R.id.btn_animator_remove).setOnClickListener(this);
        findViewById(R.id.btn_animator_change).setOnClickListener(this);

        //創建佈局管理器-線性佈局
        LinearLayoutManager manager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(manager);

        //添加動畫效果
        recyclerView.setItemAnimator(new DefaultItemAnimator());

        List<String> stringList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            stringList.add("第 " + i + " 個item");
        }
        mAdapter = new ItemDecorationAdapter(this, stringList);
        recyclerView.setAdapter(mAdapter);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_animator_add://添加item時動畫
                mAdapter.setItem_Animator_type(ItemDecorationAdapter.ITEM_ADD);
                break;
            case R.id.btn_animator_remove://移除item時動畫
                mAdapter.setItem_Animator_type(ItemDecorationAdapter.ITEM_REMOVE);
                break;
            case R.id.btn_animator_change://改變item數據
                mAdapter.setItem_Animator_type(ItemDecorationAdapter.ITEM_NOTIFY);
                break;
        }
    }
}

效果如下:

四、添加頭尾佈局

  在上一篇RecyclerView(一)中也講解了如何根據類型創建不同類型的item,主要原理是根據viewType來創建不同的ViewHolder,從而生成不同類型的item。RecyclerView默認沒有提供類似addHeaderView()addFooterView()添加頭尾佈局的API,那麼我們也可以根據這個原理來爲RecyclerView添加頭佈局和尾佈局。
創建一個adapter,並繼承RecyclerView.Adapter,並重寫相關方法,首先添加幾個item_type,在getItemViewType()方法中返回不同的type:

private static final int ITEM_TYPE_NORMAL = 0;//普通類型
private static final int ITEM_TYPE_HEAD = 1;//頭佈局類型
private static final int ITEM_TYPE_FOOT = 2;//尾部局類型

@Override
public int getItemViewType(int position) {
    if (mHeadView != null && position == 0) {//頭佈局
        return ITEM_TYPE_HEAD;
    } else if (mFootView != null && position == mData.size() - 1) {//尾部局
        return ITEM_TYPE_FOOT;
    }
    return ITEM_TYPE_NORMAL;//普通類型
}

然後在onCreateViewHolder()方法中根據不同的type返回不同類型的ViewHolder

 	@NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        //根據不同類型來獲取不同的ViewHolder,裏面裝載不同的佈局
        if (viewType == ITEM_TYPE_HEAD) {//頭佈局
            return new RecyclerView.ViewHolder(mHeadView) {
            };
        } else if (viewType == ITEM_TYPE_FOOT) {//尾部局
            return new RecyclerView.ViewHolder(mFootView) {
            };
        } else {
            return new NormalHolder(inflater.inflate(R.layout.item_linear, parent, false));
        }
    }

最後,在onBindViewHolder()實現控件與數據邏輯的綁定,adapter還提供了set()方法提供頭佈局和尾部局實例:

	@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NormalHolder) {//普通類型ViewHolder
            NormalHolder viewHolder = (NormalHolder) holder;
            viewHolder.mTv_name.setText(mData.get(position));
        }
    }
    
   public void setHeadView(View headView) {
        mHeadView = headView;
    }
    public void setFootView(View footView) {
        mFootView = footView;
    }

效果如下:

下面給出代碼,在Activity中設置數據並添加頭部尾部:HeadFootViewActivity.java

public class HeadFootViewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        //創建佈局管理器-線性佈局
        LinearLayoutManager manager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(manager);

        //設置數據
        List<String> stringList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            stringList.add("第 " + i + " 個item");
        }

        //數據適配器
        HeadFootViewAdapter adapter = new HeadFootViewAdapter(this, stringList);

        //頭佈局
        View headView = LayoutInflater.from(this).inflate(R.layout.head_banner, recyclerView, false);
        //尾部局
        View footView = LayoutInflater.from(this).inflate(R.layout.foot_loadmore, recyclerView, false);
        
        adapter.setHeadView(headView);//添加頭佈局
        adapter.setFootView(footView);//添加尾佈局

        recyclerView.setAdapter(adapter);
    }
}

adapter的全部代碼:HeadFootViewAdapter.java

public class HeadFootViewAdapter extends RecyclerView.Adapter {
    private Context mContext;
    private List<String> mData;

    private View mHeadView;//頭佈局
    private View mFootView;//尾部局

    private static final int ITEM_TYPE_NORMAL = 0;//普通類型
    private static final int ITEM_TYPE_HEAD = 1;//頭佈局類型
    private static final int ITEM_TYPE_FOOT = 2;//尾部局類型

    public HeadFootViewAdapter(Context context, List<String> stringList) {
        this.mContext = context;
        this.mData = stringList;
    }

    @Override
    public int getItemViewType(int position) {
        if (mHeadView != null && position == 0) {//頭佈局
            return ITEM_TYPE_HEAD;
        } else if (mFootView != null && position == mData.size() - 1) {//尾部局
            return ITEM_TYPE_FOOT;
        }
        return ITEM_TYPE_NORMAL;//普通類型
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        //根據不同類型來獲取不同的ViewHolder,裏面裝載不同的佈局
        if (viewType == ITEM_TYPE_HEAD) {//頭佈局
            return new RecyclerView.ViewHolder(mHeadView) {};
        } else if (viewType == ITEM_TYPE_FOOT) {//尾部局
            return new RecyclerView.ViewHolder(mFootView) {};
        } else {
            return new NormalHolder(inflater.inflate(R.layout.item_linear, parent, false));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NormalHolder) {//普通類型ViewHolder
            NormalHolder viewHolder = (NormalHolder) holder;
            viewHolder.mTv_name.setText(mData.get(position));
        }
    }

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

    //普通類型ViewHolder
    public class NormalHolder extends RecyclerView.ViewHolder {
        TextView mTv_name;

        NormalHolder(@NonNull View itemView) {
            super(itemView);
            mTv_name = itemView.findViewById(R.id.tv_name);
        }
    }

    public void setHeadView(View headView) {
        mHeadView = headView;
        if (mData == null) return;
        mData.add(0, "頭佈局");
    }

    public void setFootView(View footView) {
        mFootView = footView;
        if (mData == null) return;
        mData.add(mData.size(), "尾佈局");
    }
}

頭尾佈局的佈局文件就不給出了,比較簡單,如果需要可以在源碼中查看(源碼在文章最後給出)。

五、拖拽和側滑刪除功能

  Android默認提供了ItemTouchHelper類,使得RecyclerView能輕易實現滑動和拖拽功能,下面我們實現上下拖拽和側滑刪除功能。

5.1實現ItemTouchHelper.Callback

創建自定義類,並且繼承ItemTouchHelper類,重寫下面的幾種方法:

  • getMovementFlags():   設置支持滑動和拖拽的方向,ItemTouchHelper.UP:上移,ItemTouchHelper.DOWN:下移,ItemTouchHelper.LEFT:左移,ItemTouchHelper.RIGHT:右移;
  • onMove():   拖拽時回調;
  • onSwiped():   滑動時回調;
  • onSelectedChanged():   狀態變化時回調,可以做一些狀態變化時的處理,比如拖拽時變化顏色背景,有三個狀態:ACTION_STATE_IDLE:空閒狀態,ACTION_STATE_SWIPE:滑動狀態,ACTION_STATE_DRAG拖拽狀態;
  • clearView():   用戶交互結束時回調,可以做一些結束清除操作,比如拖拽後結束後還原背景色;
  • isLongPressDragEnabled():   是否支持長按拖拽,默認爲true。如果不允許則可以重寫該方法並返回false。

首先同過set()方法設置Adapter給自定義類,下面需要使用到相關數據:

public MyItemTouchCallback(LinearVerticalAdapter adapter) {
        this.adapter = adapter;
    }

然後重寫getMovementFlags()設置拖拽和滑動的方向:

   //設置支持滑動和拖拽的方向
    //ItemTouchHelper.UP:上移,ItemTouchHelper.DOWN:下移,ItemTouchHelper.LEFT:左移,ItemTouchHelper.RIGHT:右移
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlag;//拖拽方向
        int swipeFlag;//滑動方向
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {//網格佈局時可以左右上下拖拽
            dragFlag = ItemTouchHelper.DOWN | ItemTouchHelper.UP
                    | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;
            swipeFlag = 0;
        } else {//其他佈局時,只能上下拖拽
            dragFlag = ItemTouchHelper.DOWN | ItemTouchHelper.UP;
            swipeFlag = ItemTouchHelper.END;//滑動結束
        }
        return makeMovementFlags(dragFlag, swipeFlag);//返回拖拽和滑動的方向
    }

接着重寫onMove()拖拽回調,將正在拖拽的item和集合的item進行元素交換,然後通知適配器不斷更新數據:

   //拖拽時回調,在拖拽時不斷回調這個方法
    //我們需要將正在拖拽的item和集合的item進行元素交換,然後通知適配器不斷更新數據
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //當前拖拽item的position
        int fromPosition = viewHolder.getAdapterPosition();
        //拖拽到位置
        int toPosition = target.getAdapterPosition();

        //根據位置重新排序更新數據
        if (fromPosition < toPosition) {
            for (int i = fromPosition; i < toPosition; i++) {
                Collections.swap(adapter.getData(), i, i + 1);
            }
        } else {
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(adapter.getData(), i, i - 1);
            }
        }
        recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
        return true;
    }

在實現onSwiped()方法,滑動時回調,我們演示的是側滑刪除,則在滑動結束時移出數據:

    //滑動時回調
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int position = viewHolder.getAdapterPosition();
        if (direction == ItemTouchHelper.END) {//滑動結束後移出數據,這裏演示的是側滑刪除
            adapter.getData().remove(position);
            adapter.notifyItemRemoved(position);
        }
    }

實現onSelectedChanged()方法,狀態變化時回調:在拖拽是改變背景顏色

   //狀態變化時回調
    //ACTION_STATE_DRAG(拖拽狀態) ACTION_STATE_IDLE(空閒狀態) ACTION_STATE_SWIPE(滑動狀態)
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {//拖拽時設置背景顏色
            viewHolder.itemView.setBackgroundColor(Color.BLUE);
        }
    }

最後實現clearView()方法,用戶交互時會回調這個方法:

    //用戶交互結束時回調,即手指鬆開時
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setBackgroundColor(0);
    }

5.2設置給RecyclerView

那麼整個自定義的拖拽側滑刪除類就實現了,我們將這個類設置給RecyclerView就可以實現效果了;

//拖拽實現類
ItemTouchHelper helper = new ItemTouchHelper(new MyItemTouchCallback(adapter));
helper.attachToRecyclerView(recyclerView);

效果如下:

給出自定義類的完成代碼:MyItemTouchCallback.java

public class MyItemTouchCallback extends ItemTouchHelper.Callback {

    private final LinearVerticalAdapter adapter;

    public MyItemTouchCallback(LinearVerticalAdapter adapter) {
        this.adapter = adapter;
    }

    //設置支持滑動和拖拽的方向
    //ItemTouchHelper.UP:上移,ItemTouchHelper.DOWN:下移,ItemTouchHelper.LEFT:左移,ItemTouchHelper.RIGHT:右移
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlag;//拖拽方向
        int swipeFlag;//滑動方向
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {//網格佈局時可以左右上下拖拽
            dragFlag = ItemTouchHelper.DOWN | ItemTouchHelper.UP
                    | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;
            swipeFlag = 0;
        } else {//其他佈局時,只能上下拖拽
            dragFlag = ItemTouchHelper.DOWN | ItemTouchHelper.UP;
            swipeFlag = ItemTouchHelper.END;//滑動結束
        }
        return makeMovementFlags(dragFlag, swipeFlag);//返回拖拽和滑動的方向
    }

    //拖拽時回調,在拖拽時不斷回調這個方法
    //我們需要將正在拖拽的item和集合的item進行元素交換,然後通知適配器不斷更新數據
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //當前拖拽item的position
        int fromPosition = viewHolder.getAdapterPosition();
        //拖拽到位置
        int toPosition = target.getAdapterPosition();

        //根據位置重新排序更新數據
        if (fromPosition < toPosition) {
            for (int i = fromPosition; i < toPosition; i++) {
                Collections.swap(adapter.getData(), i, i + 1);
            }
        } else {
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(adapter.getData(), i, i - 1);
            }
        }
        recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    //滑動時回調
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int position = viewHolder.getAdapterPosition();
        if (direction == ItemTouchHelper.END) {//滑動結束後移出數據,這裏演示的是側滑刪除
            adapter.getData().remove(position);
            adapter.notifyItemRemoved(position);
        }
    }

    //狀態變化時回調
    //ACTION_STATE_DRAG(拖拽狀態) ACTION_STATE_IDLE(空閒狀態) ACTION_STATE_SWIPE(滑動狀態)
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {//拖拽時設置背景顏色
            viewHolder.itemView.setBackgroundColor(Color.BLUE);
        }
    }

    //用戶交互結束時回調,即手指鬆開時
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setBackgroundColor(0);
    }

    //是否支持長按拖拽,默認返回true
    @Override
    public boolean isLongPressDragEnabled() {
        return super.isLongPressDragEnabled();
    }
}

至此,本文結束!

源碼地址:https://github.com/FollowExcellence/RecyclerViewDemo

發佈了37 篇原創文章 · 獲贊 28 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章