Android系统的Launcher改造在国内算是一个不算很低频的需求,尤其是相当多的三方硬件设备以及部分手机厂商的个性化ROM等,受限于国内整体的开源环境,网上能找到相关的开发资源并不多,最近公司有相关业务需求的开发,就自己涉及到的改造点作一些分享。
原生Launcher自带的长按图标快捷操作区是“应用信息”、“微件”以及其它一些个性化支持操作,而通常需求方出于方便操作及简化用户理解的考虑,希望改造补充“卸载”,且大多同时希望去掉拖动图标时显示在顶部的“卸载、移除”,涉及到的改动点如下:
在长按快捷区增加”卸载“
移除拖动icon时顶部的卸载布局及边界框
不显示顶部的“卸载、移除”
在长按快捷区增加”卸载“
com.android.launcher3.Launcher
com.android.launcher3.popup.SystemShortcut
com.android.launcher3.popup.PopupContainerWithArrow
string.xml
移除拖动icon时顶部的卸载布局及边界框
com.android.launcher3.Workspace
不显示顶部的“卸载、移除”
com.android.launcher3.Launcher
在长按快捷区增加”卸载“
参考APP_INFO内部对象,创建UNINSTALL对象,并添加相应的卸载处理,参考SecondaryDropTarget的performDropAction方法改写。并在string.xml中添加
<string name="app_info_drop_uninstall_label">卸载string>
SystemShortcut.java
public static final Factory<BaseDraggingActivity> APP_INFO = AppInfo::new;
public static class AppInfo extends SystemShortcut {
// ... //
}
//====修改start==== 创建UNINSTALL内部对象
public static final Factory<BaseDraggingActivity> UNINSTALL = UnInstall::new;
public static class UnInstall extends SystemShortcut {
public UnInstall(BaseDraggingActivity target, ItemInfo itemInfo) {
super(R.drawable.ic_uninstall_no_shadow, R.string.app_info_drop_uninstall_label, target,
itemInfo);
}
@Override
public void onClick(View view) {
dismissTaskMenuView(mTarget);
ComponentName cn = getUninstallTarget(mItemInfo);
if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
// We may give them the option of disabling apps this way.
Toast.makeText(mTarget, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
return;
}
try {
Intent i = Intent.parseUri(mTarget.getString(R.string.delete_package_intent), 0)
.setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.putExtra(Intent.EXTRA_USER, mItemInfo.user);
mTarget.startActivity(i);
FileLog.d("Uninstall", "start uninstall activity " + cn.getPackageName());
return;
} catch (URISyntaxException e) {
Log.e("Uninstall", "Failed to parse intent to start uninstall activity for item=" + mItemInfo);
return;
}
}
}
/**
* @return the component name that should be uninstalled or null.
*/
ComponentName getUninstallTarget(ItemInfo item) {
Intent intent = null;
UserHandle user = null;
if (item != null &&
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
intent = item.getIntent();
user = item.user;
}
if (intent != null) {
LauncherActivityInfo info = mTarget.getSystemService(LauncherApps.class)
.resolveActivity(intent, user);
if (info != null
&& (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
return info.getComponentName();
}
}
return null;
}
//====修改end====
public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
// ... //
};
Launcher.java
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
//====修改==== 添加UNINSTALL
return Stream.of(APP_INFO, WIDGETS,INSTALL,UNINSTALL);
}
PopupContainerWithArrow.java
public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends ArrowPopup<T>
implements DragSource, DragController.DragListener {
// ... //
@TargetApi(Build.VERSION_CODES.P)
public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
//====修改==== 将hasDeepShortcuts置为false,以屏蔽部分APP额外支持的快捷操
boolean hasDeepShortcuts = false;//shortcutCount > 0;
int containerWidth = (int) getResources().getDimension(R.dimen.bg_popup_item_width);
// if there are deep shortcuts, we might want to increase the width of shortcuts to fit
// horizontally laid out system shortcuts.
//hasDeepShortcuts表示该图标是否还有快捷操作,比如时钟APP还有快捷的"启动屏保、启支秒表"等
if (hasDeepShortcuts) {
containerWidth = (int) Math.max(containerWidth,
systemShortcuts.size() * getResources().getDimension(
R.dimen.system_shortcut_header_icon_touch_size));
}
// Add views
if (mNumNotifications > 0) {
// Add notification entries
View.inflate(getContext(), R.layout.notification_content, this);
mNotificationItemView = new NotificationItemView(this);
if (mNumNotifications == 1) {
mNotificationItemView.removeFooter();
}
else {
mNotificationItemView.setFooterWidth(containerWidth);
}
updateNotificationHeader();
}
int viewsToFlip = getChildCount();
mSystemShortcutContainer = this;
//有快捷操作才进,咱不进
if (hasDeepShortcuts) {
if (mNotificationItemView != null) {
mNotificationItemView.addGutter();
}
for (int i = shortcutCount; i > 0; i--) {
DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, this);
v.getLayoutParams().width = containerWidth;
mShortcuts.add(v);
}
updateHiddenShortcuts();
if (!systemShortcuts.isEmpty()) {
mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
for (SystemShortcut shortcut : systemShortcuts) {
initializeSystemShortcut(
R.layout.system_shortcut_icon_only, mSystemShortcutContainer, shortcut);
}
}
} else if (!systemShortcuts.isEmpty()) {
if (mNotificationItemView != null) {
mNotificationItemView.addGutter();
}
for (SystemShortcut shortcut : systemShortcuts) {
//这里是长按图标后弹出的操作项item样式
//====修改start==== 添加快捷操作过滤,如果想保留“应用信息”,可同理处理
if(shortcut instanceof SystemShortcut.UnInstall){
initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
}
//====修改end====
}
}
reorderAndShow(viewsToFlip);
ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setAccessibilityPaneTitle(getTitleForAccessibility());
}
mOriginalIcon.setForceHideDot(true);
// All views are added. Animate layout from now on.
setLayoutTransition(new LayoutTransition());
// Load the shortcuts on a background thread and update the container as it animates.
MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
this, mShortcuts, notificationKeys));
}
// ... //
/**
* Utility class to handle updates while the popup is visible (like widgets and
* notification changes)
*/
private class LiveUpdateHandler implements
PopupDataChangeListener, View.OnAttachStateChangeListener {
private final Launcher mLauncher;
LiveUpdateHandler(Launcher launcher) {
mLauncher = launcher;
}
@Override
public void onViewAttachedToWindow(View view) {
mLauncher.getPopupDataProvider().setChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View view) {
mLauncher.getPopupDataProvider().setChangeListener(null);
}
@Override
public void onWidgetsBound() {
ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
View widgetsView = null;
int count = mSystemShortcutContainer.getChildCount();
for (int i = 0; i < count; i++) {
View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
widgetsView = systemShortcutView;
break;
}
}
if (widgetInfo != null && widgetsView == null) {
// We didn't have any widgets cached but now there are some, so enable the shortcut.
if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
initializeSystemShortcut(R.layout.system_shortcut_icon_only,
mSystemShortcutContainer, widgetInfo);
} //else {
//====修改start==== 将else处理注释掉,否则在某些系统APP长按时会有循环闪烁动画
// If using the expanded system shortcut (as opposed to just the icon), we need
// to reopen the container to ensure measurements etc. all work out. While this
// could be quite janky, in practice the user would typically see a small
// flicker as the animation restarts partway through, and this is a very rare
// edge case anyway.
// close(false);
// PopupContainerWithArrow.showForIcon(mOriginalIcon);
// }
//====修改end====
} else if (widgetInfo == null && widgetsView != null) {
// No widgets exist, but we previously added the shortcut so remove it.
if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
mSystemShortcutContainer.removeView(widgetsView);
} else {
close(false);
PopupContainerWithArrow.showForIcon(mOriginalIcon);
}
}
}
// ... //
}
移除拖动icon时顶部的卸载布局及边界框
Workspace.java
@Override
public void onDragStart(DragObject dragObject, DragOptions options) {
// ... //
if (addNewPage) {
mDeferRemoveExtraEmptyScreen = false;
addExtraEmptyScreenOnDrag();
if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
&& dragObject.dragSource != this) {
// When dragging a widget from different source, move to a page which has
// enough space to place this widget (after rearranging/resizing). We special case
// widgets as they cannot be placed inside a folder.
// Start at the current page and search right (on LTR) until finding a page with
// enough space. Since an empty screen is the furthest right, a page must be found.
int currentPage = getPageNearestToCenterOfScreen();
for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
CellLayout page = (CellLayout) getPageAt(pageIndex);
if (page.hasReorderSolution(dragObject.dragInfo)) {
setCurrentPage(pageIndex);
break;
}
}
}
}
// Always enter the spring loaded mode
//====修改==== 注释掉这一行即可移除长按出现的卸载布局及边界框
// mLauncher.getStateManager().goToState(SPRING_LOADED);
mStatsLogManager.logger().withItemInfo(dragObject.dragInfo)
.withInstanceId(dragObject.logInstanceId)
.log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
}
不显示顶部的“卸载、移除”
Launcher.java
protected void setupViews() {
mDragLayer = findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
mOverviewPanel = findViewById(R.id.overview_panel);
mHotseat = findViewById(R.id.hotseat);
mHotseat.setWorkspace(mWorkspace);
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
mWorkspace.setup(mDragController);
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
// default state, otherwise we will update to the wrong offsets in RTL
mWorkspace.lockWallpaperToDefaultPage();
mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
mDragController.addDragListener(mWorkspace);
// Get the search/delete/uninstall bar
mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);
// Setup Apps
mAppsView = findViewById(R.id.apps_view);
// Setup Scrim
mScrimView = findViewById(R.id.scrim_view);
// Setup the drag controller (drop targets have to be added in reverse order in priority)
//====修改==== 这里注释掉顶部卸载栏的初始化及监听,则长按不会显示卸载、移除按钮
// mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mAppsView, mScrimView);
}
以上为将系统初始的双层屏改为单层涉及到的全部改动,供参考。