跨多個RecyclerView拖動排序

先介紹一下本文出現的背景,項目中有這樣一個需求,拖動一個列表的某一項排序或者拖動到另外一個列表中。

其效果如下圖:

這裏寫圖片描述

拖動排序在RecyclerView有現成的解決方案,配合ItemTouchHelper即可輕鬆實現。但是要將一個列表中的Item拖動到另一個列表中去,就不那麼容易了。

先來看看這種跨界面實現由那些難點,我們先分析,再一個一個的解決。

  • 拖動視圖的保存,ItemTouchHelper的實現思路是獲取到長按的View後,對這個View做Translate位移,但是這樣做的侷限在於這個View就只能在它所屬的RecyclerView中移動,超出的部分就被隱藏了。
  • 當拖動Item到邊緣後,兩個界面怎麼切換,是使用系統的ViewPager還是自定義ViewGroup。
  • 在一個RecyclerView中拖動和跨多個拖動用到的策略是否一樣。
  • 由於RecyclerView已經接收了觸摸事件,那麼怎麼獲取觸摸信息,比如判斷滑到邊緣了,以及Item是否長按。

現在我們來一個一個的解決上面的問題:
1. 對於第一個問題,解決方法是當判斷Item被長按後,將當前Item記錄爲一張圖片,然後設置給一個隱藏的ImageView,對這個ImageView做Translate平移。這裏需要額外費心的是這個ImageView與長按Item視圖的重合以及顯隱判斷。
2. 如果使用ViewPager,由於要跨越兩個page,那麼觸摸事件,保存的圖片等都要交給其父控制器,還不如自定義一個ViewGroup。
3. 由於系統提供了在一個RecyclerView中拖動的Helper類,我們無需去解決拖動的起始位置這些,那何樂不爲呢?所以,跨界面拖動用自己的實現。
4. 觸摸point信息的獲取:dispatchTouchEvent方法是最先到的,在我們自定義的ViewPager中重寫該方法。在adapter中對itemview添加手勢監聽,獲取到被長按的View。

下面來看代碼實現:

(1) 自定義的父視圖:

public class NewLinearLayout extends LinearLayout {

    /*將觸摸點座標傳遞出去*/
    private IGetX iGetX;
    /*彈性滑動*/
    private Scroller mScroller;

    public NewLinearLayout(Context context) {
        super(context);
        init(context);
    }

    public NewLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public NewLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /*initial*/
    public void init(Context context){
        mScroller = new Scroller(context);
    }

    @Override
    public void computeScroll(){
        super.computeScroll();
        /*判斷是否完成滑動*/
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX() , 0);
            /*循環調用該方法*/
            postInvalidate();
        }
    }

    /*開啓彈性滑動*/
    public void beginScroll(int dx ){
        mScroller.startScroll(mScroller.getCurrX() , 0 , dx , 0 , 1000);
        /*調用computeScroll()方法*/
        invalidate();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event){
        float dx = event.getX();
        float dy = event.getY();
        if(event.getAction() == MotionEvent.ACTION_MOVE)
            iGetX.point(dx , dy);
        return super.dispatchTouchEvent(event);
    }

    public void set(IGetX iGetX){
        this.iGetX = iGetX;
    }

    public interface IGetX{
        void point(float dx , float dy);
    }
}

(2) View轉bitmap

    public Bitmap view2bitmap(View view) {
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        view.draw(new Canvas(bitmap));
        return bitmap;
    }

(3) Adapter的實現

    private class Recycler1Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        private ArrayList<String> mData;

        public void setData(ArrayList<String> mData) {
            this.mData = mData;
        }

        public void addData(ArrayList<String> mData) {
            this.mData.addAll(mData);
            notifyDataSetChanged();
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(SpanDragActivity.this).inflate(R.layout.item_launch_title, null);
            final Recycler1ViewHolder viewHolder = new Recycler1ViewHolder(itemView);
            viewHolder.itemView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    mDetector.onTouchEvent(event);
                    if(event.getAction() == MotionEvent.ACTION_DOWN){
                        bitmap = null;
                        TempItemView = viewHolder.itemView;
                        startX = TempItemView.getLeft();
                        startY = TempItemView.getTop();
                    }
                    /*恢復計點*/
                    if(event.getAction() == MotionEvent.ACTION_UP){
                        isFirstPoint = true;
                    }

                    return false;
                }
            });
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ((Recycler1ViewHolder) holder).mTv.setText(mData.get(position));
        }

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

        /*create viewholder*/
        class Recycler1ViewHolder extends RecyclerView.ViewHolder {

            private TextView mTv;

            public Recycler1ViewHolder(View itemView) {
                super(itemView);
                mTv = (TextView) itemView.findViewById(R.id.item_launch_title_tv_title);
            }
        }
    }

(4) 滑動到邊緣後觸發的事件

        /*設置接口獲取手指位置*/
        activitySpanLl.set(new NewLinearLayout.IGetX() {
            @Override
            public void point(float dx , float dy) {
                Log.v("out", "手指位置:" + dx);
                if(isFirstPoint){
                    firstX = dx;
                    firstY = dy;
                    isFirstPoint = false;
                }
                if(bitmap != null) {
                    activitySpanImg.setTranslationX(dx-firstX+startX);
                    activitySpanImg.setTranslationY(dy-firstY+startY);
                }
                if(dx >=800){
                }
                if (isFirstScroll)
                    if (dx >= 1000) {
                        activitySpanLl.beginScroll(1080);
                        isFirstScroll = false;
                        /*向右邊滑動了,所以現在可以向左滑動了*/
                        isFirstLeftScroll = true;
                        int a = activitySpanLl.getScrollX();
                        float b = activitySpanLl.getX();
                    }
                if(isFirstLeftScroll)
                    if(dx <= 80){
                        activitySpanLl.beginScroll(-1080);
                        isFirstLeftScroll = false;
                        /*向左邊滑動了,所以現在可以向右滑動了*/
                        isFirstScroll = true;
                        int a = activitySpanLl.getScrollX();
                        float b = activitySpanLl.getX();
                    }
            }
        });
基本思路和重要的代碼都在上面了,在實際項目中應用還需要對座標,滑動等作出優化。

有更多問題留言交流。

注意:
因爲電腦更換,本demo源碼已經找不到了,但是鑑於很多人需要,所以這周我重新寫一個demo,需要的先加這個QQ羣(之前由於一些原因博客停止更新,接下來會繼續更新,主要是音視頻相關的,也可以入羣交流)

                            703660703

歡迎入羣

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