时间:2021/04/08
之前公司不允许csdn,笔记写在其它地方。最近整理过来
背景描述:
展讯平台R版本homesettings中可以切换图标形状,多次切换之后,图标小圆点位置异常
问题分析:
切换形状时 , 小圆点需要根据当前图标形状进行调整位置 , 查看相关逻辑是否异常
分析一下图标形状获取 , 设置 , 调整小圆点位置代码:
1、图标形状菜单(获取内容、设置菜单、点击监听和处理)
//porting change icon sharp
static final String PACKAGE_DEVICE_DEFAULT = "package_device_default";
private static final Comparator<OverlayInfo> OVERLAY_INFO_COMPARATOR = Comparator.comparingInt(a -> a.priority);
private static String TAG = "iconsharp";
private final IOverlayManager mOverlayManager;
private final PackageManager mPackageManager;
private String mCategory = "android.theme.customization.adaptive_icon_shape";
private static final String OVERLAY_TARGET_PACKAGE = "android";
private ListPreference mPreference;
private Context mContext;
//获取图标形状
//返回值类型 list OverlayInfo : frameworks/base/core/java/android/content/om/OverlayInfo.java
private List<OverlayInfo> getOverlayInfos() {
final List<OverlayInfo> filteredInfos = new ArrayList<>();
try {
List<OverlayInfo> overlayInfos = mOverlayManager.getOverlayInfosForTarget(OVERLAY_TARGET_PACKAGE, USER_SYSTEM);
for (OverlayInfo overlayInfo : overlayInfos) {
if (mCategory.equals(overlayInfo.category)) {
filteredInfos.add(overlayInfo);
}
}
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
filteredInfos.sort(OVERLAY_INFO_COMPARATOR);
return filteredInfos;
}
//填充菜单
public void populatePreference(Preference preference) {
final List<String> pkgs = new ArrayList<>();
final List<String> labels = new ArrayList<>();
//添加默认形状的包名和文字显示
String selectedPkg = PACKAGE_DEVICE_DEFAULT;
String selectedLabel = mContext.getString(R.string.overlay_option_device_default);
// Add the default package / label before all of the overlays
pkgs.add(selectedPkg);
labels.add(selectedLabel);
//查询系统支持的图标形状 , 拿到apk的包名和应用名称
//循环列表 把包名和应用名称添加到列表中
for (OverlayInfo overlayInfo : getOverlayInfos()) {
pkgs.add(overlayInfo.packageName);
try {
labels.add(mPackageManager.getApplicationInfo(overlayInfo.packageName, 0)
.loadLabel(mPackageManager).toString());
} catch (PackageManager.NameNotFoundException e) {
labels.add(overlayInfo.packageName);
}
if (overlayInfo.isEnabled()) {
selectedPkg = pkgs.get(pkgs.size() - 1);
selectedLabel = labels.get(labels.size() - 1);
}
}
//菜单设置内容,ListPreference的使用
mPreference.setEntries(labels.toArray(new String[labels.size()]));
mPreference.setEntryValues(pkgs.toArray(new String[pkgs.size()]));
mPreference.setValue(selectedPkg);
mPreference.setSummary(selectedLabel);
//这只item点击监听
mPreference.setOnPreferenceChangeListener((pref, newValue) -> {
setOverlay((String) newValue);
return true;
});
}
//end
//设置图标形状, 需要传入图标形状apk的包名
private boolean setOverlay(String packageName) {
final String currentPackageName = getOverlayInfos().stream()
.filter(info -> info.isEnabled())
.map(info -> info.packageName)
.findFirst()
.orElse(null);
if (PACKAGE_DEVICE_DEFAULT.equals(packageName) && TextUtils.isEmpty(currentPackageName)
|| TextUtils.equals(packageName, currentPackageName)) {
// Already set.
return true;
}
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
try {
//设置默认形状或者其它包名的图标形状
if (PACKAGE_DEVICE_DEFAULT.equals(packageName)) {
return mOverlayManager.setEnabled(currentPackageName, false, USER_SYSTEM);
} else {
return mOverlayManager.setEnabledExclusiveInCategory(packageName,
USER_SYSTEM);
}
} catch (RemoteException re) {
Log.w(TAG, "Error enabling overlay.", re);
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
populatePreference(mPreference);
if (!success) {
Toast.makeText(
mContext, R.string.overlay_toast_failed_to_apply, Toast.LENGTH_LONG)
.show();
}
}
}.execute();
return true; // Assume success; toast on failure.
}
2、当修改图标形状后,launcher如何修改图标形状
packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java中
//注册广播接收器监听广播,并调用onConfigChange方法
private class OverlayMonitor extends BroadcastReceiver {
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
OverlayMonitor(Context context) {
context.registerReceiver(this, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
}
@Override
public void onReceive(Context context, Intent intent) {
onConfigChanged(context);
}
}
private void onConfigChanged(Context context) {
// Config changes, what shall we do?
InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
//初始化桌面网格
if (mMonitor.getDesktopGridController() != null) {
// Re-init grid by using the new grid name
initGrid(context,
Utilities.getPrefs(context).getString(mIdpGridKey, getDefaultGridName(context)));
} else {
// TODO(b/131867841): We pass in null here so that we can calculate the closest profile
// without the bias of the grid name.
initGrid(context, null);
}
//判断桌面行列数是否有改变
int changeFlags = 0;
if (numRows != oldProfile.numRows ||
numColumns != oldProfile.numColumns ||
numFolderColumns != oldProfile.numFolderColumns ||
numFolderRows != oldProfile.numFolderRows ||
numHotseatIcons != oldProfile.numHotseatIcons) {
changeFlags |= CHANGE_FLAG_GRID;
}
//图标大小对比
if (iconSize != oldProfile.iconSize || iconBitmapSize != oldProfile.iconBitmapSize ||
!iconShapePath.equals(oldProfile.iconShapePath)) {
changeFlags |= CHANGE_FLAG_ICON_PARAMS;
}
//图标形状对比
if (!iconShapePath.equals(oldProfile.iconShapePath)) {
//图标形状发生变化,初始化选择最合适的轨迹
IconShape.init(context);
}
apply(context, changeFlags);
}
private String initGrid(Context context, String gridName) {
...
//忽略掉其它代码,主要看图标形状相关的初始化
landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
...
return closestProfile.name;
}
packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java的构造方法
public DeviceProfile(Context context, InvariantDeviceProfile inv,
Point minSize, Point maxSize,
int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
...
// This is done last, after iconSizePx is calculated above.
mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(),
IconShape.DEFAULT_PATH_SIZE);
}
packages/apps/Launcher3/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
//这个类负责计算图标形状和小圆点坐标 , 画小圆点
public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
builder.ambientShadowAlpha = 88;
mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
mCircleRadius = builder.radius;
mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
//根据传入的图标形状计算左边小圆点和右边小圆点的坐标
//通俗的讲 , 就是根据图标形状轨迹 计算出 左上角和右上角的点
//画小圆点的最佳点
// Find the points on the path that are closest to the top left and right corners.
mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
}
轨迹转换成点 draw 这里先不讲主要讲一下问题原因
设置图标形状之后 , 计算图形坐标 , 再找到最适合的形状 逻辑顺序不对
DotRenderer的初始化应该在IconShape.init之后
3、修改方案:
为了不影响之前的逻辑,我在IconShape.init之后再次initDotRenderer
修正了角标的位置
packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java中
IconShape.init(context);
+ landscapeProfile.initDotRenderer();
+ portraitProfile.initDotRenderer();