android Launcher源碼解析06:長按案頭添加表徵圖

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.