在原生launcher中,长按桌面会触发很多种行为。其分类包括:1、空白桌面;2、桌面内容(文件夹、快捷方式、文件夹等);3、桌面既有控件(左右两个屏幕切换按钮,all app list按钮)等;因此我们很容易理解Launcher.java文件中onLongClick函数的行为:
public boolean onLongClick(View v) { switch (v.getId()) { case R.id.previous_screen: if (!isAllAppsVisible()) { mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showPreviews(v); } return true; case R.id.next_screen: if (!isAllAppsVisible()) { mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showPreviews(v); } return true; case R.id.all_apps_button: if (!isAllAppsVisible()) { mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showPreviews(v); } return true; } if (isWorkspaceLocked()) { return false; } if (!(v instanceof CellLayout)) { v = (View) v.getParent(); } //获得CellInfo信息 CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag(); // This happens when long clicking an item with the dpad/trackball if (cellInfo == null) { return true; } if (mWorkspace.allowLongPress()) { if (cellInfo.cell == null) {//如果CellInfo为空且有效,则弹出选择对话框 if (cellInfo.valid) { // User long pressed on empty space mWorkspace.setAllowLongPress(false);//接下来的长按桌面操作将失效,直至回复为止 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); showAddDialog(cellInfo); } } else { if (!(cellInfo.cell instanceof Folder)) {//如果CellInfo不为空,同时不是文件夹的话,则此时长按将触发拖动操作 // User long pressed on an item mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); mWorkspace.startDrag(cellInfo); } } } return true; }
我们暂时不关注非空内容,先处理长按空白桌面后的操作。此时调用的是showAddDialog(cellInfo)函数:
private void showAddDialog(CellLayout.CellInfo cellInfo) { mAddItemCellInfo = cellInfo; mWaitingForResult = true; showDialog(DIALOG_CREATE_SHORTCUT); }此函数只是调用了view的内置函数showDialog,因此我们很容易想到会有一个创建对话框的函数:
@Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_CREATE_SHORTCUT: return new CreateShortcut().createDialog();//弹出添加内容对话框 case DIALOG_RENAME_FOLDER: return new RenameFolder().createDialog();//弹出文件夹编辑对话框 } return super.onCreateDialog(id); }对于添加内容的对话框,我们知道内容如下:
对于这个对话框,涉及到3方面内容:1、对话框显示及操作;2、可以选择的列表;3、点击某个列表项后的操作:
1、对话框:
/** * Displays the shortcut creation dialog and launches, if necessary, the * appropriate activity. */ private class CreateShortcut implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, DialogInterface.OnDismissListener, DialogInterface.OnShowListener { private AddAdapter mAdapter; Dialog createDialog() { mAdapter = new AddAdapter(Launcher.this); final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this); builder.setTitle(getString(R.string.menu_item_add_item)); builder.setAdapter(mAdapter, this); builder.setInverseBackgroundForced(true); AlertDialog dialog = builder.create(); dialog.setOnCancelListener(this); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); return dialog; } public void onCancel(DialogInterface dialog) { mWaitingForResult = false; cleanup(); } public void onDismiss(DialogInterface dialog) { } private void cleanup() { try { dismissDialog(DIALOG_CREATE_SHORTCUT); } catch (Exception e) { // An exception is thrown if the dialog is not visible, which is fine } } /** * Handle the action clicked in the "Add to home" dialog. */ public void onClick(DialogInterface dialog, int which) { Resources res = getResources(); cleanup(); switch (which) { case AddAdapter.ITEM_SHORTCUT: { // Insert extra item to handle picking application pickShortcut(); break; } case AddAdapter.ITEM_APPWIDGET: { int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId(); Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); // start the pick activity startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET); break; } case AddAdapter.ITEM_LIVE_FOLDER: { // Insert extra item to handle inserting folder Bundle bundle = new Bundle(); ArrayList<String> shortcutNames = new ArrayList<String>(); shortcutNames.add(res.getString(R.string.group_folder)); bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>(); shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this, R.drawable.ic_launcher_folder)); bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER)); pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_live_folder)); pickIntent.putExtras(bundle); startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER); break; } case AddAdapter.ITEM_WALLPAPER: { startWallpaper(); break; } } } public void onShow(DialogInterface dialog) { mWaitingForResult = true; } }2、内容列表AddAdapter:
public AddAdapter(Launcher launcher) { super(); mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Create default actions Resources res = launcher.getResources(); mItems.add(new ListItem(res, R.string.group_shortcuts, R.drawable.ic_launcher_shortcut, ITEM_SHORTCUT)); mItems.add(new ListItem(res, R.string.group_widgets, R.drawable.ic_launcher_appwidget, ITEM_APPWIDGET)); mItems.add(new ListItem(res, R.string.group_live_folders, R.drawable.ic_launcher_folder, ITEM_LIVE_FOLDER)); mItems.add(new ListItem(res, R.string.group_wallpapers, R.drawable.ic_launcher_wallpaper, ITEM_WALLPAPER)); }这里我们可以明显看到,添加了4项,分别对应上面那张图的四个图标。
3、添加快捷方式:
添加快捷方式,最简洁的方式是调用系统快捷方式列表:
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT)); pickIntent.putExtra(Intent.EXTRA_TITLE, res.getString(R.string.title_select_app)); pickIntent.putExtras(bundle); startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
这个获取到的就是所有可以创建的快捷方式,但是我们需要在这个列表中加入其他项怎么办呢?因为我们知道,我需要在这个列表中加入“应用程序”,这一项,这样当我们点击应用程序的时候,就可以显示应用程序列表。这个时候,我们只需要传入额外的信息就可以了,如下:
Resources res = getResources(); /** * 用一个Bundle对象,传递两个list信息 * 一个list中存放需要附加到快捷列表中的项的文字 * 一个list中存放每一个对应的图标信息 */ Bundle bundle = new Bundle(); ArrayList<String> shortcutNames = new ArrayList<String>(); shortcutNames.add(res.getString(R.string.group_application)); shortcutNames.add("其他"); //显示在List第一个的应用快捷方式的名字 bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); //显示图标 ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>(); shortcutIcons.add(ShortcutIconResource.fromContext(UorderLauncher.this, R.drawable.icon)); shortcutIcons.add(ShortcutIconResource.fromContext(UorderLauncher.this, R.drawable.icon)); bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT)); pickIntent.putExtra(Intent.EXTRA_TITLE, res.getString(R.string.title_select_app)); //将附加信息加入到pickIntent pickIntent.putExtras(bundle); startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);此时,点击以后,会弹出一个选择列表:
其中第一项“Applitions”是我们附加上去的,其他的都是已经存在的shortcut。因此我们知道我们点击第一项和点击其他项,需要做的操作时不一样。此时,若点击某一项触发onActivityResult函数中的processShortcut函数:
void processShortcut(Intent intent) { // Handle case where user selected "Applications" String applicationName = getResources().getString(R.string.group_applications); String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); //说明用户选择的是应用程序,进入应用程序列表 if (applicationName != null && applicationName.equals(shortcutName)) { Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application)); startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION); } else { //否则,直接创建快捷方式 startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT); } }此后,才开始真正的建立shotcut的操作:
/** * Add a shortcut to the workspace. * * @param data The intent describing the shortcut. * @param cellInfo The position on screen where to create the shortcut. */ private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) { cellInfo.screen = mWorkspace.getCurrentScreen(); if (!findSingleSlot(cellInfo)) return;//寻找空的单个cellInfo final ShortcutInfo info = mModel.addShortcut(this, data, cellInfo, false); if (!mRestoring) { final View view = createShortcut(info); mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked()); } }这里,存在两个问题:1、在桌面寻找空间,只有桌面存在空间才可以添加快捷方式;2、生成快捷方式并添加到桌面;
对于第一步寻找可用空间:
private boolean findSingleSlot(CellLayout.CellInfo cellInfo) { final int[] xy = new int[2]; if (findSlot(cellInfo, xy, 1, 1)) { cellInfo.cellX = xy[0]; cellInfo.cellY = xy[1]; return true; } return false; } private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) { if (!cellInfo.findCellForSpan(xy, spanX, spanY)) { boolean[] occupied = mSavedState != null ? mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null; cellInfo = mWorkspace.findAllVacantCells(occupied); if (!cellInfo.findCellForSpan(xy, spanX, spanY)) { Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); return false; } } return true; }这里实际上对于添加快捷方式来说,是肯定有空间的。如果没有空间,则弹出一个空间不足的提示。(因为你长按的本身就是空白的cellInfo,但是如果在你操作的过程中,有其他程序占用了这个空白空间,那么就会有问题了,因此需要判断是否有可用空间)。
如果有可用空间,则创建一个快捷方式,并将其信息添加到数据库:
ShortcutInfo addShortcut(Context context, Intent data, CellLayout.CellInfo cellInfo, boolean notify) { final ShortcutInfo info = infoFromShortcutIntent(context, data); addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); return info; }此时,这个快捷方式只是存在于数据库中,并未真正显示在桌面。因此需要生产一个图标,
/** * Creates a view representing a shortcut. * * @param info The data structure describing the shortcut. * * @return A View inflated from R.layout.application. */ View createShortcut(ShortcutInfo info) { return createShortcut(R.layout.application, (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info); } /** * Creates a view representing a shortcut inflated from the specified resource. * * @param layoutResId The id of the XML layout used to create the shortcut. * @param parent The group the shortcut belongs to. * @param info The data structure describing the shortcut. * * @return A View inflated from layoutResId. */ View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false); favorite.setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(info.getIcon(mIconCache)), null, null); favorite.setText(info.title); favorite.setTag(info); favorite.setOnClickListener(this); return favorite; }最后将这个图标,添加到桌面进行显示:
/** * Adds the specified child in the specified screen. The position and dimension of * the child are defined by x, y, spanX and spanY. * * @param child The child to add in one of the workspace's screens. * @param screen The screen in which to add the child. * @param x The X position of the child in the screen's grid. * @param y The Y position of the child in the screen's grid. * @param spanX The number of cells spanned horizontally by the child. * @param spanY The number of cells spanned vertically by the child. * @param insert When true, the child is inserted at the beginning of the children list. */ void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) { if (screen < 0 || screen >= getChildCount()) { Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() + " (was " + screen + "); skipping child"); return; } clearVacantCache(); final CellLayout group = (CellLayout) getChildAt(screen); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); if (lp == null) { lp = new CellLayout.LayoutParams(x, y, spanX, spanY); } else { lp.cellX = x; lp.cellY = y; lp.cellHSpan = spanX; lp.cellVSpan = spanY; } group.addView(child, insert ? 0 : -1, lp); if (!(child instanceof Folder)) { //添加该view的长按操作Listener child.setHapticFeedbackEnabled(false); child.setOnLongClickListener(mLongClickListener); } if (child instanceof DropTarget) { mDragController.addDropTarget((DropTarget)child); } }以上我们介绍了添加shotcut的流程,对于其他内容包括widget等,流程类似,就不介绍了。
参考资料:
《说说Android桌面(Launcher应用)背后的故事(二)——应用程序的添加》