ItemTouchHelper 實現交互動畫

目錄介紹

  • 01.拖拽需要實現功能
  • 02.幾個重要的方法說明
  • 03.簡單實現思路
  • 04.拖拽效果上優化
  • 05.完整代碼展示

好消息

  • 博客筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!

01.拖拽需要實現功能

  • 需要實現拖拽的功能如下所示
    • 長按item後拖動,與其他item交換位置
    • 按住item右面的圖標後拖動,與其他item交換位置
    • 左滑item變透明並縮小,超出屏幕後,其他item補上
    • 右滑item變透明並縮小,超出屏幕後,其他item補上

02.幾個重要的方法說明

  • 幾個重要的方法說明
    • 需要自定義類實現ItemTouchHelper.Callback類,並重寫其中幾個方法
    isLongPressDragEnabled                  是否可以長按拖拽排序
    isItemViewSwipeEnabled                  Item是否可以被滑動
    getMovementFlags                        當用戶拖拽或者滑動Item的時候需要我們告訴系統滑動或者拖拽的方向
    onMove                                  當Item被拖拽的時候被回調
    onSwiped                                當View被滑動刪除的時候
    onSelectedChanged                       當item被拖拽或側滑時觸發
    

03.簡單實現思路

  • 幾個方法中代碼思路
    • 要想達到上面功能需求,在getMovementFlags方法中,當用戶拖拽或者滑動Item的時候需要我們告訴系統滑動或者拖拽的方向,那我們知道支持拖拽和滑動刪除的無非就是LinearLayoutManager和GridLayoutManager了,所以可以根據佈局管理器的不同做了響應的區分。
    • 在onMove方法中處理拖拽的回調邏輯,那麼什麼時候被調用?當Item被拖拽排序移動到另一個Item的位置的時候被調用。在onSwiped方法[當Item被滑動刪除到不見]中處理被刪除後的邏輯。爲了降低代碼耦合度,可以通過接口listener回調的方式交給外部處理。
  • 上下拖動時與其他item進行位置交換
    • ItemTouchHelper.Callback本身不具備將兩個item互換位置的功能,但RecyclerView可以,我們可以在item拖動的時候把當前item與另一個item的數據位置交換,再調用RecyclerView的notifyItemMoved()方法刷新佈局,同時,因爲RecyclerView自帶item動畫,就可以完成上面的交互效果。
  • 左右滑出屏幕時其他item補上
    • 只要在item滑出屏幕時,將對應的數據刪掉,再調用RecyclerView的notifyItemRemoved()方法刷新佈局即可。

04.拖拽效果上優化

  • 拖拽效果優化
    • 在item被拖拽或側滑時修改背景色,當動作結束後將背景色恢復回來,而ItemTouchHelper.Callback中正好有對應這兩個狀態的方法,分別是:onSelectedChanged()、clearView()。那麼優化處理其實可以放到這兩個方法中處理。
    • 左右滑動使item透明度變淺且縮小該如何實現呢?讓item執行了兩種屬性動畫而已,在ItemTouchHelper.Callback中有一個方法可以拿到item被拖拽或滑動時的位移變化,那就是onChildDraw()方法,在該方法中設置item漸變和縮放屬性動畫。
    • 出現問題,按照上面做法會出現刪除後有空白item留出來,那麼爲什麼會出現這種情況呢?並不是多出了兩條空白數據,它們是正常的數據,只是看不到了,這是因爲RecyclerView條目(itemView)覆用導致的,前面在onChildDraw()方法中對itemView設置了透明和縮小,而一個列表中固定只有幾個itemView而已,當那兩個透明縮小的itemView被再次使用時,之前設置的透明度和高度比例已經是0,所以就出現了這種情況,解決方法也很簡單,只要在item被移除後,將itemView的透明度和高度比例設置回來即可

