【问题标题】:How to detect drop outside draggable views in Android如何在Android中检测拖放到可拖动视图之外
【发布时间】:2014-11-13 14:54:42
【问题描述】:

我正在开发一个 Android 应用程序,我正在使用拖放视图。

我在我的视图(屏幕截图中的圆圈)上实现了 onLongClick(实际上是使用具有 1.5 秒长点击阈值的 onTouch)。当检测到长按时,我会显示两个放置区域(分别附加了 onDragListener 的图像视图,屏幕截图中的绿色框)。

当我的视图被拖放到任一放置区域(调用 ACTION_DROP)时,我的代码工作正常,但问题是,我还需要能够判断我的圆圈何时被放置在放置区域之外,所以我可以重置状态。在这种情况下,我没有从两个放置区域中的任何一个触发任何回调。我认为我也没有看到从我使用 startDrag 方法的视图中调用的任何方法。

在将视图放到任何放置区域之外后,我从 logcat 得到的唯一信息是:

I/ViewRootImpl﹕报告丢弃结果:假

有什么方法可以从我的代码中检测到“丢弃结果:错误”?帮助将不胜感激!

截图链接如下:

--------- 11/21 更新 ----------

当我第一次发布这个问题时,我还在尝试掌握高级概念,但现在我意识到我应该包含更多细节。

以下是目前我的应用程序中使用的一些代码 sn-ps。顺便说一句,我在服务中使用了这些视图——圆形(锚)和绿色框(dropRegions)——而不是活动,所以我使用 WindowManager 来添加视图。

我仍然有同样的问题,我没有得到的主要是 ACTION_DRAG_ENTERED 和 ACTION_DRAG_EXITED 从未被调用过。到目前为止,只有 ACTION_DROP 对我有用。我还缺少什么吗?

以下是代码的相关部分。

长按圆圈时调用(在sandrstar响应后稍作修改):

private void anchorOnLongClick(View view) {
    Log.d("####", "LONG CLICK");
    showAnchorDropRegion();

    ClipData.Item item = new ClipData.Item((String) view.getTag());
    ClipData data = new ClipData((String) view.getTag(),
            new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);

    View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
    view.startDrag(data, shadowBuilder, null, 0);
    setupAnchorDropRegion();
    showAnchorDropRegion();
}

将拖动侦听器附加到绿色框:

