Android:RecyclerView,Drag-and-Drop(外部视图)和Auto-Scroll? [英] Android: RecyclerView, Drag-and-Drop (External View), and Auto-Scroll?

查看:439
本文介绍了Android:RecyclerView,Drag-and-Drop(外部视图)和Auto-Scroll?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个我想要做的事情:

Here is an image of what I am trying to do:

图像:拖放应用程序与列表和列表之外的项目

我正在尝试创建一个浏览器像文件浏览器,拖放即可移动文件,但我遇到问题。

I am trying to make an explorer-like file browser, with drag-and-drop to move the files, but I run into a problem.

我知道有一个特殊的RecyclerView拖放界面(有一个这个,例如),但是我还没有找到例子来告诉如何在里面和在之外。

I know there is a special RecyclerView drag-and-drop interface (there is this, for example), but I haven't been able to find examples that tell how to move things between the inside and outside of the list.

这是我的XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="net.wbord.recyclerviewdragdropautoscrolltest.MainActivity">

    <TextView
        android:id="@+id/exampleItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:text="Hello World!"/>

    <Space
        android:layout_width="match_parent"
        android:layout_height="50dp"

        />


    <android.support.v7.widget.RecyclerView
        android:id="@+id/mainList"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"

        android:paddingLeft="100dp"
        android:paddingRight="100dp"
        android:clipToPadding="false"
        />


</LinearLayout>

而Java:

package net.wbord.recyclerviewdragdropautoscrolltest;

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipDescription;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.DragEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

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

        // Find Views: 
        final TextView exampleItem = (TextView) findViewById (R.id.exampleItem); 
        final RecyclerView rv = (RecyclerView) findViewById (R.id.mainList); 

        // Define Drag Listener: 
        final View.OnDragListener onDrag = new View.OnDragListener () {
            @Override
            public boolean onDrag (View v, DragEvent event) {
                switch (event.getAction ()) { 
                    case DragEvent.ACTION_DRAG_ENTERED: 
                        v.setScaleX (1.5f); v.setScaleY (1.5f); 
                        handleScroll (rv, v); 
                        break; 
                    case DragEvent.ACTION_DRAG_EXITED: 
                    case DragEvent.ACTION_DRAG_ENDED: 
                        v.setScaleX (1); v.setScaleY (1); 
                        break; 
                    case DragEvent.ACTION_DROP: 
                        ClipData data = event.getClipData (); 
                        String folder = (String) v.getTag (); 
                        String msg = "File '" + data.getItemAt (0).getText () + "' " + 
                                             "moved into folder '" + folder + "'"; 
                        Toast.makeText (MainActivity.this, msg, Toast.LENGTH_LONG).show (); 
                        break; 
                } 
                return true; 
            }
        }; 

        // The "file" for the user to drag-drop into a folder: 
        exampleItem.setOnLongClickListener (new View.OnLongClickListener () {
            @Override
            public boolean onLongClick (View v) {
                // Start drag: 
                ClipData.Item item = new ClipData.Item (exampleItem.getText ()); 
                ClipData data = new ClipData (exampleItem.getText (), 
                                                     new String [] {ClipDescription.MIMETYPE_TEXT_PLAIN}, 
                                                     item); 
                View.DragShadowBuilder builder = new View.DragShadowBuilder (exampleItem); 
                v.startDrag (data, builder, null, 0); 
                return true; 
            }
        }); 

        // The list of "folders" that can accept the file: 
        rv.setLayoutManager (new LinearLayoutManager (this, LinearLayoutManager.VERTICAL, false)); 
        rv.setAdapter (new RecyclerView.Adapter () {
            class ViewHolder extends RecyclerView.ViewHolder { 
                private final TextView vItem; 
                public ViewHolder (TextView textView) { 
                    super (textView); 
                    vItem = textView; 
                } 
                public void bind (String itemName) { 
                    vItem.setText (itemName); 
                    vItem.setTag (itemName); 
                    vItem.setOnDragListener (onDrag); 
                } 
            } 
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) { 
                TextView vText = new TextView (MainActivity.this); 
                vText.setTextSize (50); 
                return new ViewHolder (vText); 
            } 
            @Override
            public void onBindViewHolder (RecyclerView.ViewHolder holder, int position) {
                if (holder instanceof ViewHolder) 
                    ((ViewHolder) holder).bind (getItem (position)); 
            }
            @Override
            public int getItemCount () {
                return 100; 
            }
            public String getItem (int position) { 
                return "Folder " + (1 + position); 
            } 
        });
    }

    protected void handleScroll (RecyclerView vList, View viewHoveredOver) { 
        LinearLayoutManager mgr = (LinearLayoutManager) vList.getLayoutManager (); 
        int iFirst = mgr.findFirstCompletelyVisibleItemPosition (); 
        int iLast = mgr.findLastCompletelyVisibleItemPosition (); 
        // Auto-Scroll: 
        if (mgr.findViewByPosition (iFirst) == viewHoveredOver) 
            vList.smoothScrollToPosition (Math.max (iFirst - 1, 0)); 
        else if (mgr.findViewByPosition (iLast) == viewHoveredOver) 
            vList.smoothScrollToPosition (Math.min (iLast + 1, 
                    mgr.getChildCount ())); 
    } 
}

