研究Launcher的一大重点的就是view的拖动的实现,但在此之前,我们需要清楚ViewGroup、View触摸事件分发拦截机制

1、View的触摸事件的分发

分发流程概括如下

dispatchKeyEvent(KeyEvent event)
            --> onTouchListener(MotionEvent ev)           
                --> onTouchEvent(MotionEvent ev)

public boolean dispatchTouchEvent(MotionEvent event) {if (onFilterTouchEventForSecurity(event)) {ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {return true;}if (onTouchEvent(event)) {return true;}}return false;
}

从上述源码中我们可以看出,触摸事件显示分发到dispatchTouchEvent(event)后,先考虑View的OnTouchListener,然后才会传给onTouchEvent(Event).

接着看onTouchEvent()方法

public boolean onTouchEvent(MotionEvent event) {final int viewFlags = mViewFlags;if ((viewFlags & ENABLED_MASK) == DISABLED) {// 该view当前是disable状态,直接消耗掉该touch事件return (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));}if (mTouchDelegate != null) {// 直接交给事件代理者if (mTouchDelegate.onTouchEvent(event)) {return true;}}if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {// 具体的事件处理switch (event.getAction()) {case MotionEvent.ACTION_DOWN:{if (isInScrollingContainer) {// 如果父View是一个ViewGroup则isInScrollingContainer为true,否则未falsemPrivateFlags |= PFLAG_PREPRESSED;if (mPendingCheckForTap == null) {mPendingCheckForTap = new CheckForTap();}// 180MS后发起一个检测长按任务postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());// 接着--> checkForLongClick(ViewConfiguration.getTapTimeout());//(500 - 180)毫秒后执行检测长按的任务 -->performLongClick()//--> postDelayed(mPendingCheckForLongPress, //ViewConfiguration.getLongPressTimeout() - delayOffset);} else {setPressed(true);checkForLongClick(0);// 500MS}}case MotionEvent.ACTION_MOVE:{// 主要是检测滑动范围是否在该view的触摸范围内final int x = (int) event.getX();final int y = (int) event.getY();if (!pointInView(x, y, mTouchSlop)) {// 判断触摸事件是否依旧在该View的范围内removeTapCallback();if ((mPrivateFlags & PFLAG_PRESSED) != 0) {// Remove any future long press/tap checksremoveLongPressCallback();setPressed(false);}}}case MotionEvent.ACTION_UP:{1) <= 180ms mPrivateFlags |= PFLAG_PREPRESSED; //把mPrivateFlags和PFLAG_PREPRESSED按位或赋给mPrivateFlags--> performClick()64毫秒后执行mUnsetPressedState --> setPressed(false) -->dispatchSetPressed(false);2) > 180ms && <=500ms:移除长按检测,执行onClick()回调3) > 500ms... ...if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {if (!mHasPerformedLongPress) {// 500ms非长按事件removeLongPressCallback();if (!focusTaken) {if (mPerformClick == null) {mPerformClick = new PerformClick();}if (!post(mPerformClick)) {performClick();}}}if (prepressed) {postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {mUnsetPressedState.run();}... ...removeTapCallback();}}... ...return true;}return false;
}

再看view的点击事件和长按事件

    // View的点击操作public boolean performClick() {sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);return true;}return false;}// view的长按操作public boolean performLongClick() {sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);boolean handled = false;ListenerInfo li = mListenerInfo;if (li != null && li.mOnLongClickListener != null)handled = li.mOnLongClickListener.onLongClick(View.this);if (!handled)handled = showContextMenu();if (handled)performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);return handled;}

2、ViewGroup的触摸事件的分发

先由dispatchTouchEvent(MotionEvent ev)接收触摸事件,然后在onInterceptTouchEvent()中是否进行拦截,不拦截则将根据x,y所在的位置将touch事件分发给该view。

public boolean dispatchTouchEvent(MotionEvent ev) {boolean handled = false;...intercepted = onInterceptTouchEvent(ev);// 在onInterceptTouchEvent(ev)中可进行拦截,但只有为拦截时,才会进入下列if语句...if (onFilterTouchEventForSecurity(ev)) {if (!canceled && !intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex(); // always 0 for downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);final View[] children = mChildren;final boolean customOrder = isChildrenDrawingOrderEnabled();for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;final View child = children[childIndex];... resetCancelNextUpFlag(child);// 分发到子View --> dispatchTransformedTouchEvent() -->dispatchTouchEvent()if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// ...break;}}}if (newTouchTarget == null && mFirstTouchTarget != null) {// Did not find a child to receive the event.// Assign the pointer to the least recently added target.newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}return handled;}
}

ViewGroup的事件分发的流程概括如下:
dispatchKeyEvent(KeyEvent event)

--> onInterceptTouchEvent(MotionEvent ev)// 可在此进行事件拦截,return true:拦截该事件,否则继续向下分发
         --> onTouchEvent(MotionEvent ev) -- 自己处理touch事件

了解完View和ViewGroup的事件分发后,我们来看Launcher是如何实现控件的拖动的。

首先在Launcher中接收到一个长按事件

Launcher -->public boolean onLongClick(View v) {// 1) 获取CellLayout上一个被拖动的对象CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();if (itemUnderLongClick == null) {// 进入widget选择界面showWidgetAddEdit(true);} else {// 开始拖动ShortCut、Widget、FolderIconif (itemUnderLongClick instanceof JrdShortcut){mWorkspace.startDrag(longClickCellInfo);return true;} else if(itemUnderLongClick instanceof AppWidgetHostView) {mWorkspace.startDrag(longClickCellInfo);return true;} else if(itemUnderLongClick instanceof FolderIcon) {...mWorkspace.startDrag(longClickCellInfo);return true;}}}

接着Workspace中的startDrag()方法

void startDrag(CellLayout.CellInfo cellInfo) {View child = cellInfo.cell;// 判断点击的icon是否未空if (child != null && child.getTag() == null) {return;}// 隐藏原先的图标mDragInfo = cellInfo;child.setVisibility(INVISIBLE);mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);beginDragShared(child, this);
}

上述child.getTag()是在Launcher的onCreateShortCur()中设置的

View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);favorite.applyFromShortcutInfo(info, mIconCache);favorite.setOnClickListener(this);//设置点击事件return favorite;
}
public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {Bitmap b = info.getIcon(iconCache);LauncherAppState app = LauncherAppState.getInstance();DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();setCompoundDrawables(null, Utilities.createIconDrawable(b), null, null);setCompoundDrawablePadding((int) ((grid.folderIconSizePx - grid.iconSizePx) / 2f));setText(info.title);setTag(info);// 设置Tag信息
}

