1.简介
学习下folder的图标咋弄的
1.1.CellLayout.java
自定义的容器
public class CellLayout extends ViewGroup {
>1.构造方法
构造方法里默认添加了一个容器
public CellLayout(Context context, AttributeSet attrs, int defStyle) {
//...
//自定义的容器
mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
mBorderSpace);
//加入CellLayout
addView(mShortcutsAndWidgets);
}
>2.addViewToCellLayout
添加child的方法,可以看到,最终是加入到mShortcutsAndWidgets里了
public boolean addViewToCellLayout(View child, int index, int childId,
CellLayoutLayoutParams params, boolean markCells) {
final CellLayoutLayoutParams lp = params;
// Hotseat不显示文字
if (child instanceof BubbleTextView) {
BubbleTextView bubbleChild = (BubbleTextView) child;
bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
}
child.setScaleX(DEFAULT_SCALE);
child.setScaleY(DEFAULT_SCALE);
if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
&& lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
// If the horizontal or vertical span is set to -1, it is taken to
// mean that it spans the extent of the CellLayout
if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
child.setId(childId);
//加入内部容器mShortcutsAndWidgets里
mShortcutsAndWidgets.addView(child, index, lp);
if (markCells) markCellsAsOccupiedForView(child);
return true;
}
return false;
}
文件夹可能存在的地方有3处,workspace,hotseat,taskbar
1.2..Workspace.java
- 自定义的ViewGroup,分页的,每页添加的其实就是1.1里的CellLayout
- 数据添加参考1.3.1
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
>1.insertNewWorkspaceScreen
可以看到Workspace添加的child就是CellLayout
public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
DeviceProfile dp = mLauncher.getDeviceProfile();
CellLayout newScreen;
if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
} else {
//看这里,添加的就是CellLayout
newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
}
newScreen.setCellLayoutContainer(this);
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
//加入容器里
addView(newScreen, insertIndex);
mStateTransitionAnimation.applyChildState(
mLauncher.getStateManager().getState(), newScreen, insertIndex);
updatePageScrollValues();
updateCellLayoutMeasures();
return newScreen;
}
1.3.Hotseat
public class Hotseat extends CellLayout implements Insettable {
>1.数据添加
参考WorkspaceLayoutManager.java,Launcher里bind数据的时候会调用
default void addInScreen(View child, int container, int screenId, int x, int y,
int spanX, int spanY) {
//..
final CellLayout layout;
//判断容器类型
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
//获取hotseat容器
layout = getHotseat();
// Hide folder title in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(false);
}
} else {
// Show folder title if not in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(true);
}
//这里是根据workspace的页码获取容器,就是1.2.1添加的
layout = getScreenWithId(screenId);
}
//..//可以看到,调用的是1.1.2的方法添加child
if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
}
//..
1.4.TaskbarView
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
>1.updateHotseatItems
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
//..
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
if (hotseatItemInfo == null) {
continue;
}
//根据数据类型加载不同的布局
final int expectedLayoutResId;
boolean isCollection = false;
//推荐应用
if (hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
} else if (hotseatItemInfo instanceof FolderInfo fi) {
//文件夹
expectedLayoutResId = fi.itemType == ITEM_TYPE_APP_PAIR
? R.layout.app_pair_icon
: R.layout.folder_icon;
isCollection = true;
} else {
//正常的应用
expectedLayoutResId = R.layout.taskbar_app_icon;
}
2.FolderIcon.java
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
DraggableView, Reorderable {
public int getViewType() {
return DRAGGABLE_ICON;
}
2.1.哪里用到了
>1.TaskbarView.java
if (isFolder) {
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
//其他几处也用的这个静态方法获取的,见2.3
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
mActivityContext, this, folderInfo);
//见2.7,隐藏文本控件
folderIcon.setTextVisible(false);
hotseatView = folderIcon;
}
2.2.构造方法
public FolderIcon(Context context) {
super(context);
init();
}
private void init() {
mLongPressHelper = new CheckLongPressHelper(this);
mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
//预览管理器
mPreviewItemManager = new PreviewItemManager(this);
//未读消息那个点
mDotParams = new DotRenderer.DrawParams();
}
2.3.inflateFolderAndIcon
public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
T activityContext, ViewGroup group, FolderInfo folderInfo) {
//见4.1,加载Folder布局
Folder folder = Folder.fromXml(activityContext);
//见补充2,加载FolderIcon布局
FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
//见4.3
folder.setFolderIcon(icon);
//绑定数据
folder.bind(folderInfo);
icon.setFolder(folder);
return icon;
}
>1.folder_icon.xml
- 目前用的布局是这个,自定义的帧布局,里边套一个自定义的文本控件
- folder_icon_name用来显示文件夹的名字的,默认是空的
<com.android.launcher3.folder.FolderIcon
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="true" >
<com.android.launcher3.views.DoubleShadowBubbleTextView
style="@style/BaseIcon.Workspace"
android:id="@+id/folder_icon_name"
android:focusable="false"
android:layout_gravity="top"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.launcher3.folder.FolderIcon>
>2.inflateIcon
public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGroup group,
FolderInfo folderInfo) {
DeviceProfile grid = activity.getDeviceProfile();
//加载布局
FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
.inflate(resId, group, false);
icon.setClipToPadding(false);
//见补充1里的布局,里边的文本控件
icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
icon.mFolderName.setCompoundDrawablePadding(0);
//文本控件的布局参数
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
//顶部margin等于图标的大小加上间距,所以正常情况显示在文件夹的底部
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
icon.setTag(folderInfo);
//点击事件
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.mInfo = folderInfo;
icon.mActivity = activity;
icon.mDotRenderer = grid.mDotRendererWorkSpace;
icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title));
//
FolderDotInfo folderDotInfo = new FolderDotInfo();
for (WorkspaceItemInfo si : folderInfo.contents) {
folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
}
icon.setDotInfo(folderDotInfo);
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
//见2.4,网格组织者,
icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
//设置folder信息
icon.mPreviewVerifier.setFolderInfo(folderInfo);
//补充3
icon.updatePreviewItems(false);
folderInfo.addListener(icon);
return icon;
}
>3.updatePreviewItems
private void updatePreviewItems(boolean animate) {
mPreviewItemManager.updatePreviewItems(animate);
mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
}
2.4.FolderGridOrganizer.java
>1.构造方法
- 读取的是配置文件里的数据device_profiles.xml
public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
public FolderGridOrganizer(InvariantDeviceProfile profile) {
//读取folder里要显示的行数和列数
mMaxCountX = profile.numFolderColumns;
mMaxCountY = profile.numFolderRows;
//每页最多显示几个
mMaxItemsPerPage = mMaxCountX * mMaxCountY;
}
>2.setFolderInfo
public FolderGridOrganizer setFolderInfo(FolderInfo info) {
//见补充3,设置folder里有几个图标
return setContentSize(info.contents.size());
}
>3.setContentSize
public FolderGridOrganizer setContentSize(int contentSize) {
if (contentSize != mNumItemsInFolder) {
//计算网格大小,见补充4
calculateGridSize(contentSize);
//是否超过最大预览数,这里是4个
mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW;
mNumItemsInFolder = contentSize;
}
return this;
}
>4.calculateGridSize
根据数据个数,计算显示的行和列
private void calculateGridSize(int count) {
boolean done;
int gridCountX = mCountX;
int gridCountY = mCountY;
//文件夹里包含的图标数超过了每页的最大值
if (count >= mMaxItemsPerPage) {
//设置为最大行 列
gridCountX = mMaxCountX;
gridCountY = mMaxCountY;
done = true;
} else {
done = false;
}
while (!done) {
int oldCountX = gridCountX;
int oldCountY = gridCountY;
if (gridCountX * gridCountY < count) {
// Current grid is too small, expand it
if ((gridCountX <= gridCountY || gridCountY == mMaxCountY)
&& gridCountX < mMaxCountX) {
//先增加列
gridCountX++;
} else if (gridCountY < mMaxCountY) {
//再增加行
gridCountY++;
}
//至少有一行
if (gridCountY == 0) gridCountY++;
} else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
gridCountY = Math.max(0, gridCountY - 1);
} else if ((gridCountX - 1) * gridCountY >= count) {
gridCountX = Math.max(0, gridCountX - 1);
}
done = gridCountX == oldCountX && gridCountY == oldCountY;
}
//最终的行和列
mCountX = gridCountX;
mCountY = gridCountY;
}
>5.previewItemsForPage
根据提供的页码以及数据集合,找到对应页面的数据
public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) {
ArrayList<R> result = new ArrayList<>();
//这个就是每页可以最多显示的个数
int itemsPerPage = mCountX * mCountY;
//乘以页面
int start = itemsPerPage * page;
//计算数据end
int end = Math.min(start + itemsPerPage, contents.size());
for (int i = start, rank = 0; i < end; i++, rank++) {
if (isItemInPreview(page, rank)) {
result.add((R) contents.get(i));
}
if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
break;
}
}
return result;
}
>6.isItemInPreview
根据页码以及排名返回图标是否在预览图里
public boolean isItemInPreview(int page, int rank) {
//页码大于0或者数据量大于4
if (page > 0 || mDisplayingUpperLeftQuadrant) {
int col = rank % mCountX;
int row = rank / mCountX;
return col < 2 && row < 2;
}
return rank < MAX_NUM_ITEMS_IN_PREVIEW;
}
2.5.dispatchDraw
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//背景不可见,点击展开的时候
if (!mBackgroundIsVisible) return;
mPreviewItemManager.recomputePreviewDrawingParams();
//见5.2
if (!mBackground.drawingDelegated()) {
//这个就是画FolderIcon的半透明的背景,见5.1
mBackground.drawBackground(canvas);
}
if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
//这个是画小图标的,见3.4
mPreviewItemManager.draw(canvas);
if (!mBackground.drawingDelegated()) {
//背景边框,见5.3,默认不画
mBackground.drawBackgroundStroke(canvas);
}
//画圆点的
drawDot(canvas);
}
2.6.drawDot
PreviewBackground mBackground = new PreviewBackground();
public void drawDot(Canvas canvas) {
if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
Rect iconBounds = mDotParams.iconBounds;
// FolderIcon draws the icon to be top-aligned (with padding) & horizontally-centered
int iconSize = mActivity.getDeviceProfile().iconSizePx;
iconBounds.left = (getWidth() - iconSize) / 2;
iconBounds.right = iconBounds.left + iconSize;
iconBounds.top = getPaddingTop();
iconBounds.bottom = iconBounds.top + iconSize;
float iconScale = (float) mBackground.previewSize / iconSize;
Utilities.scaleRectAboutCenter(iconBounds, iconScale);
// If we are animating to the accepting state, animate the dot out.
mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
mDotParams.dotColor = mBackground.getDotColor();
mDotRenderer.draw(canvas, mDotParams);
}
}
2.7.setTextVisible
设置文本控件的可见性
public void setTextVisible(boolean visible) {
if (visible) {
mFolderName.setVisibility(VISIBLE);
} else {
mFolderName.setVisibility(INVISIBLE);
}
}
3.PreviewItemManager.java
3.1.构造方法
public PreviewItemManager(FolderIcon icon) {
mContext = icon.getContext();
mIcon = icon;
//icon的大小
mIconSize = ActivityContext.lookupContext(
mContext).getDeviceProfile().folderChildIconSizePx;
mClipThreshold = Utilities.dpToPx(1f);
}
3.2.updatePreviewItems
void updatePreviewItems(boolean animate) {
//获取旧的预览个数
int numOfPrevItemsAux = mFirstPageParams.size();
buildParamsForPage(0, mFirstPageParams, animate);//补充1
//记录旧的预览个数
mNumOfPrevItems = numOfPrevItemsAux;
}
>1.buildParamsForPage
构建对应页面的预览数据
void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
//先获取对应页面的数据
List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
// We adjust the size of the list to match the number of items in the preview.
//我们调整列表的大小以匹配预览中的项目数量
while (items.size() < params.size()) {
params.remove(params.size() - 1);//多了,删除
}
while (items.size() > params.size()) {
params.add(new PreviewItemDrawingParams(0, 0, 0));//少了,添加
}
int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW;
for (int i = 0; i < params.size(); i++) {
PreviewItemDrawingParams p = params.get(i);
setDrawable(p, items.get(i));
if (!animate) {
if (p.anim != null) {
p.anim.cancel();
}
computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
if (mReferenceDrawable == null) {
mReferenceDrawable = p.drawable;
}
} else {
FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i,
mNumOfPrevItems, i, numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION,
null);
if (p.anim != null) {
if (p.anim.hasEqualFinalState(anim)) {
// do nothing, let the current animation finish
continue;
}
p.anim.cancel();
}
p.anim = anim;
p.anim.start();
}
}
}
3.3.recomputePreviewDrawingParams
public void recomputePreviewDrawingParams() {
if (mReferenceDrawable != null) {
computePreviewDrawingParams(mReferenceDrawable.getIntrinsicWidth(),
mIcon.getMeasuredWidth());
}
}
>1.computePreviewDrawingParams
private void computePreviewDrawingParams(int drawableSize, int totalSize) {
//下边3种数据发生变化
if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize ||
mPrevTopPadding != mIcon.getPaddingTop()) {
mIntrinsicIconSize = drawableSize;
mTotalWidth = totalSize;
mPrevTopPadding = mIcon.getPaddingTop();
mIcon.mBackground.setup(mIcon.getContext(), mIcon.mActivity, mIcon, mTotalWidth,
mIcon.getPaddingTop());
mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize,
Utilities.isRtl(mIcon.getResources()));
updatePreviewItems(false);
}
}
3.4.draw
public void draw(Canvas canvas) {
int saveCount = canvas.getSaveCount();
// The items are drawn in coordinates relative to the preview offset
PreviewBackground bg = mIcon.getFolderBackground();
Path clipPath = bg.getClipPath();
float firstPageItemsTransX = 0;
//是否应该滑动到第一页:这个只有打开文件夹,滑动到非第一页,然后关闭文件夹的时候为true
if (mShouldSlideInFirstPage) {
PointF firstPageOffset = new PointF(bg.basePreviewOffsetX + mCurrentPageItemsTransX,
bg.basePreviewOffsetY);
boolean shouldClip = mCurrentPageItemsTransX > mClipThreshold;
drawParams(canvas, mCurrentPageParams, firstPageOffset, shouldClip, clipPath);
firstPageItemsTransX = -ITEM_SLIDE_IN_OUT_DISTANCE_PX + mCurrentPageItemsTransX;
}
PointF firstPageOffset = new PointF(bg.basePreviewOffsetX + firstPageItemsTransX,
bg.basePreviewOffsetY);
boolean shouldClipFirstPage = firstPageItemsTransX < -mClipThreshold;
//见补充1
drawParams(canvas, mFirstPageParams, firstPageOffset, shouldClipFirstPage, clipPath);
canvas.restoreToCount(saveCount);
}
>1.drawParams
public void drawParams(Canvas canvas, ArrayList<PreviewItemDrawingParams> params,
PointF offset, boolean shouldClipPath, Path clipPath) {
// The first item should be drawn last (ie. on top of later items)
for (int i = params.size() - 1; i >= 0; i--) {
PreviewItemDrawingParams p = params.get(i);
if (!p.hidden) {
// Exiting param should always be clipped.
boolean isExiting = p.index == EXIT_INDEX;
//见补充2
drawPreviewItem(canvas, p, offset, isExiting | shouldClipPath, clipPath);
}
}
}
>2.drawPreviewItem
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params, PointF offset,
boolean shouldClipPath, Path clipPath) {
canvas.save();
if (shouldClipPath) {
canvas.clipPath(clipPath);
}
canvas.translate(offset.x + params.transX, offset.y + params.transY);
canvas.scale(params.scale, params.scale);
Drawable d = params.drawable;
if (d != null) {
Rect bounds = d.getBounds();
canvas.save();
canvas.translate(-bounds.left, -bounds.top);
canvas.scale(mIntrinsicIconSize / bounds.width(), mIntrinsicIconSize / bounds.height());
d.draw(canvas);
canvas.restore();
}
canvas.restore();
}
3.5.效果图
对比两张图,可以看到,文件夹缩略图画的是左上角4个
>1.文件夹
>2.展开文件夹
4.Folder
- 继承的是自定义的容器,线性布局,这个就是点击folderIcon图标展开以后看到的东西
public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
4.1.fromXml
static <T extends Context & ActivityContext> Folder fromXml(T activityContext) {
return (Folder) LayoutInflater.from(activityContext).cloneInContext(activityContext)
.inflate(R.layout.user_folder_icon_normalized, null);
}
>1.user_folder_icon_normalized
<com.android.launcher3.folder.Folder
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<!--图标列表,可以翻页的-->
<com.android.launcher3.folder.FolderPagedView
android:id="@+id/folder_content"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:pageIndicator="@+id/folder_page_indicator" />
<!--一个可编译的文本框,显示文件夹的名字,以及一个游标显示上边pagedView的页面-->
<LinearLayout
android:id="@+id/folder_footer"
android:layout_width="match_parent"
android:layout_height="48dp"
android:clipChildren="false"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingRight="12dp" >
<com.android.launcher3.folder.FolderNameEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
style="@style/TextHeadline"
android:layout_weight="1"
android:background="@android:color/transparent"
android:gravity="center_horizontal"
android:hint="@string/folder_hint_text"
android:imeOptions="flagNoExtractUi"
android:singleLine="true"
android:textColor="?attr/folderTextColor"
android:textColorHighlight="?android:attr/colorControlHighlight"
android:textColorHint="?attr/folderHintColor"/>
<com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/folder_page_indicator"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
/>
</LinearLayout>
</com.android.launcher3.folder.Folder>
4.2.构造方法
public Folder(Context context, AttributeSet attrs) {
super(context, attrs);
setAlwaysDrawnWithCacheEnabled(false);
mActivityContext = ActivityContext.lookupContext(context);
mLauncherDelegate = LauncherDelegate.from(mActivityContext);
mStatsLogManager = StatsLogManager.newInstance(context);
setFocusableInTouchMode(true);
}
4.3.setFolderIcon
public void setFolderIcon(FolderIcon icon) {
mFolderIcon = icon;
//见4.4.1
mLauncherDelegate.init(this, icon);
}
4.4.LauncherDelegate.java
>1.init
void init(Folder folder, FolderIcon icon) {
folder.setDragController(mLauncher.getDragController());
icon.setOnFocusChangeListener(mLauncher.getFocusHandler());
}
4.5.bind
绑定数据
void bind(FolderInfo info) {
mInfo = info;
mFromTitle = info.title;
mFromLabelState = info.getFromLabelState();
ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);//补充1
BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
if (lp == null) {
lp = new BaseDragLayer.LayoutParams(0, 0);
lp.customPosition = true;
setLayoutParams(lp);
}
mItemsInvalidated = true;
mInfo.addListener(this);
if (!isEmpty(mInfo.title)) {
mFolderName.setText(mInfo.title);
mFolderName.setHint(null);
} else {
mFolderName.setText("");
mFolderName.setHint(R.string.folder_hint_text);
}
// In case any children didn't come across during loading, clean up the folder accordingly
mFolderIcon.post(() -> {
if (getItemCount() <= 1) {
replaceFolderWithFinalItem();
}
});
}
>1.updateItemLocationsInDatabaseBatch
private void updateItemLocationsInDatabaseBatch(boolean isBind) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
ArrayList<ItemInfo> items = new ArrayList<>();
int total = mInfo.contents.size();
for (int i = 0; i < total; i++) {
WorkspaceItemInfo itemInfo = mInfo.contents.get(i);
if (verifier.updateRankAndPos(itemInfo, i)) {
items.add(itemInfo);
}
}
if (!items.isEmpty()) {
mLauncherDelegate.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
}
if (!isBind && total > 1 /* no need to update if there's one icon */) {
Executors.MODEL_EXECUTOR.post(() -> {
FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
fnp.getSuggestedFolderName(
getContext(), mInfo.contents, nameInfos);
mInfo.suggestedFolderNames = nameInfos;
});
}
}
5.PreviewBackground.java
此对象表示FolderIcon预览背景。它存储绘图/测量*信息,处理绘图和动画(接受状态<- >休息状态)
5.1.drawBackground
public void drawBackground(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(getBgColor());
//shape见小节6,最终用的6.4的RoundedSquare
getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
//阴影默认不画,暂时不看了
drawShadow(canvas);
}
5.2.drawingDelegated
private CellLayout mDrawingDelegate;
boolean drawingDelegated() {
return mDrawingDelegate != null;
}
5.3.drawBackgroundStroke
public void drawBackgroundStroke(Canvas canvas) {
if (!DRAW_STROKE) {//默认是false的
return;
}
mPaint.setColor(setColorAlphaBound(mStrokeColor, mStrokeAlpha));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
float inset = 1f;
getShape().drawShape(canvas,
getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint);
}
5.4.setup
- availableSpaceX 就是FolderIcon的测量宽度
- topPadding 就是FolderIcon容器的padding
public void setup(Context context, ActivityContext activity, View invalidateDelegate,
int availableSpaceX, int topPadding) {
mInvalidateDelegate = invalidateDelegate;
TypedArray ta = context.getTheme().obtainStyledAttributes(R.styleable.FolderIconPreview);
mDotColor = ta.getColor(R.styleable.FolderIconPreview_folderDotColor, 0);
mStrokeColor = ta.getColor(R.styleable.FolderIconPreview_folderIconBorderColor, 0);
mBgColor = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0);
ta.recycle();
DeviceProfile grid = activity.getDeviceProfile();
//读取size
previewSize = grid.folderIconSizePx;
//容器宽减去预览图标的大小,除以2,为了居中显示
basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
//默认的padding加上预览图的偏移量
basePreviewOffsetY = topPadding + grid.folderIconOffsetYPx;
// Stroke width is 1dp
mStrokeWidth = context.getResources().getDisplayMetrics().density;
if (DRAW_SHADOW) {
float radius = getScaledRadius();
float shadowRadius = radius + mStrokeWidth;
int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
mShadowShader = new RadialGradient(0, 0, 1,
new int[]{shadowColor, Color.TRANSPARENT},
new float[]{radius / shadowRadius, 1},
Shader.TileMode.CLAMP);
}
invalidate();//见补充1,
}
>1.invalidate
void invalidate() {
if (mInvalidateDelegate != null) {
mInvalidateDelegate.invalidate();
}
if (mDrawingDelegate != null) {
mDrawingDelegate.invalidate();
}
}
5.5.drawLeaveBehind
这个就是FolderIcon点击展开Foler以后,自己显示的效果,可以看到,缩放了一半
public void drawLeaveBehind(Canvas canvas) {
float originalScale = mScale;
mScale = 0.5f;//缩放一半
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.argb(160, 245, 245, 245));
getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
mScale = originalScale;//数据恢复原本的
}
>1.getScaledRadius
//原本的半径是预览大小的一半
public int getRadius() {
return previewSize / 2;
}
//缩放后的半径
int getScaledRadius() {
return (int) (mScale * getRadius());
}
>2.getOffsetX/Y
- basePreviewOffsetX,原本的x方向偏移量
- 返回的是缩放后的图片偏移量
int getOffsetX() {
return basePreviewOffsetX - (getScaledRadius() - getRadius());
}
int getOffsetY() {
return basePreviewOffsetY - (getScaledRadius() - getRadius());
}
6.IconShape
6.1.getShape
- 我开始以为这个实例就是Circle了,可我们看到的明明是圆角矩形。
private static IconShape sInstance = new Circle();
private static float sNormalizationScale = ICON_VISIBLE_AREA_FACTOR;//0.92f
public static IconShape getShape() {
return sInstance;
}
>1.init
这个静态方法里会选择最合适的shape,也就是会修改上边的sInstance的值,最终选择的是圆角矩形
public static void init(Context context) {
pickBestShape(context);//见6.2
}
6.2.pickBestShape
- AdaptiveIconDrawable就是差不多的圆角矩形,所以这里最终取的也是RoundedSquare
protected static void pickBestShape(Context context) {
// Pick any large size
final int size = 200;
Region full = new Region(0, 0, size, size);
Region iconR = new Region();
//自适应的图片,以这个图片占用的区域作为参考
AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
drawable.setBounds(0, 0, size, size);
//设置路径,就是上边的AdaptiveIconDrawable,这个region和下边所有的shape进行对比
iconR.setPath(drawable.getIconMask(), full);
Path shapePath = new Path();
Region shapeR = new Region();
// Find the shape with minimum area of divergent region.
int minArea = Integer.MAX_VALUE;
IconShape closestShape = null;
for (IconShape shape : getAllShapes(context)) {//见6.3
shapePath.reset();
shape.addToPath(shapePath, 0, 0, size / 2f);
shapeR.setPath(shapePath, full);
//循环所有的shpae和上边设置的iconR取差集
shapeR.op(iconR, Op.XOR);
//看谁和iconR的差距最小就用谁。
int area = GraphicsUtils.getArea(shapeR);
if (area < minArea) {
minArea = area;
closestShape = shape;
}
}
if (closestShape != null) {
sInstance = closestShape;
}
// Initialize shape properties,打印了下是0.8094864
sNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
}
6.3.getAllShapes
private static List<IconShape> getAllShapes(Context context) {
ArrayList<IconShape> result = new ArrayList<>();
//配置文件见补充1
try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
// Find the root tag
int type;
while ((type = parser.next()) != XmlPullParser.END_TAG
&& type != XmlPullParser.END_DOCUMENT
&& !"shapes".equals(parser.getName()));
final int depth = parser.getDepth();
int[] radiusAttr = new int[] {R.attr.folderIconRadius};
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG) {
AttributeSet attrs = Xml.asAttributeSet(parser);
TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
IconShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
a.recycle();
result.add(shape);
}
}
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
return result;
}
>1.folder_shapes.xml
- 参数表示shape类型,以及圆角半径的百分比(后续和提供的半径相乘)
<shapes xmlns:launcher="http://schemas.android.com/apk/res-auto" >
<Circle launcher:folderIconRadius="1" />
<!-- Default icon for AOSP ,打印了下,官方源码里用的是这个-->
<RoundedSquare launcher:folderIconRadius="0.16" />
<!-- Rounded icon from RRO -->
<RoundedSquare launcher:folderIconRadius="0.6" />
<!-- Square icon -->
<RoundedSquare launcher:folderIconRadius="0" />
<TearDrop launcher:folderIconRadius="0.3" />
<Squircle launcher:folderIconRadius="0.2" />
</shapes>
>2.getShapeDefinition
private static IconShape getShapeDefinition(String type, float radius) {
switch (type) {
case "Circle":
return new Circle();
case "RoundedSquare":
return new RoundedSquare(radius);
case "TearDrop":
return new TearDrop(radius);
case "Squircle":
return new Squircle(radius);
default:
throw new IllegalArgumentException("Invalid shape type: " + type);
}
}
6.4.RoundedSquare
public static class RoundedSquare extends SimpleRectShape {
>1.drawShape
可以看到画的就是个圆角矩形
public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
float cx = radius + offsetX;
float cy = radius + offsetY;
float cr = radius * mRadiusRatio;
canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
}
>2.addToPath
添加的路径也是个圆角矩形
public void addToPath(Path path, float offsetX, float offsetY, float radius) {
float cx = radius + offsetX;
float cy = radius + offsetY;
float cr = radius * mRadiusRatio;
path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
Path.Direction.CW);
}