基本上每次文件夹是ACTION_DRAG_ENTER-ed调用handleScroll()方法:它检查拖动文件悬停在哪个文件夹中,并使用它来滚动RecyclerView。

Basically every time a "folder" is ACTION_DRAG_ENTER-ed, the handleScroll () method is called: it checks which folder the dragged file is hovering over, and uses that to scroll the RecyclerView.

问题是RecyclerView的回收机制:据了解,回收池中的视图不是ACTION_DRAG_STARTED,因此自动滚动到视图中的视图不是能够接收文件,也不能进一步自动滚动列表。

The problem is the RecyclerView's recycling mechanism: as I understand it, the views in the recycling pool are not ACTION_DRAG_STARTED, so the views that are auto-scrolled into view are not capable of receiving the file, nor capable of auto-scrolling the list further.

RecyclerView与其外部之间的拖放工作如何?有自动滚动?

How does drag-and-drop work between a RecyclerView and its outside? With auto-scroll?

即使拖动开始,有没有办法将新视图添加到拖动中?

Is there a way to add the new views to the drag even after the drag has been started?

谢谢。

推荐答案

我做了更多的研究,似乎 ViewGroup View 中包含所需的信息:

I did a little more research, and it seems that ViewGroup and View have the needed pieces of information in them:


  1. View.mPrivateFlags2需要设置View.DRAG_CAN_ACCEPT标志; OR

  2. ViewGroup.notifyChildOfDrag()需要调用