在Workspace.beginDragShared()中调用DragConoller()的startDrag()

public void beginDragShared(View child, DragSource source) {// 创建被拖动时的Bitmapfinal Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);...mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);}

DragController是拖动事件的控制中心

public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,float initialDragViewScale) {// 1) 调用各个监听对象for (DragListener listener : mListeners) {listener.onDragStart(source, dragInfo, dragAction);}// 记录当前的状态mDragging = true;// 2) 创建DragView对象final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);...// 3) 显示DragView对象(将该DragView添加到DragLayer上)dragView.show(mMotionDownX, mMotionDownY); --> DragView.show(){mDragLayer.addView(this)}// 4) 根据当前的位置处理移动事件handleMoveEvent(mMotionDownX, mMotionDownY);
}
private void handleMoveEvent(int x, int y) {// 1) 移动View,DragView继承至View,在方法move()中设置setTranslationX()、setTranslationY()即可移动viewmDragObject.dragView.move(x, y);// 2)查找移动目标DropTarget dropTarget = findDropTarget(x, y, coordinates);checkTouchMove(dropTarget);...checkScrollState(x, y);
}

上述的DragView的show()方法

public void show(int touchX, int touchY) {mDragLayer.addView(this);...setLayoutParams(lp);// 设置显示位置setTranslationX(touchX - mRegistrationX);setTranslationY(touchY - mRegistrationY);// 播放动画post(new Runnable() {public void run() {mAnim.start();}});
}

至此长按一个图标到到开始拖动已经准备好,详细的拖动过程需要从DragController.onInterceptTouchEvent()说起,

