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来对桌面进行向左向右滚动,想深入了解如何实现的,敬请阅读我相关blog:Launcher——桌面移动详解
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 桌面分页滑动代码.rar
安卓Android源码——Android Launcher 源码修改可编译.zip
安卓Android源码——Launcher2.rar
安卓Android源码——高仿小米launcher(ZAKER)跨屏拖动item.zip
安卓Android源码——安卓Android Launcher 桌面分页滑动代码.zip
安卓Android源码——Launcher源码修改可编译.zip
安卓Android源码——Launcher桌面分页滑动代码.zip
Androidlauncher开发.pdf
本版本基于Launcher3最新版进行开发,开发包括:双层改单层,特效,菜单,点击卸载等功能。
Android中实现Launcher功能之添加快捷方式
Android Launcher源码下载,PagedView,PagedViewIcon
安卓Android源码——高仿小米launcher(ZAKER)跨屏拖动item.rar
android laucnher3 源码,仅供学习使用。
Android Launcher研究.pdfAndroid Launcher研究.pdfAndroid Launcher研究.pdfAndroid Launcher研究.pdfAndroid Launcher研究.pdfAndroid Launcher研究.pdfAndroid Launcher研究.pdfAndroid Launcher研究.pdfAndroid ...
android的launcher是一款非常强大的系统应用,值得去好好的研究一番,这里对 Launcher2文件夾功能作详细的分析介绍。
Android 6.0 Launcher3 增加屏幕切换动画的资源和源码包
Android Launcher 源码修改可编译.zip源码资源下载Android Launcher 源码修改可编译.zip源码资源下载
更改官方的Launcher3使得可以在Android Studio编译 原始地址,从5892520提交开始 最小sdk版本为16 Android 5.0 版本及以上可能会出现因为相同权限声明而不能安装的问题 如果你想要可以在Eclipse编译的版本,可以看...
Android系统桌面Launcher3源码,可直接在Android Studio中编译。
这个是android Launcher2.2 源码,我最近在学习,有人想跟我交流就到我的博客:http://blog.csdn.net/gqdy365