Android RecyclerView 拖曳item復(fù)制到其他界面上

簡(jiǎn)述

在A(yíng)ndroid 開(kāi)發(fā)中昧狮,可能有些小伙伴會(huì)遇到類(lèi)似這種功能:
1.在畫(huà)板上添加一些貼圖明场,從圖片列表中拖曳到畫(huà)板上
2.將某個(gè)新聞條目拖動(dòng)到收藏夾包里保存
對(duì)此寫(xiě)了關(guān)于RecyclerView 簡(jiǎn)單拖曳復(fù)制View的文章 北秽,給小伙伴們提供一下靈感和思路,也算是拋磚引玉,有更好的文章或者想法也希望能在評(píng)論區(qū)留言一下畦韭,共同進(jìn)步断盛。
老規(guī)矩罗洗,先上圖:


錄制效果圖.gif
解決思路

首先我們把gif圖所展示整體功能拆分成幾個(gè)步驟
1.item長(zhǎng)按點(diǎn)擊時(shí)生成一個(gè)新的View
2.View 的滑動(dòng)處理事件效果編輯
3.解決RecyclerView 與生成的View 在觸摸事件上的沖突
這邊會(huì)通過(guò)代碼和文字把以上這幾個(gè)問(wèn)題如何一一解決呈現(xiàn)給小伙伴們,先提出總提方向是讓大家有個(gè)大致思路钢猛,再往下就比較好理解伙菜。

頁(yè)面布局(只截圖不給代碼,相信小伙伴們能看懂)

適配器布局:


item_pic.png

主界面布局:


activity_main.png
代碼部分

PicAdapter的實(shí)現(xiàn)非常簡(jiǎn)單命迈,也不是本次討論重點(diǎn)贩绕,這邊主要就是就把長(zhǎng)按點(diǎn)擊通過(guò)接口回調(diào)處理
代碼如下:

public class PicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context context;
    private List<DataEntity> dataEntityList;
    private OnItemLongClickListener onItemLongClickListener;


    public PicAdapter(Context context, List<DataEntity> dataEntityList, OnItemLongClickListener onItemLongClickListener) {
        this.context=context;
        this.dataEntityList = dataEntityList;
        this.onItemLongClickListener=onItemLongClickListener;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(context).inflate(R.layout.item_pic,null);
        ViewHolder viewHolder=new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ((ViewHolder) holder).lyItem.setBackgroundColor(Color.parseColor(dataEntityList.get(position).getColor()));
        ((ViewHolder) holder).tvView.setText(dataEntityList.get(position).getName());
        ((ViewHolder) holder).lyItem.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(onItemLongClickListener!=null)onItemLongClickListener.onItemClickEvent(v,position);
                return true;
            }
        });
    }

    @Override
    public int getItemCount() {
        return dataEntityList==null? 0: dataEntityList.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder{
         LinearLayout lyItem;
         TextView tvView;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            lyItem=itemView.findViewById(R.id.lyItem);
            tvView=itemView.findViewById(R.id.tvView);
        }
    }

    public interface OnItemLongClickListener {
        void onItemClickEvent(View view, int selectPosition);
    }
}

DataEntity 是一個(gè)擁有名字和顏色屬性的簡(jiǎn)單類(lèi),用在主界面添加循環(huán)生成隨機(jī)顏色的集合對(duì)象,代碼如下:

public class DataEntity {
    private String name;
    private String color;