/* set up the region where anchor can be drag-and-dropped */
private void setupAnchorDropRegion() {
    ivDropRight = new ImageView(this);
    ivDropRight.setImageDrawable(getResources().getDrawable(R.drawable.dropregion_normal));
    ivDropRight.setOnDragListener(new View.OnDragListener() {
        @Override
        public boolean onDrag(View v, DragEvent event) {
            Log.d("####", "onDrag");
            switch (event.getAction()) {

                case DragEvent.ACTION_DRAG_STARTED:
                    // do nothing
                    return true;

                case DragEvent.ACTION_DRAG_ENTERED:
                    Log.d("####", "DRAG_ENTERED");
                    break;

                case DragEvent.ACTION_DRAG_EXITED:
                    Log.d("####", "DRAG_EXITED");
                    break;

                case DragEvent.ACTION_DROP:
                    Log.d("####", "DROP ON: " + event.getX() + ", " + event.getY());
                    hideDropRegion();
                    // initialize anchor locations to the right
                    break;

                case DragEvent.ACTION_DRAG_ENDED:
                    Log.d("####", "DRAG ENDED");
                    break;

                default:
                    break;
            }
            return true;
        }
    });

// repeat for ivDropLeft

显示绿色框(放置区域):

/* display anchor drop region left and right */
private void showAnchorDropRegion() {
    WindowManager.LayoutParams dropRegionParmas = new WindowManager.LayoutParams(
            app.dropRegionSize, app.dropRegionSize, 0, 0,
            WindowManager.LayoutParams.TYPE_PHONE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT);
    dropRegionParmas.gravity = Gravity.BOTTOM | Gravity.LEFT;
    mWindowManager.addView(ivDropLeft, dropRegionParmas);

    dropRegionParmas.gravity = Gravity.BOTTOM | Gravity.RIGHT;
    mWindowManager.addView(ivDropRight, dropRegionParmas);
}

【问题讨论】:

    标签: android drag-and-drop


    【解决方案1】:

    主要思想来自DragEvent.getResult()的这个文档:

    返回拖放操作结果的指示。 此方法仅在操作类型为时返回有效数据 ACTION_DRAG_ENDED。返回值取决于之后发生的事情 用户释放拖动阴影。

    所以,drop 后,你可以通过getResult() 获取结果:如果返回true,那么它已经在某个接受drop 的视图中被drop。请注意,接受 drop 的视图返回 trueACTION_DROP

    视图应从其 onDragEvent(DragEvent) 处理程序返回 true 或 OnDragListener.onDrag() 侦听器,如果它接受了丢弃,则为 false 它忽略了下降。

    下面是一些小例子,类似于你的布局,展示了这种方法:

    public class MainActivity extends Activity {
        private static final String TAG = "MainActivity";
    
        private View mCircleView = null;
        private Boolean mDroppedIn = null;
    
        private View.OnDragListener mLeftDragListener = new View.OnDragListener() {
            private boolean mInView = false;
    
            @Override
            public boolean onDrag(final View v, final DragEvent event) {
                final int action = event.getAction();
    
                switch (action) {
                    case DragEvent.ACTION_DRAG_STARTED:
                        mDroppedIn = null;
                        mCircleView.setVisibility(View.INVISIBLE);
                        v.setBackgroundResource(android.R.color.holo_green_dark);
                        break;
    
                    case DragEvent.ACTION_DRAG_ENTERED:
                        mInView = true;
                        v.setBackgroundResource(android.R.color.holo_green_light);
                        break;
    
                    case DragEvent.ACTION_DRAG_EXITED:
                        mInView = false;
                        v.setBackgroundResource(android.R.color.holo_green_dark);
                        break;
    
                    case DragEvent.ACTION_DRAG_ENDED:
                        mCircleView.post(new Runnable() {
                            @Override
                            public void run() {
                                if (mCircleView.getVisibility() != View.VISIBLE) {
                                    mCircleView.setVisibility(View.VISIBLE);
                                }
                            }
                        });
                        reportResult(R.id.leftView, event.getResult());
                        break;
    
                    case DragEvent.ACTION_DROP:
                        return mInView;
                }
    
                return true;
            }
        };
    
        private View.OnDragListener mRightDragListener = new View.OnDragListener() {
            private boolean mInView = false;
    
            @Override
            public boolean onDrag(final View v, final DragEvent event) {
                final int action = event.getAction();
    
                switch (action) {
                    case DragEvent.ACTION_DRAG_STARTED:
                        mDroppedIn = null;
                        mCircleView.setVisibility(View.INVISIBLE);
                        v.setBackgroundResource(android.R.color.holo_green_dark);
                        break;
    
                    case DragEvent.ACTION_DRAG_ENTERED:
                        mInView = true;
                        v.setBackgroundResource(android.R.color.holo_green_light);
                        break;
    
                    case DragEvent.ACTION_DRAG_EXITED:
                        mInView = false;
                        v.setBackgroundResource(android.R.color.holo_green_dark);
                        break;
    
                    case DragEvent.ACTION_DRAG_ENDED:
                        mCircleView.post(new Runnable() {
                            @Override
                            public void run() {
                                if (mCircleView.getVisibility() != View.VISIBLE) {
                                    mCircleView.setVisibility(View.VISIBLE);
                                }
                            }
                        });
                        reportResult(R.id.rightView, event.getResult());
                        break;
    
                    case DragEvent.ACTION_DROP:
                        return mInView;
                }
    
                return true;
            }
        };
    
        private void reportResult(final int viewId, final boolean result) {
            if (mDroppedIn == null) {
                mDroppedIn = result;
                Toast.makeText(this, "Dropped in: " + result, Toast.LENGTH_SHORT).show();
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mCircleView = findViewById(R.id.circleView);
    
            mCircleView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(final View v) {
                    final ClipData.Item item = new ClipData.Item((String)v.getTag());
                    final ClipData dragData = new ClipData((String)v.getTag(),
                            new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN}, item);
    
                    mCircleView.startDrag(dragData, new CircleShadowBuilder(v), null, 0);
                    findViewById(R.id.leftView).setOnDragListener(mLeftDragListener);
                    findViewById(R.id.rightView).setOnDragListener(mRightDragListener);
                    return true;
                }
            });
        }
    
        private class CircleShadowBuilder extends View.DragShadowBuilder {
    
            private final Drawable mShadow;
    
            CircleShadowBuilder(final View v) {
                super(v);
                mShadow = v.getResources().getDrawable(R.drawable.circle);
            }
    
            @Override
            public void onProvideShadowMetrics(final Point shadowSize, final Point shadowTouchPoint) {
                int width = getView().getWidth();
                int height = getView().getHeight();
    
                mShadow.setBounds(0, 0, width, height);
                shadowSize.set(width, height);
                shadowTouchPoint.set(width, height);
            }
    
            @Override
            public void onDrawShadow(final Canvas canvas) {
                mShadow.draw(canvas);
            }
        }
    }
    

    【讨论】:

    • 我添加了有关我在编辑中遇到的问题的更多详细信息。另外,我看到您编辑了我的原始帖子以显示屏幕截图。这是我错过的事情,还是您有特殊权限为我启用它?谢谢!
    • @AnotherDev 是的,似乎需要一些代表点数才能在问题中提供图像。关于问题,这很奇怪,因为在我的代码中我收到了事件。可能与您添加视图的方式有关?
    【解决方案2】:

    我希望你已经找到了解决方案。

    我遇到了同样的问题。我试图检测所有可拖动视图之外的放置。

    通过查看拖放过程部分中的documentation,我发现无论拖放发生在何处,都会在拖放完成时调用ACTION_DRAG_ENDED

    "用户释放拖动阴影后,系统发出(如果需要)动作类型为 ACTION_DROP 的拖动事件后,系统发出动作类型为 ACTION_DRAG_ENDED 的拖动事件,表示拖动操作结束了。无论用户在何处释放拖动阴影,都会执行此操作。该事件被发送到每个注册接收拖动事件的监听器,即使监听器收到了 ACTION_DROP 事件。"

    所以,我们可以使用它。

    您的拖动侦听器可以通过 ACTION_DROP 知道拖放发生在可拖放视图内。您的拖动监听器可以知道拖放以 ACTION_DRAG_ENDED

    结束

    对于任何感兴趣的人,我有一个示例,说明如何使用拖放在布局之间移动图像HandleImage

    样本的作用是:

    1. 开始拖动时,它会隐藏图像并仅显示拖动阴影图像。
    2. 当放置发生时,它会将图像移动到另一个布局中。
    3. 如果放置在任何布局之外,它会在开始的位置再次显示图像。

    DragAndDropActivity.java

    package com.xengar.android.handleimage.ui;
    
    import android.content.ClipData;
    import android.graphics.drawable.Drawable;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.Toolbar;
    import android.view.DragEvent;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.View.DragShadowBuilder;
    import android.view.View.OnDragListener;
    import android.view.View.OnTouchListener;
    import android.widget.LinearLayout;
    
    import com.xengar.android.handleimage.R;
    
    /**
     * DragAndDropActivity
     */
    public class DragAndDropActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_drag_and_drop);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            /*
            FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();
                }
            });*/
    
            findViewById(R.id.myimage1).setOnTouchListener(new MyTouchListener());
            findViewById(R.id.myimage2).setOnTouchListener(new MyTouchListener());
            findViewById(R.id.myimage3).setOnTouchListener(new MyTouchListener());
            findViewById(R.id.myimage4).setOnTouchListener(new MyTouchListener());
            findViewById(R.id.topleft).setOnDragListener(new MyDragListener());
            findViewById(R.id.topright).setOnDragListener(new MyDragListener());
            findViewById(R.id.bottomleft).setOnDragListener(new MyDragListener());
            findViewById(R.id.bottomright).setOnDragListener(new MyDragListener());
    
        }
    
        private final class MyTouchListener implements OnTouchListener {
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                    ClipData data = ClipData.newPlainText("", "");
                    DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
                            view);
                    view.startDrag(data, shadowBuilder, view, 0);
                    view.setVisibility(View.INVISIBLE);
                    return true;
                } else {
                    return false;
                }
            }
        }
    
        class MyDragListener implements OnDragListener {
            Drawable enterShape = getResources().getDrawable(
                    R.drawable.shape_droptarget);
            Drawable normalShape = getResources().getDrawable(R.drawable.shape);
    
            @Override
            public boolean onDrag(View v, DragEvent event) {
                View view;
                int action = event.getAction();
                switch (event.getAction()) {
                    case DragEvent.ACTION_DRAG_STARTED:
                        // do nothing
                        break;
                    case DragEvent.ACTION_DRAG_ENTERED:
                        v.setBackgroundDrawable(enterShape);
                        break;
                    case DragEvent.ACTION_DRAG_EXITED:
                        v.setBackgroundDrawable(normalShape);
                        break;
                    case DragEvent.ACTION_DROP:
                        // Dropped, reassign View to ViewGroup
                        view = (View) event.getLocalState();
                        ViewGroup owner = (ViewGroup) view.getParent();
                        owner.removeView(view);
                        LinearLayout container = (LinearLayout) v;
                        container.addView(view);
                        //view.setVisibility(View.VISIBLE);
                        break;
                    case DragEvent.ACTION_DRAG_ENDED:
                        v.setBackgroundDrawable(normalShape);
                        // Regardless of valid or invalid drop show it again.
                        // See https://developer.android.com/reference/android/view/DragEvent.html#ACTION_DRAG_ENDED
                        view = (View) event.getLocalState();
                        view.setVisibility(View.VISIBLE);
                    default:
                        break;
                }
                return true;
            }
        }
    
    }
    

    activity_drag_and_drop.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.xengar.android.handleimage.ui.DragAndDropActivity">
    
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay" />
    
        </android.support.design.widget.AppBarLayout>
    
        <!--<include layout="@layout/content_drag_and_drop" /> -->
    
        <android.support.constraint.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            tools:context="com.xengar.android.handleimage.ui.DragAndDropActivity"
            tools:showIn="@layout/activity_drag_and_drop">
    
            <GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:columnCount="2"
                android:columnWidth="320dp"
                android:orientation="vertical"
                android:rowCount="2"
                android:stretchMode="columnWidth" >
    
                <LinearLayout
                    android:id="@+id/topleft"
                    android:layout_width="160dp"
                    android:layout_height="160dp"
                    android:layout_row="0"
                    android:background="@drawable/shape" >
    
                    <ImageView
                        android:id="@+id/myimage1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/gallery_lion" />
                </LinearLayout>
    
                <LinearLayout
                    android:id="@+id/topright"
                    android:layout_width="160dp"
                    android:layout_height="160dp"
                    android:background="@drawable/shape" >
    
                    <ImageView
                        android:id="@+id/myimage2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/gallery_lion" />
                </LinearLayout>
    
                <LinearLayout
                    android:id="@+id/bottomleft"
                    android:layout_width="160dp"
                    android:layout_height="160dp"
                    android:background="@drawable/shape" >
    
                    <ImageView
                        android:id="@+id/myimage3"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/gallery_lion" />
                </LinearLayout>
    
                <LinearLayout
                    android:id="@+id/bottomright"
                    android:layout_width="160dp"
                    android:layout_height="160dp"
                    android:background="@drawable/shape" >
    
                    <ImageView
                        android:id="@+id/myimage4"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/gallery_lion" />
                </LinearLayout>
    
            </GridLayout>
    
    
        </android.support.constraint.ConstraintLayout>
    
        <!--
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:srcCompat="@android:drawable/ic_dialog_email" /> -->
    
    </android.support.design.widget.CoordinatorLayout>
    

    【讨论】:

      【解决方案3】:

      我需要解决的另一个问题是跟踪拖动监听器之间的空白空间的拖动,以防用户没有放在监听器上,所以我可以将拖动的项目动画化回到它的开始,就像一个橡胶乐队。

      DragEvent.ACTION_DRAG_LOCATION 不会触发,除非它位于侦听器之上。所以我在我的 FrameLayout 中添加了一个拖动监听器,只是另一个视图。现在,当用户拖动时,我会获取位置更新,存储它们,当用户放下时,我会检查 FrameLayout 是否捕捉到了下降。这解决了你的问题,我有我的放置位置,无论它是在一个想要的拖动侦听器上还是早期放置,在这种情况下,我将原始项目橡皮筋带回它的开始。

      【讨论】:

        猜你喜欢
        • 2016-05-04
        • 1970-01-01
        • 2015-09-21
        • 2011-12-26
        • 1970-01-01
        • 2014-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多