DragLayer是Launcher所有布局的父容器,它的onInterceptTouchEvent()已交由DragController.onInterceptTouchEvent()来处理,

public boolean onInterceptTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());final int dragLayerX = dragLayerPos[0];final int dragLayerY = dragLayerPos[1];switch (action) {case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_DOWN:// Remember location of down touchmMotionDownX = dragLayerX;mMotionDownY = dragLayerY;mLastDropTarget = null;break;case MotionEvent.ACTION_UP:mLastTouchUpTime = System.currentTimeMillis();if (mDragging) {PointF vec = isFlingingToDelete(mDragObject.dragSource);if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {vec = null;}if (vec != null) {dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);} else {drop(dragLayerX, dragLayerY);}}endDrag();break;case MotionEvent.ACTION_CANCEL:cancelDrag();break;}return mDragging;
}

在startDrag中已经将mDragging设为true,所以move状态下,DragLayer对touch事件进行了拦截,DragContrlller.onTouchEvent()中的做统一处理

public boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());final int dragLayerX = dragLayerPos[0];final int dragLayerY = dragLayerPos[1];switch (action) {case MotionEvent.ACTION_DOWN:// Remember where the motion event startedmMotionDownX = dragLayerX;mMotionDownY = dragLayerY;if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {mScrollState = SCROLL_WAITING_IN_ZONE;mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);} else {mScrollState = SCROLL_OUTSIDE_ZONE;}handleMoveEvent(dragLayerX, dragLayerY);break;case MotionEvent.ACTION_MOVE:handleMoveEvent(dragLayerX, dragLayerY);break;case MotionEvent.ACTION_UP:// Ensure that we've processed a move event at the current pointer location.handleMoveEvent(dragLayerX, dragLayerY);mHandler.removeCallbacks(mScrollRunnable);if (mDragging) {// 判断是否到达可删除的区域PointF vec = isFlingingToDelete(mDragObject.dragSource);if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {vec = null;}if (vec != null) {// 拖动到垃圾箱中进行删除dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);} else {drop(dragLayerX, dragLayerY);}}// 拖放结束endDrag();break;case MotionEvent.ACTION_CANCEL:mHandler.removeCallbacks(mScrollRunnable);cancelDrag();break;}return true;
}// 拖放结束
private void drop(float x, float y) {final int[] coordinates = mCoordinatesTemp;// x,y所在区域是否有合适的目标final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);boolean accepted = false;if (dropTarget != null) {mDragObject.dragComplete = true;dropTarget.onDragExit(mDragObject);if (dropTarget.acceptDrop(mDragObject)) {dropTarget.onDrop(mDragObject);accepted = true;}}mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
}private void endDrag() {if (mDragging) {// 回复拖放状态值mDragging = false;clearScrollRunnable();...mDragObject.dragView.remove();...for (DragListener listener : mListeners) {listener.onDragEnd();}}releaseVelocityTracker();
}
// 更新界面,更新数据库Workspace.acceptDrop()--> CellLayout.createArea()--> CellLayout.commitTempPlacement()--> Workspace.updateItemLocationsInDatabase(this);

至此ShortCut的拖动就完成了,数据库的更新就不在此显示了,数据库的更新方法在Workspace.updateItemLocationsInDatabase(this)中




Android Launcher3(二) -- Drag拖动实现相关推荐

  1. Android Launcher3简介

    一.Launcher3概述 Launcher顾名思义,就是桌面的意思,也是android系统启动后第一个启动的应用程序,这里以android11为例,和其他应用并无区别,只是增加了对其他app和wid ...

  2. android launcher3,Android Launcher3 基本功能分析

    Android Launcher3 基本功能分析 1, 界面的布局, 从上往下分别为:DeleteDropTarget(应用卸载区域,它是一个DropTarget) Workspace(页面容器,一个 ...

  3. Android进阶七:RecyclerView拖动滑动之ItemTouchHelper

    ItemTouchHelper ItemTouchHelper是一个强大的工具,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情. 它是RecyclerView.ItemDe ...

  4. android launcher3源码分析,Android Launcher3源码分析与修改

    Launcher和Setting是客户需求经常改动的地方,不过其代码量也不容小觑.今天就初略来看一下,以下内容都是本人查阅资料加上自己的理解得出,由于自己水平有限,如果误导还请指出: 先从Androi ...

  5. android camera(二):摄像头工作原理、s5PV310 摄像头接口(CAMIF)

    关键词: android  camera CMM 模组 camera参数  CAMIF 平台信息: 内核: linux 系统: android 平台:S5PV310(samsung exynos 42 ...

  6. android p ify 三星,Enjarify - Android逆向(二)

    Enjarify - Android逆向(二) 首先奉上enjarify的Github地址,小伙伴们可以clone到本地使用哦 Enjarify介绍 上一节我们说了,在开发Android应用时,And ...

  7. 从斗鱼Android开发二面被刷,到VR微创公司收留,我的NDK开发梦究竟缺了什么

    APP如何 瘦身? 自定义控件要重写哪些方法? 安卓中哪些地方用到了设计模式?使用到的是什么模式? 使用过注解吗? 如何自定义注解? 看过Android的源码吗? 如何在链表中判断是否存在环?(快慢指 ...

  8. 2020.8.13 京东Android开发二面

    本次面试着重考察了计算机基础知识,Android相关均未涉及,及时暴露了我的一些漏洞,时间尚早,尽快补上来吧. 2020.8.13 京东Android开发二面 问题 聊项目 用过哪些数据库 数据库索引 ...

  9. Android 自定义二维码

    Android生成二维码使用的是zxing. 1.加入依赖,或者自己选择zxing版本:Releases · zxing/zxing · GitHub dependencies {...impleme ...

  10. 2020.8.25 斗鱼Android开发二面面经

    本次面试全是开发技术相关问题,暴露了我只具备基础知识,开发经验不足的问题,希望自己以后抓紧推进后续学习,早日补上漏洞. 斗鱼Android开发二面面经 面试问题 自我介绍 疫情期间的学习安排 介绍一下 ...

最新文章

  1. COLLATE 函数
  2. Pandownload 下线了,我花了 30 分钟自己搭建了一个网盘
  3. XML Schema简介
  4. c++ map 析构函数_说说C++的虚析构函数
  5. WebStorm 常用功能的使用技巧分享
  6. 关于通过反汇编查看dll的方法【转】(
  7. NEO技术文章征集大赛
  8. C#获取电脑硬件信息(CPU ID、主板ID、硬盘ID、BIOS编号)
  9. 设置idea类注释模板
  10. 墨刀和axure你应该用哪个?
  11. win2012金蝶服务器不能运行,解决在win7、win10 下无法安装 金蝶KIS 12.3 专业版 的问题...
  12. C语言实现Base64编解码(加密和解密)
  13. xdm,外包能干吗?
  14. 如何用ps把照片变成白底
  15. 微软确认:从4月13日起,Win10系统将强制卸载旧版Edge浏览器
  16. 如何将xlsx表格文件转换成txt文件?
  17. 如何修改知乎绑定的手机号(2021)
  18. Linux ARM平台开发系列讲解(SPI与TTY实战) 2.6.1 SPI主设备驱动WK2124实战
  19. 微信小程序 - 获取屏幕的大小
  20. 教育邮箱怎么申请?国际教育电子邮箱

热门文章

  1. 策略模式(封装一系列的功能,使之可以相互替换)
  2. 对python的认识800字_我对python里True和False的理解
  3. sega+model+3+android,世嘉MODEL2经典老游戏移植登场 追加联网对战
  4. java short long_谈谈Java中整数类型(short int long)的存储方式
  5. Spring Boot 2.x整合Websocket(基于Spring Boot 2.x 前后端分离 iview admin vue 集成activiti工作流...
  6. WCF NetTcpBinding Transport安全模式(6) ClientCredentialType证书验证模式---- PeerTrust验证模式...
  7. 解读ASP.NET 5 MVC6系列(7):依赖注入
  8. Google在中国打败百度的方法其实很简单.只要需改变5点.
  9. 读写配置文件app.config
  10. ----移动端移动端调试神器vConsole----