android 9.0 Launcher3 去掉抽屜式,顯示所有 app

  • 2019 年 11 月 8 日
  • 筆記

效果圖

在这里插入图片描述

修改思路

1、增加全局控制變數 sys.launcher3.is_full_app,用來動態切換

2、增加兩套布局,對應有抽屜和無抽屜

3、去除 allAppsButton

4、將 AllAppsContainerView 中的圖標載入到 Workspace

5、新安裝的 app 自動添加圖標到 Workspace

6、替換 Workspace 圖標長按刪除選項為取消

7、屏蔽上拉顯示抽屜頁面手勢

8、修改頁面指示線為圓點

上程式碼

1、增加全局控制變數 sys.launcher3.is_full_app

1) 在 LauncherAppState 中增加靜態方法 isDisableAllApps(), 通過修改 Settings 中自定義的值 sys.launcher3.is_full_app

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3LauncherAppState.java

   private static  Context mContext;    public static boolean isDisableAllApps() {          if (mContext != null) {              return Settings.System.getInt(mContext.getContentResolver(),                  "sys.launcher3.is_full_app", 0) == 1;          }          return true;      }

vendormediatekproprietarypackagesappsLauncher3AndroidManifest-common.xml

2) AndroidManifest-common.xml 中增加許可權

<uses-permission android:name="android.permission.WRITE_SETTINGS" />

3) 在 SettingsActivity 中增加 SwitchPreference 用以動態修改 sys.launcher3.is_full_app

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3SettingsActivity.java

在內部類 LauncherSettingsFragment 中重寫 onPreferenceTreeClick() 用以監聽 SwitchPreference 點擊

