Android中ICS4.0Launcher中Fold的功能詳解【androidICS4.0-->Launcher系列三】

         AndroidICS4.0的文件夾和2.3的文件夾區別比較大,主要區別有:

       一、android2.3的文件夾大小是固定的,4.0的文件夾大小是按照裏面的元素大小決定的。

       二、android2.3的文件夾圖標是固定的文件夾的形式展示的,而4.0是從文件中取前3個的縮略圖垂直展示在屏幕上的。估計谷歌怕侵犯蘋果文件夾的知識產權,所以沒有做成和蘋果一樣的效果。

       三、android2.3的文件中可以放多於16的應用程序的快捷方式,而4.0最多隻能放16個快捷方式。

       四、android2.3的文件夾中的圖標不可以交換位置,而4.0的文件夾中的圖標可以相互交換位置。

轉載請標明出處:http://blog.csdn.net/wdaming1986/article/details/7748738

 對比圖如下:

                      android2.3的文件夾                                                                android4.0的文件夾

                                             

 

下面來看看4.0的代碼怎麼實現文件夾的:

 

Step 1:如果系統一開始有fold,一啓動launcher的時候,在Launcher.java類中bindFolders回調方法中:

 /**
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindFolders(HashMap<Long, FolderInfo> folders) {
        setLoadOnResume();
        sFolders.clear();
        sFolders.putAll(folders);
    }

綁定所有fold的對象交給sFolders,去處理。
private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();

 

Step 2:如果是把一個圖標拖放到另一圖標上面,也形成folder。具體流程如下:

1、首先在workspace中的onDrop()方法中會判斷是否會形成一個fold。代碼如下:

public void onDrop(DragObject d) {

    ...  ... 

    // If the item being dropped is a shortcut and the nearest drop
                // cell also contains a shortcut, then create a folder with the two shortcuts.
                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
                        dropTargetLayout, mTargetCell, false, d.dragView, null)) {
                    return;
                }

    ... ...
}


2、在Workspace.java類的createUserFolderIfNecessary()方法中來增加fold,具體代碼如下:

boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
            int[] targetCell, boolean external, DragView dragView, Runnable postAnimationRunnable) {

    。。。 。。。
 FolderIcon fi =
                mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
            destInfo.cellX = -1;
            destInfo.cellY = -1;
            sourceInfo.cellX = -1;
            sourceInfo.cellY = -1;

  。。。 。。。
}

通過mLauncher.addFolder來傳遞folder的信息,包含一些位置信息綁定哪個屏幕的。


 

3、在Launcher.java類的addFolder()這個方法是真正形成folder的,以及在launcher的數據庫中插入一條信息,代碼如下:

 FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX,
            int cellY) {
        final FolderInfo folderInfo = new FolderInfo();
        folderInfo.title = getText(R.string.folder_name);

        // Update the model
        LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY,
                false);
        sFolders.put(folderInfo.id, folderInfo);

        // Create the view
        FolderIcon newFolder =
            FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
        mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1,
                isWorkspaceLocked());
        return newFolder;
    }

FolderIcon.fromXml()這個方法是從xml中形成folder,addInScreen(),把相應的信息插入數據庫。

 

4、在FolderIcon.java中fromXml()方法中的代碼如下:

 

static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
            FolderInfo folderInfo, IconCache iconCache) {

        if (INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION) {
            throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
                    "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
                    "is dependent on this");
        }

        FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);

        icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
        icon.mFolderName.setText(folderInfo.title);
        icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);

        icon.setTag(folderInfo);
        icon.setOnClickListener(launcher);
        icon.mInfo = folderInfo;
        icon.mLauncher = launcher;
        icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
                folderInfo.title));
        Folder folder = Folder.fromXml(launcher);
        folder.setDragController(launcher.getDragController());
        folder.setFolderIcon(icon);
        folder.bind(folderInfo);
        icon.mFolder = folder;

        icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
        folderInfo.addListener(icon);

        return icon;
    }

     Folder folder = Folder.fromXml(launcher);是真正產生了一個folder對象。代碼如下:

 /**
     * Creates a new UserFolder, inflated from R.layout.user_folder.
     *
     * @param context The application's context.
     *
     * @return A new UserFolder.
     */
    static Folder fromXml(Context context) {
        return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
    }

 

並且給folder設置拖拽的控制器,綁定folderInfo設置folderInfo.addListener(icon)圖標改變的監聽。
這個接口 interface  FolderListener定義了一個方法---->如下:

   interface FolderListener {
        public void onAdd(ShortcutInfo item);
        public void onRemove(ShortcutInfo item);
        public void onTitleChanged(CharSequence title);
        public void onItemsChanged();
    }

 

