`
lx82319214
  • 浏览: 110039 次
  • 性别: Icon_minigender_1
  • 来自: 贵州
社区版块
存档分类
最新评论

android Launcher——拖放功能深入研究

 
阅读更多

Luancher有一个相对比较复杂的功能就是拖放功能,要深入了解launcher,深入理解拖放功能是有必要的,这篇blog,我将对launcher的拖放功能做深入的了解
1.首先直观感受什么时候开始拖放?我们长按桌面一个应用图标或者控件的时候拖放就开始了,包括在all app view中长按应用图标,下面就是我截取的拖放开始的代码调用堆栈
at com.android.launcher2.DragController.startDrag(DragController.java:170)
at com.android.launcher2.Workspace.startDrag(Workspace.java:1068)
at com.android.launcher2.Launcher.onLongClick(Launcher.java:1683)
at android.view.View.performLongClick(View.java:2427)
at android.widget.TextView.performLongClick(TextView.java:7286)
at android.view.View$CheckForLongPress.run(View.java:8792)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
桌面应用图标由Launcher.onLongClick负责监听处理,插入断点debug进入onLongclick函数
         if (!(v instanceof CellLayout)) {
            v = (View) v.getParent();
        }
                                 //获取桌面CellLayout上一个被拖动的对象
         CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
               ...
        if (mWorkspace.allowLongPress()) {
            if (cellInfo.cell == null) {
                ...
            } else {
                if (!(cellInfo.cell instanceof Folder)) {
                    ...
                    //调用Workspace.startDrag处理拖动
                    mWorkspace.startDrag(cellInfo);
                }
            }
        }
我上面只写出关键代码,首先是获取被拖动的对象v.getTag(),Tag什么时候被设置进去的了
   public boolean onInterceptTouchEvent(MotionEvent ev) {
        ...
        if (action == MotionEvent.ACTION_DOWN) {
                        ...
            boolean found = false;
            for (int i = count - 1; i >= 0; i--) {
                final View child = getChildAt(i);

                if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
                    child.getHitRect(frame);
                    //判断区域是否在这个子控件的区间,如果有把child信息赋给mCellInfo
                    if (frame.contains(x, y)) {
                        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                        cellInfo.cell = child;
                        cellInfo.cellX = lp.cellX;
                        cellInfo.cellY = lp.cellY;
                        cellInfo.spanX = lp.cellHSpan;
                        cellInfo.spanY = lp.cellVSpan;
                        cellInfo.valid = true;
                        found = true;
                        mDirtyTag = false;
                        break;
                    }
                }
            }
            
            mLastDownOnOccupiedCell = found;

            if (!found) {
                            ...
                            //没有child view 说明没有点击桌面图标项
                cellInfo.cell = null;               
            }
            setTag(cellInfo);
        }
看了上面代码知道,当开始点击桌面时,celllayout就会根据点击区域去查找在该区域是否有child存在,若有把它设置为tag.cell,没有,tag.cell设置为null,后面在开始拖放时launcher.onlongclick中对tag进行处理,
这个理顺了,再深入到workspace.startDrag函数,workspace.startDrag调用DragController.startDrag去处理拖放
mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
再分析一下上面调用的几个参数
child = tag.cell
this = workspace
child.getTag()是什么呢?在什么时候被设置?再仔细回顾原来launcher加载过程代码,在launcher.createShortcut中它被设置了:注意下面我代码中的注释
    View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
        TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);

        favorite.setCompoundDrawablesWithIntrinsicBounds(null,
                new FastBitmapDrawable(info.getIcon(mIconCache)),
                null, null);
        favorite.setText(info.title);
        //设置favorite(一个桌面Shortcut类型的图标)的tag
        favorite.setTag(info);
        favorite.setOnClickListener(this);

        return favorite;
    }
继续深入解读DragController.startDrag函数
    public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
            //设置拖放源view
            mOriginator = v;
        //获取view的bitmap
        Bitmap b = getViewBitmap(v);

        if (b == null) {
            // out of memory?
            return;
        }
        //获取源view在整个屏幕的坐标
        int[] loc = mCoordinatesTemp;
        v.getLocationOnScreen(loc);
        int screenX = loc[0];
        int screenY = loc[1];
                                //该函数功能解读请继续往下看
        startDrag(b, screenX, screenY, 0, 0, b.getWidth(), b.getHeight(),
                source, dragInfo, dragAction);

        b.recycle();
        //设置原来view不可见
        if (dragAction == DRAG_ACTION_MOVE) {
            v.setVisibility(View.GONE);
        }
    }

////////////////////////////////////////////////////////////
    public void startDrag(Bitmap b, int screenX, int screenY,
            int textureLeft, int textureTop, int textureWidth, int textureHeight,
            DragSource source, Object dragInfo, int dragAction) {
        //隐藏软键盘
        if (mInputMethodManager == null) {
            mInputMethodManager = (InputMethodManager)
                    mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
                                //mListener = deletezone,在blog laucher ui框架中有说明该函数,主要就是现实deletezone
        if (mListener != null) {
            mListener.onDragStart(source, dragInfo, dragAction);
        }
                                //记住手指点击位置与屏幕左上角位置偏差
        int registrationX = ((int)mMotionDownX) - screenX;
        int registrationY = ((int)mMotionDownY) - screenY;

        mTouchOffsetX = mMotionDownX - screenX;
        mTouchOffsetY = mMotionDownY - screenY;

        mDragging = true;
        mDragSource = source;
        mDragInfo = dragInfo;

        mVibrator.vibrate(VIBRATE_DURATION);
                                //创建DragView对象
        DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY,
                textureLeft, textureTop, textureWidth, textureHeight);
        //显示Dragview对象
        dragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY);
    }
到这里,拖放开始处理的框框基本清楚,但是DragView的创建和显示还有必要进一步深究
        DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY,
                textureLeft, textureTop, textureWidth, textureHeight);
//函数参数说明:
mContext = launcher
b = 根据拖放源view创建的大小一致的bitmap对象
registrationX = 手指点击位置与拖放源view 坐标x方向的偏移        
registrationY = 手指点击位置与拖放源view 坐标y方向的偏移        
textureLeft = 0
textureTop = 0
textureWidth = b.getWidth()
textureHeight =  b.getHeight()
//函数体
        super(context);
                                //获取window管理器
        mWindowManager = WindowManagerImpl.getDefault();
        //一个动画,开始拖放时显示
        mTween = new SymmetricalLinearTween(false, 110 /*ms duration*/, this);
                                //对源b 做一个缩放产生一个新的bitmap对象
        Matrix scale = new Matrix();
        float scaleFactor = width;
        scaleFactor = mScale = (scaleFactor + DRAG_SCALE) / scaleFactor;
        scale.setScale(scaleFactor, scaleFactor);

        mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height, scale, true);

        // The point in our scaled bitmap that the touch events are located
        mRegistrationX = registrationX + (DRAG_SCALE / 2);
        mRegistrationY = registrationY + (DRAG_SCALE / 2);
其实函数很简单,就是记录一些参数,然后对view图片做一个缩放处理,并且准备一个tween动画,在长按桌面图标后图标跳跃到手指上显示该动画,了解这些,有助于理解函数dragView.show
//windowToken来自与workspace.onattchtowindow时候获取的view 所有attch的window标识,有这个参数,可以把dragview添加到
workspace所属的同一个window对象
//touchX,手指点击在屏幕的位置x
//touchy,手指点击在屏幕的位置y
    public void show(IBinder windowToken, int touchX, int touchY) {
        WindowManager.LayoutParams lp;
        int pixelFormat;

        pixelFormat = PixelFormat.TRANSLUCENT;
        //布局参数值的注意的是view位置参数,
        //x=touchX-mRegistrationX=touchX-(registrationX + (DRAG_SCALE / 2))=手指点击位置-view坐标与手指点击位置偏差加上缩放值
        lp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                touchX-mRegistrationX, touchY-mRegistrationY,
                WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                    /*| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM*/,
                pixelFormat);
//        lp.token = mStatusBarView.getWindowToken();
        lp.gravity = Gravity.LEFT | Gravity.TOP;
        lp.token = windowToken;
        lp.setTitle("DragView");
        mLayoutParams = lp;
                                //dragview的父类是Window,也就是说dragview可以拖放到屏幕的任意位置
        mWindowManager.addView(this, lp);

        mAnimationScale = 1.0f/mScale;
        //播放开始拖动动画(直观感觉是图标变大了)
        mTween.start(true);
    }

2,拖放过程
拖放过程的处理需要深入了解DragController.onTouchEvent(MotionEvent ev)函数的实现,我下面列出关键的MotionEvent.ACTION_MOVE部分代码并作出注释说明
                        case MotionEvent.ACTION_MOVE:
                                // 根据手指坐标移动dragview
                                mDragView.move((int) ev.getRawX(), (int) ev.getRawY());

                                // 根据手指所在屏幕坐标获取目前所在的拖放目的view
                                final int[] coordinates = mCoordinatesTemp;
                                DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
                                // 根据不同状态调用DropTarget的生命周期处理函数
                                if (dropTarget != null) {
                                        if (mLastDropTarget == dropTarget) {
                                                dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX,
                                                                (int) mTouchOffsetY, mDragView, mDragInfo);
                                        } else {
                                                if (mLastDropTarget != null) {
                                                        mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
                                                                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
                                                }
                                                dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX,
                                                                (int) mTouchOffsetY, mDragView, mDragInfo);
                                        }
                                } else {
                                        if (mLastDropTarget != null) {
                                                mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX,
                                                                (int) mTouchOffsetY, mDragView, mDragInfo);
                                        }
                                }
                                mLastDropTarget = dropTarget;

                                //判断是否在delete区域
                                boolean inDeleteRegion = false;
                                if (mDeleteRegion != null) {
                                        inDeleteRegion = mDeleteRegion.contains(screenX, screenY);
                                }
                                 //不在delete区域,在左边切换区
                                if (!inDeleteRegion && screenX < SCROLL_ZONE) {
                                        if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                                                mScrollState = SCROLL_WAITING_IN_ZONE;
                                                mScrollRunnable.setDirection(SCROLL_LEFT);
                                                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                                        }
                                }
                                //不在delete区,在右边切换区
                                else if (!inDeleteRegion && screenX > scrollView.getWidth() - SCROLL_ZONE) {
                                        if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                                                mScrollState = SCROLL_WAITING_IN_ZONE;
                                                mScrollRunnable.setDirection(SCROLL_RIGHT);
                                                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                                        }
                                }
                                //在delete区域
                                else {
                                        if (mScrollState == SCROLL_WAITING_IN_ZONE) {
                                                mScrollState = SCROLL_OUTSIDE_ZONE;
                                                mScrollRunnable.setDirection(SCROLL_RIGHT);
                                                mHandler.removeCallbacks(mScrollRunnable);
                                        }
                                }

                                break;
拖放过程总的处理思路就是根据当前坐标位置获取dropTarget的目标位置,然后又根据相关状态和坐标位置调用dropTarget的对应生命周期函数,这里面有两个点需要进一步深入了解,一是查找dropTarget:findDropTarget(screenX, screenY, coordinates),二是mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
--1.findDropTarget
    private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
        final Rect r = mRectTemp;
                                //mDropTargets是一个拖放目标view别表,在laucher初始化等被添加
        final ArrayList<DropTarget> dropTargets = mDropTargets;
        final int count = dropTargets.size();
        //遍历dropTargets列表,查看{x,y}是否落在dropTarget坐标区域,若是,返回dropTarget。
        for (int i=count-1; i>=0; i--) {
            final DropTarget target = dropTargets.get(i);
            target.getHitRect(r);
            //获取target左上角屏幕坐标
            target.getLocationOnScreen(dropCoordinates);
            r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
            if (r.contains(x, y)) {
                dropCoordinates[0] = x - dropCoordinates[0];
                dropCoordinates[1] = y - dropCoordinates[1];
                return target;
            }
        }
        return null;
    }
--2.mScrollRunnable
//看mScrollRunnable对象的构造类,通过setDirection设置滚动方向,然后通过一步调用DragScroller.scrollLeft/scrollRight来对桌面进行向左向右滚动,想深入了解如何实现的,敬请阅读我相关blogauncher——桌面移动详解
    private class ScrollRunnable implements Runnable {
        private int mDirection;

        ScrollRunnable() {
        }

        public void run() {
            if (mDragScroller != null) {
                if (mDirection == SCROLL_LEFT) {
                    mDragScroller.scrollLeft();
                } else {
                    mDragScroller.scrollRight();
                }
                mScrollState = SCROLL_OUTSIDE_ZONE;
            }
        }

        void setDirection(int direction) {
            mDirection = direction;
        }
    }
3.拖放结束,入口还是在DragController.onTouchEvent(MotionEvent ev)
        先看调用堆栈:
at com.android.launcher2.DragController.endDrag(DragController.java:315)
at com.android.launcher2.DragController.onTouchEvent(DragController.java:471)
at com.android.launcher2.DragLayer.onTouchEvent(DragLayer.java:64)
at android.view.View.dispatchTouchEvent(View.java:3766)
        onTouchEvent关键代码:
                        case MotionEvent.ACTION_UP:
                                mHandler.removeCallbacks(mScrollRunnable);
                                if (mDragging) {
                                        // 拖动过程手指离开屏幕
                                        drop(screenX, screenY);
                                }
                                endDrag();
                                break;
--1.drop(screenX, screenY);
        final int[] coordinates = mCoordinatesTemp;
        //获取dropTarget对象
        DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
        //coordinates=点触点在dropTarget 中的xy坐标

        if (dropTarget != null) {
            dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
                    //根据相关参数判断是否可dropTarget是否接受该drag view
            if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo)) {
                dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
                mDragSource.onDropCompleted((View) dropTarget, true);
                return true;
            } else {
                mDragSource.onDropCompleted((View) dropTarget, false);
                return true;
            }
        }
        return false

分享到:
评论

相关推荐

    安卓Android源码——Android Launcher 源码修改可编译.zip

    通过以上知识点,我们可以了解到,解压并研究"安卓Android源码——Android Launcher 源码修改可编译.zip"文件,不仅可以深入了解Android系统的工作机制,还能动手实践,创造出独具特色的Android启动器。

    安卓Android源码——安卓Android Launcher 桌面分页滑动代码.rar

    以上就是关于“安卓Android源码——安卓Android Launcher 桌面分页滑动代码”这个主题的一些关键知识点。通过深入理解这些概念和技术,开发者可以更好地理解和定制自己的启动器,提供独特的用户体验。

    安卓Android源码——Launcher2.rar

    总之,研究`安卓Android源码——Launcher2`能够帮助开发者深入理解Android系统的桌面逻辑和交互机制,提升APP开发和系统优化的能力。通过分析源码,我们可以学习到Android的UI设计、数据存储、事件处理、动画实现等...

    安卓Android源码——高仿小米launcher(ZAKER)跨屏拖动item.zip

    本压缩包"安卓Android源码——高仿小米launcher(ZAKER)跨屏拖动item.zip"提供了对小米Launcher(ZAKER风格)跨屏拖动item功能的实现,这对于开发者来说是一个宝贵的资源,可以帮助他们学习如何构建类似的应用启动器...

    Android-根据最新版Launcher修改单层桌面

    【Android开发-根据最新版Launcher修改单层桌面】 在Android操作系统中,...对于Android开发者来说,深入研究此类项目不仅可以学习到启动器的实现原理,还能掌握如何通过源码修改来优化和定制系统组件,提升个人技能。

    安卓Android源码——Launcher源码修改可编译.zip

    总的来说,这份"安卓Android源码——Launcher源码修改可编译.zip"资料将带你深入Android系统的内核,提供一个实践和学习的机会,有助于你成为一名更优秀的Android开发者。通过源码级别的修改和调试,你可以更深入地...

    android launcher3源码 循环翻页

    通过对Android Launcher3源码的深入研究,我们可以了解到循环翻页的实现细节,这对于我们定制自己的桌面应用或者优化系统性能有着重要的指导意义。理解这些底层机制,可以让我们更好地控制用户界面的行为,提供更...

    安卓Android源码——安卓Android Launcher 桌面分页滑动代码.zip

    这份“安卓Android源码——安卓Android Launcher 桌面分页滑动代码.zip”包含的源码着重解析了如何实现桌面分页滑动的逻辑。现在我们将深入探讨这一主题,讲解其中的关键知识点。 首先,我们来看看Android Launcher...

    Androidlauncher开发.pdf

    Android_launcher 开发概述 Android_launcher 是 Android 系统中的一個重要组件,负责管理用户的桌面环境。作为一个 GUI,它不仅需要提供对所有应用程序的映射,还需要具备良好的交互性和美观的界面设计。在 ...

    androidlauncher应用开发完整清晰版

    ### Android Launcher ...通过以上对《androidlauncher应用开发完整清晰版》一书的内容总结,可以看出该书旨在全面深入地介绍Launcher开发的核心技术和实践经验,适合希望深入了解Android桌面开发的技术人员阅读学习。

    android Launcher源码详解

    本文将对 Launcher 的源码进行深入分析,探讨其运行机制和设计思想。 一、Launcher 的整体结构 Launcher 的整体结构可以通过查看 Launcher.xml 布局文件和使用 hierarchyviewer 布局查看工具来了解。从 hierarchy...

    android Launcher2文件夾功能分析

    **Android Launcher2 文件夹功能分析** 在Android操作系统中,Launcher是用户界面的核心组成部分,它扮演着桌面的角色,允许用户启动应用程序、创建快捷方式以及管理主屏幕上的图标布局。`Launcher2`是Android早期...

    Android Launcher3源码

    通过分析`Launcher3`的源码,我们可以深入理解Android系统桌面的工作原理,了解如何定制自己的启动器。 1. **项目结构** `Launcher3`源码结构分为几个主要部分:UI组件、数据模型、后台服务、偏好设置和资源文件。...

    安卓Android源码——Launcher桌面分页滑动代码.zip

    这份"安卓Android源码——Launcher桌面分页滑动代码.zip"提供了关于如何实现Launcher应用中分页滑动功能的源代码。下面将详细解析这个主题,帮助你理解其背后的实现机制。 首先,我们来看"TestPagedView"这个名字,...

    android Launcher2.2源码

    以上是对Android Launcher2.2源码的基本解读,深入学习源码能帮助开发者更深入地理解Android系统的运行机制,为自定义Launcher或者优化现有应用提供理论基础。对于想要进一步研究的读者,建议配合Android SDK、相关...

    android手把手教你开发launcher.pdf

    Launcher 通常提供了桌面元素的显示、编辑和管理功能,並且可以根据用户的需求进行自定义。 开发自己的 Launcher 要开发自己的 Launcher,我们需要创建一个新的 Android 项目,并在 AndroidManifest.xml 文件中...

    Android Launcher 源码修改可编译.zip源码资源下载

    5. **DragAndDrop**: Launcher中的拖放功能涉及`DragController`和`DropTarget`接口,用于处理元素的移动和放置。 6. **Preference**: 对于设置界面,`PreferenceFragment`和`PreferenceScreen`会用来构建用户可以...

    android launcher2源码

    《深入剖析Android ...通过对Android Launcher2源码的深入研究,开发者不仅可以掌握桌面应用的开发技术,还能了解到Android系统级别的交互逻辑和组件通信机制。这将为更高级的定制化开发和性能优化提供宝贵的参考。

    安卓Launcher桌面相关-Android实现图标拖拽.rar

    "安卓Launcher桌面相关-Android实现图标拖拽"这个压缩包文件似乎包含了一些关于如何在Android Launcher中实现图标拖放功能的源代码或示例项目。下面将详细介绍Android Launcher桌面的原理以及实现图标拖拽的相关知识...

    安卓Android源码——高仿小米launcher(ZAKER)跨屏拖动item.rar

    "安卓Android源码——高仿小米launcher(ZAKER)跨屏拖动item.rar" 这个标题揭示了我们即将探讨的核心内容:一个针对Android操作系统的源代码项目,该项目旨在模仿小米(Xiaomi)启动器(Launcher)的特性,特别是...

Global site tag (gtag.js) - Google Analytics