这是我想要做的事情的图像:
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及其外部进行拖放工作?有自动滚动?
即使在拖动开始后,有没有办法将新视图添加到拖动中?
感谢。
答案 0 :(得分:2)
我做了一些研究,似乎ViewGroup和View包含了所需的信息:
1的问题是mPrivateFlags2是一个仅包访问变量:从包外部,它既不能通过直接代码也不能通过反射访问(reflect给出IllegalAccessException)。另一个问题是ViewGroup只会将DragEvent分派给 ViewGroup 知道的子项,不碰巧拥有正确标志的子项(这可以在dispatchDragEvent中找到) ()实现,例如在第1421行附近,如:for (View child : mDragNotifiedChildren)
...)。
2的问题是notifyChildOfDrag()也是一个仅包的成员。那么我能想到的唯一答案是从ACTION_DRAG_STARTED中保存DragEvent,然后每当添加到RecyclerView的新子项时重新发送事件。无论如何,这基本上就是notifyChildOfDrag()的作用;只有这也会通知已经通知的孩子,所以它不是最佳的。
为此,我这样走了:
我还对代码进行了其他一些小修复和调整,但这些是使其工作的主要步骤。
以下是生成的新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.
}
}
我试图通过评论引起注意。
可以对此代码进行一些优化,但主要思想是存在的。如果这种方法存在严重错误,请告诉我。