上一章墨香带你学Launcher之(六)--拖拽咱们介绍了Launcher的拖拽过程,涉及到的范围比较广,包括图标的拖拽,桌面上CellLayout的拖拽,小部件的拖拽,以及跨不一样部件的拖拽,设计思想很是巧妙,不过整个流程相对也比较好掌握,只要跟着上一章的流程本身多跟踪几遍基本就熟悉了。按照计划本章咱们继续学习Launcher的Widget的加载、添加以及Widget的大小调节。javascript
其实咱们在第二章墨香带你学Launcher之(二)-数据加载流程介绍过Widget数据的加载,相对只是简单的作了介绍,下面咱们稍微讲的详细点。java
咱们知道Widget的数据加载开始在LauncherModel中的updateWidgetsModel方法中,咱们看下代码:git
void updateWidgetsModel(boolean refresh) {
PackageManager packageManager = mApp.getContext().getPackageManager();
final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
}复制代码
上面代码咱们能够看到是经过调用getWidgetProviders(mApp.getContext(), refresh)方法来获取全部Widget的,代码:github
public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
boolean refresh) {
ArrayList<LauncherAppWidgetProviderInfo> results =
new ArrayList<LauncherAppWidgetProviderInfo>();
try {
synchronized (sBgLock) {
if (sBgWidgetProviders == null || refresh) {
HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
= new HashMap<>();
AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
LauncherAppWidgetProviderInfo info;
List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
for (AppWidgetProviderInfo pInfo : widgets) {
info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
UserHandleCompat user = wm.getUser(info);
tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
}
Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
for (CustomAppWidget widget : customWidgets) {
info = new LauncherAppWidgetProviderInfo(context, widget);
UserHandleCompat user = wm.getUser(info);
tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
}
// Replace the global list at the very end, so that if there is an exception,
// previously loaded provider list is used.
sBgWidgetProviders = tmpWidgetProviders;
}
results.addAll(sBgWidgetProviders.values());
return results;
}
} catch (Exception e) {
...
}
}复制代码
咱们看到首先是初始化AppWidgetManagerCompat,咱们以前介绍过带有Compat的是兼容组件,咱们看看是怎么兼容的,spring
咱们下面代码:微信
public static AppWidgetManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
if (Utilities.ATLEAST_LOLLIPOP) {
sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
} else {
sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
}
}
return sInstance;
}
}复制代码
咱们能够看到AppWidgetManagerCompat的初始化有两个,一个是当Api版本高于21(包含21)时,用AppWidgetManagerCompatVL,低于21时用AppWidgetManagerCompatV16,这两个有什么不一样,咱们下面分析。app
下面咱们看如何获取Widget列表对象:异步
List<AppWidgetProviderInfo> widgets = wm.getAllProviders();复制代码
getAllProviders()方法是一个抽象方法,因此咱们看哪里进行了复写,ide
能够看到仍是上面两个兼容类复写了该方法,咱们看这个两个类中作了什么处理,先看V16中的:函数
@Override
public List<AppWidgetProviderInfo> getAllProviders() {
return mAppWidgetManager.getInstalledProviders();
}复制代码
咱们再看mAppWidgetManager这个是在哪里初始化,
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
Bundle options) {
if (Utilities.ATLEAST_JB_MR1) {
return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
} else {
return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
}
}复制代码
里面有个if语句,咱们能够看到当Api大于等于17时,调用第一个进行初始化,不然调用第二个方法进行初始化,这就是对不一样手机版本作的兼容。在咱们写App的时候若是遇到类似状况也能够这么处理。
咱们再看一下VL中的getAllProviders()方法:
@Override
public List<AppWidgetProviderInfo> getAllProviders() {
ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
for (UserHandle user : mUserManager.getUserProfiles()) {
providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
}
return providers;
}复制代码
和V16中的不同了,这里面是经过for循环来获取的,其中有个UserHandle,那么在源码中给出的解释是设备中的每一个用户,我的理解应该是每一个应用,每一个应用会有0-N个Widget,也就是从每一个应用中获取每一个应用的Widget列表。这样for循环就能够获取整个手机中全部应用的widget列表了。
再回到上面getWidgetProviders方法的代码中,咱们接着看,接着for循环AppWidgetProviderInfo列表信息,重构LauncherAppWidgetProviderInfo对象,这里有点怪,为啥有了AppWidgetProviderInfo对象还要重构一个LauncherAppWidgetProviderInfo对象,咱们知道在写插件的时候每一个Widget都会有一个类继承AppWidgetProvider,这样才会有一个插件,所以咱们知道AppWidgetProviderInfo对象确定是AppWidgetProvider的对象,那么LauncherAppWidgetProviderInfo是什么,咱们接着看能不能找到答案,LauncherAppWidgetProviderInfo的初始化时经过
LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);复制代码
方法进行初始化的,咱们再看LauncherAppWidgetProviderInfo又继承AppWidgetProviderInfo,愈来愈怪,咱们接着看fromProviderInfo(context, pInfo)方法:
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
Parcel p = Parcel.obtain();
info.writeToParcel(p, 0);
p.setDataPosition(0);
LauncherAppWidgetProviderInfo lawpi = new LauncherAppWidgetProviderInfo(p);
p.recycle();
return lawpi;
}复制代码
咱们看到最后是经过new LauncherAppWidgetProviderInfo来生成一个LauncherAppWidgetProviderInfo对象,那么这个对象构造函数中有什么:
public LauncherAppWidgetProviderInfo(Parcel in) {
super(in);
initSpans();
}复制代码
这个构造函数调用了initSpans方法,咱们接着追寻:
private void initSpans() {
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
// We only care out the cell size, which is independent of the the layout direction.
Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);
Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);
// Always assume we're working with the smallest span to make sure we
// reserve enough space in both orientations.
float smallestCellWidth = DeviceProfile.calculateCellWidth(Math.min(
idp.landscapeProfile.widthPx - paddingLand.left - paddingLand.right,
idp.portraitProfile.widthPx - paddingPort.left - paddingPort.right),
idp.numColumns);
float smallestCellHeight = DeviceProfile.calculateCellWidth(Math.min(
idp.landscapeProfile.heightPx - paddingLand.top - paddingLand.bottom,
idp.portraitProfile.heightPx - paddingPort.top - paddingPort.bottom),
idp.numRows);
// We want to account for the extra amount of padding that we are adding to the widget
// to ensure that it gets the full amount of space that it has requested.
Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
app.getContext(), provider, null);
spanX = Math.max(1, (int) Math.ceil(
(minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
spanY = Math.max(1, (int) Math.ceil(
(minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
minSpanX = Math.max(1, (int) Math.ceil(
(minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
minSpanY = Math.max(1, (int) Math.ceil(
(minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
}复制代码
这段代码也不难,是为了算四个参数:spanX、spanY、minSpanX、minSpanY,看过我前面博客的都知道这个spanX和spanY参数是什么,其实这个LauncherAppWidgetProviderInfo对象比系统自带的AppWidgetProviderInfo带有的就是多了这几个参数,也就是方便咱们添加到桌面是计算占用位置。
最后获得HashMap
mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);复制代码
将这个集合放到WidgetsModel中:
public void setWidgetsAndShortcuts(ArrayList<Object> rawWidgetsShortcuts) {
...
HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
// clear the lists.
...
InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
// add and update.
for (Object o: rawWidgetsShortcuts) {
String packageName = "";
UserHandleCompat userHandle = null;
ComponentName componentName = null;
if (o instanceof LauncherAppWidgetProviderInfo) {
LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
// Ensure that all widgets we show can be added on a workspace of this size
int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
if (minSpanX <= (int) idp.numColumns &&
minSpanY <= (int) idp.numRows) {
componentName = widgetInfo.provider;
packageName = widgetInfo.provider.getPackageName();
userHandle = mAppWidgetMgr.getUser(widgetInfo);
} else {
...
continue;
}
} else if (o instanceof ResolveInfo) {
ResolveInfo resolveInfo = (ResolveInfo) o;
componentName = new ComponentName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
packageName = resolveInfo.activityInfo.packageName;
userHandle = UserHandleCompat.myUserHandle();
}
if (componentName == null || userHandle == null) {
...
continue;
}
...
PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(pInfo);
if (widgetsShortcutsList != null) {
widgetsShortcutsList.add(o);
} else {
widgetsShortcutsList = new ArrayList<>();
widgetsShortcutsList.add(o);
pInfo = new PackageItemInfo(packageName);
mIconCache.getTitleAndIconForApp(packageName, userHandle,
true /* userLowResIcon */, pInfo);
pInfo.titleSectionName = mIndexer.computeSectionName(pInfo.title);
mWidgetsList.put(pInfo, widgetsShortcutsList);
tmpPackageItemInfos.put(packageName, pInfo);
mPackageItemInfos.add(pInfo);
}
}
// 排序.
...
}
}复制代码
在这里将不一样应用的Widget放到同一个列表中而后在放到mWidgetsList中,以供应加载Widget列表。接着执行绑定过程,绑定过程咱们在第三章墨香带你学Launcher之(三)-绑定屏幕、图标、文件夹和Widget介绍过,可是里面还有些东西在这里须要介绍一下,咱们看源码知道其实Widget是经过适配器放置到WidgetsRecyclerView里面的,WidgetsRecyclerView是一个RecyclerView,而每一个Widget视图是一个WidgetCell,那么WidgetCell是什么,咱们看WidgetsListAdapter适配器,这个咱们就不详细介绍了,在里面的onBindViewHolder方法中对WidgetCell进行了初始化,其中在里面会调动下面方法:
widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader);复制代码
咱们看看这个方法:
public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
WidgetPreviewLoader loader) {
InvariantDeviceProfile profile =
LauncherAppState.getInstance().getInvariantDeviceProfile();
mInfo = info;
// TODO(hyunyoungs): setup a cache for these labels.
mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
int hSpan = Math.min(info.spanX, profile.numColumns);
int vSpan = Math.min(info.spanY, profile.numRows);
mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
mWidgetPreviewLoader = loader;
}复制代码
上面代码经过mWidgetName.setText显示名字,经过mWidgetDims.setText显示大小。最后给mWidgetPreviewLoader赋值,咱们看到这个loader是从WidgetsListAdapter中传递进来的,在WidgetsListAdapter中,是经过LauncherAppState.getInstance().getWidgetCache()获取的,其实这个loader是在LauncherAppState初始化的时候就初始化了。
在WidgetCell初始化后调用了widget.ensurePreview()方法:
public void ensurePreview() {
...
int[] size = getPreviewSize();
mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this);
}复制代码
在这里调用mWidgetPreviewLoader.getPreview方法:
public PreviewLoadRequest getPreview(final Object o, int previewWidth,
int previewHeight, WidgetCell caller) {
String size = previewWidth + "x" + previewHeight;
WidgetCacheKey key = getObjectKey(o, size);
PreviewLoadTask task = new PreviewLoadTask(key, o, previewWidth, previewHeight, caller);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return new PreviewLoadRequest(task);
}复制代码
在这里执行了一个异步任务PreviewLoadTask,咱们看一下这个异步任务,首先看doInBackground方法:
...
preview = generatePreview(launcher, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
...复制代码
接着看generatePreview方法:
Bitmap generatePreview(Launcher launcher, Object info, Bitmap recycle,
int previewWidth, int previewHeight) {
if (info instanceof LauncherAppWidgetProviderInfo) {
return generateWidgetPreview(launcher, (LauncherAppWidgetProviderInfo) info,
previewWidth, recycle, null);
} else {
return generateShortcutPreview(launcher,
(ResolveInfo) info, previewWidth, previewHeight, recycle);
}
}复制代码
咱们看到是生成一个Bitmap,而后调用generateWidgetPreview生成Bitmap:
public Bitmap generateWidgetPreview(Launcher launcher, LauncherAppWidgetProviderInfo info,
int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
Drawable drawable = null;
if (info.previewImage != 0) {
// 获取相对应的drawable
drawable = mManager.loadPreview(info);
...
}
// Draw the scaled preview into the final bitmap
int x = (preview.getWidth() - previewWidth) / 2;
if (widgetPreviewExists) {
drawable.setBounds(x, 0, x + previewWidth, previewHeight);
drawable.draw(c);
} else {
...
for (int i = 0; i < spanX; i++, tx += tileW) {
float ty = 0;
for (int j = 0; j < spanY; j++, ty += tileH) {
dst.offsetTo(tx, ty);
c.drawBitmap(tileBitmap, src, dst, p);
}
}
...
try {
Drawable icon = mutateOnMainThread(mManager.loadIcon(info, mIconCache));
if (icon != null) {
...
icon.draw(c);
}
} catch (Resources.NotFoundException e) { }
c.setBitmap(null);
}
int imageHeight = Math.min(preview.getHeight(), previewHeight + mProfileBadgeMargin);
return mManager.getBadgeBitmap(info, preview, imageHeight);
}复制代码
整个过程就是从系统加载出Widget对应的Drawable而后绘制到Bitmap上面返回,而后在onPostExecute方法中将该图片添加到WidgetCell上面,就显示到了WidgetCell列表中。整个加载就完成了。
咱们以前讲过,Widget列表最后是绑定到WidgetsContainerView中的,而咱们将Widget放置到桌面是经过长按拖拽到桌面来完成的,所以咱们能够知道添加的触发事件是经过长按事件来触发的,由于咱们找到WidgetsContainerView中的长按事件:
@Override
public boolean onLongClick(View v) {
...
boolean status = beginDragging(v);
if (status && v.getTag() instanceof PendingAddWidgetInfo) {
WidgetHostViewLoader hostLoader = new WidgetHostViewLoader(mLauncher, v);
boolean preloadStatus = hostLoader.preloadWidget();
...
mLauncher.getDragController().addDragListener(hostLoader);
}
return status;
}复制代码
首先调用beginDragging方法:
private boolean beginDragging(View v) {
if (v instanceof WidgetCell) {
if (!beginDraggingWidget((WidgetCell) v)) {
return false;
}
} else {
Log.e(TAG, "Unexpected dragging view: " + v);
}
// We don't enter spring-loaded mode if the drag has been cancelled
if (mLauncher.getDragController().isDragging()) {
// Go into spring loaded mode (must happen before we startDrag())
mLauncher.enterSpringLoadedDragMode();
}
return true;
}复制代码
若是是Widget的视图(WidgetCell)也就是长按的是Widget布局则调用beginDraggingWidget方法:
private boolean beginDraggingWidget(WidgetCell v) {
WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
...
if (createItemInfo instanceof PendingAddWidgetInfo) {
...
Bitmap icon = image.getBitmap();
float minScale = 1.25f;
int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);
...
preview = getWidgetPreviewLoader().generateWidgetPreview(mLauncher,
createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
...
scale = bounds.width() / (float) preview.getWidth();
} else {
// shortcut
...
}
// Don't clip alpha values for the drag outline if we're using the default widget preview
boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
(((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
// Start the drag
mLauncher.lockScreenOrientation();
mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha);
mDragController.startDrag(image, preview, this, createItemInfo,
bounds, DragController.DRAG_ACTION_COPY, scale);
preview.recycle();
return true;
}复制代码
上面代码中的generateWidgetPreview方法咱们在上面已经讲过了,就是生产WidgetCell图片的,而后锁定屏幕旋转,而后调用onDragStartedWithItem方法:
public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
int[] size = estimateItemSize(info, false);
// The outline is used to visualize where the item will land if dropped
mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
}复制代码
整个方法在拖拽中讲过,就是在workspace中生成一个拖拽view的轮廓边框,而后调用mDragController.startDrag方法,以后的过程在拖拽章节中有很详细的讲解,因此在此再也不重复了,没看过拖拽的能够去看拖拽过程详解。下面只是个提示过程。
在放置到桌面时会调用onDrop方法,而后调用onDropExternal方法,而后调用addPendingItem方法:
public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
int[] cell, int spanX, int spanY) {
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
int span[] = new int[2];
span[0] = spanX;
span[1] = spanY;
addAppWidgetFromDrop((PendingAddWidgetInfo) info,
container, screenId, cell, span);
break;
...
}
}复制代码
若是是Widget则调用addAppWidgetFromDrop方法,而后调用addAppWidgetImpl方法,而后调用completeAddAppWidget方法,最后调用mWorkspace.addInScreen方法就讲WidgetCell添加到了桌面上。
咱们在桌面上添加完Widget后,若是长按你会发如今Widget四个边缘会出现拖动框,若是拖动能够调节小插件的大小,那么这个拖动框在哪里添加的呢,咱们看一下,其实这个方法是DragLayer中的addResizeFrame方法,这个方法是在Workspace中的onDrop方法中调用的,也就是放到桌面上的时候就添加了。
咱们看一下这个方法:
public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
CellLayout cellLayout) {
AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
widget, cellLayout, this);
LayoutParams lp = new LayoutParams(-1, -1);
lp.customPosition = true;
addView(resizeFrame, lp);
mResizeFrames.add(resizeFrame);
resizeFrame.snapToWidget(false);
}复制代码
首先建立AppWidgetResizeFrame对象,传入参数LauncherAppWidgetHostView、CellLayout,还有draglayer:
public AppWidgetResizeFrame(Context context,
LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
//初始化数据
...
// 初始化左侧拖动点
mLeftHandle = new ImageView(context);
mLeftHandle.setImageResource(R.drawable.ic_widget_resize_handle);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
Gravity.LEFT | Gravity.CENTER_VERTICAL);
lp.leftMargin = handleMargin;
addView(mLeftHandle, lp);
// 初始化右侧拖动点
// 初始化顶部拖动点
// 初始化底部拖动点
...
}复制代码
拖动调整大小是在DragLayer中的onTouchEvent方法中:
@Override
public boolean onTouchEvent(MotionEvent ev) {
...
if (mCurrentResizeFrame != null) {
handled = true;
switch (action) {
case MotionEvent.ACTION_MOVE:
mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
mCurrentResizeFrame.onTouchUp();
mCurrentResizeFrame = null;
}
}
if (handled) return true;
return mDragController.onTouchEvent(ev);
}复制代码
由上面代码能够看出拖拽的的时候调用visualizeResizeForDelta方法,手指抬起的时候调用visualizeResizeForDelta方法和onTouchUp方法,咱们先看visualizeResizeForDelta方法:
private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
updateDeltas(deltaX, deltaY);
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
if (mLeftBorderActive) {
lp.x = mBaselineX + mDeltaX;
lp.width = mBaselineWidth - mDeltaX;
} else if (mRightBorderActive) {
lp.width = mBaselineWidth + mDeltaX;
}
if (mTopBorderActive) {
lp.y = mBaselineY + mDeltaY;
lp.height = mBaselineHeight - mDeltaY;
} else if (mBottomBorderActive) {
lp.height = mBaselineHeight + mDeltaY;
}
resizeWidgetIfNeeded(onDismiss);
requestLayout();
}复制代码
首先调用updateDeltas方法:
public void updateDeltas(int deltaX, int deltaY) {
if (mLeftBorderActive) {
mDeltaX = Math.max(-mBaselineX, deltaX);
mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
} else if (mRightBorderActive) {
mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
}
if (mTopBorderActive) {
mDeltaY = Math.max(-mBaselineY, deltaY);
mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
} else if (mBottomBorderActive) {
mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
}
}复制代码
主要是根据上下左右点来计算mDeltaX和mDeltaY的值,而后设定DragLayer.LayoutParams的值,而后调用resizeWidgetIfNeeded方法:
private void resizeWidgetIfNeeded(boolean onDismiss) {
...
if (mLeftBorderActive) {
cellXInc = Math.max(-cellX, hSpanInc);
cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
hSpanInc *= -1;
hSpanInc = Math.min(cellX, hSpanInc);
hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
hSpanDelta = -hSpanInc;
}
...
// Update the widget's dimensions and position according to the deltas computed above
if (mLeftBorderActive || mRightBorderActive) {
spanX += hSpanInc;
cellX += cellXInc;
if (hSpanDelta != 0) {
mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
}
}
...
if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
mDirectionVector, onDismiss)) {
lp.tmpCellX = cellX;
lp.tmpCellY = cellY;
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
mRunningVInc += vSpanDelta;
mRunningHInc += hSpanDelta;
if (!onDismiss) {
updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
}
}
mWidgetView.requestLayout();
}复制代码
这里计算拖拽过程当中的参数,而后调用updateWidgetSizeRanges方法:
static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
int spanX, int spanY) {
getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
sTmpRect.right, sTmpRect.bottom);
}复制代码
首先调用getWidgetSizeRanges方法来设定sTmpRect参数,而后调用widgetView.updateAppWidgetSize方法更新widget大小,而后调用mWidgetView.requestLayout方法刷新widget。
咱们再看onTouchUp方法:
public void onTouchUp() {
int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
...
post(new Runnable() {
@Override
public void run() {
snapToWidget(true);
}
});
}复制代码
这个方法是调整完widget大小手指离开屏幕时调用的,主要调用了snapToWidget方法,这个方法代码就不贴了,主要是四个点的动画,代码很简单。
到此widget的加载、添加以及大小调整就介绍完了,整个过程也是比较复杂的,因此仍是要好好熟悉一下。
同步发布:www.codemx.cn/2016/12/18/…
Github地址:github.com/yuchuangu85…
微信公众帐号:Code-MX
注:本文原创,转载请注明出处,多谢。