Android的默认设计中桌面Launcher采用的是抽屉式的二级桌面样式,而在实际中大部分人都在使用的是小米式的一级桌面,我们这里使用Android6.0的源码做一个类似的修改。
首先分析一下怎么做,其它也很简单,无非就是将一级桌面workspace上没有的图标从二级桌面上挪上去,同时删除一级桌面上的“所有程序”菜单。然后要避免和解决的问题是在新的APP添加时,使图标自动添加到IDLE界面,而在长按APP图标时桌面上显示的是卸载而不是删除,同时针对某些APP首次启动会自动生成快捷方式的情况要避免图标的重复。
下面是具体的实现过程(在Android5.1上有类似的整体设计,不过并不完整)
config.xml中增加一个总的控制开关:
<bool name="config_isDisableAllApps">true</bool>
LauncherAppState.java中读取该参数:
public static boolean isDisableAllApps(){
return sContext.getResources().getBoolean(R.bool.config_isDisableAllApps);
}
DeleteDropTarget.java中确定是否支持删除
public static boolean supportsDrop(Object info) {
if(LauncherAppState.isDisableAllApps()){
if(info instanceof ShortcutInfo){
ShortcutInfo shortcut=(ShortcutInfo)info;
return shortcut.itemType!=LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
}else
return info instanceof LauncherAppWidgetInfo;
}else{
return (info instanceof ShortcutInfo)
|| (info instanceof LauncherAppWidgetInfo)
|| (info instanceof FolderInfo);
}
}
InfoDropTarget.java中确定是否支持查看应用信息
@Override
protected boolean supportsDrop(DragSource source, Object info) {
if(LauncherAppState.isDisableAllApps()){
if (info instanceof ShortcutInfo){
ShortcutInfo shortcut=(ShortcutInfo)info;
return shortcut.itemType==LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
}else{
return false;
}
}else{
return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info);
}
}
UninstallDropTarget.java中确定是否支持卸载
@Override
protected boolean supportsDrop(DragSource source, Object info) {
//zyl add 20180201
if(LauncherAppState.isDisableAllApps()){
if(info instanceof ShortcutInfo){
ShortcutInfo shortcut=(ShortcutInfo)info;
if(Utilities.isSystemApp(getContext(), shortcut.getIntent()))
return false;
else
return shortcut.itemType==LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
}else{
return false;
}
}else{
return supportsDrop(getContext(), info);
}
}
Hotseat.java中删除中间的“所有程序”
public boolean isAllAppsButtonRank(int rank) {
//zyl modify 20180228
if(LauncherAppState.isDisableAllApps())
return false;
else
return rank == mAllAppsButtonRank;
}
void resetLayout() {
mContent.removeAllViewsInLayout();
//zyl add 20180228
if(!LauncherAppState.isDisableAllApps()){
// Add the Apps button
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
TextView allAppsButton = (TextView)
inflater.inflate(R.layout.all_apps_button, mContent, false);
Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
mLauncher.resizeIconDrawable(d);
int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
Rect bounds = d.getBounds();
d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
bounds.bottom - scaleDownPx / 2);
allAppsButton.setCompoundDrawables(null, d, null, null);
allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
allAppsButton.setOnKeyListener(new HotseatIconKeyEventListener());
if (mLauncher != null) {
mLauncher.setAllAppsButton(allAppsButton);
allAppsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
allAppsButton.setOnClickListener(mLauncher);
allAppsButton.setOnLongClickListener(mLauncher);
allAppsButton.setOnFocusChangeListener(mLauncher.mFocusHandler);
}
// Note: We do this to ensure that the hotseat is always laid out in the orientation of
// the hotseat in order regardless of which orientation they were added
int x = getCellXFromOrder(mAllAppsButtonRank);
int y = getCellYFromOrder(mAllAppsButtonRank);
CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1);
lp.canReorder = false;
mContent.addViewToCellLayout(allAppsButton, -1, allAppsButton.getId(), lp, true);
}
InvariantDeviceProfile.java中重新选择新的workspace的xml文件并且删除针对Hotseat中图标个数的合法性检查
//zyl add 20180228
private boolean hasDA=LauncherAppState.isDisableAllApps();
//zyl modify 20180228
if (hs % 2 == 0&&!hasDA) {
throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
}
ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>();
// width, height, #rows, #columns, #folder rows, #folder columns,
// iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
//zyl modify 20180228
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
255, 300, 2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
255, 400, 3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
275, 420, 3, 4, 3, 4, 4, 48, 13, hasDA?4:5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
255, 450, 3, 4, 3, 4, 4, 48, 13, hasDA?4:5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
296, 491.33f, 4, 4, 4, 4, 4, 48, 13, hasDA?4:5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
359, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, hasDA?4:5, 56, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
335, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, hasDA?4:5, 56, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
406, 694, 5, 5, 4, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
// The tablet profile is odd in that the landscape orientation
// also includes the nav bar on the side
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7",
575, 904, 5, 6, 4, 5, 4, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6));
// Larger tablet profiles always have system bars on the top & bottom
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
727, 1207, 5, 6, 4, 5, 4, 76, 14.4f, 7, 76, R.xml.default_workspace_5x6));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
1527, 2527, 7, 7, 6, 6, 4, 100, 20, 7, 72, R.xml.default_workspace_5x6));
return predefinedDeviceProfiles;
}
由于我们修改了在一级桌面上支持查看应用信息,而默认的是从二级桌面拖动到一级桌面上方的信息栏进行查看,这里因为动画的原因会造成图标卡死在信息栏上,我们需要在Workspace.java中消除有关的动画
public void onDropCompleted(final View target, final DragObject d,
final boolean isFlingToDelete, final boolean success) {
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "onDropCompleted: target = " + target + ", d = " + d
+ ", isFlingToDelete = " + isFlingToDelete + ", mDragInfo = "
+ mDragInfo + ", success = " + success);
}
//zyl add 20180228
if(LauncherAppState.isDisableAllApps()){
if(target instanceof InfoDropTarget){
mDragController.cancelDrag();
return;
}
}
...
}
如果有APP请求添加新的快捷方式,我们要排除重复的情况,修改InstallShortcutReceiver.java,添加一个判断的方法
//zyl add 20180228
public static boolean verifyShortcut(PendingInstallShortcutInfo pendingInfo){
String installPackageName,installClassName;
try{
installPackageName=pendingInfo.getShortcutInfo().getIntent().getComponent().getPackageName();
installClassName=pendingInfo.getShortcutInfo().getIntent().getComponent().getClassName();
}catch(NullPointerException ex){
Log.i(TAG,"NullPointerException");
return false;
}
String mPackageName,mClassName;
ShortcutInfo mShortCut;
Intent mIntent;
LauncherAppState app = LauncherAppState.getInstance();
synchronized (app.getModel().sBgLock) {
for(ItemInfo minfo:LauncherModel.sBgItemsIdMap){
if (minfo instanceof ShortcutInfo) {
Log.i(TAG,"iteminfo="+minfo);
mShortCut=(ShortcutInfo)minfo;
mIntent=mShortCut.getIntent();
mPackageName=mIntent.getComponent().getPackageName();
mClassName=mIntent.getComponent().getClassName();
if(installPackageName.equals(mPackageName)&&installClassName.equals(mClassName)){
return true;
}
}
}
}
return false;
}
在 static void flushInstallQueue(Context context) 方法中做判断
//zyl add 20180228
if(LauncherAppState.isDisableAllApps()){
if(verifyShortcut(pendingInfo))
return;
}
最关键的是将Workspace上没有的APP添加上去,修改LauncherModel.java中LoaderTask部分,在加载完所有应用后进行判断,实际上我们并不是不执行加载二级桌面的操作,只是不显示
keep_running: {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
if (mStopped) {
LauncherLog.d(TAG, "LoadTask break in the middle, this = " + this);
break keep_running;
}
waitForIdle();
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
//zyl add 20180228
if(LauncherAppState.isDisableAllApps()){
verifyApplications();
}
}
添加新的方法verifyApplications()
//zyl add 20180228
private void verifyApplications() {
final Context context = mApp.getContext();
// Cross reference all the applications in our apps list with items in the workspace
ArrayList<ItemInfo> tmpInfos;
ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
synchronized (sBgLock) {
for (AppInfo app : mBgAllAppsList.data) {
tmpInfos = getItemInfoForComponentName(app.componentName, app.user);
if (tmpInfos.isEmpty()) {
// We are missing an application icon, so add this to the workspace
added.add(app);
// This is a rare event, so lets log it
Log.e(TAG, "Missing Application on load: " + app);
}
}
}
if (!added.isEmpty()) {
addAndBindAddedWorkspaceItems(context, added);
}
}
另外既然只有一级桌面,那当一个APP被卸载后,我们一般希望图标会自动排列,而不是空出来,我们添加一个算法来修改数据库
//zyl add 20180228
private void changeItemFromDatabase(Context context,final ArrayList<ItemInfo> items){
int cellX=0, cellY=0;
int screenId;
for (ItemInfo item : items){
screenId=(int)item.screenId;
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
final int xCount = (int) profile.numColumns;
ArrayList<ItemInfo> changeditems =new ArrayList<ItemInfo>();
for (ItemInfo i : sBgWorkspaceItems){
if (i.container == LauncherSettings.Favorites.CONTAINER_DESKTOP){
if(screenId == i.screenId && i.cellY>=item.cellY ){
if(i.cellY==item.cellY && i.cellX>item.cellX){
cellX = i.cellX-1;
cellY = i.cellY;
}else if(i.cellY>item.cellY){
if(i.cellX==0){
cellX = xCount-1;
cellY = i.cellY-1;
}else{
cellX = i.cellX -1;
cellY = i.cellY;
}
}else{
cellX = i.cellX;
cellY = i.cellY;
}
i.cellX = cellX;
i.cellY = cellY;
changeditems.add(i);
}
}
}
moveItemsInDatabase(context, changeditems, item.container, screenId);
}
}
还是LauncherModel.java,PackageUpdatedTask中做修改
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
for (String pn : removedPackages) {
//zyl add 20180228
if(LauncherAppState.isDisableAllApps()){
changeItemFromDatabase(context,getItemsByPackageName(pn,mUser));
}
deletePackageFromDatabase(context, pn, mUser);
}
...
此外,我们顺便将workspace上边距减小(删除QSB后Idle上方留白过多),修改DeviceProfile.java的getWorkspacePadding方法
...
} else {
// Pad the top and bottom of the workspace with search/hotseat bar sizes
padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
2*edgeMarginPx,//zyl modify 20180228
desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
paddingBottom);
}
...
同时调整参数文件dimens.xml
<dimen name="dynamic_grid_search_bar_height">25dp</dimen> <!--zyl modify 20180228-->
<!-- We want 46dp extra for the tall search bar. -->
<dimen name="dynamic_grid_search_bar_height_tall">94dp</dimen>
<dimen name="qsb_internal_padding_top">0dp</dimen> <!--zyl modify 20180228-->
<dimen name="qsb_internal_padding_bottom">0dp</dimen> <!--zyl modify 20180228-->
这样我们的一级桌面就成形了,不会有其它什么问题。
网友评论