本週又要來聊RecyclerView ʕ•㉨•ʔ <-這是熊
之前聊過關於RecyclerView基本設置
2020更新
目前本網誌已更新五篇關於RecyclerView的文章
歡迎參考
碼農日常-『Android studio』基本RecyclerView用法
碼農日常-『Android studio』基本RecyclerView 用法-2 基本版下拉更新以及點擊事件
碼農日常-『Android studio』基本RecyclerView 用法-3 RecyclerView上下滑動排序與側滑刪除(RecyclerView Swipe)
碼農日常-『Android studio』基本RecyclerView 用法-4 左滑顯示Button Menu
碼農日常-『Android studio』進階RecyclerView 用法-5 RecyclerView item混合介面
在做專案時,為了使用者的方便
一些列表操作的元件都會使用上下左右的控制事件
像Google信箱就是個好例子
雖然我是不知道他是不是用了這個元件啦
但是說真的像這樣的操作深深地滲到了我們的身邊
因此今天就是要來聊關於RecyclerView的滑動各種事件
包含左右滑動刪除、上下拖曳等
而其核心就是ItemTouchHelper在處理
關於ItemTouchHelper的官方文件->https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper
對於這類動態餐做事件..我的感想是
基本上只要搞清楚ItemTouchHelper以及該可使用的複寫(Override)方法們以及回傳值,就可以搞定絕大多數的相關需求了٩(•౪•٩)三
好啦,了解這些後,事不宜拖台錢,開始(っ・∀・)っ
首先是完成圖
Gif畫質不好請見諒...
再來是Github
https://github.com/thumbb13555/RecyclerViewSwipeDelete
好的,那麼來描述一下這次的需求
1.生成RecyclerView,內容為A~K
2.K必須置底不可被變換位置
3.其他的字母可以拖曳滑動改變順序
4.左滑或右滑都可以刪除,並在空白處帶入紅底刪除提示
在開始之前我導入了一個第三方的函式庫
https://github.com/xabaras/RecyclerViewSwipeDecorator
這是一個在左右滑動刪除時帶入底圖的函式庫
我覺得還挺方便的,之前有試著不靠函式庫寫過,真的是會寫到起笑(*-ω-)
我是覺得如果沒有特別要什麼功能,就用它吧!而且真的好寫XD
導入函式很簡單,請在build.gradle(Module:app)內的dependencies內加入
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.2.2'
就可以了
然後點下Sync Now
即完成
接著再設置RecyclerView前,最重要是先畫Layout ( ・_・)ノ
我就不貼了,直接在Github內
activity_main.xml
item.xml (RecyclerView配合的列表內容)
https://github.com/thumbb13555/RecyclerViewSwipeDelete/blob/master/app/src/main/res/layout/item.xml
再來就是要設置資料以及RecyclerView的Adapter
public class MainActivity extends AppCompatActivity {
String[] A2J = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
ArrayList<String> arrayList = new ArrayList<>();
RecyclerView recyclerView;
MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//生成資料
for (int i = 0; i < A2J.length; i++) {
arrayList.add(A2J[i]);
}
}
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
}
紅色部分是Adapter的設置
忘記如何設置的朋友可以回去參考我之前寫的文章(・ωー)~☆
在這裡->http://thumbb13555.pixnet.net/blog/post/311803031
藍色的部分則是生成資料
放Log去觀察arrayList的話可以看到會生成一個A~K的陣列
OK,再加入一般設置的內容
public class MainActivity extends AppCompatActivity {
String[] A2J = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
ArrayList<String> arrayList = new ArrayList<>();
RecyclerView recyclerView;
MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//生成資料
for (int i = 0; i < A2J.length; i++) {
arrayList.add(A2J[i]);
}
//設置RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
}
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView_item);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(arrayList.get(position));
}
@Override
public int getItemCount() {
return arrayList.size();
}
}
}
如此這般如此這般~就完成了RecyclerView的基本設置(・ω・)b
好滴,接著加入滑動事件
請在onCreate的外面(跟MyAdapter同一層)內加入副程式
我是命名recyclerViewAction
並且加入三個接口
RecyclerView recyclerView
ArrayList<String> choose
MyAdapter myAdapter
private void recyclerViewAction(RecyclerView recyclerView, final ArrayList<String> choose, final MyAdapter myAdapter) {
.
.
.
}
接著加入ItemTouchHelper方法
最後就會得出它的基礎架構
private void recyclerViewAction(RecyclerView recyclerView, final ArrayList<String> choose, final MyAdapter myAdapter){
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return 0; //這裡是告訴RecyclerView你想開啟哪些操作
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;//管理上下拖曳
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//管理滑動情形
}
});
}
首先來破解綠色部分
綠色部分的重點是要回傳你想要有哪些操作
例如像我的範例,是要上下左右都操作的狀況
就在return的部分打上
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN , ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
如果不要上下只要左右則是
return makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
反之則是
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN ,0);
如果只要左不要右就是
return makeMovementFlags(0,temTouchHelper.RIGHT);
大概就是按照這樣的規則跑
於是這邊輸入像下面程式這樣
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN
, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
這時候按執行就會發現,項目可以上下滑動了!
(`▽´)
只是大概會這樣
拖曳後會回到自己的位置、左右側滑項目會消失但是下面的物件不會遞補(ノ`□´)ノ⌒┻━┻
沒有啦~其實這是一個很簡單的概念
因為本身RecyclerView就是將陣列具象化的一種表現
而又因RecyclerView很貼心地幫你完成視覺配置了
所以只要你來完成陣列修改就好(´υ`)
於是乎,關於陣列修改刪除的問題我是還沒寫文章啦..
但是不清楚的人可以Google這幾個關鍵字
ArrayList remove
ArrayList set
Adapter notifyItemMoved
Adapter notifyItemRemoved
Adapter notifyDataSetChanged
等等逐一去了解
廢話不多我先貼上onMove 橘色部分的程式
@Override
public boolean onMove(@NonNull RecyclerView recyclerView
, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int position_dragged = viewHolder.getAdapterPosition();
int position_target = target.getAdapterPosition();
Collections.swap(choose, position_dragged, position_target);
myAdapter.notifyItemMoved(position_dragged, position_target);
if (choose.contains("K") && choose.indexOf("K") != choose.size() - 1) {
Collections.swap(choose, position_target, position_dragged);
myAdapter.notifyItemMoved(position_target, position_dragged);
}
return false;
}
橘色的部分是變換的程式,可以直接抄過去
橘色前兩行是定義一起始位置以及最終變換位置
橘色第三行是利用java內的集合函式處理陣列位置修改
最後橘色第四行則是通知Adapter元素有成功變換位置
姑且先不輸入藍色部分,紅色部分執行的話就可以得出想要的效果了
藍色部分則是為了完成第二項要求:K必須置底不能被變換位置
就邏輯而言這不是一個最好的做法┐(´д`)┌
因為我只是把陣列比對後,若選中值為K的情形就把它放回原位罷了
我就不再多說
最後是紅色部分
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//管理滑動情形
}
在這邊可以管理說什麼樣的手勢做什麼樣的事情
並且在內回傳一回傳值direction
因此我要在左右滑動時刪除資料的話,其對應動作就在這設定啦~(・ω・`)………..
直接上程式,應該就好理解了!
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
switch (direction) {
case ItemTouchHelper.LEFT:
case ItemTouchHelper.RIGHT:
choose.remove(position);
myAdapter.notifyItemRemoved(position);
break;
}
}
最後整體recyclerViewAction長這樣
private void recyclerViewAction(RecyclerView recyclerView, final ArrayList<String> choose, final MyAdapter myAdapter) {
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN
, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView
, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int position_dragged = viewHolder.getAdapterPosition();
int position_target = target.getAdapterPosition();
Collections.swap(choose, position_dragged, position_target);
myAdapter.notifyItemMoved(position_dragged, position_target);
if (choose.contains("K") && choose.indexOf("K") != choose.size() - 1) {
Collections.swap(choose, position_target, position_dragged);
myAdapter.notifyItemMoved(position_target, position_dragged);
}
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
switch (direction) {
case ItemTouchHelper.LEFT:
case ItemTouchHelper.RIGHT:
choose.remove(position);
myAdapter.notifyItemRemoved(position);
break;
}
}
helper.attachToRecyclerView(recyclerView);
}
喔對了,設定完後最後要記得把這些設定帶入給RecyclerView才有用喔~(・ωー)~☆
這時候執行,就會像這樣
就可以正常的執行刪除跟排序囉~(´υ`)
最後,左滑刪除總覺得還是缺了點什麼...
沒錯~~
左滑的底圖(´・з・)
前面提過,這項功能是用第三方庫完成的
但說歸說,總是要會用啊(╭ರ_⊙)
首先一樣在recyclerViewAction副程式內加入新的複寫onChildDraw
出來長這樣
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
onChildDraw顧名思義,就是一個在每個RecyclerView的Child下面做的事情
之前寫別功能時也都是用這個方法處理
那我們在開始前可以看一下他這個庫的官方介紹
https://github.com/xabaras/RecyclerViewSwipeDecorator
底下有教你寫在哪裡,怎麼用,以及有哪些函式可以用
建議英文一定程度的就去看一看囉(b^_^)b
好的那我們加入這行
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
new RecyclerViewSwipeDecorator.Builder(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive)
.addBackgroundColor(ContextCompat.getColor(MainActivity.this,android.R.color.holo_red_dark))
.addActionIcon(R.drawable.ic_delete_black_24dp)
.create()
.decorate();
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
就可以完成了!
對了關於Icon圖標,我是使用Android內建的圖庫
首先在res內的drawable內點擊右鍵
在new裡面點擊Vector Asset, 進到這畫面
然後點擊CilpArt
搜尋到你要的圖,點下OK
回到原畫面,到Color內把顏色換成白色,點擊Next
加入!Coding ! Finish!
最後全部長這樣
public class MainActivity extends AppCompatActivity {
String[] A2J = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
ArrayList<String> arrayList = new ArrayList<>();
RecyclerView recyclerView;
MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//生成資料
for (int i = 0; i < A2J.length; i++) {
arrayList.add(A2J[i]);
}
//設置RecyclerView
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
//設置RecyclerView滑動事件
recyclerViewAction(recyclerView, arrayList, myAdapter);
}
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView_item);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(arrayList.get(position));
}
@Override
public int getItemCount() {
return arrayList.size();
}
}
private void recyclerViewAction(RecyclerView recyclerView, final ArrayList<String> choose, final MyAdapter myAdapter) {
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN
, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView
, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int position_dragged = viewHolder.getAdapterPosition();
int position_target = target.getAdapterPosition();
Collections.swap(choose, position_dragged, position_target);
myAdapter.notifyItemMoved(position_dragged, position_target);
if (choose.contains("K") && choose.indexOf("K") != choose.size() - 1) {
Collections.swap(choose, position_target, position_dragged);
myAdapter.notifyItemMoved(position_target, position_dragged);
}
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
switch (direction) {
case ItemTouchHelper.LEFT:
case ItemTouchHelper.RIGHT:
choose.remove(position);
myAdapter.notifyItemRemoved(position);
break;
}
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
new RecyclerViewSwipeDecorator.Builder(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive)
.addBackgroundColor(ContextCompat.getColor(MainActivity.this,android.R.color.holo_red_dark))
.addActionIcon(R.drawable.ic_delete_black_24dp)
.create()
.decorate();
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
});
helper.attachToRecyclerView(recyclerView);
}
}
結論:
今天的文章是RecyclerView進階應用型之一
而且還算簡單的(´・_・`)
所以應該算基本應用型之一了吧..
其實每個禮拜寫文章的時候都覺得有好多可以寫
但是要真正決定要寫什麼還蠻難決定的
當初我在寫這個功能的時候,我相信RecyclerView在幾年前早就一堆人用得得心應手了
包含今天介紹的這個功能
但是每次找網路文章時,看見的大多是簡體的介紹...
也就是都是大陸地區的朋友寫的
也算基於一種好勝心吧!多少希望繁體的文章也能越來越有一些稀奇古怪的功能
也許這樣的文章沒什麼人欣賞,點閱率也低
但是這個部落格還是會努力寫出好懂,好抄,以及稀奇古怪的功能來幫助大家
最後還是要推廣一下
RecyclerView基本設置
基本設置在這裡->http://thumbb13555.pixnet.net/blog/post/311803031
以及RecyclerView下拉更新
如果覺得自己RecyclerView不太會的朋友~歡迎參考這兩篇文章喔~
下次見~2019新年快樂!