在folder.bind(folderInfo);方法中的操作如下:

 

    void bind(FolderInfo info) {
        mInfo = info;
        ArrayList<ShortcutInfo> children = info.contents;
        ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
        setupContentForNumItems(children.size());
        int count = 0;
        for (int i = 0; i < children.size(); i++) {
            ShortcutInfo child = (ShortcutInfo) children.get(i);
            if (!createAndAddShortcut(child)) {
                overflow.add(child);
            } else {
                count++;
            }
        }

        // We rearrange the items in case there are any empty gaps
        setupContentForNumItems(count);

        // If our folder has too many items we prune them from the list. This is an issue 
        // when upgrading from the old Folders implementation which could contain an unlimited
        // number of items.
        for (ShortcutInfo item: overflow) {
            mInfo.remove(item);
            LauncherModel.deleteItemFromDatabase(mLauncher, item);
        }

        mItemsInvalidated = true;
        updateTextViewFocus();
        mInfo.addListener(this);

        if (!sDefaultFolderName.contentEquals(mInfo.title)) {
            mFolderName.setText(mInfo.title);
        } else {
            mFolderName.setText("");
        }
    }

主要的操作是:給拖拽進來的快捷方式安排位置,判斷Folder是否已經放滿,設置監聽,設置folder的Name;

 

5、folder桌面的縮略圖怎麼形成的,是在第2步Workspace.java的createUserFolderIfNecessary()方法中

// If the dragView is null, we can't animate
            boolean animate = dragView != null;
            if (animate) {
                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
                        postAnimationRunnable);
            } else {
                fi.addItem(destInfo);
                fi.addItem(sourceInfo);
            }

fi.performCreateAnimation()這個方法是給folder添加個動畫。

 

6、在FolderIcon.java中的performCreateAnimation()方法中:

 public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
            final ShortcutInfo srcInfo, final View srcView, Rect dstRect,
            float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {

        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), destView.getMeasuredWidth());

        // This will animate the dragView (srcView) into the new folder
        onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);

        // This will animate the first item from it's position as an icon into its
        // position as the first item in the preview
        animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION);

        postDelayed(new Runnable() {
            public void run() {
                addItem(destInfo);
            }
        }, INITIAL_ITEM_ANIMATION_DURATION);
    }

computePreviewDrawingParams()這個方法是計算繪製folder圖標的方法;

 

7、在FolderIcon.java類中的computePreviewItemDrawingParams()方法中:

 private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
            PreviewItemDrawingParams params) {
        index = NUM_ITEMS_IN_PREVIEW - index - 1;
        float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));

        float offset = (1 - r) * mMaxPerspectiveShift;
        float scaledSize = scale * mBaselineIconSize;
        float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;

        // We want to imagine our coordinates from the bottom left, growing up and to the
        // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
        float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection);
        float transX = offset + scaleOffsetCorrection;
        float totalScale = mBaselineIconScale * scale;
        final int overlayAlpha = (int) (80 * (1 - r));

        if (params == null) {
            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
        } else {
            params.transX = transX;
            params.transY = transY;
            params.scale = totalScale;
            params.overlayAlpha = overlayAlpha;
        }
        return params;
    }

主要工作是:計算圖標的排列,每一個相對上一個有點偏移的距離;效果圖如下:
                                                 
以上基本是把Step 2流程大致過了一遍。

 

Step 3:folder類中的長按事件的傳遞,以及Fold中長按交換位置的流程過一下:

1、先來說給Folder設置長按監聽的地方,因爲Folder.java類是繼承了View.OnClickListener,
        View.OnLongClickListener事件,所以,長按事件就交給自己的onLongClick()事件來處理。

 

2、所以Folder中的長按事件,被自己的public boolean onLongClick(View v) {}時間捕獲,代碼如下:

public boolean onLongClick(View v) {

  ...  ...
   mLauncher.getWorkspace().onDragStartedWithItem(v);
            mLauncher.getWorkspace().beginDragShared(v, this);
            mIconDrawable = ((TextView) v).getCompoundDrawables()[1];

            mCurrentDragInfo = item;
            mEmptyCell[0] = item.cellX;
            mEmptyCell[1] = item.cellY;
            mCurrentDragView = v;

            mContent.removeView(mCurrentDragView);
            mInfo.remove(mCurrentDragInfo);
            mDragInProgress = true;
            mItemAddedBackToSelfViaIcon = false;

   ...  ...
}

