[Android系统开发]Launcher Hotseat图标居中排列

目录

        • 背景
        • 问题点
        • 解决过程
        • 完整代码
        • 遗留问题
            • 1.初始化刷新问题。
            • 2.图标覆盖问题。
        • 后续思考

​之前接手一个和Hotseat自动排列相关的Bug,本身实现方案是参考博文 Hotseat 自动排列 的,但是在实现之后出现将Hotseat当中的图标移除后有一定概率使两个图标重叠的Bug。在此记录一下解决过程。

背景

  1. 整体项目基于原生Android 9.0

  2. Launcher Hotseat自动排列方案参考博文 Hotseat 自动排列

  3. 竖屏状态Hotseat在底部,横屏状态Hotseat在右边

问题点

​将Hotseat的“电话”拖拽至CellLayout当中,再从文件夹当中拖拽“相机”至Hotseat当中时,Hotseat不会提示“相机”应该放在第5个位置,而是直接覆盖在“信息”上面。

解决过程

  1. 通过Android Device Monitor DDMS显示两个图标的坐标重叠,确定图标依然存在,并未消失。

  2. 了解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);
    }
    
  3. 参考上面两个方法添加对指定单元格的标记方法即可。

    // 将单元格标记为占用
    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);
    }
    
  4. 遍历标记各个位置图标状态即可。

    //  标记 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();
}

挖坑部分

遗留问题

1.初始化刷新问题。

​ 应用启动以及横竖屏切换时,页面需要重新加载,这就导致需要在初始化时便设置Margin值,当前计算方式依赖布局信息,需要在View完成布局之后才能获取到相关信息,为此我将设置Margin的方法放置在Hotseat.java->onLayout中,解决了横竖屏切换的问题,但是启动时依然会有一个Margin值变动的过程。

2.图标覆盖问题。
将Hotseat文件夹中“电话”拖拽至Hotseat中,再将“Paly 商店”拖拽至CellLayout当中,出现图标错位问题。 目前没有找到问题所在。

后续思考

当前通过设置Hotseat的Margin以及对图标重新排序的方法来进行自动居中终究不是一个最优解,甚至可以说是一种“最差解”存在各种不自然的现象(eg. 图标拖动至Hotseat左端远比右端要难)。

对比MIUI12的实现方案:

[Android系统开发]Launcher Hotseat图标居中排列_第1张图片 [Android系统开发]Launcher Hotseat图标居中排列_第2张图片
各图标均匀分布在Hotseat,当需要添加图标时缩小图标间的间距并添加一个预览位置。我认为这是一种要好的多的解决方案。

要对Launcher做出如此大的改动,需要对Launcher有更深的理解,恕我目前无能为力。后续有时间研究的话再继续。如果有更好的相关文章,欢迎留言推荐给我,非常感谢!

你可能感兴趣的:(Android,安卓)