当前位置: 代码迷 >> Android >> android Launcher源码解析06:长按圆桌面添加图标
  详细解决方案

android Launcher源码解析06:长按圆桌面添加图标

热度:12   发布时间:2016-05-01 12:45:10.0
android Launcher源码解析06:长按桌面添加图标

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

  相关解决方案