本週又要來聊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'

就可以了

截圖 2019-12-29 上午1.27.38

截圖 2019-12-29 上午1.27.56

然後點下Sync Now

截圖 2019-12-29 上午1.30.39

即完成


接著再設置RecyclerView前,最重要是先畫Layout ( ・_・)ノ

我就不貼了,直接在Github內

 

activity_main.xml

https://github.com/thumbb13555/RecyclerViewSwipeDelete/blob/master/app/src/main/res/layout/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

基本設置RecyclerVIew

好滴,接著加入滑動事件

請在onCreate的外面(跟MyAdapter同一層)內加入副程式

我是命名recyclerViewAction

並且加入三個接口

  • RecyclerView recyclerView
  • ArrayList<String> choose
  • MyAdapter myAdapter
private void recyclerViewAction(RecyclerView recyclerView, final ArrayList<String> choose, final MyAdapter myAdapter) {
    .
    .
    .
}

接著加入ItemTouchHelper方法

截圖 2019-12-29 下午2.40.24

最後就會得出它的基礎架構

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才有用喔~(・ωー)~☆

這時候執行,就會像這樣

就可以正常的執行刪除跟排序囉~(´υ`)

最後,左滑刪除總覺得還是缺了點什麼...

沒錯~~

Screenshot_20191229-174505_Video Player

左滑的底圖(´・з・)

 

前面提過,這項功能是用第三方庫完成的

但說歸說,總是要會用啊(╭ರ_⊙)

首先一樣在recyclerViewAction副程式內加入新的複寫onChildDraw

截圖 2019-12-29 下午5.53.18

出來長這樣

@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內點擊右鍵

截圖 2019-12-28 下午11.53.17

在new裡面點擊Vector Asset, 進到這畫面

截圖 2019-12-28 下午11.53.45

然後點擊CilpArt

截圖 2019-12-28 下午11.54.21

搜尋到你要的圖,點下OK

截圖 2019-12-29 下午6.10.13

回到原畫面,到Color內把顏色換成白色,點擊Next

截圖 2019-12-29 下午6.11.01

加入!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下拉更新

下拉更新在這裡->http://thumbb13555.pixnet.net/blog/post/312844960-android-studio-%e4%b9%8b%e5%9f%ba%e6%9c%acrecycleview-%e7%94%a8%e6%b3%95-2--%e5%9f%ba%e6%9c%ac%e7%89%88%e4%b8%8b

如果覺得自己RecyclerView不太會的朋友~歡迎參考這兩篇文章喔~

下次見~2019新年快樂!

arrow
arrow

    碼農日常 發表在 痞客邦 留言(4) 人氣()