整体项目基于原生Android 9.0
Launcher Hotseat自动排列方案参考博文 Hotseat 自动排列
竖屏状态Hotseat在底部,横屏状态Hotseat在右边
通过Android Device Monitor DDMS显示两个图标的坐标重叠,确定图标依然存在,并未消失。
了解Launcher图标放置流程。
在Workspace.java->onDrop当中,有着findNearestArea方法的调用,溯源至CellLayout.java->findNearestArea,其中有一段代码:
// First, let's see if this thing fits anywhere
for (int i = 0; i < minSpanX; i++) {
for (int j = 0; j < minSpanY; j++) {
if (mOccupied.cells[x + i][y + j]) {
continue inner;
}
}
}
在CellLayout当中正好有着markCellsAsOccupiedForView以及markCellsAsUnoccupiedForView两个方法用来给mOccupied进行cell的占用状态标记。
public void markCellsAsOccupiedForView(View view) {
if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
}
public void markCellsAsUnoccupiedForView(View view) {
if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
}
参考上面两个方法添加对指定单元格的标记方法即可。
// 将单元格标记为占用
public void markCellAsOccupied(int cellX, int cellY, int cellHSpan, int cellVSpan) {
mOccupied.markCells(cellX, cellY, cellHSpan, cellVSpan, true);
}
// 将单元格标记为未占用
public void markCellAsUnoccupied(int cellX, int cellY, int cellHSpan, int cellVSpan) {
mOccupied.markCells(cellX, cellY, cellHSpan, cellVSpan, false);
}
遍历标记各个位置图标状态即可。
// 标记 Cell 的占用状态
for (int i = 0; i < maxHotseatIconNum; i++) {
if (i < curIconPos) {
mCellLayout.markCellAsOccupied(i, 0, 1, 1);
} else {
mCellLayout.markCellAsUnoccupied(i, 0, 1, 1);
}
}
原谅我的水平有限,原有的Margin计算方式没有看懂,只能自己重写了一遍,因此可能与原有的代码不大一样。与此同时,也加上横竖屏的适配。
public void resetLayout() {
Hotseat mHotSeat = mLauncher.getHotseat();
CellLayout mCellLayout = mHotSeat.getLayout();
ShortcutAndWidgetContainer mContainer = mCellLayout.getShortcutsAndWidgets();
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
mLauncher.getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
int mMargins;
int curIconPos; // 当前 Icon 位置
int hotseatLength; // Hotseat 长度
int maxHotseatIconNum = mLauncher.getDeviceProfile().inv.numHotseatIcons; // Hotseat Icon 最大个数
int curOrientation = mLauncher.getResources().getConfiguration().orientation;
boolean isOrientationPortrait = curOrientation == Configuration.ORIENTATION_PORTRAIT;
if (isOrientationPortrait) { // 竖屏
curIconPos = 0;
hotseatLength = mDisplayMetrics.widthPixels;
// 移除或添加一个图标后,循环将图标重新排序
for (int i = 0; i < maxHotseatIconNum; i++) {
View child = mContainer.getChildAt(i, 0);
if (child != null) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
lp.cellX = curIconPos;
curIconPos++;
// 将重新排序存入数据库之中
mLauncher.getModelWriter().modifyItemInDatabase((ItemInfo) child.getTag(),
LauncherSettings.Favorites.CONTAINER_HOTSEAT,
-1, lp.cellX, lp.cellY, 1, 1);
}
}
// 标记 Cell 的占用状态
for (int i = 0; i < maxHotseatIconNum; i++) {
if (i < curIconPos) {
mCellLayout.markCellAsOccupied(i, 0, 1, 1);
} else {
mCellLayout.markCellAsUnoccupied(i, 0, 1, 1);
}
}
} else { // 横屏
curIconPos = maxHotseatIconNum - 1;
hotseatLength = mDisplayMetrics.heightPixels;
// 移除或添加一个图标后,循环将图标重新排序
for (int i = maxHotseatIconNum - 1; i >= 0; i--) {
View child = mCellLayout.getShortcutsAndWidgets().getChildAt(0, i);
if (child != null) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
lp.cellY = curIconPos;
curIconPos--;
//将重新排序存入数据库之中
mLauncher.getModelWriter().modifyItemInDatabase((ItemInfo) child.getTag(),
LauncherSettings.Favorites.CONTAINER_HOTSEAT,
-1, lp.cellX, lp.cellY, 1, 1);
}
}
// 标记 Cell 的占用状态
for (int i = 0; i < maxHotseatIconNum; i++) {
if (i > curIconPos) {
mCellLayout.markCellAsOccupied(0, i, 1, 1);
} else {
mCellLayout.markCellAsUnoccupied(0, i, 1, 1);
}
}
}
// Margin计算
View child = mContainer.getChildAt(0);
if (child == null || mContainer.getChildCount() == maxHotseatIconNum) {
mMargins = 0;
} else {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
int iconLength = isOrientationPortrait ? lp.getWidth() : lp.getHeight();
int padding = isOrientationPortrait ? mCellLayout.getPaddingLeft() * 2 : mCellLayout.getPaddingTop();
int cost = mContainer.getChildCount() * iconLength + padding; // 已占用的长度
mMargins = (hotseatLength - cost) / 2;
}
// 设置Margin,居中显示 Hotseat
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mHotSeat.getLayoutParams();
if (isOrientationPortrait) {
layoutParams.setMargins(mMargins, 0, -mMargins, 0);
} else {
layoutParams.setMargins(0, -mMargins, 0, mMargins);
}
mHotSeat.setLayoutParams(layoutParams);
// 重载 Hotseat
mHotSeat.requestLayout();
}
挖坑部分
应用启动以及横竖屏切换时,页面需要重新加载,这就导致需要在初始化时便设置Margin值,当前计算方式依赖布局信息,需要在View完成布局之后才能获取到相关信息,为此我将设置Margin的方法放置在Hotseat.java->onLayout中,解决了横竖屏切换的问题,但是启动时依然会有一个Margin值变动的过程。
当前通过设置Hotseat的Margin以及对图标重新排序的方法来进行自动居中终究不是一个最优解,甚至可以说是一种“最差解”存在各种不自然的现象(eg. 图标拖动至Hotseat左端远比右端要难)。
对比MIUI12的实现方案:
要对Launcher做出如此大的改动,需要对Launcher有更深的理解,恕我目前无能为力。后续有时间研究的话再继续。如果有更好的相关文章,欢迎留言推荐给我,非常感谢!