Android7.0Launcher3Folder加载流程

    Launcher3界面中比较复杂的UI就是folder了。3个与之相关的类就是FolderIcon.java, Folder.java, FolderInfo.java,FolderPageView.java。大致思路就是mv模式:典型的数据驱动界面。

一、数据加载

1.1 加载逻辑时序图


1.1.1 主要流程

这边的逻辑和我之前Android Launcher7.0首次数据加载逻辑写的逻辑类似,主要方法对象组装方法还是在4方法loadWorkSpace

(1)解析ItemType为ITEM_TYPE_APPLICATION,ITEM_TYPE_SHORTCUT,ITEM_TYPE_DEEP_SHORTCUT,container不为DESKTOP | HOTSEAT的数据。

(2)根据container有无在集合sBgFolders找到FolderInfo或者创建一个FolderInfo加入到sBgFolders。    

(3)将相同container的shortcutInfo加入到folderInfo。

(4)更新folderInfo位置信息,检查信息正确性,加入到全局变量中。

private void loadWorkspace() {
	switch (itemType) {
		case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
		case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
		case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
			switch (container) {
				default:
    					// Item is in a user folder
					FolderInfo folderInfo =
							findOrMakeFolder(sBgFolders, container);
					folderInfo.add(info, false);
					break;
			}
		break;

		case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
			id = c.getLong(idIndex);
			FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);

			// Do not trim the folder label, as is was set by the user.
			folderInfo.title = c.getString(cursorIconInfo.titleIndex);
			folderInfo.id = id;
			folderInfo.container = container;
			folderInfo.screenId = c.getInt(screenIndex);
			folderInfo.cellX = c.getInt(cellXIndex);
			folderInfo.cellY = c.getInt(cellYIndex);
			folderInfo.spanX = 1;
			folderInfo.spanY = 1;
			folderInfo.options = c.getInt(optionsIndex);

			// check & update map of what's occupied
			if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
				itemsToRemove.add(id);
				break;
			}

			switch (container) {
				case LauncherSettings.Favorites.CONTAINER_DESKTOP:
				case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
					sBgWorkspaceItems.add(folderInfo);
					break;
			}

			if (restored) {
				// no special handling required for restored folders
				restoredRows.add(id);
			}

			sBgItemsIdMap.put(folderInfo.id, folderInfo);
			sBgFolders.put(folderInfo.id, folderInfo);
		break;
}

二、界面绑定

2.1 UI示例图


2.2 文件夹创建时序图

       加载逻辑时序图7方法bindItems中如果当前info属性itemType为LauncherSettings.Favorites.ITEM_TYPE_FOLDER,就会准备执行此时序图。


2.2.1 流程介绍

2.2.1.1(1~3    )   FolderIcon布局生成,文件夹名,位置确认。 Folder布局生成。

方法1 流程介绍

(1)创建folderIcon对象

(2)绘制文件夹名称

(3)创建folder对象

public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
                                    FolderInfo folderInfo, IconCache iconCache) {
    DeviceProfile grid = launcher.getDeviceProfile();
    //create FolderIcon
    FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
    icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
    icon.setClipToPadding(false);
    //draw bubbleTextView
    icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
    icon.mFolderName.setText(folderInfo.title);
    icon.mFolderName.setCompoundDrawablePadding(0);
    FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
    lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
    //init icon
    icon.setTag(folderInfo);
    icon.setOnClickListener(launcher);
    icon.mInfo = folderInfo;
    icon.mLauncher = launcher;
    icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
    //create Folder
    Folder folder = Folder.fromXml(launcher);
    folder.setDragController(launcher.getDragController());
    folder.setFolderIcon(icon);
    folder.bind(folderInfo);
    //invalidate FolderIcon
    icon.setFolder(folder);

    icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
    folderInfo.addListener(icon);
    icon.setOnFocusChangeListener(launcher.mFocusHandler);
    return icon;
}

2.2.1.2(4~8  )  FolderPageView界面绑定数据。

方法4流程介绍

(1)获取folderInfo里面的shortcutInfo集合contents,比较器由小到大排序。执行方法5。

(2)这边的逻辑也很有意思,如果你的文件夹设置无法滚动,那么超过文件夹最大页面个数我就不加载了,并且通知数据库干掉他。

(3)检查逻辑:界面绑定结束如果发现里面的item个数不超过1个,就干掉文件夹,创建BubbleTextView对象

