问题
What I want to achieve:
I want to create a drag and drop functionality in Android. I'd like to use a specific layout (different from the dragged object itself) as a drag shadow.
What result I'm getting instead:
Neither of my approaches works as expected - I end up with no visible drag shadow at all (although the target does receive the drop).
What I tried:
I tried
- inflating the
drag_itemlayout in the activity, then passing it as an argument to the shadow builder's constructor
and
- inflating the
drag_itemlayout in the shadow builder'sonDrawShadowmethod, then drawing it on thecanvas
Layouts:
My activity layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.app.DragDropTestActivity"
tools:ignore="MergeRootFrame">
<TextView
android:id="@+id/tvReceiver"
android:text="Drop here"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btnDragged"
android:layout_height="wrap_content"
android:text="Drag me"
android:layout_width="match_parent"/>
</LinearLayout>
The layout I want to use as a drag shadow:
dragged_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dragged Item"/>
</LinearLayout>
Source code:
Here's the code with both approaches (represented by 1, BuilderOne and 2, BuilderTwo, respectively):
package com.example.app;
import android.graphics.Canvas;
import android.graphics.Point;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
public class DragDropTestActivity extends ActionBarActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drag_drop_test);
Button dragged = (Button) findViewById(R.id.btnDragged);
dragged.setOnTouchListener(
new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
LayoutInflater inflater = getLayoutInflater();
int approach = 1;
// both approaches fail
switch (approach) {
case 1: {
View draggedItem = inflater.inflate(R.layout.dragged_item, null);
BuilderOne builder = new BuilderOne(draggedItem);
v.startDrag(null, builder, null, 0);
break;
}
case 2: {
BuilderTwo builder = new BuilderTwo(inflater, v);
v.startDrag(null, builder, null, 0);
break;
}
}
return true;
}
});
}
My BuilderOne class:
public static class BuilderOne extends View.DragShadowBuilder
{
public BuilderOne(View view)
{
super(view);
}
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint)
{
super.onProvideShadowMetrics(
shadowSize,
shadowTouchPoint);
}
}
And BuilderTwo class:
public static class BuilderTwo extends View.DragShadowBuilder
{
final LayoutInflater inflater;
public BuilderTwo(LayoutInflater inflater, View view)
{
super(view);
this.inflater = inflater;
}
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint)
{
super.onProvideShadowMetrics(
shadowSize,
shadowTouchPoint);
}
@Override
public void onDrawShadow(Canvas canvas)
{
final View draggedItem = inflater.inflate(R.layout.dragged_item, null);
if (draggedItem != null) {
draggedItem.draw(canvas);
}
}
}
}
Question:
What do I do wrong?
Update:
Bounty added.
回答1:
Kurty is correct in that you shouldn't need to subclass DragShadowBuilder in this case. My thought is that the view you're passing to the DragShadowBuilder doesn't actually exist in the layout, and therefore it doesn't render.
Rather than passing null as the second argument to inflater.inflate, try actually adding the inflated View to the hierarchy somewhere, and then passing it to a regular DragShadowBuilder:
View dragView = findViewById(R.id.dragged_item);
mDragShadowBuilder = new DragShadowBuilder(dragView);
v.startDrag(null, mDragShadowBuilder, null, 0);
EDIT
I'm aware that having the dragged_item view being rendered all the time isn't what you want, but if it works then at least we know where the problem is and can look for a solution to that instead!
回答2:
Simply put it, you only need this:
private final class TouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
v.startDrag(ClipData.newPlainText("", ""), new View.DragShadowBuilder(v), v, 0);
}
return true;
}
}
(You don't necessarily need the BuilderOne and BuilderTwo class)
来源:https://stackoverflow.com/questions/22274384/how-to-create-a-custom-drag-shadow