05.完整代碼展示

  • 代碼的GitHub地址:https://github.com/yangchong211/YCRefreshView
  • 完整代碼如下所示
    /**
     * <pre>
     *     @author 楊充
     *     blog  : https://github.com/yangchong211
     *     time  : 2017/5/2
     *     desc  : 自定義ItemTouchHelper
     *     revise: 參考嚴正傑大神博客:https://blog.csdn.net/yanzhenjie1003/article/details/51935982
     * </pre>
     */
    public class ItemTouchHelpCallback extends ItemTouchHelper.Callback {
    
        /**
         * Item操作的回調,去更新UI和數據源
         */
        private OnItemTouchCallbackListener onItemTouchCallbackListener;
        /**
         * 是否可以拖拽
         */
        private boolean isCanDrag = false;
        /**
         * 是否可以被滑動
         */
        private boolean isCanSwipe = false;
        /**
         * 按住拖動item的顏色
         */
        private int color = 0;
    
        public ItemTouchHelpCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {
            this.onItemTouchCallbackListener = onItemTouchCallbackListener;
        }
    
        /**
         * 設置是否可以被拖拽
         *
         * @param canDrag 是true,否false
         */
        public void setDragEnable(boolean canDrag) {
            isCanDrag = canDrag;
        }
    
        /**
         * 設置是否可以被滑動
         *
         * @param canSwipe 是true,否false
         */
        public void setSwipeEnable(boolean canSwipe) {
            isCanSwipe = canSwipe;
        }
    
        /**
         * 設置按住拖動item的顏色
         * @param color     顏色
         */
        public void setColor(@ColorInt int color){
            this.color = color;
        }
    
        /**
         * 當Item被長按的時候是否可以被拖拽
         *
         * @return                      true
         */
        @Override
        public boolean isLongPressDragEnabled() {
            return isCanDrag;
        }
    
        /**
         * Item是否可以被滑動(H:左右滑動,V:上下滑動)
         * isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以
         * @return                      true
         */
        @Override
        public boolean isItemViewSwipeEnabled() {
            return isCanSwipe;
        }
    
        /**
         * 當用戶拖拽或者滑動Item的時候需要我們告訴系統滑動或者拖拽的方向
         * 動作標識分:dragFlags和swipeFlags
         * dragFlags:列表滾動方向的動作標識(如豎直列表就是上和下,水平列表就是左和右)
         * wipeFlags:與列表滾動方向垂直的動作標識(如豎直列表就是左和右,水平列表就是上和下)
         *
         * 思路:如果你不想上下拖動,可以將 dragFlags = 0
         *      如果你不想左右滑動,可以將 swipeFlags = 0
         *      最終的動作標識(flags)必須要用makeMovementFlags()方法生成
         */
        @Override
        public int getMovementFlags(@NonNull RecyclerView recyclerView,
                                    @NonNull RecyclerView.ViewHolder viewHolder) {
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                // flag如果值是0,相當於這個功能被關閉
                int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT
                        | ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                int swipeFlag = 0;
                // create make
                return makeMovementFlags(dragFlag, swipeFlag);
            } else if (layoutManager instanceof LinearLayoutManager) {
                LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                int orientation = linearLayoutManager.getOrientation();
    
                int dragFlag = 0;
                int swipeFlag = 0;
    
                // 爲了方便理解,相當於分爲橫着的ListView和豎着的ListView
                // 如果是橫向的佈局
                if (orientation == LinearLayoutManager.HORIZONTAL) {
                    swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                    dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                } else if (orientation == LinearLayoutManager.VERTICAL) {
                    // 如果是豎向的佈局,相當於ListView
                    dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                    swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                }
                //第一個參數是拖拽flag,第二個是滑動的flag
                return makeMovementFlags(dragFlag, swipeFlag);
            }
            return 0;
        }
    
    
        /**
         * 當Item被拖拽的時候被回調
         *
         * @param recyclerView          recyclerView
         * @param srcViewHolder         當前被拖拽的item的viewHolder
         * @param targetViewHolder      當前被拖拽的item下方的另一個item的viewHolder
         * @return                      是否被移動
         */
        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView,
                              @NonNull RecyclerView.ViewHolder srcViewHolder,
                              @NonNull RecyclerView.ViewHolder targetViewHolder) {
            if (onItemTouchCallbackListener != null) {
                int srcPosition = srcViewHolder.getAdapterPosition();
                int targetPosition = targetViewHolder.getAdapterPosition();
                return onItemTouchCallbackListener.onMove(srcPosition, targetPosition);
            }
            return false;
        }
    
    
        /**
         * 當item側滑出去時觸發(豎直列表是側滑,水平列表是豎滑)
         *
         * @param viewHolder            viewHolder
         * @param direction             滑動的方向
         */
        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
            if (onItemTouchCallbackListener != null) {
                onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
            }
        }
    
        /**
         * 當item被拖拽或側滑時觸發
         *
         * @param viewHolder            viewHolder
         * @param actionState           當前item的狀態
         */
        @Override
        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
            super.onSelectedChanged(viewHolder, actionState);
            //不管是拖拽或是側滑,背景色都要變化
            if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                if (color==0){
                    viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext()
                            .getResources().getColor(android.R.color.darker_gray));
                }else {
                    viewHolder.itemView.setBackgroundColor(color);
                }
            }
        }
    
    
        /**
         * 當item的交互動畫結束時觸發
         *
         * @param recyclerView          recyclerView
         * @param viewHolder            viewHolder
         */
        @Override
        public void clearView(@NonNull RecyclerView recyclerView,
                              @NonNull RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
            viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources()
                    .getColor(android.R.color.white));
            viewHolder.itemView.setAlpha(1);
            viewHolder.itemView.setScaleY(1);
        }
    
    
        @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);
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
                viewHolder.itemView.setAlpha(value);
                viewHolder.itemView.setScaleY(value);
            }
        }
    
    
        public interface OnItemTouchCallbackListener {
            /**
             * 當某個Item被滑動刪除的時候
             *
             * @param adapterPosition   item的position
             */
            void onSwiped(int adapterPosition);
    
            /**
             * 當兩個Item位置互換的時候被回調
             *
             * @param srcPosition       拖拽的item的position
             * @param targetPosition    目的地的Item的position
             * @return                  開發者處理了操作應該返回true,開發者沒有處理就返回false
             */
            boolean onMove(int srcPosition, int targetPosition);
        }
    }
    
  • 如何使用,代碼如下所示
    ItemTouchHelpCallback callback = new ItemTouchHelpCallback((srcPosition, targetPosition) -> {
            if (imageBeans != null) {
                try {
                    // 更換數據源中的數據Item的位置。更改list中開始和結尾position的位置
                    Collections.swap(imageBeans, srcPosition, targetPosition);
                    // 更新UI中的Item的位置,主要是給用戶看到交互效果
                    mAdapter.notifyItemMoved(srcPosition, targetPosition);
                } catch (Exception e){
                    e.printStackTrace();
                }
                return true;
            }
            return true;
        });
    callback.setDragEnable(true);
    callback.setSwipeEnable(true);
    callback.setColor(this.getResources().getColor(R.color.base_background_block));
    //創建helper對象,callback監聽recyclerView item 的各種狀態
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
    try{
        //關聯recyclerView,一個helper對象只能對應一個recyclerView
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }catch (Exception e){
        e.printStackTrace();
    }
    