void bind(FolderInfo info) {
   (1)
    mInfo = info;
    ArrayList<ShortcutInfo> children = info.contents;
    
    Collections.sort(children, ITEM_POS_COMPARATOR);
   (2)
    ArrayList<ShortcutInfo> overflow = mContent.bindItems(children);

    // 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.
    // TODO: Remove this, as with multi-page folders, there will never be any overflow
    for (ShortcutInfo item: overflow) {
        mInfo.remove(item, false);
            LauncherModel.deleteItemFromDatabase(mLauncher, item);
    }
   (3)
    // In case any children didn't come across during loading, clean up the folder accordingly
    mFolderIcon.post(new Runnable() {
        public void run() {
            if (getItemCount() <= 1) {
                replaceFolderWithFinalItem();
            }
        }
    });
}

方法5流程介绍

(1)调用方法6创建BubbleTextView对象,存入集合icons,调用方法7

public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
	ArrayList<View> icons = new ArrayList<View>();
	ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();

	for (ShortcutInfo item : items) {
		if (!ALLOW_FOLDER_SCROLL && icons.size() >= mMaxItemsPerPage) {
			extra.add(item);
		} else {
			icons.add(createNewView(item));
		}
	}
	arrangeChildren(icons, icons.size(), false);
	return extra;
}

方法7流程介绍

(1)遍历已有页CellLayout,移除里面所有子View,等待重新绑定子View。

(2)根据item个数生成对应网格布局。

(3)遍历item对象,如果currentPage为空或者当前的position大于页面最大item个数,就要去获取cellLayout,如果集合没有就去生成。(google真的考虑性能极致化,我们写完全可以直接干掉FolderPageView里面所有的子View,然后每次生成新的cellLayout。然后在接着绑定数据,然后根本就不需要做4的逻辑)。

(4)根据position生成横纵坐标点,绑定布局。

(5)万一以有的比新生成的页数多,这里就会删除多余的空页。

(6)更新页面指示器位置,文件夹名称位置。

private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
   (1)
	ArrayList<CellLayout> pages = new ArrayList<CellLayout>();
	for (int i = 0; i < getChildCount(); i++) {
		CellLayout page = (CellLayout) getChildAt(i);
		page.removeAllViews();
		pages.add(page);
	}
   (2)
	setupContentDimensions(itemCount);
   (3)
	Iterator<CellLayout> pageItr = pages.iterator();
	CellLayout currentPage = null;

	int position = 0;
	int newX, newY, rank;

	rank = 0;
	for (int i = 0; i < itemCount; i++) {
		View v = list.size() > i ? list.get(i) : null;
		if (currentPage == null || position >= mMaxItemsPerPage) {
			// Next page
			if (pageItr.hasNext()) {
				currentPage = pageItr.next();
			} else {
				currentPage = createAndAddNewPage();
			}
			position = 0;
		}
   (4)
		if (v != null) {
			CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
			newX = position % mGridCountX;
			newY = position / mGridCountX;
			ItemInfo info = (ItemInfo) v.getTag();
			if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
				info.cellX = newX;
				info.cellY = newY;
				info.rank = rank;
				if (saveChanges) {
					LauncherModel.addOrMoveItemInDatabase(getContext(), info,
							mFolder.mInfo.id, 0, info.cellX, info.cellY);
				}
			}
			lp.cellX = info.cellX;
			lp.cellY = info.cellY;
			currentPage.addViewToCellLayout(
					v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
			if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
				((BubbleTextView) v).verifyHighRes();
			}
		}

		rank ++;
		position++;
	}
   (5)
	// Remove extra views.
	boolean removed = false;
	while (pageItr.hasNext()) {
		removeView(pageItr.next());
		removed = true;
	}
	if (removed) {
		setCurrentPage(0);
	}

	setEnableOverscroll(getPageCount() > 1);
   (6)
	// Update footer
	mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
	// Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
	mFolder.mFolderName.setGravity(getPageCount() > 1 ?
			(mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
}

2.2.1.3(9~11)  FolderIcon图标绘制数据准备。

方法10流程介绍

    方法10和方法13作用一样(但是我不了解google这样写的意图是什么,完全可以直接在13方法完成逻辑,但是他拆成2块)。就是要确认folderIcon上面所有小icon的左顶点坐标。但是方法10缺少15,16两个方法,所以无法计算真实大小,确认icon位置。

10方法仅只有如下作用:

(1)确认绘制icon个数。

(2)创建PreviewItemDrawingParams对象添加到全局变量集合mDrawingParams中。

(3)给mReferenceDrawable。

private void updateItemDrawingParams(boolean animate) {
   (1)
	ArrayList<View> items = mFolder.getItemsInReadingOrder();
	int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());

	int prevNumItems = mDrawingParams.size();

	// We adjust the size of the list to match the number of items in the preview
	while (nItemsInPreview < mDrawingParams.size()) {
		mDrawingParams.remove(mDrawingParams.size() - 1);
	}
   (2)	
	while (nItemsInPreview > mDrawingParams.size()) {
		mDrawingParams.add(new PreviewItemDrawingParams(0, 0, 0, 0));
	}
   (3)
	for (int i = 0; i < mDrawingParams.size(); i++) {
		PreviewItemDrawingParams p = mDrawingParams.get(i);
		p.drawable = getTopDrawable((TextView) items.get(i));
		if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
			computePreviewItemDrawingParams(i, nItemsInPreview, p);
			if (mReferenceDrawable == null) {
				mReferenceDrawable = p.drawable;
			}
		}
	}
}