同樣拖拽事件是交給Workspace來處理,最後也是統一交給DragController.java類處理和分發相應的事件。這個過程在
Android-->Launcher拖拽事件詳解【androidICS4.0--Launcher系列二】中做了詳細的介紹,這裏就不做贅述了。

 

3、主要看Folder.java類中的onDragOver()這個方法,當在文件夾中拖拽到另一個快捷方式的上面的時候,發生交換,

來看代碼如下:

 public void onDragOver(DragObject d) {
        float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
        mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);
        if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) {
            mReorderAlarm.cancelAlarm();
            mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
            mReorderAlarm.setAlarm(150);
            mPreviousTargetCell[0] = mTargetCell[0];
            mPreviousTargetCell[1] = mTargetCell[1];
        }
    }

這個方法主要做的操作是:判斷拖拽的是哪個對象mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);判斷在

哪個目標的附近,然後判斷和是否是正在拖拽的對象的座標,設置mReorderAlarmListener來進行交換,設置150毫秒用來處理動畫的。

 

4、在ReorderAlarmListener內部類的代碼如下:

  OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
        public void onAlarm(Alarm alarm) {
            realTimeReorder(mEmptyCell, mTargetCell);
        }
    };

 

5、在realTimeReorder()方法中傳遞這個快捷方式在屏幕x軸,y軸上的爲止,進行交換,代碼如下:

 private void realTimeReorder(int[] empty, int[] target) {
        boolean wrap;
        int startX;
        int endX;
        int startY;
        int delay = 0;
        float delayAmount = 30;
        if (readingOrderGreaterThan(target, empty)) {
            wrap = empty[0] >= mContent.getCountX() - 1;
            startY = wrap ? empty[1] + 1 : empty[1];
            for (int y = startY; y <= target[1]; y++) {
                startX = y == empty[1] ? empty[0] + 1 : 0;
                endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
                for (int x = startX; x <= endX; x++) {
                    View v = mContent.getChildAt(x,y);
                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
                            REORDER_ANIMATION_DURATION, delay)) {
                        empty[0] = x;
                        empty[1] = y;
                        delay += delayAmount;
                        delayAmount *= 0.9;
                    }
                }
            }
        } else {
            wrap = empty[0] == 0;
            startY = wrap ? empty[1] - 1 : empty[1];
            for (int y = startY; y >= target[1]; y--) {
                startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
                endX = y > target[1] ? 0 : target[0];
                for (int x = startX; x >= endX; x--) {
                    View v = mContent.getChildAt(x,y);
                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
                            REORDER_ANIMATION_DURATION, delay)) {
                        empty[0] = x;
                        empty[1] = y;
                        delay += delayAmount;
                        delayAmount *= 0.9;
                    }
                }
            }
        }
    }

readingOrderGreaterThan()這個方法的作用是判斷是從上往下拖動,還是從下往上拖動,這兩種情況的交換方式不一樣。循環也就不一樣。交換的過程中通過animateChildToPosition();這個方法設置了一個動畫。

 

6、在放下的時候會觸發Folder.java的onDrop()方法,

 public void onDrop(DragObject d) {
        ShortcutInfo item;

   。。。  。。。
if (d.dragView.hasDrawn()) {
                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView);
            } else {
                mCurrentDragView.setVisibility(VISIBLE);
            }

  mInfo.add(item); 。。。  。。。 
}

作用是設置放下的view可見,把當前的這個快捷方式添加到mInfo中。

 

7、當執行完onDrop()方法後會走onDropCompleted()方法:

public void onDropCompleted(View target, DragObject d, boolean success) {
  ... ...
 // Reordering may have occured, and we need to save the new item locations. We do this once
        // at the end to prevent unnecessary database operations.
        updateItemLocationsInDatabase();
  ...  ...
}

這個方法的作用是更新item的位置信息在數據庫中。

private void updateItemLocationsInDatabase() {
        ArrayList<View> list = getItemsInReadingOrder();
        for (int i = 0; i < list.size(); i++) {
            View v = list.get(i);
            ItemInfo info = (ItemInfo) v.getTag();
            LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
                        info.cellX, info.cellY);
        }
    }

好了,folder的大致流程就是這些,更詳細的請參考launcher源代碼。

寫的倉促,歡迎大家指出裏面的錯誤,如果有不解的歡迎留言!


 

 

 

 

發佈了88 篇原創文章 · 獲贊 25 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章