/**       * This fragment shows the launcher preferences.       */      public static class LauncherSettingsFragment extends PreferenceFragment {        .....        @Override      public void onCreate(Bundle savedInstanceState) {              super.onCreate(savedInstanceState);        ....                //讀取保存的值,初始化 SwitchPreference 的初始狀態,是否選中              int isFull = Settings.System.getInt(getActivity().getContentResolver(),                  "sys.launcher3.is_full_app", 0);              Log.d("Launcher3", "sys.launcher3.is_full_app="+isFull);              SwitchPreference fullSwitch = (SwitchPreference) findPreference("pref_is_full_app");              fullSwitch.setChecked(isFull==1);       }            //add for change is_full_app value      @Override      public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {              boolean result = true;              final String key = preference.getKey();                 if ("pref_is_full_app".equals(key)) {                  boolean checked = ((SwitchPreference) preference).isChecked();                    Settings.System.putInt(getActivity().getContentResolver(), "sys.launcher3.is_full_app",                          checked ? 1 : 0);                    Log.e("Launcher3", "SwitchPreference checked="+checked);                    // Value has changed                  ProgressDialog.show(getActivity(),                          null /* title */,                          getActivity().getString(R.string.full_app_override_progress),                          true /* indeterminate */,                          false /* cancelable */);                  new LooperExecutor(LauncherModel.getWorkerLooper()).execute(                          new OverrideApplyHandler(getActivity()));                }              return result;          }    }  

點擊 SwitchPreference 後需要保存 sys.launcher3.is_full_app 新值,同時清除 Launcher3 的快取,延時啟動並結束當前進程

清除快取方法 clearApplicationUserData 在 Launcher3 中編譯報錯,所以通過發送廣播到 Setting 中進行真正的清快取操作

      //add for change is_full_app value      private static class OverrideApplyHandler implements Runnable {            private final Context mContext;            private OverrideApplyHandler(Context context) {              mContext = context;          }            @Override          public void run() {              // Clear the icon cache.              LauncherAppState.getInstance(mContext).getIconCache().clear();                // Wait for it              try {                  Thread.sleep(1000);              } catch (Exception e) {                  Log.e("Launcher3", "Error waiting", e);              }                // Schedule an alarm before we kill ourself.              Intent homeIntent = new Intent(Intent.ACTION_MAIN)                      .addCategory(Intent.CATEGORY_HOME)                      .setPackage(mContext.getPackageName())                      .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);              PendingIntent pi = PendingIntent.getActivity(mContext, 42,                      homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);              mContext.getSystemService(AlarmManager.class).setExact(                      AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi);                //clear data will kill process              Intent intent = new Intent("com.android.action.CLEAR_APP_DATA");              intent.putExtra("pkgName", "com.android.launcher3");              intent.addFlags(0x01000000);              mContext.sendBroadcast(intent);              Log.i("Launcher3", "Clearing user data com.android.launcher3");                // Kill process              android.os.Process.killProcess(android.os.Process.myPid());          }      }  

4) SettingsActivity 對應的 xml 文件修改 launcher_preferences

vendormediatekproprietarypackagesappsLauncher3resxmllauncher_preferences.xml

 <SwitchPreference          android:key="pref_is_full_app"          android:title="@string/is_full_app_title"          android:summary="@string/is_full_app_desc"          android:defaultValue="false"          android:persistent="true" />

對應的 string 文件就不貼了,自己增加下就行

2、增加兩套布局,對應有抽屜和無抽屜

載入布局文件對應的 xml 為 vendormediatekproprietarypackagesappsLauncher3resxmldevice_profiles.xml

Launcher3 通過獲取 minWidthDps 和 minHeightDps 來確定載入哪一個 profile,我的平板解析度是 1280*800 的,增加兩個 profile 節點

 <profile          launcher:name="Tablet"          launcher:minWidthDps="376"          launcher:minHeightDps="586"          launcher:numRows="4"          launcher:numColumns="5"          launcher:numFolderRows="4"          launcher:numFolderColumns="5"          launcher:iconSize="50"          launcher:iconTextSize="11"          launcher:numHotseatIcons="5"          launcher:defaultLayoutId="@xml/default_workspace_tb_5x6"          />        <profile          launcher:name="Tablet_no_all_app"          launcher:minWidthDps="380"          launcher:minHeightDps="590"          launcher:numRows="4"          launcher:numColumns="5"          launcher:numFolderRows="4"          launcher:numFolderColumns="5"          launcher:iconSize="50"          launcher:iconTextSize="11"          launcher:numHotseatIcons="4"          launcher:defaultLayoutId="@xml/default_workspace_tb_5x6_no_all_app"          />

對應的你需要在 xml 文件下增加 4 個文件,分別是 default_workspace_tb_5x6.xml dw_hotseat_tb.xml default_workspace_tb_5x6_no_all_app.xml dw_hotseat_tb_no_all_app.xml

這樣的好處是你可以自定義不同的布局文件載入內容,上面的配置含義簡單說一下,分別是最小寬度、最小高度、布局的行和列、文件夾中布局行和列、圖標大小、圖標文字大小、HotSeat 個數,載入的布局文件

在 InvariantDeviceProfile() 判斷是否需要載入 Tablet_no_all_app profile

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3InvariantDeviceProfile.java

public InvariantDeviceProfile(Context context) {            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);          Display display = wm.getDefaultDisplay();          DisplayMetrics dm = new DisplayMetrics();          display.getMetrics(dm);            Point smallestSize = new Point();          Point largestSize = new Point();          display.getCurrentSizeRange(smallestSize, largestSize);            // This guarantees that width < height          minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);          minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);          Log.i("Launcher3.profiles", "orignalminWidthDps="+minWidthDps + "  orignalminHeightDps="+minHeightDps);            //add for load no_all_app xml          if (LauncherAppState.isDisableAllApps()) {              Log.e("Launcher3.profiles", "load no all app profiles");              //對應 device_profiles.xml 中 Tablet_no_all_app 的值              minWidthDps = 380.0f;              minHeightDps = 590.0f;          }          .....  }

3、去除 allAppsButton

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3Hotseat.java

將 resetLayout() 中 FeatureFlags.NO_ALL_APPS_ICON 替換為 LauncherAppState.isDisableAllApps()

void resetLayout(boolean hasVerticalHotseat) {          mContent.removeAllViewsInLayout();          mHasVerticalHotseat = hasVerticalHotseat;          InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;          if (hasVerticalHotseat) {              mContent.setGridSize(1, idp.numHotseatIcons);          } else {              mContent.setGridSize(idp.numHotseatIcons, 1);          }            //if (!FeatureFlags.NO_ALL_APPS_ICON) {          /// add for check is need allappbutton          if (!LauncherAppState.isDisableAllApps()) {              // Add the Apps button              Context context = getContext();              DeviceProfile grid = mLauncher.getDeviceProfile();            ...  }

4、將 AllAppsContainerView 中的圖標載入到 Workspace

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3modelLoaderTask.java

run() 中增加判斷,添加 verifyApplications(), 修改 InstallShortcutReceiver 中 PendingInstallShortcutInfo 為 public

public void run() {          synchronized (this) {              // Skip fast if we are already stopped.              if (mStopped) {                  return;              }          }          ....            // second step          TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");          loadAllApps();            //add for load all app on workspace          if (LauncherAppState.isDisableAllApps()) {              android.util.Log.e("Launcher3", "verifyApplications()");              verifyApplications();          }            ....  }     //add for load all app on workspace  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<InstallShortcutReceiver.PendingInstallShortcutInfo>();              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);              }          }      }  

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3modelBaseModelUpdateTask.java

注釋 run() 中的 return

@Override      public final void run() {          if (!mModel.isModelLoaded()) {              if (DEBUG_TASKS) {                  Log.d(TAG, "Ignoring model task since loader is pending=" + this);              }              // Loader has not yet run.              //annotaion for load all app on workspace              // return;          }          execute(mApp, mDataModel, mAllAppsList);      }

5、新安裝的 app 自動添加圖標到 Workspace

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3modelPackageUpdatedTask.java

execute() 中增加判斷,添加 updateToWorkSpace()

public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {        ....         final ArrayList<AppInfo> addedOrModified = new ArrayList<>();      addedOrModified.addAll(appsList.added);        //add for load new install app on workspace      if (LauncherAppState.isDisableAllApps()) {          android.util.Log.e("cczLauncher3", "updateToWorkSpace()");          updateToWorkSpace(context, app, appsList);      }        ...  }    //add for load new install app on workspace  public void updateToWorkSpace(Context context, LauncherAppState app , AllAppsList appsList){           ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();          final List<UserHandle> profiles = UserManagerCompat.getInstance(context).getUserProfiles();          ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo> added          = new ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo>();            for (UserHandle user : profiles) {              final List<LauncherActivityInfo> apps = LauncherAppsCompat.getInstance(context).getActivityList(null, user);              synchronized (this) {                  for (LauncherActivityInfo info : apps) {                      for (AppInfo appInfo : appsList.added) {                          if(info.getComponentName().equals(appInfo.componentName)){                              InstallShortcutReceiver.PendingInstallShortcutInfo mPendingInstallShortcutInfo                              =  new InstallShortcutReceiver.PendingInstallShortcutInfo(info,context);                              added.add(mPendingInstallShortcutInfo);                              installQueue.add(mPendingInstallShortcutInfo.getItemInfo());                          }                      }                  }              }          }          if (!added.isEmpty()) {              app.getModel().addAndBindAddedWorkspaceItems(installQueue);          }      }

6、替換 Workspace 圖標長按刪除選項為取消

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3DeleteDropTarget.java

在 setTextBasedOnDragSource() 、setControlTypeBasedOnDragSource()、onAccessibilityDrop() 中分別增加判斷是否需要刪除圖標

private void setTextBasedOnDragSource(ItemInfo item) {          if (!TextUtils.isEmpty(mText)) {              mText = getResources().getString(item.id != ItemInfo.NO_ID                      ? R.string.remove_drop_target_label                      : android.R.string.cancel);              //add for hide deletedroptarget              if (LauncherAppState.isDisableAllApps()) {                  android.util.Log.e("Launcher3", "hide delete drop target");                  mText = getResources().getString(isCanDrop(item)                              ? R.string.remove_drop_target_label                              : android.R.string.cancel);              }                requestLayout();          }      }        private void setControlTypeBasedOnDragSource(ItemInfo item) {          mControlType = item.id != ItemInfo.NO_ID ? ControlType.REMOVE_TARGET                  : ControlType.CANCEL_TARGET;            //add for hide deletedroptarget [S]          if (LauncherAppState.isDisableAllApps()) {              mControlType = isCanDrop(item) ? ControlType.REMOVE_TARGET                  : ControlType.CANCEL_TARGET;          }      }    public void onAccessibilityDrop(View view, ItemInfo item) {          // Remove the item from launcher and the db, we can ignore the containerInfo in this call          // because we already remove the drag view from the folder (if the drag originated from          // a folder) in Folder.beginDrag()          //add if juge is need remove item from workspace          if (!LauncherAppState.isDisableAllApps() || isCanDrop(item)) {              mLauncher.removeItem(view, item, true /* deleteFromDb */);              mLauncher.getWorkspace().stripEmptyScreens();              mLauncher.getDragLayer()                      .announceForAccessibility(getContext().getString(R.string.item_removed));          }  }    private boolean isCanDrop(ItemInfo item){          return !(item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||                  item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER);  }  

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3dragndropDragController.java

drop() 中增加判斷,取消當前拖拽操作

private void drop(DropTarget dropTarget, Runnable flingAnimation) {      ....        boolean accepted = false;          if (dropTarget != null) {              dropTarget.onDragExit(mDragObject);              if (dropTarget.acceptDrop(mDragObject)) {                  if (flingAnimation != null) {                      flingAnimation.run();                  } else {                      dropTarget.onDrop(mDragObject, mOptions);                  }                  accepted = true;                    //add for cancel canceldroptarget handle                  if (LauncherAppState.isDisableAllApps() && dropTarget instanceof DeleteDropTarget &&                          isNeedCancelDrag(mDragObject.dragInfo)) {                      cancelDrag();                  }              }          }          ...  }    private boolean isNeedCancelDrag(ItemInfo item){          return (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||                  item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER);   }

7、屏蔽上拉顯示抽屜頁面手勢

vendormediatekproprietarypackagesappsLauncher3quickstepsrccomandroidlauncher3uioverridesOverviewToAllAppsTouchController.java

canInterceptTouch() 中增加判斷是否直接攔截

@Override      protected boolean canInterceptTouch(MotionEvent ev) {          //add for forbidden workspace drag change GradientView alph          if (LauncherAppState.isDisableAllApps()){              android.util.Log.e("Launcher3", "canInterceptTouch()");              return false;          }            if (mCurrentAnimation != null) {              // If we are already animating from a previous state, we can intercept.              return true;          }          if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {              return false;          }          if (mLauncher.isInState(ALL_APPS)) {              // In all-apps only listen if the container cannot scroll itself              return mLauncher.getAppsView().shouldContainerScroll(ev);          } else if (mLauncher.isInState(NORMAL)) {              return true;          } else if (mLauncher.isInState(OVERVIEW)) {              RecentsView rv = mLauncher.getOverviewPanel();              return ev.getY() > (rv.getBottom() - rv.getPaddingBottom());          } else {              return false;          }      }

8、修改頁面指示線為圓點

vendormediatekproprietarypackagesappsLauncher3reslayoutlauncher.xml

WorkspacePageIndicator 改為 PageIndicatorDots

<com.android.launcher3.pageindicators.PageIndicatorDots              android:id="@+id/page_indicator"              android:layout_width="match_parent"              android:layout_height="4dp"              android:layout_gravity="bottom|center_horizontal"              android:theme="@style/HomeScreenElementTheme" />

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3pageindicatorsPageIndicatorDots.java

增加 PageIndicatorDots 繼承 Insettable,複寫setInsets(), 調整圓點的位置

public class PageIndicatorDots extends View implements PageIndicator, Insettable {    // add for change WorkspacePageIndicator line to dot      @Override      public void setInsets(Rect insets) {          DeviceProfile grid = mLauncher.getDeviceProfile();          FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();            if (grid.isVerticalBarLayout()) {              Rect padding = grid.workspacePadding;              lp.leftMargin = padding.left + grid.workspaceCellPaddingXPx;              lp.rightMargin = padding.right + grid.workspaceCellPaddingXPx;              lp.bottomMargin = padding.bottom;          } else {              lp.leftMargin = lp.rightMargin = 0;              lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;              lp.bottomMargin = grid.hotseatBarSizePx + insets.bottom;          }          setLayoutParams(lp);      }        @Override      public void setScroll(int currentScroll, int totalScroll) {          if (mNumPages > 1) {              if (mIsRtl) {                  currentScroll = totalScroll - currentScroll;              }              int scrollPerPage = totalScroll / (mNumPages - 1);                // add for change WorkspacePageIndicator line to dot              if (scrollPerPage == 0) {                  return;              }              int pageToLeft = currentScroll / scrollPerPage;              int pageToLeftScroll = pageToLeft * scrollPerPage;              int pageToRightScroll = pageToLeftScroll + scrollPerPage;            ...    }

vendormediatekproprietarypackagesappsLauncher3srccomandroidlauncher3statesSpringLoadedState.java

注釋 setShouldAutoHide(),避免長按 workSpace 時發生崩潰

@Override      public void onStateEnabled(Launcher launcher) {          Workspace ws = launcher.getWorkspace();          ws.showPageIndicatorAtCurrentScroll();          //annotaion for WorkspacePageIndicator line to dot          // ws.getPageIndicator().setShouldAutoHide(false);            // Prevent any Un/InstallShortcutReceivers from updating the db while we are          // in spring loaded mode          InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);          launcher.getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);      }        @Override      public void onStateDisabled(final Launcher launcher) {          //annotaion for WorkspacePageIndicator line to dot          // launcher.getWorkspace().getPageIndicator().setShouldAutoHide(true);            // Re-enable any Un/InstallShortcutReceiver and now process any queued items          InstallShortcutReceiver.disableAndFlushInstallQueue(                  InstallShortcutReceiver.FLAG_DRAG_AND_DROP, launcher);      }  

歡迎關注我的英文公眾號【十句英文】,每日1首英文金曲+10句英文,伴你共同進步。

微信掃一掃下方二維碼即可關註:

ME71HS.jpg

Tags: