Android:RecyclerView,拖放(外部视图)和自动滚动?

时间:2016-01-03 09:34:50

标签: android drag-and-drop android-recyclerview

这是我想要做的事情的图像:

Image: Drag-and-drop app with a list and an item outside the list

我正在尝试创建一个类似资源管理器的文件浏览器,通过拖放来移动文件,但我遇到了一个问题。

我知道有一个特殊的RecyclerView拖放界面(例如this),但是我无法找到告诉如何在内部和< em>列出的。

这是我的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。

问题是RecyclerView的回收机制:据我所知,回收池中的视图不是ACTION_DRAG_STARTED,因此自动滚动到视图中的视图无法接收文件,也无法自动滚动清单进一步。

如何在RecyclerView及其外部进行拖放工作?有自动滚动?

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

感谢。

1 个答案:

答案 0 :(得分:2)

我做了一些研究,似乎ViewGroupView包含了所需的信息:

  1. View.mPrivateFlags2需要设置View.DRAG_CAN_ACCEPT标志;
  2. 需要调用ViewGroup.notifyChildOfDrag()
  3. 1的问题是mPrivateFlags2是一个仅包访问变量:从包外部,它既不能通过直接代码也不能通过反射访问(reflect给出IllegalAccessException)。另一个问题是ViewGroup只会将DragEvent分派给 ViewGroup 知道的子项,碰巧拥有正确标志的子项(这可以在dispatchDragEvent中找到) ()实现,例如在第1421行附近,如:for (View child : mDragNotifiedChildren) ...)。

    2的问题是notifyChildOfDrag()也是一个仅包的成员。那么我能想到的唯一答案是从ACTION_DRAG_STARTED中保存DragEvent,然后每当添加到RecyclerView的新子项时重新发送事件。无论如何,这基本上就是notifyChildOfDrag()的作用;只有这也会通知已经通知的孩子,所以它不是最佳的。

    为此,我这样走了:

    • 在DragEventListener内部,在ACTION_DRAG_STARTED上保存事件。
    • 要保存该事件,请使用事件持有者类。
    • 在RecyclerView.Adapter的onBind()方法中:设置稍后将执行的定时Runnable。
    • 在Runnable中,检查新绑定的RecyclerView项目是否已添加到其父级,或者尚未添加:
    • 如果尚未添加,请稍等一下再试一次......
    • 添加后,使用ViewGroup.dispatchDragEvent(),其参数是我们之前保存的拖动事件。

    我还对代码进行了其他一些小修复和调整,但这些是使其工作的主要步骤。

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

    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. 
        } 
    }
    

    我试图通过评论引起注意。

    可以对此代码进行一些优化,但主要思想是存在的。如果这种方法存在严重错误,请告诉我。

相关问题