    public DataEntity(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

MainActivity的所有代碼(有的小伙伴看不懂就可以往下看我簡(jiǎn)單的解釋?zhuān)?/p>

public class MainActivity extends AppCompatActivity {
    private Button btnTool;
    private RecyclerView ryTool;
    private LinearLayout lyTool,deleteView;
    private RelativeLayout rlView;
    private boolean showToolView ,itemPress;
    private PicAdapter picAdapter;
    private List<DataEntity>dataEntityList=new ArrayList<>();
    private List<View> copyView=new ArrayList<>();
    private View.OnTouchListener onTouchListener;
    private int startY;
    private int startX;
    private boolean ryCanScroll=true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initAdapter();
        initListener();
    }

    private void initListener() {
        btnTool.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showRy(showToolView=!showToolView);
            }
        });
        onTouchListener=new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                moveViewEvent( v,  event);
                return true;
            }
        };

        ryTool.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                //當(dāng)生成復(fù)制View 的時(shí)候壶愤,禁止RecyclerView 滑動(dòng)
                return !ryCanScroll;
            }

            @Override
            public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                //觸發(fā)長(zhǎng)按之后,item的觸摸事件來(lái)到這里了.MotionEvent返回手指移動(dòng)的位置.以及up事件
                if(copyView.size()>0 && itemPress){
                    ryMoveEvent(copyView.get(copyView.size()-1),e);
                }
            }

            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

            }
        });
    }

    private void ryMoveEvent(View v, MotionEvent event) {
        if(v==null)return;
        v.setScaleX(1.3f);
        v.setScaleY(1.3f);
        int[] location = new int[2];
        v.getLocationOnScreen(location);
        //手指按下后View 產(chǎn)生位移偏差淑倾,好看得出復(fù)制了
        startX = location[0]+dp2px(this,30);
        startY = location[1]+dp2px(this,60);;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                break;
            case MotionEvent.ACTION_MOVE:

                //獲取移動(dòng)后的坐標(biāo)
                int moveX = (int) event.getRawX();
                int moveY = (int) event.getRawY();
                //拿到手指移動(dòng)距離的大小
                int move_bigX = moveX - startX;
                int move_bigY = moveY - startY;

                //拿到當(dāng)前控件未移動(dòng)的坐標(biāo):只需要計(jì)算該控件離左邊和上邊的距離即可
                int left = v.getLeft();
                int top = v.getTop();
                left += move_bigX;
                top += move_bigY;
                RelativeLayout.LayoutParams params= new RelativeLayout.LayoutParams(dp2px(this,100),dp2px(this,80));
                params.setMargins(left, top, 0, 0);
                v.setLayoutParams(params);
                startX = moveX;
                startY = moveY;
                break;
            case MotionEvent.ACTION_CANCEL://手指抬起來(lái)的同時(shí)判斷是否紅色范圍內(nèi),是則回收該View
            case MotionEvent.ACTION_UP:
                v.setScaleX(1.0f);
                v.setScaleY(1.0f);
                itemPress=false;
                ryCanScroll=true;
                if(isInChangeImageZone(deleteView,(int)event.getRawX(),(int)event.getRawY())){
                    copyView.remove(v);
                    rlView.removeView(v);
                }
                break;
        }
    }

    private void moveViewEvent(View v, MotionEvent event) {
        if(v==null)return;
        v.setScaleX(1.3f);
        v.setScaleY(1.3f);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //獲取當(dāng)前按下的坐標(biāo)
                startX = (int) event.getRawX();
                startY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                //獲取移動(dòng)后的坐標(biāo)
                int moveX = (int) event.getRawX();
                int moveY = (int) event.getRawY();
                //拿到手指移動(dòng)距離的大小
                int move_bigX = moveX - startX;
                int move_bigY = moveY - startY;
                //拿到當(dāng)前控件未移動(dòng)的坐標(biāo):只需要計(jì)算該控件離左邊和上邊的距離即可
                int left = v.getLeft();
                int top = v.getTop();
                left += move_bigX;
                top += move_bigY;
                RelativeLayout.LayoutParams params= new RelativeLayout.LayoutParams(dp2px(this,100),dp2px(this,80));
                params.setMargins(left, top, 0, 0);
                v.setLayoutParams(params);
                startX = moveX;
                startY = moveY;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                v.setScaleX(1.0f);
                v.setScaleY(1.0f);
                if(isInChangeImageZone(deleteView,(int)event.getRawX(),(int)event.getRawY())){
                    copyView.remove(v);
                    rlView.removeView(v);
                }
                break;
        }
    }

    private void initAdapter() {
        for (int i=0;i<18;i++){
            DataEntity dataEntity=new DataEntity("圖片"+i,getRandColor());
            dataEntityList.add(dataEntity);
        }
        picAdapter=new PicAdapter(this, dataEntityList, new PicAdapter.OnItemLongClickListener() {
            @Override
            public void onItemClickEvent(View view, int selectPosition) {
                //長(zhǎng)按
                copyItem(view ,selectPosition);
                itemPress=true;
                ryCanScroll=false;

            }
        });

        ryTool.setLayoutManager(new GridLayoutManager(this,3));
        ryTool.setAdapter(picAdapter);

    }

    private void copyItem(View view,int selectPosition) {
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        LinearLayout linearLayout=new LinearLayout(this);
        linearLayout.setGravity(Gravity.CENTER);
        LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(dp2px(this,100),dp2px(this,80));
        linearLayout.setLayoutParams(layoutParams);
        layoutParams.setMargins(location[0],location[1],0,0);
        linearLayout.setOnTouchListener(onTouchListener);
        linearLayout.setBackgroundColor(Color.parseColor(dataEntityList.get(selectPosition).getColor()));

        TextView textView=new TextView(this);
        textView.setText("復(fù)制:"+dataEntityList.get(selectPosition).getName());
        ViewGroup.LayoutParams params=new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp2px(this,80));
        textView.setLayoutParams(params);
        textView.setTextColor(Color.WHITE);
        textView.setGravity(Gravity.CENTER);
        linearLayout.addView(textView);

        copyView.add(linearLayout);
        rlView.addView(linearLayout);

    }


    private void showRy(boolean b) {
        lyTool.setVisibility(b? View.VISIBLE:View.GONE);
    }

    private void initView() {
        btnTool=findViewById(R.id.btnTool);
        deleteView=findViewById(R.id.deleteView);
        ryTool =findViewById(R.id.ry);
        lyTool=findViewById(R.id.lyTool);
        rlView=findViewById(R.id.rlView);
    }



    /**
     * 獲取十六進(jìn)制的顏色代碼.例如  "#5A6677"
     * 分別取R征椒、G娇哆、B的隨機(jī)值,然后加起來(lái)即可
     * 通過(guò)Color.parseColor()轉(zhuǎn)為color值即可使用
     * @return String
     */
    public static String getRandColor() {
        String R, G, B;
        Random random = new Random();
        R = Integer.toHexString(random.nextInt(256)).toUpperCase();
        G = Integer.toHexString(random.nextInt(256)).toUpperCase();
        B = Integer.toHexString(random.nextInt(256)).toUpperCase();
        R = R.length() == 1 ? "0" + R : R;
        G = G.length() == 1 ? "0" + G : G;
        B = B.length() == 1 ? "0" + B : B;
        return "#" + R + G + B;
    }


    /**
     * dp 轉(zhuǎn) px
     */
    public static int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 用于判斷某個(gè)坐標(biāo)是否在View 范圍內(nèi)
     *
     * */
    private Rect mChangeImageBackgroundRect = null;
    private boolean isInChangeImageZone(View view, int x, int y) {
        if (null == mChangeImageBackgroundRect) {
            mChangeImageBackgroundRect = new Rect();
        }
        view.getDrawingRect(mChangeImageBackgroundRect);
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        mChangeImageBackgroundRect.left = location[0];
        mChangeImageBackgroundRect.top = location[1];
        mChangeImageBackgroundRect.right = mChangeImageBackgroundRect.right + location[0];
        mChangeImageBackgroundRect.bottom = mChangeImageBackgroundRect.bottom + location[1];
        return mChangeImageBackgroundRect.contains(x, y);
    }
}

