Launcher3 开发定制 (Android10)
1 Workspace 加载全部应用
1.1 Workspace 加载全部应用
Launcher 启动,会在oncreate中调用LauncherModel.startLoader()执行加载桌面图标到workspace的方法,这里是修改入口。
// src/com/android/launcher3/Launcher.java
private LauncherModel mModel;
protected void onCreate(Bundle savedInstanceState) {
//...
if (!mModel.startLoader(currentScreen)) {
...
}
}
后面会调用startLoaderForResults(loaderResults)方法执行一次图标加载的任务
// src/com/android/launcher3/LauncherModel.java
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
MODEL_EXECUTOR.post(mLoaderTask);
}
}
在LoaderTask这个任务的run()方法中,我们可以定义加载应用图标到workspace的方法:
- loadAllAppsToWorkspace方法负责加载所有可显示的应用信息列表,然后调用 LauncherModel.addAndBindAddedWorkspaceItems()方法添加到workspace上,具体的添加逻辑可前往 AddWorkspaceItemsTask 这个任务类中查看。
- 修改内部类InstallShortcutReceiver.PendingInstallShortcutInfo的访问修饰符为public, 使外部可访问。其中 getItemInfo的相关参数可自行查看。
// src/com/android/launcher3/model/LoaderTask.java
public void run() {
//...
// second step
TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
List<LauncherActivityInfo> allActivityList = loadAllApps();
// 添加此逻辑,LauncherAppState.isDisableAllApps()方法默认返回true
if (LauncherAppState.isDisableAllApps()) {
loadAllAppsToWorkspace();
}
}
private void loadAllAppsToWorkspace() {
List<LauncherActivityInfo> allActivityList = getAllApps();
if (!allActivityList.isEmpty()) {
final Context context = mApp.getContext();
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
synchronized (this) {
for (LauncherActivityInfo launcherActivityInfo : allActivityList) {
MyLogUtils.i(TAG, "pkg: " + launcherActivityInfo.getComponentName().getPackageName());
// 构造快捷方式的信息, 这里需要修改内部类InstallShortcutReceiver.PendingInstallShortcutInfo的访问修饰符为public, 使外部可访问。
InstallShortcutReceiver.PendingInstallShortcutInfo shortcutInfo =
new InstallShortcutReceiver.PendingInstallShortcutInfo(launcherActivityInfo, context);
installQueue.add(shortcutInfo.getItemInfo());
}
}
if (!installQueue.isEmpty()) {
MyLogUtils.i(TAG, "loadAllAppsToWorkspace installQueue size: " + installQueue.size());
mApp.getModel().addAndBindAddedWorkspaceItems(installQueue);
}
}
}
// src/com/android/launcher3/LauncherModel.java
public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
Callbacks callbacks = getCallback();
if (callbacks != null) {
callbacks.preAddApps();
}
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
我们可以来看一下AddWorkspaceItemsTask任务类的构造方法和execute()方法,在这里,系统会默认不处理加载两种情况的应用图标:1.已存在workspace中的,2.系统应用, 我们需要修改第二种情况,系统应用和非系统应用都需要添加到workspace。
// src/com/android/launcher3/model/AddWorkspaceItemsTask.java
public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
mItemList = itemList;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
for (Pair<ItemInfo, Object> entry : mItemList) {
// item类型, 具体的item信息可参考 ItemInfo、PendingInstallShortcutInfo.getItemInfo方法
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
if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
// 注释此行代码,不排除系统应用的情况
//continue;
}
}
// 以下是处理逻辑,不需要修改,暂不分析
//...
}
1.2 Workspace 列表响应应用安装/更新/删除的情况
到这里,workspace会加载全部应用的图标,我们还需要考虑应用安装/更新/删除的情况,此时也需要更新workspace的图标状态。
在LauncherModel类中,会有应用安装卸载后等一系列的回调方法,此时会调用 PackageUpdatedTask任务类对桌面图标执行一些操作,我们也需要对workspace进行一些更新处理。此处以应用安装为例:
// src/com/android/launcher3/LauncherModel.java
@Override
public void onPackageAdded(String packageName, UserHandle user) {
int op = PackageUpdatedTask.OP_ADD;
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
这里需要添加updateAllAppToWorkspace()方法来更新workspace图标,逻辑和加载全部应用图标到workspace大致相似,可以自行比对。
// src/com/android/launcher3/model/PackageUpdatedTask.java
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
//...
// 在方法末尾添加更新workspace图标方法
if (LauncherAppState.isDisableAllApps()) {
updateAllAppToWorkspace(app, appsList);
}
}
private void updateAllAppToWorkspace(LauncherAppState app, AllAppsList appsList) {
if (appsList.data.isEmpty()) {
return;
}
Context context = app.getContext();
final List<UserHandle> userProfiles = UserManagerCompat.getInstance(context).getUserProfiles();
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
for (UserHandle user : userProfiles) {
final List<LauncherActivityInfo> apps =
LauncherAppsCompat.getInstance(context).getActivityList(null, user);
synchronized (this) {
for (LauncherActivityInfo launcherActivityInfo : apps) {
for (AppInfo appInfo : appsList.data) {
if (launcherActivityInfo.getComponentName().equals(appInfo.componentName)) {
InstallShortcutReceiver.PendingInstallShortcutInfo shortcutInfo =
new InstallShortcutReceiver.PendingInstallShortcutInfo(launcherActivityInfo, context);
installQueue.add(shortcutInfo.getItemInfo());
}
}
}
}
}
if (!installQueue.isEmpty()) {
MyLogUtils.i(TAG, "updateAllAppToWorkspace, installQueue size: " + installQueue.size());
app.getModel().addAndBindAddedWorkspaceItems(installQueue);
}
appsList.data.clear();
}
另外,PackageUpdatedTask类继承于BaseModelUpdateTask,在BaseModelUpdateTask的execute()方法中,会有一个判断的短路逻辑,阻止更新任务的执行,我们需要手动注释,否则,更新方法将会不执行。
// src/com/android/launcher3/model/BaseModelUpdateTask.java
@Override
public final void run() {
if (!mModel.isModelLoaded()) {
if (DEBUG_TASKS) {
Log.d(TAG, "Ignoring model task since loader is pending=" + this);
}
// 注释return, 继续向下执行
// return;
}
execute(mApp, mDataModel, mAllAppsList);
}
2 Workspace 隐藏抽屉功能
到这里,workspace已经实现了应用列表展示,抽屉入口的功能可以移除。可以看到的是,我们在wworkspace界面通过手势上滑操作来打开抽屉。
// src/com/android/launcher3/dragndrop/DragLayer.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
ev.offsetLocation(getTranslationX(), 0);
try {
return super.dispatchTouchEvent(ev);
} finally {
ev.offsetLocation(-getTranslationX(), 0);
}
}
我们可以取消滑动时的监听,达到不能通过抽屉进入all app界面的效果。
// src/com/android/launcher3/touch/BaseSwipeDetector.java
public boolean onTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_MOVE:
//...
// handle state and listener calls.
// 添加以下代码取消上滑监听,隐藏抽屉入口
// BaseFlags.DRAG_TO_ALL_APPS_DISABLED 默认设置为true
if (!BaseFlags.DRAG_TO_ALL_APPS_DISABLED) {
// handle state and listener calls.
if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
setState(ScrollState.DRAGGING);
}
if (mState == ScrollState.DRAGGING) {
reportDragging(ev);
}
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
break;
}
将在workspace界面的提示向上的小箭头去掉。注释以下代码,不绘制箭头:
// src/com/android/launcher3/views/ScrimView.java
private void updateDragHandleVisibility(Drawable recycle) {
boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
boolean wasVisible = mDragHandle != null;
if (visible != wasVisible) {
// 添加以下代码取消隐藏箭头
if (BaseFlags.DRAG_TO_ALL_APPS_DISABLED) {
// set mDragHandle as null to hide arrow
mDragHandle = null;
} else {
if (visible) {
mDragHandle = recycle != null ? recycle :
mLauncher.getDrawable(R.drawable.drag_handle_indicator);
mDragHandle.setBounds(mDragHandleBounds);
updateDragHandleAlpha();
} else {
mDragHandle = null;
}
}
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean value = super.onTouchEvent(event);
if (BaseFlags.DRAG_TO_ALL_APPS_DISABLED) {
return value;
}
if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN
&& mDragHandle.getAlpha() == 255
&& mHitRect.contains(event.getX(), event.getY())) {
//...
return true;
}
return value;
}
侧滑指示器的修改一般有两种:1.隐藏指示器;2.修改指示器样式
- 隐藏workspace的侧滑指示器。当页面滑动时会通过调用到WorkspacePageIndicator的invalidate()->draw()进行指示器的绘制。
// src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@Override
protected void onDraw(Canvas canvas) {
if (mTotalScroll == 0 || mNumPagesFloat == 0) {
return;
}
// 隐藏 WorkspacePageIndicator
// Compute and draw line rect.
/*float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
int availableWidth = getWidth();
int lineWidth = (int) (availableWidth / mNumPagesFloat);
int lineLeft = (int) (progress * (availableWidth - lineWidth));
int lineRight = lineLeft + lineWidth;
canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight,
getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);*/
}
- 修改指示器样式。ondraw 方法进行以下修改。
private boolean mShouldAutoHide = false;
private final int mLineRadius;
private int mLineWidth = 22;
private final float mCircleRadius = 6;
private final float mCircleSpacing = mCircleRadius * 2 + 17;
@Override
protected void onDraw(Canvas canvas) {
//Modify workspacePageIndicator style
mLinePaint.setAlpha(45);
float circleFirstX = getWidth() / 2.0f - ((mNumPagesFloat / 2.0f) * mCircleSpacing);
for (int i = 0; i < mNumPagesFloat; i++) {
canvas.drawCircle(circleFirstX + i * mCircleSpacing, getHeight() / 2.0f, mCircleRadius, mLinePaint);
}
mLinePaint.setAlpha(255);
if (mTotalScroll == 0 || mNumPagesFloat == 0) {
float left = circleFirstX - mLineWidth / 2.0f;
float right = left + mLineWidth;
canvas.drawRoundRect(left, getHeight() / 2.0f - mLineHeight /