開源庫地址[融合大多數recyclerView使用案例,可以直接下載demo]:https://github.com/yangchong211/YCRefreshView

  • 00.RecyclerView複雜封裝庫
    • 幾乎融合了該系列博客中絕大部分的知識點,歡迎一遍看博客一遍實踐,一步步從簡單實現功能強大的庫
  • 01.RecyclerView
    • RecycleView的結構,RecyclerView簡單用法介紹
  • 02.Adapter
    • RecyclerView.Adapter扮演的角色,一般常用的重寫方法說明,數據變更通知之觀察者模式,查看.notifyChanged();源碼
  • 03.ViewHolder
    • ViewHolder的作用,如何理解對於ViewHolder對象的數量“夠用”之後就停止調用onCreateViewHolder方法,ViewHolder簡單封裝
  • 04.LayoutManager
    • LayoutManager作用是什麼?setLayoutManager源碼分析
  • 05.SnapHelper
    • SnapHelper作用,什麼是Fling操作 ,SnapHelper類重要的方法,
  • 06.ItemTouchHelper
  • 07.SpanSizeLookup
    • SpanSizeLookup如何使用,同時包含列表,2列的網格,3列的網格如何優雅實現?
  • 08.ItemDecoration
    • ItemDecoration的用途,addItemDecoration()源碼分析
  • 09.RecycledViewPool
    • RecyclerViewPool用於多個RecyclerView之間共享View。
  • 11.RecyclerView上拉加載
    • 添加recyclerView的滑動事件,上拉加載分頁數據,設置上拉加載的底部footer佈局,顯示和隱藏footer佈局
  • 12.RecyclerView緩存原理
    • RecyclerView做性能優化要說複雜也複雜,比如說佈局優化,緩存,預加載,複用池,刷新數據等等
  • 13.SnapHelper源碼分析
    • SnapHelper旨在支持RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點或者容器中的任何像素點。
  • 16.自定義SnapHelper
    • 自定義SnapHelper
  • 19.自定義ItemDecoration分割線
    • 需要自定義類實現RecyclerView.ItemDecoration類,並選擇重寫合適方法
  • 22.RecyclerView問題彙總
    • getLayoutPosition()和getAdapterPosition()的區別
  • 23.RecyclerView滑動衝突
    • 01.如何判斷RecyclerView控件滑動到頂部和底部
    • 02.RecyclerView嵌套RecyclerView 條目自動上滾的Bug
    • 03.ScrollView嵌套RecyclerView滑動衝突
    • 04.ViewPager嵌套水平RecyclerView橫向滑動到底後不滑動ViewPager
    • 05.RecyclerView嵌套RecyclerView的滑動衝突問題
    • 06.RecyclerView使用Glide加載圖片導致圖片錯亂問題解決
  • 24.ScrollView嵌套RecyclerView問題
    • 要實現在NestedScrollView中嵌入一個或多個RecyclerView,會出現滑動衝突,焦點搶佔,顯示不全等。如何處理?

其他介紹

01.關於博客彙總鏈接

02.關於我的博客

  • 我的個人站點:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:[email protected]
  • 阿里雲博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e

代碼案例:https://github.com/yangchong211/YCRefreshView

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章