item長(zhǎng)按點(diǎn)擊時(shí)生成一個(gè)新的View (看此函數(shù) copyItem(View view,int selectPosition) )
陕靠,是通過(guò)根布局rlView添加到界面中去迂尝,同時(shí)為了方便管理移除,這邊會(huì)把添加copyView 集合list中去剪芥,實(shí)際上這樣寫(xiě)很繁瑣,可以通過(guò)繼承線(xiàn)性布局琴许,將其封裝成一個(gè)類(lèi)這樣比較直觀(guān)税肪。

View 的滑動(dòng)處理事件效果編輯則是通過(guò)onTouchListener,在里面實(shí)現(xiàn)縮放,移動(dòng)榜田,與手指抬起判斷益兄。
注意:這里邊view的移動(dòng)不可通過(guò)view.layout(l,t,r,b) 來(lái)實(shí)現(xiàn),因?yàn)楫?dāng)父組件rlView.addView(View v)時(shí)箭券,都會(huì)將重置子View位置

        onTouchListener=new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                moveViewEvent( v,  event);
                return true;
            }
        };


    private void moveViewEvent(View v, MotionEvent event) {
        if(v==null)return;
        v.setScaleX(1.3f);
        v.setScaleY(1.3f);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //獲取當(dāng)前按下的坐標(biāo)
                startX = (int) event.getRawX();
                startY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                //獲取移動(dòng)后的坐標(biāo)
                int moveX = (int) event.getRawX();
                int moveY = (int) event.getRawY();
                //拿到手指移動(dòng)距離的大小
                int move_bigX = moveX - startX;
                int move_bigY = moveY - startY;
                //拿到當(dāng)前控件未移動(dòng)的坐標(biāo):只需要計(jì)算該控件離左邊和上邊的距離即可
                int left = v.getLeft();
                int top = v.getTop();
                left += move_bigX;
                top += move_bigY;
                RelativeLayout.LayoutParams params= new RelativeLayout.LayoutParams(dp2px(this,100),dp2px(this,80));
                params.setMargins(left, top, 0, 0);
                v.setLayoutParams(params);
                startX = moveX;
                startY = moveY;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                v.setScaleX(1.0f);
                v.setScaleY(1.0f);
                if(isInChangeImageZone(deleteView,(int)event.getRawX(),(int)event.getRawY())){
                    copyView.remove(v);
                    rlView.removeView(v);
                }
                break;
        }
    }

