赞
踩
Android系统的Launcher改造在国内算是一个不算很低频的需求,尤其是相当多的三方硬件设备以及部分手机厂商的个性化ROM等,受限于国内整体的开源环境,网上能找到相关的开发资源并不多,最近公司有相关业务需求的开发,就自己涉及到的改造点作一些分享。
通常我们遇到的第一个改造点就是去掉原生Launcher自带的底部”全部“图标以及上拉显示的全部APP列表样式,这个不太符合如今国人的操作习惯,而在实际的开发中,发现其涉及到的改造点有如下几个:
去掉上拉展示列表
去掉导航条下方的上拉箭头
确保新安装的APP直接加载到桌面
确保系统APP直接显示到桌面
确保初次启动时即加载全部APP而不是需要安装任一APP时才触发
去掉上拉展示列表(方法不唯一,暂用最简单方式)
com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController
去掉导航栏下方箭头
com.android.launcher3.view.ScrimView
确保新安装的APP直接加载到桌面
com.android.launcher3.model.LoaderTask
com.android.launcher3.model.PackageUpdatedTask
com.android.launcher3.InstallShortcutReceiver
确保系统APP直接显示到桌面
com.android.launcher3.model.AddWorkspaceItemsTask
确保初次启动时即加载全部APP而不是需要安装任一APP时才触发
com.android.launcher3.model.BaseModelUpdateTask
去掉上拉展示列表
这个改动其实有更稳妥的方式,但如果你暂时没有上拉触发个性化交互的其它需求,可采用以下方式。(这里补充说明下,如果是横屏,可能需要改动OverviewToAllAppsTouchController,修改点相同)
PortraitStatesTouchController.java
@Override protected boolean canInterceptTouch(MotionEvent ev) { //移除竖向抽屉====start==== if(true){ return false; } //移除竖向抽屉====end==== if (mCurrentAnimation != null) { if (mFinishFastOnSecondTouch) { mCurrentAnimation.getAnimationPlayer().end(); } AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()) { // If we are already animating from a previous state, we can intercept as long as // the touch is below the current all apps progress (to allow for double swipe). return true; } // Otherwise, make sure everything is settled and don't intercept so they can scroll // recents, dismiss a task, etc. if (mAtomicAnim != null) { mAtomicAnim.end(); } return false; } if (mLauncher.isInState(ALL_APPS)) { // In all-apps only listen if the container cannot scroll itself if (!mLauncher.getAppsView().shouldContainerScroll(ev)) { return false; } } else if (mLauncher.isInState(OVERVIEW)) { if (!mOverviewPortraitStateTouchHelper.canInterceptTouch(ev)) { return false; } } else { // If we are swiping to all apps instead of overview, allow it from anywhere. boolean interceptAnywhere = mLauncher.isInState(NORMAL) && !mAllowDragToOverview; // For all other states, only listen if the event originated below the hotseat height if (!interceptAnywhere && !isTouchOverHotseat(mLauncher, ev)) { return false; } } if (getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE | TYPE_ALL_APPS_EDU) != null) { return false; } return true; }
去掉导航条下方的上拉箭头
在某些设备上可能受屏幕宽高比或其它影响,这个箭头可能不显示,但考虑兼容性还是建议加上处理,这里改完之后,调试时可能会发现有一个不影响运行的报错”ShortcutRequest: Failed to query for shortcuts“,源于缺失ensureShortcutPermission,当为默认启动器时,推断无此报错,可暂不处理。
ScrimView.java
private void updateDragHandleVisibility(@Nullable Drawable recycle) { boolean visible = shouldDragHandleBeVisible(); boolean wasVisible = mDragHandle != null; if (visible != wasVisible) { if (visible) { //隐藏上拉箭头,这里取最小改动,永不展示该箭头====改动start==== mDragHandle = recycle != null ? recycle : mLauncher.getDrawable(0);//R.drawable.drag_handle_indicator_shadow); //====改动end==== mDragHandle.setBounds(mDragHandleBounds); updateDragHandleAlpha(); } else { mDragHandle = null; } invalidate(); } }
确保新安装的APP直接加载到桌面
这里涉及到三个类的改动LoaderTask.java、PackageUpdatedTask.java、InstallShortReceiver.java。
InstallShortReceiver.java,这个只是需要将静态内部类PendingInstallShortcutInfo的可见性改为public。
LoaderTask.java
public void run() { synchronized (this) { // Skip fast if we are already stopped. if (mStopped) { return; } } Object traceToken = TraceHelper.INSTANCE.beginSection(TAG); TimingLogger logger = new TimingLogger(TAG, "run"); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { //...// // fifth step if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { loadFolderNames(); } verifyNotStopped(); updateHandler.finish(); logger.addSplit("finish icon update"); //预制应用展示在Workspace区====改动start==== verifyApplications(); //====改动end==== transaction.commit(); } catch (CancellationException e) { // Loader stopped, ignore logger.addSplit("Cancelled"); } finally { logger.dumpToLog(); } TraceHelper.INSTANCE.endSection(traceToken); } //====增加方法==== private void verifyApplications(){ final Context context = mApp.getContext(); ArrayList<Pair<ItemInfo,Object>> installQueue = new ArrayList<>(); final List<UserHandle> profiles = mUserManager.getUserProfiles(); for(UserHandle user : profiles){ final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null,user); ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo> added = new ArrayList<>(); synchronized (this){ for(LauncherActivityInfo app : apps){ InstallShortcutReceiver.PendingInstallShortcutInfo pendingInstallShortcutInfo = new InstallShortcutReceiver.PendingInstallShortcutInfo(app,context); added.add(pendingInstallShortcutInfo); installQueue.add(pendingInstallShortcutInfo.getItemInfo()); } } if(!added.isEmpty()){ mApp.getModel().addAndBindAddedWorkspaceItems(installQueue); } } }
PackageUpdatedTask.java
@Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) { final Context context = app.getContext(); final IconCache iconCache = app.getIconCache(); final String[] packages = mPackages; final int N = packages.length; FlagOp flagOp = FlagOp.NO_OP; final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages)); ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser); final HashSet<ComponentName> removedComponents = new HashSet<>(); //...// bindApplicationsIfNeeded(); //====改动start==== updateToWorkSpace(context,app,appsList); //====改动end==== final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>(); //...// if (Utilities.ATLEAST_OREO && mOp == OP_ADD) { // Load widgets for the new package. Changes due to app updates are handled through // AppWidgetHost events, this is just to initialize the long-press options. for (int i = 0; i < N; i++) { dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser)); } bindUpdatedWidgets(dataModel); } } //添加方法===改动==== //安装的app添加到workspace工作区 public void updateToWorkSpace(Context context,LauncherAppState app,AllAppsList appsList){ ArrayList<Pair<ItemInfo,Object>> installQueue = new ArrayList<>(); UserManager mUserManager = app.getContext().getSystemService(UserManager.class); final List<UserHandle> profiles = mUserManager.getUserProfiles(); ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo> added = new ArrayList<>(); LauncherApps mLauncherApps = app.getContext().getSystemService(LauncherApps.class); for(UserHandle user : profiles){ final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null,user); synchronized (this){ for(LauncherActivityInfo info :apps){ for(AppInfo appInfo:appsList.data){ String packageName = info.getComponentName().getPackageName(); if(info.getComponentName().equals(appInfo.componentName)){ if(DEBUG){ Log.d(TAG,"updateToWorkSpace packageName: " + packageName); } InstallShortcutReceiver.PendingInstallShortcutInfo mPendingInstallShortcutInfo = new InstallShortcutReceiver.PendingInstallShortcutInfo(info,context); added.add(mPendingInstallShortcutInfo); installQueue.add(mPendingInstallShortcutInfo.getItemInfo()); } } } } } if(!added.isEmpty()){ app.getModel().addAndBindAddedWorkspaceItems(installQueue); } } private WorkspaceItemInfo createWorkSpaceItemInfo(Intent data,UserHandle user,LauncherAppState app){ if(data == null){ Log.e(TAG,"Can't construct WorkspaceItemInfo with null data"); return null; } Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); if(null == intent){ //If the intent is null ,return null as we can't construct a valid WorkspaceItemInfo return null; } final WorkspaceItemInfo info = new WorkspaceItemInfo(); info.user = user; BitmapInfo iconInfo = null; LauncherIcons li = LauncherIcons.obtain(app.getContext()); if(bitmap instanceof Bitmap){ iconInfo = li.createIconBitmap((Bitmap) bitmap); }else{ Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); if(extra instanceof Intent.ShortcutIconResource){ info.iconResource = (Intent.ShortcutIconResource)extra; iconInfo = li.createIconBitmap(info.iconResource); } } li.recycle(); if(null == iconInfo){ iconInfo = app.getIconCache().getDefaultIcon(info.user); } info.bitmap = iconInfo; info.title = Utilities.trim(name); info.contentDescription = app.getContext().getPackageManager().getUserBadgedLabel(info.title,info.user); info.intent = intent; return info; }
确保系统APP直接显示到桌面
经过上面的改动之后,运行发现预装的APP和安装的APP都可以显示,但是系统自带的APP如相册、通讯录等不见了,这里是因为原代码中将该部分APP过滤了,需要去掉
AddWorkspaceItemsTask.java
@Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { if (mItemList.isEmpty()) { return; } final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>(); final IntArray addedWorkspaceScreensFinal = new IntArray(); synchronized(dataModel) { IntArray workspaceScreens = dataModel.collectWorkspaceScreens(); List<ItemInfo> filteredItems = new ArrayList<>(); for (Pair<ItemInfo, Object> entry : mItemList) { ItemInfo item = entry.first; Log.e("item=====",item.toString()); if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { // Short-circuit this logic if the icon exists somewhere on the workspace if (shortcutExists(dataModel, item.getIntent(), item.user)) { continue; } // b/139663018 Short-circuit this logic if the icon is a system app //去掉对系统app的忽略处理====改动start==== // if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) { // continue; // } //====改动end===== } if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { if (item instanceof AppInfo) { item = ((AppInfo) item).makeWorkspaceItem(); } } if (item != null) { filteredItems.add(item); } } InstallSessionHelper packageInstaller = InstallSessionHelper.INSTANCE.get(app.getContext()); LauncherApps launcherApps = app.getContext().getSystemService(LauncherApps.class); //...// } }
确保初次启动时即加载全部APP而不是需要安装任一APP时才触发
在完成以上处理之后会发现,运行完毕后并不能将所有APP铺展到桌面上,但执行任一APP的安装过程之后就会触发了,这里需要作一下处理
BaseModelUpdateTask.java
@Override
public final void run() {
//注掉这个判断,就能让初次启动就加载所有到workspace
if (!mModel.isModelLoaded()) {
if (DEBUG_TASKS) {
Log.d(TAG, "Ignoring model task since loader is pending=" + this);
}
// Loader has not yet run.
//====改动start====
//或者这里简单处理,注掉return,能让初次启动就加载所有到workspace,否则AddWorkspaceItemsTask的execute未执行
//return;
//====改动end====
}
execute(mApp, mDataModel, mAllAppsList);
}
以上为将系统初始的双层屏改为单层涉及到的全部改动,供参考。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。