2.2.1.3(12~24)  FolderIcon图标绘制。

(12       )绘制阶段入口。

(13~20)计算所有小icon的位置。

方法20流程介绍

       这个是这段方法核心逻辑。没法删他的一点代码,我可以根据他的代码得到数值,但是让我用自己的数学能力,我觉得我写不出来这么漂亮的代码。可能用策略模式,2个图标一个逻辑,3个图标一个逻辑,4个图标一个逻辑。来自学霸对学渣的碾压。

private void getPosition(int index, int curNumItems, float[] result) {
	// The case of two items is homomorphic to the case of one.
	curNumItems = Math.max(curNumItems, 2);

	// We model the preview as a circle of items starting in the appropriate piece of the
	// upper left quadrant (to achieve horizontal and vertical symmetry).
	double theta0 = mIsRtl ? 0 : Math.PI;
	// In RTL we go counterclockwise
	int direction = mIsRtl ? 1 : -1;

	double thetaShift = 0;
	if (curNumItems == 3) {
		thetaShift = Math.PI / 6;
	} else if (curNumItems == 4) {
		thetaShift = Math.PI / 4;
	}
	theta0 += direction * thetaShift;

	// We want the items to appear in reading order. For the case of 1, 2 and 3 items, this
	// is natural for the circular model. With 4 items, however, we need to swap the 3rd and
	// 4th indices to achieve reading order.
	if (curNumItems == 4 && index == 3) {
		index = 2;
	} else if (curNumItems == 4 && index == 2) {
		index = 3;
	}

	// We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increasex
	float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems -
			MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
	double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;



	float halfIconSize = (mIconSize * scaleForNumItems(curNumItems)) / 2;

	// Map the location along the circle, and offset the coordinates to represent the center
	// of the icon, and to be based from the top / left of the preview area. The y component
	// is inverted to match the coordinate system.
	result[0] = mAvailableSpace / 2 + (float) (radius * Math.cos(theta) / 2) - halfIconSize;
	result[1] = mAvailableSpace / 2 + (float) (- radius * Math.sin(theta) / 2) - halfIconSize;
}

以2个小icon为例:在方法12dispatchDraw中有这一句

canvas.translate(mBackground.basePreviewOffsetX, mBackground.basePreviewOffsetY);

这个是平移到文件夹背景的左顶点,小图标的绘制是文件夹背景的左顶点为坐标原点,向右为X轴正半轴,向下为Y轴正半轴建立坐标系。如下图黑色线坐标系。

接下来计算绘制每个图标的左顶点坐标。图标索引是从左往右,从上往下排序。这里看一下方法20流程介绍getPosition最后一个Google注释。他是以icon中心点为坐标系原点,向右为X轴正半轴,向上为Y轴正半轴建立坐标系,如下图红色线部分。



根据如上图通过极坐标方式得到图标中心点坐标,接着换算成图片左顶点坐标。存入对应对象中以供绘制对象调用。

(21      )绘制文件夹背景

(22      )根据文件背景剪裁画布,确立文件夹背景为可是区域

(23      )绘制item

(24      )绘制白色边框

这边最复杂的就是位置的确认,后面绘制没什么难度,也就不介绍了。






  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值