在原生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應用)背後的故事(二)——應用程式的添加》