桌面拖拽编辑按钮调整widget大小
1、添加布局
AppWidgetResizeFrame 是调整小部件大小的编辑区域
布局文件:res/layout/app_widget_resize_frame.xml
<com.android.launcher3.AppWidgetResizeFrame
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Frame -->
<ImageView
android:id="@+id/widget_resize_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="@dimen/resize_frame_margin"
android:src="@drawable/widget_resize_frame" />
<!-- Left -->
<ImageView
android:id="@+id/widget_resize_left_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
android:tint="?attr/workspaceAccentColor" />
<!-- Top -->
<ImageView
android:id="@+id/widget_resize_top_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
android:tint="?attr/workspaceAccentColor" />
<!-- Right -->
<ImageView
android:id="@+id/widget_resize_right_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:layout_marginRight="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
android:tint="?attr/workspaceAccentColor" />
<!-- Bottom -->
<ImageView
android:id="@+id/widget_resize_bottom_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/widget_handle_margin"
android:src="@drawable/ic_widget_resize_handle"
android:tint="?attr/workspaceAccentColor" />
<ImageButton
android:id="@+id/widget_reconfigure_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/widget_reconfigure_button_padding"
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/widget_reconfigure_button_margin"
android:layout_marginEnd="@dimen/widget_reconfigure_button_margin"
android:src="@drawable/widget_reconfigure_button_frame"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone"
android:contentDescription="@string/widget_reconfigure_button_content_description" />
</FrameLayout>
</com.android.launcher3.AppWidgetResizeFrame>
com/android/launcher3/AppWidgetResizeFrame.java
public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
// If widget is not added to view hierarchy, we cannot show resize frame at correct location
if (widget.getParent() == null) {
return;
}
Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
AbstractFloatingView.closeAllOpenViews(launcher);
DragLayer dl = launcher.getDragLayer();
AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater().inflate(R.layout.app_widget_resize_frame, dl, false);
frame.setupForWidget(widget, cellLayout, dl);
((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
dl.addView(frame);//添加编辑框到dragLayer View中
frame.mIsOpen = true;
frame.post(() -> frame.snapToWidget(false));
}
private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
mCellLayout = cellLayout;
mWidgetView = widgetView;
LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)widgetView.getAppWidgetInfo();//获取widget小部件信息
mDragLayer = dragLayer;
mMinHSpan = info.minSpanX;
mMinVSpan = info.minSpanY;
mMaxHSpan = info.maxSpanX;
mMaxVSpan = info.maxSpanY;
// Only show resize handles for the directions in which resizing is possible.
InvariantDeviceProfile idp = LauncherAppState.getIDP(cellLayout.getContext());
mVerticalResizeActive = (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0
&& mMinVSpan < idp.numRows && mMaxVSpan > 1&& mMinVSpan < mMaxVSpan; //判断是否可以垂直方向可以拖拽
if (!mVerticalResizeActive) {//垂直方向不可以拖拽时隐藏上下的拖拽按钮
mDragHandles[INDEX_TOP].setVisibility(GONE);
mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
}
mHorizontalResizeActive = (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0
&& mMinHSpan < idp.numColumns && mMaxHSpan > 1 && mMinHSpan < mMaxHSpan;//判断是否可以水平方向可以拖拽
if (!mHorizontalResizeActive) {//水平方向不可以拖拽时隐藏左右的拖拽按钮
mDragHandles[INDEX_LEFT].setVisibility(GONE);
mDragHandles[INDEX_RIGHT].setVisibility(GONE);
}
mReconfigureButton = (ImageButton) findViewById(R.id.widget_reconfigure_button);
if (info.isReconfigurable()) {
mReconfigureButton.setVisibility(VISIBLE);
mReconfigureButton.setOnClickListener(view -> {
mLauncher.setWaitingForResult(
PendingRequestArgs.forWidgetInfo(
mWidgetView.getAppWidgetId(),
// Widget add handler is null since we're reconfiguring an existing
// widget.
/* widgetHandler= */ null,
(ItemInfo) mWidgetView.getTag()));
mLauncher .getAppWidgetHolder().startConfigActivity(
mLauncher,
mWidgetView.getAppWidgetId(),
ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET);
});
if (!hasSeenReconfigurableWidgetEducationTip()) {
post(() -> {
if (showReconfigurableWidgetEducationTip() != null) {
LauncherPrefs.get(getContext()).put( RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, true);
}
});
}
}
if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get()) {
mWidgetView.setCellChildViewPreLayoutListener(mCellChildViewPreLayoutListener);
mWidgetViewOldRect.set(mWidgetView.getLeft(), mWidgetView.getTop(), mWidgetView.getRight(),mWidgetView.getBottom());
mWidgetViewNewRect.set(mWidgetViewOldRect);
}
//获取widget view的一些基本信息
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mWidgetView.getLayoutParams();
ItemInfo widgetInfo = (ItemInfo) mWidgetView.getTag();
CellPos presenterPos = mLauncher.getCellPosMapper().mapModelToPresenter(widgetInfo);
lp.setCellX(presenterPos.cellX);
lp.setTmpCellX(presenterPos.cellX);
lp.setCellY(presenterPos.cellY);
lp.setTmpCellY(presenterPos.cellY);
lp.cellHSpan = widgetInfo.spanX;
lp.cellVSpan = widgetInfo.spanY;
lp.isLockedToGrid = true;
//在创建调整大小框架时,会先把 mCellLayout 中与 mWidgetView 相关的所有单元格标记为未占用状态。等到调整大小框架关闭(dismissed)时,再将合适的单元格(如果小部件大小未改变则是原来的单元格,若改变了则是新的单元格)标记为已占用状态。
// When we create the resize frame, we first mark all cells as unoccupied. The appropriate
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
mLauncher.getStatsLogManager().logger().withInstanceId(logInstanceId).withItemInfo(widgetInfo).log(LAUNCHER_WIDGET_RESIZE_STARTED);
setOnKeyListener(this);
setCornerRadiusFromWidget();
mWidgetView.addOnLayoutChangeListener(mWidgetViewLayoutListener);
}
2、MotionEvent事件处理
com/android/launcher3/AppWidgetResizeFrame.java
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
return handleTouchDown(ev);
case MotionEvent.ACTION_MOVE:
visualizeResizeForDelta(x - mXDown, y - mYDown);//拖动边框调整widget大小
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
visualizeResizeForDelta(x - mXDown, y - mYDown);
onTouchUp();
mXDown = mYDown = 0;
break;
}
return true;
}
DOWN事件
处理down事件,主要是记录矩阵区域和按下的位置
private boolean handleTouchDown(MotionEvent ev) {
Rect hitRect = new Rect();
int x = (int) ev.getX();
int y = (int) ev.getY();
getHitRect(hitRect);
if (hitRect.contains(x, y)) {
if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
mXDown = x;
mYDown = y;
return true;
}
}
return false;
}
beginResizeIfPointInRegion 方法的主要功能是判断给定的点 (x, y) 是否处于可调整大小的区域内。如果该点处于可调整大小的区域,就激活相应的边界,并设置拖动句柄的透明度,同时为水平和垂直方向的调整范围及基线进行设置。最后返回是否有边界被激活的布尔值。
public boolean beginResizeIfPointInRegion(int x, int y) {
mLeftBorderActive = (x < mTouchTargetWidth) && mHorizontalResizeActive;
mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && mHorizontalResizeActive;
mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && mVerticalResizeActive;
mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment) && mVerticalResizeActive;
boolean anyBordersActive = mLeftBorderActive || mRightBorderActive || mTopBorderActive || mBottomBorderActive;
if (anyBordersActive) {//所有边界激活了后,拖拽按钮alpha值设置成1
mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
}
/* 如果左侧边界被激活,设置水平方向的调整范围为 -getLeft() 到 getWidth() - 2 * mTouchTargetWidth。
如果右侧边界被激活,设置水平方向的调整范围为 2 * mTouchTargetWidth - getWidth() 到 mDragLayer.getWidth() - getRight()。
如果左右边界都未激活,设置水平方向的调整范围为 0 到 0。
同时设置水平方向的基线为组件的左边界和右边界。*/
if (mLeftBorderActive) {
mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
} else if (mRightBorderActive) {
mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
} else {
mDeltaXRange.set(0, 0);
}
mBaselineX.set(getLeft(), getRight());
/* 如果顶部边界被激活,设置垂直方向的调整范围为 -getTop() 到 getHeight() - 2 * mTouchTargetWidth。
如果底部边界被激活,设置垂直方向的调整范围为 2 * mTouchTargetWidth - getHeight() 到 mDragLayer.getHeight() - getBottom()。
如果上下边界都未激活,设置垂直方向的调整范围为 0 到 0。
同时设置垂直方向的基线为组件的上边界和下边界。
*/
if (mTopBorderActive) {
mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
} else if (mBottomBorderActive) {
mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
} else {
mDeltaYRange.set(0, 0);
}
mBaselineY.set(getTop(), getBottom());
return anyBordersActive;
}
MOVE事件
visualizeResizeForDelta 方法主要用于根据给定的 deltaX 和 deltaY(在 x 和 y 方向上的拖拽间距)来可视化地调整一个视图的大小。该方法会对拖拽间距进行边界限制,更新视图的布局参数,处理可能涉及跨 CellLayout 的无效调整情况,并最终请求重新布局以应用这些更改。
public void visualizeResizeForDelta(int deltaX, int deltaY) {//deltaX deltaY x,y方向上拖拽的间距
mDeltaX = mDeltaXRange.clamp(deltaX);
mDeltaY = mDeltaYRange.clamp(deltaY);
/*更新布局参数 ,mBaselineX 和 mBaselineY 是表示基线的对象,applyDelta 方法根据边界激活状态(如左侧、右侧、顶部、底部边界是否激活)和 deltaX、deltaY 来更新临时范围 mTempRange1。根据更新后的 mTempRange1 设置布局参数的 x、y 坐标以及宽度和高度。
*/
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
mDeltaX = mDeltaXRange.clamp(deltaX);
mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
lp.x = mTempRange1.start;
lp.width = mTempRange1.size();
mDeltaY = mDeltaYRange.clamp(deltaY);
mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
lp.y = mTempRange1.start;
lp.height = mTempRange1.size();
//调用 resizeWidgetIfNeeded 方法来根据需要调整小部件的大小,传入 false 作为参数
resizeWidgetIfNeeded(false);
/* 根据调整方向(从右到左或从左到右)和边界情况计算进度 progress。 根据 progress 计算透明度 alpha 和弹簧加载进度 springLoadedProgress。调用 updateInvalidResizeEffect 方法来更新无效调整时的效果。*/
// Handle invalid resize across CellLayouts in the two panel UI.
if (mCellLayout.getParent() instanceof Workspace) {
Workspace<?> workspace = (Workspace<?>) mCellLayout.getParent();
CellLayout pairedCellLayout = workspace.getScreenPair(mCellLayout); //针对双屏CellLayout的,比如折叠
if (pairedCellLayout != null) {
Rect focusedCellLayoutBound = sTmpRect;
mDragLayerRelativeCoordinateHelper.viewToRect(mCellLayout, focusedCellLayoutBound);
Rect resizeFrameBound = sTmpRect2;
findViewById(R.id.widget_resize_frame).getGlobalVisibleRect(resizeFrameBound);
float progress = 1f;
if (workspace.indexOfChild(pairedCellLayout) < workspace.indexOfChild(mCellLayout)
&& mDeltaX < 0 && resizeFrameBound.left < focusedCellLayoutBound.left) {
// Resize from right to left.
progress = (mDragAcrossTwoPanelOpacityMargin + mDeltaX) / mDragAcrossTwoPanelOpacityMargin;
} else if (workspace.indexOfChild(pairedCellLayout) > workspace.indexOfChild(mCellLayout)
&& mDeltaX > 0 && resizeFrameBound.right > focusedCellLayoutBound.right) {
// Resize from left to right.
progress = (mDragAcrossTwoPanelOpacityMargin - mDeltaX) / mDragAcrossTwoPanelOpacityMargin;
}
float alpha = Math.max(MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE, progress);
float springLoadedProgress = Math.min(1f, 1f - progress);
updateInvalidResizeEffect(mCellLayout, pairedCellLayout, alpha, springLoadedProgress);
}
}
requestLayout();
}
resizeWidgetIfNeeded 方法的主要功能是根据当前的偏移量(mDeltaX 和 mDeltaY)来判断是否需要调整小部件(mWidgetView)的大小。如果需要调整,则会计算新的跨度(span)和位置,并且在满足条件时更新小部件的布局参数,最后请求重新布局。onDismiss 参数用于指示是否在小部件消失时进行调整。
private void resizeWidgetIfNeeded(boolean onDismiss) {
ViewGroup.LayoutParams wlp = mWidgetView.getLayoutParams();
if (!(wlp instanceof CellLayoutLayoutParams)) {
return;
}
DeviceProfile dp = mLauncher.getDeviceProfile();
//计算水平和垂直方向的阈值 xThreshold 和 yThreshold,用于后续计算跨度增量。
float xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacePx.x;
float yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacePx.y;
//调用 getSpanIncrement 方法计算水平和垂直方向的跨度增量 hSpanInc 和 vSpanInc。mDeltaX 和 mDeltaY 是当前的偏移量,mDeltaXAddOn 和 mDeltaYAddOn 可能是额外的偏移量,mRunningHInc 和 mRunningVInc 是之前累积的增量。
int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
// 如果不是在小部件消失时进行调整,并且水平和垂直方向的跨度增量都为 0,则直接返回,不进行后续操作。
if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
mDirectionVector[0] = 0;
mDirectionVector[1] = 0;
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) wlp;
int spanX = lp.cellHSpan;
int spanY = lp.cellVSpan;
int cellX = lp.useTmpCoords ? lp.getTmpCellX() : lp.getCellX();
int cellY = lp.useTmpCoords ? lp.getTmpCellY() : lp.getCellY();
// For each border, we bound the resizing based on the minimum width, and the maximum
// expandability.
mTempRange1.set(cellX, spanX + cellX);
int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
hSpanInc, mMinHSpan, mMaxHSpan, mCellLayout.getCountX(), mTempRange2);
cellX = mTempRange2.start;
spanX = mTempRange2.size();
if (hSpanDelta != 0) {
mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
}
mTempRange1.set(cellY, spanY + cellY);
int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
vSpanInc, mMinVSpan, mMaxVSpan, mCellLayout.getCountY(), mTempRange2);
cellY = mTempRange2.start;
spanY = mTempRange2.size();
if (vSpanDelta != 0) {
mDirectionVector[1] = mTopBorderActive ? -1 : 1;
}
if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
// We always want the final commit to match the feedback, so we make sure to use the
// last used direction vector when committing the resize / reorder.
if (onDismiss) {
mDirectionVector[0] = mLastDirectionVector[0];
mDirectionVector[1] = mLastDirectionVector[1];
} else {
mLastDirectionVector[0] = mDirectionVector[0];
mLastDirectionVector[1] = mDirectionVector[1];
}
// We don't want to evaluate resize if a widget was pending config activity and was already
// occupying a space on the screen. This otherwise will cause reorder algorithm evaluate a
// different location for the widget and cause a jump.
if (!(mWidgetView instanceof PendingAppWidgetHostView) && mCellLayout.createAreaForResize(
cellX, cellY, spanX, spanY, mWidgetView, mDirectionVector, onDismiss)) {
if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
mStateAnnouncer.announce(mLauncher.getString(R.string.widget_resized, spanX, spanY));
}
lp.setTmpCellX(cellX);
lp.setTmpCellY(cellY);
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
mRunningVInc += vSpanDelta;
mRunningHInc += hSpanDelta;
if (!onDismiss) {
WidgetSizes.updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
}
}
mWidgetView.requestLayout();
}
createAreaForResize主要用于在调整小部件大小时,尝试为小部件创建一个合适的布局区域。它会根据给定的小部件位置(cellX, cellY)、跨度(spanX, spanY`)等信息,查找一个重新布局的解决方案, 比如拖拽大了后不足以显示当前widget。如果找到合适的解决方案,会根据是否需要提交来更新临时状态、动画显示以及提交布局更改。
boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
View dragView, int[] direction, boolean commit) {
int[] pixelXY = new int[2];
regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
// First we determine if things have moved enough to cause a different layout
ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY, spanX, spanY, direction, dragView, true);
setUseTempCoords(true);
if (swapSolution != null && swapSolution.isSolution) {
// If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother committing anything or animating anything as we just want to determine if a solution exists
copySolutionToTempState(swapSolution, dragView);
setItemPlacementDirty(true);
animateItemsToSolution(swapSolution, dragView, commit);
if (commit) {
commitTempPlacement(null);
completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
beginOrAdjustReorderPreviewAnimations(swapSolution, dragView, ReorderPreviewAnimation.MODE_PREVIEW);
}
mShortcutsAndWidgets.requestLayout();
}
return swapSolution.isSolution;
}
更新widget 大小范围
com/android/launcher3/widget/util/WidgetSizes.java
public static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Context context, int spanX, int spanY) {
updateWidgetSizeRangesAsync(
widgetView.getAppWidgetId(), widgetView.getAppWidgetInfo(), context, spanX, spanY);
}
public static void updateWidgetSizeRangesAsync(int widgetId, AppWidgetProviderInfo info, Context context, int spanX, int spanY) {
if (widgetId <= 0 || info == null) {
return;
}
UI_HELPER_EXECUTOR.execute(() -> {
AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
Bundle sizeOptions = getWidgetSizeOptions(context, info.provider, spanX, spanY);
if (sizeOptions.<SizeF>getParcelableArrayList( AppWidgetManager.OPTION_APPWIDGET_SIZES).equals(
widgetManager.getAppWidgetOptions(widgetId).<SizeF>getParcelableArrayList( AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
return;
}
widgetManager.updateAppWidgetOptions(widgetId, sizeOptions);//调用系统接口,将最新的widget信息传给应用
});
}
public static Bundle getWidgetSizeOptions(Context context, ComponentName provider, int spanX,
int spanY) {
ArrayList<SizeF> paddedSizes = getWidgetSizesDp(context, spanX, spanY);
Rect rect = getMinMaxSizes(paddedSizes);
Bundle options = new Bundle();
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, rect.left);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, rect.top);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, rect.right);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, rect.bottom);
options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
Log.d("b/267448330", "provider: " + provider + ", paddedSizes: " + paddedSizes + ", getMinMaxSizes: " + rect);
return options;
}
public static ArrayList<SizeF> getWidgetSizesDp(Context context, int spanX, int spanY) {
ArrayList<SizeF> sizes = new ArrayList<>(2);
final float density = context.getResources().getDisplayMetrics().density;
for (DeviceProfile profile : LauncherAppState.getIDP(context).supportedProfiles) {
Size widgetSizePx = getWidgetSizePx(profile, spanX, spanY);
sizes.add(new SizeF(widgetSizePx.getWidth() / density, widgetSizePx.getHeight() / density));
}
return sizes;
}
/** Returns the size, in pixels, a widget of given spans & {@code profile}. */
public static Size getWidgetSizePx(DeviceProfile profile, int spanX, int spanY) {
final int hBorderSpacing = (spanX - 1) * profile.cellLayoutBorderSpacePx.x;
final int vBorderSpacing = (spanY - 1) * profile.cellLayoutBorderSpacePx.y;
Point cellSize = profile.getCellSize();
Rect padding = profile.widgetPadding;
return new Size(
(spanX * cellSize.x) + hBorderSpacing - padding.left - padding.right,
(spanY * cellSize.y) + vBorderSpacing - padding.top - padding.bottom);
}
private static Rect getMinMaxSizes(List<SizeF> sizes) {
if (sizes.isEmpty()) {
return new Rect();
} else {
SizeF first = sizes.get(0);
Rect result = new Rect((int) first.getWidth(), (int) first.getHeight(),
(int) first.getWidth(), (int) first.getHeight());
for (int i = 1; i < sizes.size(); i++) {
result.union((int) sizes.get(i).getWidth(), (int) sizes.get(i).getHeight());
}
return result;
}
}
UP事件
private void onTouchUp() {
DeviceProfile dp = mLauncher.getDeviceProfile();
int xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacePx.x;
int yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacePx.y;
mDeltaXAddOn = mRunningHInc * xThreshold;
mDeltaYAddOn = mRunningVInc * yThreshold;
mDeltaX = 0;
mDeltaY = 0;
post(() -> snapToWidget(true));
}
snapToWidget 主要是将小部件对齐到一个指定的位置和大小。根据传入的 animate 参数,该方法可以选择是否以动画的方式进行对齐操作,比如拖拽到一格的中间位置,松手后会回弹到整格位置。同时,该方法还会处理小部件触摸区域的调整,以及在必要时更新相关的动画效果。
private void snapToWidget(boolean animate) {
// The widget is guaranteed to be attached to the cell layout at this point, thus setting the transition here
if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get() && mWidgetView.getLayoutTransition() == null) {
final LayoutTransition transition = new LayoutTransition();
transition.setDuration(RESIZE_TRANSITION_DURATION_MS);
transition.enableTransitionType(LayoutTransition.CHANGING);
mWidgetView.setLayoutTransition(transition);
}
//调用 getSnappedRectRelativeToDragLayer 方法,把对齐后的矩形区域信息存储在 sTmpRect 中。
getSnappedRectRelativeToDragLayer(sTmpRect);
int newWidth = sTmpRect.width();
int newHeight = sTmpRect.height();
int newX = sTmpRect.left;
int newY = sTmpRect.top;
//检查小部件的新位置是否超出了 mDragLayer 的边界。 若小部件顶部超出了 mDragLayer 顶部,就调整顶部触摸区域的偏移量 mTopTouchRegionAdjustment。 若小部件底部超出了 mDragLayer 底部,就调整底部触摸区域的偏移量 mBottomTouchRegionAdjustment。
// We need to make sure the frame's touchable regions lie fully within the bounds of the
// DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
// down accordingly to provide a proper touch target.
if (newY < 0) {
// In this case we shift the touch region down to start at the top of the DragLayer
mTopTouchRegionAdjustment = -newY;
} else {
mTopTouchRegionAdjustment = 0;
}
if (newY + newHeight > mDragLayer.getHeight()) {
// In this case we shift the touch region up to end at the bottom of the DragLayer
mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
} else {
mBottomTouchRegionAdjustment = 0;
}
final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
final CellLayout pairedCellLayout;
if (mCellLayout.getParent() instanceof Workspace) {
Workspace<?> workspace = (Workspace<?>) mCellLayout.getParent();
pairedCellLayout = workspace.getScreenPair(mCellLayout);
} else {
pairedCellLayout = null;
}
if (!animate) { //处理非动画对齐
lp.width = newWidth;
lp.height = newHeight;
lp.x = newX;
lp.y = newY;
for (int i = 0; i < HANDLE_COUNT; i++) {
mDragHandles[i].setAlpha(1f);
}
if (pairedCellLayout != null) {
updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f,/* springLoadedProgress= */ 0f);
}
requestLayout();
} else { //处理动画对齐
ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp,
PropertyValuesHolder.ofInt(LAYOUT_WIDTH, lp.width, newWidth),
PropertyValuesHolder.ofInt(LAYOUT_HEIGHT, lp.height, newHeight),
PropertyValuesHolder.ofInt(LAYOUT_X, lp.x, newX),
PropertyValuesHolder.ofInt(LAYOUT_Y, lp.y, newY));
mFirstFrameAnimatorHelper.addTo(oa).addUpdateListener(a -> requestLayout());
AnimatorSet set = new AnimatorSet();
set.play(oa);
for (int i = 0; i < HANDLE_COUNT; i++) {
set.play(mFirstFrameAnimatorHelper.addTo(ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f)));
}
if (pairedCellLayout != null) {
updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f, /* springLoadedProgress= */ 0f, /* animatorSet= */ set);
}
set.setDuration(SNAP_DURATION_MS);
set.start();
}
setFocusableInTouchMode(true);
requestFocus();
}
468

被折叠的 条评论
为什么被折叠?