1的问题是mPrivateFlags2是一个仅限程序包访问的变量:从程序包外部,它不能被直接代码访问,也不能被反射(反映给出一个IllegalAccessException)。另一个问题是ViewGroup只会向ViewGroup 知道的 的孩子发送DragEvent,而不是恰好有正确的标志设置的孩子(这可以在dispatchDragEvent中找到()实现,例如在第1421行附近,作为(查看子代:mDragNotifiedChildren) ...的

The problem with 1 is that mPrivateFlags2 is a package-access-only variable: from outside the package, it can be accessed neither by direct code nor by reflect (reflect gives an IllegalAccessException). The other problem is that ViewGroup will only dispatch DragEvent to children the ViewGroup knows about, not the children who happen to have the right flag set (this can be found in the dispatchDragEvent () implementation, near line 1421 for example, as: for (View child : mDragNotifiedChildren) ...).

2的问题是notifyChildOfDrag()也是一个仅包的成员。那么我唯一可以想到的答案就是将DragEvent从ACTION_DRAG_STARTED中保存起来,然后当新的子进入RecyclerView时重新发送事件。这基本上是notifyChildOfDrag()做的,反正;只有这样也通知已经通知的孩子,所以它不是最佳的。

The problem with 2 is that notifyChildOfDrag () is also a package-only member. The only answer I could think of, then, is to save the DragEvent from ACTION_DRAG_STARTED, and then re-send the event whenever new children to the RecyclerView are added. This is basically what notifyChildOfDrag () does, anyway; only this also notifies the already-notified children, so it is not as optimal.

为此,我以这种方式:


  • 在DragEventListener内,在ACTION_DRAG_STARTED上保存事件。

  • 要保存该事件,请使用事件持有者类。

  • RecyclerView.Adapter的onBind()方法内部:设置一个稍后执行的定时Runnable。

  • 在Runnable中,检查新绑定的RecyclerView项目是否已经添加到其父级,或尚未添加:

  • 如果没有已添加,稍等一下,再试一次...

  • 添加后,使用ViewGroup.dispatchDragEvent(),其参数是我们之前保存的拖动事件。

  • Inside the DragEventListener, on ACTION_DRAG_STARTED save the event.
  • To save that event, use an event holder class.
  • Inside the onBind () method for the RecyclerView.Adapter: set a timed Runnable that would execute later.
  • In the Runnable, check if the newly-bound RecyclerView item has been added to its parent already, or not yet:
  • If it has not been added yet, wait a little and try again ...
  • When it has been added, use ViewGroup.dispatchDragEvent (), with its argument being the drag event we saved earlier.

还有对代码执行的其他小修改和调整,但这些是使其正常工作的主要步骤。

There are also other minor fixes and tweaks I did to the code, but these are the main steps to make it work.

这是生成的新Java代码(XML保持不变,所以我不会重新发布):

Here is the resulting new Java code (the XML stays the same, so I will not re-post it):

package net.wbord.recyclerviewdragdropautoscrolltest;

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipDescription;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.DragEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    // DEFINE HOLDER CLASS: 
    public static class DragEventHolder { 
        DragEvent mStartDrag = null; 
    } 

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

        // Find Views: 
        final TextView exampleItem = (TextView) findViewById (R.id.exampleItem); 
        final RecyclerView rv = (RecyclerView) findViewById (R.id.mainList); 

        // Define Drag Listener: 
        final DragEventHolder dragEventHolder = new DragEventHolder (); // PART OF ANSWER: VARIABLE TO HOLD DRAG EVENT 
        final View.OnDragListener onDrag = new View.OnDragListener () {
            @Override
            public boolean onDrag (View v, DragEvent event) {
                switch (event.getAction ()) { 
                    case DragEvent.ACTION_DRAG_STARTED: 
                        dragEventHolder.mStartDrag = event; // PART OF ANSWER 
                        v.setScaleX (1); v.setScaleY (1); // MINOR TWEAK (makes appearance better) 
                        break; 
                    case DragEvent.ACTION_DRAG_ENTERED: 
                        v.setScaleX (1.5f); v.setScaleY (1.5f); 
                        break; 
                    case DragEvent.ACTION_DRAG_ENDED: 
                        dragEventHolder.mStartDrag = null; // PART OF ANSWER 
                    case DragEvent.ACTION_DRAG_EXITED: 
                        v.setScaleX (1); v.setScaleY (1); 
                        break; 
                    case DragEvent.ACTION_DROP: 
                        ClipData data = event.getClipData (); 
                        String folder = (String) v.getTag (); 
                        String msg = "File '" + data.getItemAt (0).getText () + "' " + 
                                             "moved into folder '" + folder + "'"; 
                        Toast.makeText (MainActivity.this, msg, Toast.LENGTH_LONG).show (); 
                        break; 
                    case DragEvent.ACTION_DRAG_LOCATION: 
                        handleScroll (rv, v); // MINOR FIX: CALL handleScroll () FROM ACTION_DRAG_LOCATION RATHER THAN ACTION_DRAG_ENTERED (helps with easier auto-scrolling) 
                } 
                return true; 
            }
        }; 

        // The "file" for the user to drag-drop into a folder: 
        exampleItem.setOnLongClickListener (new View.OnLongClickListener () {
            @Override
            public boolean onLongClick (View v) {
                // Start drag: 
                ClipData.Item item = new ClipData.Item (exampleItem.getText ()); 
                ClipData data = new ClipData (exampleItem.getText (), 
                                                     new String [] {ClipDescription.MIMETYPE_TEXT_PLAIN}, 
                                                     item); 
                View.DragShadowBuilder builder = new View.DragShadowBuilder (exampleItem); 
                v.startDrag (data, builder, null, 0); 
                return true; 
            }
        }); 

        // The list of "folders" that can accept the file: 
        final android.os.Handler updateDragHandler = new Handler (); 
        rv.setLayoutManager (new LinearLayoutManager (this, LinearLayoutManager.VERTICAL, false)); 
        rv.setAdapter (new RecyclerView.Adapter () {
            class ViewHolder extends RecyclerView.ViewHolder { 
                private final TextView vItem; 
                public ViewHolder (TextView textView) { 
                    super (textView); 
                    vItem = textView; 
                } 
                public void bind (String itemName) { 
                    vItem.setText (itemName); 
                    vItem.setTag (itemName); 
                    vItem.setOnDragListener (onDrag); 
                    // Re-send DragEvent: 
                    updateDragHandler.postDelayed (new Runnable () {
                        @Override
                        public void run () {
                            ViewParent parent = vItem.getParent (); 
                            if (parent == null || !(parent instanceof ViewGroup)) { 
                                updateDragHandler.postDelayed (this, 50); 
                                return; 
                            } 
                            if (dragEventHolder.mStartDrag != null)
                                ((ViewGroup) parent).dispatchDragEvent (dragEventHolder.mStartDrag);
                        } 
                    }, 100); 
                } 
            } 
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) { 
                TextView vText = new TextView (MainActivity.this); 
                vText.setTextSize (50); 
                return new ViewHolder (vText); 
            } 
            @Override
            public void onBindViewHolder (RecyclerView.ViewHolder holder, int position) {
                if (holder instanceof ViewHolder) 
                    ((ViewHolder) holder).bind (getItem (position)); 
            }
            @Override
            public int getItemCount () {
                return 100; 
            }
            public String getItem (int position) { 
                return "Folder " + (1 + position); 
            } 
        });
    }

    protected void handleScroll (RecyclerView vList, View viewHoveredOver) { 
        LinearLayoutManager mgr = (LinearLayoutManager) vList.getLayoutManager (); 
        int iFirst = mgr.findFirstCompletelyVisibleItemPosition (); 
        int iLast = mgr.findLastCompletelyVisibleItemPosition (); 
        // Auto-Scroll: 
        if (mgr.findViewByPosition (iFirst) == viewHoveredOver) 
            vList.smoothScrollToPosition (Math.max (iFirst - 1, 0)); 
        else if (mgr.findViewByPosition (iLast) == viewHoveredOver) 
            vList.smoothScrollToPosition (Math.min (iLast + 1, 
                    vList.getAdapter ().getItemCount ())); // MINOR FIX:  Was getting the wrong count before. 
    } 
}

我试图使用注释注意更改。

I tried to bring attention to the changes using comments.

可以对此代码进行一些优化,但主要思想是。让我知道这种方法是否有明显的错误。

There are some optimizations that can be done to this code, but the main idea is there. Let me know if there is something significantly wrong about this approach.

这篇关于Android:RecyclerView,Drag-and-Drop(外部视图)和Auto-Scroll?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