關(guān)于RecyclerView 與生成的View 在觸摸事件上的沖突净捅,這邊著重要解決的問(wèn)題就是長(zhǎng)按時(shí)(沒(méi)有松開(kāi)),實(shí)際上事件分發(fā)到我們的ryTool辩块,但是滑動(dòng)的時(shí)候卻是移動(dòng)產(chǎn)生在ryTool 上面的view蛔六, 這里我們要做的兩點(diǎn)就是
1.長(zhǎng)按時(shí),將禁止 RecyclerView 的滾動(dòng)
這邊通過(guò)設(shè)置 ryCanScroll ,在長(zhǎng)按時(shí)的回調(diào)設(shè)置為false


image.png

2.將RecyclerView上產(chǎn)生的任何觸摸事件傳給最新生成的View 移動(dòng),且當(dāng)手指抬起時(shí)废亭,則允許RecyclerView重新滾動(dòng)国章。


image.png

至此,整篇文章講解完畢豆村,這次寫(xiě)的也算比較粗糙液兽,講解的也是一些很表面上的東西,深層次的事件分發(fā)也沒(méi)有展開(kāi)講掌动,只算給小伙伴們提供一些思路四啰,若是哪里有誤宁玫,歡迎留言提出,共同進(jìn)步柑晒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撬统,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敦迄,更是在濱河造成了極大的恐慌恋追,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罚屋,死亡現(xiàn)場(chǎng)離奇詭異苦囱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)脾猛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)撕彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人猛拴,你說(shuō)我怎么就攤上這事羹铅。” “怎么了愉昆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵职员,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我跛溉,道長(zhǎng)焊切,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任芳室,我火速辦了婚禮专肪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堪侯。我一直安慰自己嚎尤,他們只是感情好伍宦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布芽死。 她就那樣靜靜地躺著,像睡著了一般雹拄。 火紅的嫁衣襯著肌膚如雪收奔。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天滓玖,我揣著相機(jī)與錄音坪哄,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛翩肌,可吹牛的內(nèi)容都是我干的模暗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼念祭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼兑宇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起粱坤,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤隶糕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后站玄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體枚驻,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年株旷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了再登。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晾剖,死狀恐怖锉矢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情齿尽,我是刑警寧澤沽损,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站雕什,受9級(jí)特大地震影響缠俺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贷岸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磷雇。 院中可真熱鬧偿警,春花似錦、人聲如沸唯笙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)崩掘。三九已至七嫌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間苞慢,已是汗流浹背诵原。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绍赛。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓蔓纠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親吗蚌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腿倚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容