用 Drag and Drop API 实现锤子 "One Step"

用 Drag and Drop API 实现锤子 "One Step"

昨天有不少非 Android 开发者朋友质疑我写文章的动机。对某些读者的困扰我表示遗憾,这里改一下引起争议标题。

本文只讨论实现类 "One Step" 的拖拽分享的原生 Android 实现,不讨论这个功能是 Android 内置还是『锤子先做出来』。非 Android 开发、或者对技术实现内容不感兴趣、或者想讨论 『体验』的可以一同去 如何评价锤子 M1 中的 One Step 功能? - 锤子科技 讨论。

---------------------------------------------------

昨天老罗发布了锤子新款手机,内置的 "One Step" 功能引爆全场,挑剔的知乎也是一片叫好。

那么,实现 "One Step" 功能真的需要去买一台锤子手机么?答案也许不是这样的:

Android 从 API 11 (3.0) 开始就支持一个 API 叫做 "Drag and Drop",可能不少开发者已经在 App 中已经使用过了。官方链接在此:developer.android.com/g 这个 API 可以让你极其容易的把页面里面的一个元素拖拽到其他元素上,甚至是其他 Apps 里 (如 Split Screen 的情况下)。利用这个 API 很容易配合 Android 的 Intent 分享机制实现 "One Step"。

以下是核心代码:

"One step" 部分:

* 启动一个 service/foreground service,如果你对此不了解,可以参阅 developer.android.com/g

private static final int FLAG_TOUCHABLE = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

private static final int FLAG_NOT_TOUCHABLE = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

...

WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mParams = new WindowManager.LayoutParams();
        mParams.format = PixelFormat.TRANSPARENT;
        mParams.gravity = Gravity.END;
        mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        mParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        //mParams.width = ...;
        mParams.flags = FLAG_NOT_TOUCHABLE;

mView = new YourCustomView(this);
        mView.setOnDragListener(new View.OnDragListener() {
            @Override
            public boolean onDrag(View v, DragEvent event) {
                switch (event.getAction()) {
                    case DragEvent.ACTION_DRAG_STARTED:
                        //drag started
                        mView.setShow(true);
                        mParams.flags = FLAG_TOUCHABLE;
                        wm.updateViewLayout(mView, mParams);
                        break;
                    case DragEvent.ACTION_DRAG_ENDED:
                        //drag ended
                        mView.setShow(false);
                        mParams.flags = FLAG_NOT_TOUCHABLE;
                        wm.updateViewLayout(mView, mParams);
                        break;
                    case DragEvent.ACTION_DROP:
                        //drop received
                         share(event.getClipData(), event.getX(), event.getY());
                        break;
                }
                return true;
            }
        });

        wm.addView(mView, mParams);

注意一点:为了能够获取 DragEvent.ACTION_ENTER, ACTION_EXIT, ACTION_DROP,这个 window 必须为 touchable 的,但是直接设置 touchable 会阻挡触摸事件,所以需要在 onDragListener 里面动态调整 FLAG。拖拽的元素包含的可供共享的数据在 event.getClipData() 里,另外可以获得 Drop event 的 x、y 坐标值来判断分享到哪个 app (这里略)。之后就简单了,大家去用微信的 sdk 也好,用标准 intent,6.0 以上的 Direct share 也好,都能很好的实现。


第三方客户端:

无需任何 SDK,直接调用 View::startDrag 或者 View::startDragAndDrop 方法就可以。

mView.setOnLongClickListener(() -> {
    mView.startDrag(
        ClipData.newPlainText("Label", "Content"),
        shadowBuilder,
        null,//这里可以不要,是应用内部拖拽用的
        View.DRAG_FLAG_GLOBAL //设置为全局,这样可以拖动到应用外
    )
});

ClipData 可以是 Plain Text 也可以是 html 文件,图片,url 等。


效果图:


效果视频,需要科学上网:

youtu.be/Hrf2lErKh4A

(知乎不支持视频和 GIF ,有需求的话我可以把视频搬到墙内)

总结:

如果锤子是用标准的 Drag And Drop api 的话,大家不用等到春季开源,现在就可以开工了,整个实现无需 root 无需专用系统,简单方便,还免费外带支持分屏拖拽数据。

如果锤子是 300 个昼夜自起炉灶,所谓开源最后只能锤子手机独享,那我也不知道应该怎么评价了。。。

祝读者玩 Android 玩的愉快。

编辑于 2016-10-21 19:46