1.简介
按home键回到主页面以后,手指往上滑动屏幕就能看到一个展示所有app图标的页面,这里就看下这个页面相关的内容。
2.所有用到的布局
2.1.all_apps.xml
- 这个布局是桌面根布局launcher.xml里边include用到的
- LauncherAllAppsContainerView的父类是小节3
<!--自定义的相对布局-->
<com.android.launcher3.allapps.LauncherAllAppsContainerView
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="true"
android:clipToPadding="false"
android:focusable="false"
android:saveEnabled="false" >
2.2.all_apps_content.xml
LauncherAllAppsContainerView加载的布局就是这个,具体见小节3.2
<!-- This file is used by multiple all_apps.xml. Layout consists of all contents
showed in all apps screen
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<include
layout="@layout/all_apps_bottom_sheet_background"
android:visibility="gone" />
<include
layout="@layout/search_results_rv_layout"
android:visibility="gone" />
<include
layout="@layout/all_apps_rv_layout"
android:visibility="gone" />
<com.android.launcher3.allapps.FloatingHeaderView
android:id="@+id/all_apps_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:layout_below="@id/search_container_all_apps"
android:paddingTop="@dimen/all_apps_header_top_padding"
android:paddingBottom="@dimen/all_apps_header_bottom_padding"
android:orientation="vertical" >
<include layout="@layout/floating_header_content" />
<include layout="@layout/all_apps_personal_work_tabs" />
</com.android.launcher3.allapps.FloatingHeaderView>
<include layout="@layout/all_apps_fast_scroller" />
</merge>
>1.all_apps_bottom_sheet_background.xml
<FrameLayout
android:id="@+id/bottom_sheet_background"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/bottom_sheet_handle_area"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_handle_area_height" />
<View
android:id="@+id/bottom_sheet_handle"
android:layout_width="@dimen/bottom_sheet_handle_width"
android:layout_height="@dimen/bottom_sheet_handle_height"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/bottom_sheet_handle_margin"
android:layout_marginBottom="@dimen/bottom_sheet_handle_margin"
android:background="@drawable/bg_rounded_corner_bottom_sheet_handle" />
</FrameLayout>
>2.search_results_rv_layout.xml
<com.android.launcher3.allapps.SearchRecyclerView
android:id="@+id/search_results_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true" />
>3.all_apps_rv_layout
<com.android.launcher3.allapps.AllAppsRecyclerView
android:id="@+id/apps_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true" />
>4.floating_header_content
下图紫线画的2个就是这个布局里对应的2种view
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<com.android.launcher3.appprediction.PredictionRowView
android:id="@+id/prediction_row"
android:accessibilityPaneTitle="@string/title_app_suggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.android.launcher3.appprediction.AppsDividerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/apps_divider_view" />
</merge>
>5.all_apps_personal_work_tabs
这个正常情况是不可见的,只有在有工作应用的情况下才可见,如下图,点击按钮切换显示不同的app数据
<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_pill_height"
android:layout_gravity="center_horizontal"
android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
android:layout_marginTop="@dimen/all_apps_tabs_margin_top"
android:orientation="horizontal"
style="@style/TextHeadline"
launcher:alignOnIcon="true">
<Button
android:id="@+id/tab_personal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/all_apps_tabs_button_horizontal_padding"
android:layout_weight="1"
android:background="@drawable/all_apps_tabs_background"
android:text="@string/all_apps_personal_tab"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/tab_work"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/all_apps_tabs_button_horizontal_padding"
android:layout_weight="1"
android:background="@drawable/all_apps_tabs_background"
android:text="@string/all_apps_work_tab"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp"
style="?android:attr/borderlessButtonStyle" />
</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
>6.all_apps_fast_scroller
如下图,快速滑动条
<merge>
<!-- Fast scroller popup -->
<TextView
android:id="@+id/fast_scroller_popup"
style="@style/FastScrollerPopup"
android:layout_alignParentEnd="true"
android:layout_alignTop="@+id/all_apps_header"
android:layout_marginTop="@dimen/all_apps_header_bottom_padding"
android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
<com.android.launcher3.views.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
android:layout_width="@dimen/fastscroll_width"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignTop="@+id/all_apps_header"
android:layout_marginTop="@dimen/all_apps_header_bottom_padding"
android:layout_marginEnd="@dimen/fastscroll_end_margin"
launcher:canThumbDetach="true" />
</merge>
>7.all_apps_tabs.xml
这个是有work app数据的时候,work tab显示用这个布局,切换显示两种数据
<com.android.launcher3.allapps.AllAppsPagedView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/all_apps_tabs_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="@dimen/all_apps_header_pill_height"
android:clipChildren="true"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:paddingTop="@dimen/all_apps_paged_view_top_padding"
launcher:pageIndicator="@+id/tabs" >
<include layout="@layout/all_apps_rv_layout" />
<include layout="@layout/all_apps_rv_layout" />
</com.android.launcher3.allapps.AllAppsPagedView>
3.BaseAllAppsContainerView
自定义的相对布局
3.1.构造方法
public abstract class BaseAllAppsContainerView<T extends Context & ActivityContext>
extends SpringRelativeLayout implements DragSource, Insettable,
OnDeviceProfileChangeListener, OnActivePageChangedListener,
ScrimView.ScrimDrawingController {
protected BaseAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivityContext = ActivityContext.lookupContext(context);
mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mHeaderThreshold = getResources().getDimensionPixelSize(
R.dimen.dynamic_grid_cell_border_spacing);
mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
mWorkManager = new WorkProfileManager(
mActivityContext.getSystemService(UserManager.class),
this, LauncherPrefs.getPrefs(mActivityContext),
mActivityContext.getStatsLogManager());
mAH = Arrays.asList(null, null, null);
mNavBarScrimPaint = new Paint();
//补充1图片里的底部横条的颜色
mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
mAllAppsStore.addUpdateListener(this::onAppsUpdated);
mActivityContext.addOnDeviceProfileChangeListener(this);
setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus && getActiveRecyclerView() != null) {
getActiveRecyclerView().requestFocus();
}
});
initContent();
}
>1.底部横条
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mNavBarScrimHeight > 0) {
//画出来的
canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
mNavBarScrimPaint);
}
}
平板模式,底部有个灰色的半透明的横条,我这里测试改成红色了,如下图
3.2.initContent
main就是默认的显示所有app的adapter,search是搜索结果用的adapter,work是特殊的应用
protected void initContent() {
mMainAdapterProvider = createMainAdapterProvider();
//集合里添加数据
mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN));
mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
mAH.set(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
//这个自定义view里直接加载了一个布局,见2.2
getLayoutInflater().inflate(R.layout.all_apps_content, this);
mHeader = findViewById(R.id.all_apps_header);
mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
mSearchRecyclerView = findViewById(R.id.search_results_list_view);
// 搜索框的添加
mSearchContainer = inflateSearchBox();
插入header之后,容器是相对布局,等于默认显示在header上层
addView(mSearchContainer, indexOfChild(mHeader) + 1);
mSearchUiManager = (SearchUiManager) mSearchContainer;
}
>1.inflateSearchBox
布局见补充2
protected View inflateSearchBox() {
return getLayoutInflater().inflate(R.layout.search_container_all_apps, this, false);
}
>2.search_container_all_apps.xml
- 说明一下,如果集成了谷歌的GMS套件,那么这个布局可能被换掉的,用的就不是这个了。
- 见小节5,就是个自定义的文本编译框
<com.android.launcher3.allapps.search.AppsSearchContainerLayout
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:layout_centerHorizontal="true"
android:layout_gravity="top|center_horizontal"
android:background="@drawable/bg_all_apps_searchbox"
android:focusableInTouchMode="true"
android:gravity="center"
android:hint="@string/all_apps_search_bar_hint"
android:imeOptions="actionSearch|flagNoExtractUi"
android:importantForAutofill="no"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:padding="8dp"
android:saveEnabled="false"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp" />
>3.谷歌的search布局
这里贴下集成谷歌套件以后,用到的search_container_all_apps.xml布局,如果有要修改的地方,别找错地方。
<com.android.searchlauncher.HotseatQsbWidget
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
android:layout_height="@dimen/search_widget_hotseat_height"
android:layout_gravity="top|center_horizontal">
<fragment
android:id="@+id/search_wrapper_view"
android:name="com.android.searchlauncher.HotseatQsbWidget$HotseatQsbFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="qsb_view"/>
<com.android.launcher3.ExtendedEditText
android:id="@+id/fallback_search_view"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:visibility="invisible" />
</com.android.searchlauncher.HotseatQsbWidget>
3.3.onFinishInflate
protected void onFinishInflate() {
super.onFinishInflate();
//searchRV的数据过滤器固定为false,也就是无数据
mAH.get(AdapterHolder.SEARCH).setup(mSearchRecyclerView,
/* Filter out A-Z apps */ itemInfo -> false);
//见3.6重新绑定adapter数据
rebindAdapters(true /* force */);
float cornerRadius = Themes.getDialogCornerRadius(getContext());
mBottomSheetCornerRadii = new float[]{
cornerRadius,
cornerRadius, // Top left radius in px
cornerRadius,
cornerRadius, // Top right radius in px
0,
0, // Bottom right
0,
0 // Bottom left
};
final TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
mBottomSheetBackgroundColor = value.data;
//见补充1
updateBackground(mActivityContext.getDeviceProfile());
}
>1.updateBackground
-
mBottomSheetBackground平板的话才可见
-
drawOnScrim方法里用到
-
就是2.2.1布局里的根容器
protected void updateBackground(DeviceProfile deviceProfile) { mBottomSheetBackground.setVisibility(deviceProfile.isTablet ? View.VISIBLE : View.GONE); }
3.4.setOnIconLongClickListener
这个方法在taskbar点击9宫格弹出的那个allapps会调用,重新设置了长按事件,至于launcher3默认的那个(带search的),长按事件用的就是默认的,没有调用这个方法
public void setOnIconLongClickListener(OnLongClickListener listener) {
for (AdapterHolder holder : mAH) {
holder.mAdapter.setOnIconLongClickListener(listener);
}
}
3.5.setInsets
public void setInsets(Rect insets) {
mInsets.set(insets);
DeviceProfile grid = mActivityContext.getDeviceProfile();
//设置recyclerView的padding,见补充1
applyAdapterSideAndBottomPaddings(grid);
//左右margin,平板竖屏模拟器数据Rect(0, 48 - 0, 96),可以看到,左右marging是0
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.leftMargin = insets.left;
mlp.rightMargin = insets.right;
setLayoutParams(mlp);
if (grid.isVerticalBarLayout()) {
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
} else {
//平板,或者手机横屏走这里,平板模式,这里的值都不是0
int topPadding = grid.allAppsTopPadding;
if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() && !grid.isTablet) {
topPadding += getResources().getDimensionPixelSize(
R.dimen.all_apps_additional_top_padding_floating_search);
}
setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
}
InsettableFrameLayout.dispatchInsets(this, insets);
}
>1.applyAdapterSideAndBottomPaddings
private void applyAdapterSideAndBottomPaddings(DeviceProfile grid) {
int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
mAH.forEach(adapterHolder -> {
//设置底部和左右的padding
adapterHolder.mPadding.bottom = bottomPadding;
adapterHolder.mPadding.left =
adapterHolder.mPadding.right = grid.allAppsLeftRightPadding;
//见补充2
adapterHolder.applyPadding();
});
}
>2.applyPadding
应用补充1里设置的padding给rv
void applyPadding() {
if (mRecyclerView != null) {
int bottomOffset = 0;
if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
}
mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right,
mPadding.bottom + bottomOffset);
}
}
3.6.rebindAdapters
protected void rebindAdapters(boolean force) {
updateSearchResultsVisibility();
//是否应该显示tabs,也就是是否有work app数据
boolean showTabs = shouldShowTabs();
if (showTabs == mUsingTabs && !force) {
//非强制更新,showTabs无变化
return;
}
if (isSearching()) {
mUsingTabs = showTabs;
mWorkManager.detachWorkModeSwitch();
return;
}
//见补充1,根据是否显示tabs更新要用的布局
replaceAppsRVContainer(showTabs);
mUsingTabs = showTabs;
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
//显示work tab的情况
if (mUsingTabs) {
//重新设置AH绑定的rv,因为布局变了,这里用的是2.2.7里的2个rv
mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
//2.2.7里include的2个rv布局一样,这里得给另外一个换个id
mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
mWorkManager.newScrollListener());
}
//默认显示main
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
findViewById(R.id.tab_personal)
.setOnClickListener((View view) -> {
if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
//切换到main对应的rv
}
mActivityContext.hideKeyboard();
});
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> {
if (mViewPager.snapToPage(AdapterHolder.WORK)) {
//切换到work对应的rv
}
mActivityContext.hideKeyboard();
});
//给2个tab设置文字
setDeviceManagementResources();
onActivePageChanged(mViewPager.getNextPage());
} else {
//初始化rv
mAH.get(AdapterHolder.MAIN).setup(findViewById(R.id.apps_list_view), null);
mAH.get(AdapterHolder.WORK).mRecyclerView = null;
}
setupHeader();
//注册rv容器,主要是找到child更新dot或者安装进度
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
}
>1.replaceAppsRVContainer
protected View replaceAppsRVContainer(boolean showTabs) {
for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
AdapterHolder adapterHolder = mAH.get(i);
if (adapterHolder.mRecyclerView != null) {
adapterHolder.mRecyclerView.setLayoutManager(null);
adapterHolder.mRecyclerView.setAdapter(null);
}
}
//获取旧的容器,可能是2.2.7也可能是2.2.3
View oldView = getAppsRecyclerViewContainer();
int index = indexOfChild(oldView);
//移除旧的
removeView(oldView);
//显示tab的时候用布局2.2.7,里边有2个rv,不显示的时候用2.2.3里边就一个rv
int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
View newView = getLayoutInflater().inflate(layout, this, false);
//添加新的
addView(newView, index);
if (showTabs) {
//显示tab的时候用的2.2.7,根容器是AllAppsPagedView
mViewPager = (AllAppsPagedView) newView;
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
//重置数据
mWorkManager.reset();
post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
} else {
//
mWorkManager.detachWorkModeSwitch();
//不显示tab的时候,根容器是2.2.3,就是个rv,所以这个vp是空
mViewPager = null;
}
return newView;
}
>2.setupHeader
void setupHeader() {
mHeader.setVisibility(View.VISIBLE);
boolean tabsHidden = !mUsingTabs;
mHeader.setup(
mAH.get(AdapterHolder.MAIN).mRecyclerView,
mAH.get(AdapterHolder.WORK).mRecyclerView,
(SearchRecyclerView) mAH.get(AdapterHolder.SEARCH).mRecyclerView,
getCurrentPage(),
tabsHidden);
int padding = mHeader.getMaxTranslation();
mAH.forEach(adapterHolder -> {
adapterHolder.mPadding.top = padding;
adapterHolder.applyPadding();
if (adapterHolder.mRecyclerView != null) {
adapterHolder.mRecyclerView.scrollToTop();
}
});
}
>3.onActivePageChanged
public void onActivePageChanged(int currentActivePage) {
if (mAH.get(currentActivePage).mRecyclerView != null) {
mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar();
}
//更新当前活动的rv
mHeader.setActiveRV(currentActivePage);
reset(true /* animate */);
mWorkManager.onActivePageChanged(currentActivePage);
}
3.7.updateSearchResultsVisibility
searching的话显示search那个rv,非searching的话,显示正常的rv
protected void updateSearchResultsVisibility() {
if (isSearching()) {
getSearchRecyclerView().setVisibility(VISIBLE);
getAppsRecyclerViewContainer().setVisibility(GONE);
mHeader.setVisibility(GONE);
} else {
getSearchRecyclerView().setVisibility(GONE);
getAppsRecyclerViewContainer().setVisibility(VISIBLE);
mHeader.setVisibility(VISIBLE);
}
if (mHeader.isSetUp()) {
mHeader.setActiveRV(getCurrentPage());
}
}
3.8.drawOnScrim
public void drawOnScrim(Canvas canvas) {
boolean isTablet = mActivityContext.getDeviceProfile().isTablet;
// Draw full background panel for tablets.
if (isTablet) {
mHeaderPaint.setColor(mBottomSheetBackgroundColor);
//这个view是match parent的,具体布局见2.2
View panel = (View) mBottomSheetBackground;
//这个是手势上划的时候会变化
float translationY = ((View) panel.getParent()).getTranslationY();
mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY,
panel.getRight(), panel.getBottom());
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
}
//下边就是debug标志为ture的话,给header弄个颜色,并把背景画出来,否则下边就return了
if (DEBUG_HEADER_PROTECTION) {
mHeaderPaint.setColor(Color.MAGENTA);
mHeaderPaint.setAlpha(255);
} else {
mHeaderPaint.setColor(mHeaderColor);
mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
}
//header没有颜色或者和scrim颜色一样,啥也不干。
if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
return;
}
int bottom = getHeaderBottom() + getVisibleContainerView().getPaddingTop();
FloatingHeaderView headerView = getFloatingHeaderView();
if (isTablet) {
// Start adding header protection if search bar or tabs will attach to the top.
if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
View panel = (View) mBottomSheetBackground;
float translationY = ((View) panel.getParent()).getTranslationY();
mTmpRectF.set(panel.getLeft(), panel.getTop() + translationY, panel.getRight(),
bottom);
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
}
} else {
canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
}
int tabsHeight = headerView.getPeripheralProtectionHeight();
if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
//..
}
}
3.9.AdapterHolder
封装了rv和adapter,以及list的逻辑
AdapterHolder(int type) {
mType = type;
//
mAppsList = new AlphabeticalAppsList<>(mActivityContext,
isSearch() ? null : mAllAppsStore,
isWork() ? mWorkManager : null);
BaseAdapterProvider[] adapterProviders =
new BaseAdapterProvider[]{mMainAdapterProvider};
mAdapter = createAdapter(mAppsList, adapterProviders);
mAppsList.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
}
void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
mAppsList.updateItemFilter(matcher);
mRecyclerView = (AllAppsRecyclerView) rv;
mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
mRecyclerView.setApps(mAppsList);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
// No animations will occur when changes occur to the items in this RecyclerView.
mRecyclerView.setItemAnimator(null);
onInitializeRecyclerView(mRecyclerView);
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mRecyclerView);
mRecyclerView.addItemDecoration(focusedItemDecorator);
mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
applyPadding();
}
3.10.LauncherAllAppsContainerView
就重写了2个方法,没啥可看的,看父类
public class LauncherAllAppsContainerView extends ActivityAllAppsContainerView<Launcher>
//这个是在allapps页面,底部画的横条的高度,为了让导航栏按钮可见
protected int getNavBarScrimHeight(WindowInsets insets) {
if (Utilities.ATLEAST_Q) {
return insets.getTappableElementInsets().bottom;
} else {
return insets.getStableInsetBottom();
}
}
@Override
public boolean isInAllApps() {
return mActivityContext.getStateManager().isInStableState(LauncherState.ALL_APPS);
}
3.11.ActivityAllAppsContainerView
public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
extends BaseAllAppsContainerView<T> {
下边2个方法是在搜索框里调用的,具体见 5.3
>onClearSearchResult
public void onClearSearchResult() {
getMainAdapterProvider().clearHighlightedItem();
animateToSearchState(false);
rebindAdapters();
}
>setSearchResults
public void setSearchResults(ArrayList<AdapterItem> results) {
getMainAdapterProvider().clearHighlightedItem();
if (getSearchResultList().setSearchResults(results)) {
getSearchRecyclerView().onSearchResultsChanged();
}
if (results != null) {
animateToSearchState(true);
}
}
下边这个方法,会根据boolean值,来切换显示search和main
>animateToSearchState
private void animateToSearchState(boolean goingToSearch) {
animateToSearchState(goingToSearch, DEFAULT_SEARCH_TRANSITION_DURATION_MS);
}
//animateToSearchState
private void animateToSearchState(boolean goingToSearch, long durationMs) {
if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
return;
}
if (goingToSearch) {
// Fade out the button to pause work apps.
mWorkManager.onActivePageChanged(SEARCH);
}
mSearchTransitionController.animateToSearchState(goingToSearch, durationMs,
/* onEndRunnable = */ () -> {
//修改isSearching的值,后边会判断这个显示不同的rv
mIsSearching = goingToSearch;
updateSearchResultsVisibility();
int previousPage = getCurrentPage();
if (mRebindAdaptersAfterSearchAnimation) {
rebindAdapters(false);
mRebindAdaptersAfterSearchAnimation = false;
}
if (!goingToSearch) {
setSearchResults(null);
if (mViewPager != null) {
mViewPager.setCurrentPage(previousPage);
}
onActivePageChanged(previousPage);
}
});
}
>updateSearchResultsVisibility
根据是否在search状态,切换显示不同的rv
protected void updateSearchResultsVisibility() {
if (isSearching()) {
getSearchRecyclerView().setVisibility(VISIBLE);
getAppsRecyclerViewContainer().setVisibility(GONE);
mHeader.setVisibility(GONE);
} else {
getSearchRecyclerView().setVisibility(GONE);
getAppsRecyclerViewContainer().setVisibility(VISIBLE);
mHeader.setVisibility(VISIBLE);
}
if (mHeader.isSetUp()) {
mHeader.setActiveRV(getCurrentPage());
}
}
4.AllAppsStore
一个维护所有app信息的工具类,它并不获取数据,其他地方获取数据以后,会把数据设置给它,它再调用里边的回调
4.1.使用
//BaseAllAppsContainerView.java
private final AllAppsStore mAllAppsStore = new AllAppsStore();
>1.launcher.java
//更新dot信息,就是图标上那个未读消息数
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
}
//推迟更新数据标志的修改
if (!isInState(ALL_APPS)) {
//不在allapps页面,不更新数据, mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
pendingTasks.add(() -> mAppsView.getAppsStore().disableDeferUpdates(
AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
}
//这个是更新图标的安装进度条的
public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
mAppsView.getAppsStore().updateProgressBar(app);
}
5.AppsSearchContainerLayout.java
就是那个搜索框,自定义的editTextView
public class AppsSearchContainerLayout extends ExtendedEditText
implements SearchUiManager, SearchCallback<AdapterItem>,
AllAppsStore.OnUpdateListener, Insettable {
5.1. layout
//宽度是计算出来的,默认是match
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Update the width to match the grid padding
DeviceProfile dp = mLauncher.getDeviceProfile();
int myRequestedWidth = getSize(widthMeasureSpec);
int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
- mAppsView.getActiveRecyclerView().getPaddingRight();
int cellWidth = DeviceProfile.calculateCellWidth(rowWidth,
dp.cellLayoutBorderSpacePx.x, dp.numShownHotseatIcons);
int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
int iconPadding = cellWidth - iconVisibleSize;
int myWidth = rowWidth - iconPadding + getPaddingLeft() + getPaddingRight();
super.onMeasure(makeMeasureSpec(myWidth, EXACTLY), heightMeasureSpec);
}
//宽度改变了,这里根据容器宽度减去自己的宽度,计算下居中的话的left位置,平移一下,保证居中
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// Shift the widget horizontally so that its centered in the parent (b/63428078)
View parent = (View) getParent();
int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
int myWidth = right - left;
int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
int shift = expectedLeft - left;
setTranslationX(shift);
offsetTopAndBottom(mContentOverlap);
}
5.2.initializeSearch
public void initializeSearch(ActivityAllAppsContainerView<?> appsView) {
mAppsView = appsView;
mSearchBarController.initialize(
new DefaultAppSearchAlgorithm(getContext(), true),
this, mLauncher, this);
}
>AllAppsSearchBarController.java
可以看到,搜索框的各种监听都在这个类里处理了
public final void initialize(
SearchAlgorithm<AdapterItem> searchAlgorithm, ExtendedEditText input,
ActivityContext launcher, SearchCallback<AdapterItem> callback) {
mCallback = callback;
mLauncher = launcher;
mInput = input;
mInput.addTextChangedListener(this);
mInput.setOnEditorActionListener(this);
mInput.setOnBackKeyListener(this);
mInput.addOnFocusChangeListener(this);
mSearchAlgorithm = searchAlgorithm;
}
5.3.onSearchResult
下边看下搜索结果的处理,以及清除结果的处理,最终都交给mAppsView处理了
public void onSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
mAppsView.setSearchResults(items);
}
}
@Override
public void clearSearchResult() {
// Clear the search query
mSearchQueryBuilder.clear();
mSearchQueryBuilder.clearSpans();
Selection.setSelection(mSearchQueryBuilder, 0);
mAppsView.onClearSearchResult();
}
6.FloatingHeaderView.java
线性布局,2.2布局里用到
public class FloatingHeaderView extends LinearLayout implements
ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow>, Insettable,
OnHeightUpdatedListener {
6.1.onFinishInflate
- mAllRows包含所有类型是FloatingHeaderRow的child
protected void onFinishInflate() {
super.onFinishInflate();
//2.2.5布局容器
mTabLayout = findViewById(R.id.tabs);
// Find all floating header rows.
ArrayList<FloatingHeaderRow> rows = new ArrayList<>();
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
//参考补充1说明child类型
if (child instanceof FloatingHeaderRow) {
rows.add((FloatingHeaderRow) child);
}
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
mAllRows = mFixedRows;
//计算所有rows的总高度
updateFloatingRowsHeight();
}
>1.child类型
- PredictionRowView,推荐应用列表,实现FloatingHeaderRow
public class PredictionRowView<T extends Context & ActivityContext>
extends LinearLayout implements OnDeviceProfileChangeListener, FloatingHeaderRow {
- AppsDividerView,分割线或者lable,满足FloatingHeaderRow,参考小节7
public class AppsDividerView extends View implements FloatingHeaderRow {
- PersonalWorkSlidingTabStrip ,未实现FloatingHeaderRow,参考2.2以及2.2.5
public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageIndicator {
6.2.setup
void setup(AllAppsRecyclerView mainRV, AllAppsRecyclerView workRV, SearchRecyclerView searchRV,
int activeRV, boolean tabsHidden) {
for (FloatingHeaderRow row : mAllRows) {
//给所有的row设置数据
row.setup(this, mAllRows, tabsHidden);
}
//更新高度
updateExpectedHeight();
mTabsHidden = tabsHidden;
//tab布局是否可见
mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
mMainRV = mainRV;
mWorkRV = workRV;
mSearchRV = searchRV;
//根据rv类型获取当前用的rv是哪一个
setActiveRV(activeRV);
reset(false);
}
6.3.findFixedRowByType
根据class类型找到对应的child
public <T extends FloatingHeaderRow> T findFixedRowByType(Class<T> type) {
for (FloatingHeaderRow row : mAllRows) {
if (row.getTypeClass() == type) {
return (T) row;
}
}
return null;
}
6.4.touchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
calcOffset(mTempOffset);
ev.offsetLocation(mTempOffset.x, mTempOffset.y);
//交给当前活动的rv处理
mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
ev.offsetLocation(-mTempOffset.x, -mTempOffset.y);
return mForwardToRecyclerView || super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//需要rv处理
if (mForwardToRecyclerView) {
// take this view's and parent view's (view pager) location into account
calcOffset(mTempOffset);
event.offsetLocation(mTempOffset.x, mTempOffset.y);
try {//交个当前活动的rv处理
return mCurrentRV.onTouchEvent(event);
} finally {
event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
}
} else {
return super.onTouchEvent(event);
}
}
>1.calcOffset
计算自己left|top 和当前rv的left|top之间的差值
private void calcOffset(Point p) {
p.x = getLeft() - mCurrentRV.getLeft() - ((ViewGroup) mCurrentRV.getParent()).getLeft();
p.y = getTop() - mCurrentRV.getTop() - ((ViewGroup) mCurrentRV.getParent()).getTop();
}
6.5.onAttachedToWindow
protected void onAttachedToWindow() {
super.onAttachedToWindow();
//监听见补充1
PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(this,
AllAppsRow.class, true /* allowMultiple */);
}
>1.onPluginConnected
这样看来,这个容器的row还可以通过外部动态添加。
public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
//获取对象
PluginHeaderRow headerRow = new PluginHeaderRow(allAppsRowPlugin, this);
//添加到末尾
addView(headerRow.mView, indexOfChild(mTabLayout));
//放入集合
mPluginRows.put(allAppsRowPlugin, headerRow);
//重新设置allRows数据,见补充2
recreateAllRowsArray();
//外部row添加高度改变监听,好刷新这边容器的高度。
allAppsRowPlugin.setOnHeightUpdatedListener(this);
}
>2.recreateAllRowsArray
private void recreateAllRowsArray() {
int pluginCount = mPluginRows.size();
if (pluginCount == 0) {
//没有外部数据
mAllRows = mFixedRows;
} else {
int count = mFixedRows.length;
//加上外部数据
mAllRows = new FloatingHeaderRow[count + pluginCount];
for (int i = 0; i < count; i++) {
mAllRows[i] = mFixedRows[i];
}
for (PluginHeaderRow row : mPluginRows.values()) {
mAllRows[count] = row;
count++;
}
}
updateFloatingRowsHeight();
}
>3.待学习?
PluginManagerWrapper里的数据获取。
7.AppsDividerView
小节6.1里的child,可能是条线,也可能是个文本
7.1.构造方法
初始化尺寸,颜色,标签
public AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
mDividerSize = new int[]{
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_width),
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height)
};
mStrokeColor = ContextCompat.getColor(context, isMainColorDark
? R.color.all_apps_prediction_row_separator_dark
: R.color.all_apps_prediction_row_separator);
mAllAppsLabelTextColor = ContextCompat.getColor(context, isMainColorDark
? R.color.all_apps_label_text_dark
: R.color.all_apps_label_text);
OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext(
getContext()).getOnboardingPrefs();
mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount(
ALL_APPS_VISITED_COUNT);
}
>1.DividerType
3种类型,
public enum DividerType {
NONE,
LINE, //画线
ALL_APPS_LABEL //显示一个lable
}
7.2.setup
容器6.2里调用
- mTabsHidden ,有worksapp的时候是false,没有的话是true,
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
mParent = parent;
mTabsHidden = tabsHidden;
mRows = rows;
updateDividerType();//见7.3
}
7.3.updateDividerType
更新divider的类型
private void updateDividerType() {
final DividerType dividerType;
if (!mTabsHidden) {
//走到这里说明works tab是显示的状态,不需要这个个divider
dividerType = DividerType.NONE;
} else {
//查看下自己上边有几行
int sectionCount = 0;
for (FloatingHeaderRow row : mRows) {
if (row == this) {
break;
} else if (row.shouldDraw()) {
//有其他可见的row
sectionCount++;
}
}
//应该显示标签并且自己上边的row不为空
if (mShowAllAppsLabel && sectionCount > 0) {
dividerType = DividerType.ALL_APPS_LABEL;
} else if (sectionCount == 1) {
//不应该显示标签并且上边只有一个row
dividerType = DividerType.LINE;
} else {
//自己上边没有row,那么自己也不需要显示了
dividerType = DividerType.NONE;
}
}
if (mDividerType != dividerType) {
//divider类型发生变化,重新绘制
mDividerType = dividerType;
int topPadding;
int bottomPadding;
switch (dividerType) {
case LINE:
topPadding = 0;
bottomPadding = getResources()
.getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
mPaint.setColor(mStrokeColor);
break;
case ALL_APPS_LABEL:
topPadding = getAllAppsLabelLayout().getHeight() + getResources()
.getDimensionPixelSize(R.dimen.all_apps_label_top_padding);
bottomPadding = getResources()
.getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding);
mPaint.setColor(mAllAppsLabelTextColor);
break;
case NONE:
default:
topPadding = bottomPadding = 0;
break;
}
setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding);
updateViewVisibility();
invalidate();
requestLayout();
if (mParent != null) {
mParent.onHeightUpdated();
}
}
}
>1.setShowAllAppsLabel
设置是否需要显示 "all apps"文字标签。
- all apps页面显示20次以后就不再显示标签了。
- 具体逻辑见上篇小节15里的ALL_APPS_VISITED_COUNT事件的逻辑
public void setShowAllAppsLabel(boolean showAllAppsLabel) {
if (showAllAppsLabel != mShowAllAppsLabel) {
mShowAllAppsLabel = showAllAppsLabel;
updateDividerType();
}
}
>2.显示line效果图
不同于2.2.4里的图,如果不需要显示lable的时候,效果如下:
>3.updateViewVisibility
private void updateViewVisibility() {
setVisibility(mDividerType == DividerType.NONE
? GONE
: (mIsScrolledOut ? INVISIBLE : VISIBLE));
}
7.4.onDraw
protected void onDraw(Canvas canvas) {
//画线
if (mDividerType == DividerType.LINE) {
int l = (getWidth() - mDividerSize[0]) / 2;
int t = getHeight() - (getPaddingBottom() / 2);
int radius = mDividerSize[1];
canvas.drawRoundRect(l, t, l + mDividerSize[0], t + mDividerSize[1], radius, radius,
mPaint);
} else if (mDividerType == DividerType.ALL_APPS_LABEL) {
//画文字
Layout textLayout = getAllAppsLabelLayout();
int x = getWidth() / 2 - textLayout.getWidth() / 2;
int y = getHeight() - getPaddingBottom() - textLayout.getHeight();
canvas.translate(x, y);
textLayout.draw(canvas);
canvas.translate(-x, -y);
}
}
>1.getAllAppsLabelLayout
这里是创建一个显示文本的layout
private Layout getAllAppsLabelLayout() {
if (mAllAppsLabelLayout == null) {
mPaint.setAntiAlias(true);
mPaint.setTypeface(Typeface.create("google-sans", Typeface.NORMAL));
mPaint.setTextSize(
getResources().getDimensionPixelSize(R.dimen.all_apps_label_text_size));
CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label);
mAllAppsLabelLayout = StaticLayout.Builder.obtain(
allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
Math.round(mPaint.measureText(allAppsLabelText.toString())))
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.setMaxLines(1)
.setIncludePad(true)
.build();
}
return mAllAppsLabelLayout;
}
8.PredictionRowView
参考2.2.4的图,推荐应用所用的布局
public class PredictionRowView<T extends Context & ActivityContext>
extends LinearLayout implements OnDeviceProfileChangeListener, FloatingHeaderRow {
8.1.构造方法
public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.HORIZONTAL);
mFocusHelper = new SimpleFocusIndicatorHelper(this);
mActivityContext = ActivityContext.lookupContext(context);
mActivityContext.addOnDeviceProfileChangeListener(this);
mNumPredictedAppsPerRow = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
updateVisibility();
}
8.2.updateVisibility
private boolean mPredictionsEnabled = false;//默认不可见,拿到数据以后再次更新可见性
private void updateVisibility() {
//更新可见性
setVisibility(mPredictionsEnabled ? VISIBLE : GONE);
if (mActivityContext.getAppsView() != null) {
if (mPredictionsEnabled) {
//可用的话监听icon的变化
mActivityContext.getAppsView().getAppsStore().registerIconContainer(this);
} else {
mActivityContext.getAppsView().getAppsStore().unregisterIconContainer(this);
}
}
}
8.3.getExpectedHeight
获取期望的高度
public int getExpectedHeight() {
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
int iconHeight = deviceProfile.allAppsIconSizePx;
int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
int verticalPadding = getResources().getDimensionPixelSize(
R.dimen.all_apps_predicted_icon_vertical_padding);
int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
MeasureSpec.EXACTLY));
}
8.4.setPredictedApps
数据来源是外部
public void setPredictedApps(List<ItemInfo> items) {
if (!FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
&& !mActivityContext.isBindingItems()
&& isShown()
&& getWindowVisibility() == View.VISIBLE) {
mPendingPredictedItems = items;
return;
}
applyPredictedApps(items);
}
private void applyPredictedApps(List<ItemInfo> items) {
mPendingPredictedItems = null;
mPredictedApps.clear();
mPredictedApps.addAll(items.stream()
//过滤WorkspaceItemInfo并转化
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
.map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
applyPredictionApps();
}
8.5.applyPredictionApps
private void applyPredictionApps() {
//每行显示的个数与当前的child个数不一样
if (getChildCount() != mNumPredictedAppsPerRow) {
//child个数多,移除
while (getChildCount() > mNumPredictedAppsPerRow) {
removeViewAt(0);
}
LayoutInflater inflater = mActivityContext.getAppsView().getLayoutInflater();
//child个数少,循环添加足够的child
while (getChildCount() < mNumPredictedAppsPerRow) {
BubbleTextView icon = (BubbleTextView) inflater.inflate(
R.layout.all_apps_icon, this, false);
icon.setOnClickListener(mActivityContext.getItemOnClickListener());
icon.setOnLongClickListener(mOnIconLongClickListener);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mFocusHelper);
LayoutParams lp = (LayoutParams) icon.getLayoutParams();
// Ensure the all apps icon height matches the workspace icons in portrait mode.
lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
lp.width = 0;
lp.weight = 1;
addView(icon);
}
}
//数据的个数
int predictionCount = mPredictedApps.size();
for (int i = 0; i < getChildCount(); i++) {
BubbleTextView icon = (BubbleTextView) getChildAt(i);
icon.reset();
//给child设置数据,并更新可见性
if (predictionCount > i) {
icon.setVisibility(View.VISIBLE);
icon.applyFromWorkspaceItem(mPredictedApps.get(i));
} else {
icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
}
}
//数据大于0,这个控件才可见,否则不可见
boolean predictionsEnabled = predictionCount > 0;
if (predictionsEnabled != mPredictionsEnabled) {
mPredictionsEnabled = predictionsEnabled;
updateVisibility();
}
//刷新父容器的高度
mParent.onHeightUpdated();
}
8.6.onDeviceProfileChanged
当设备配置发生变化的时候,移除所有child,重新加载数据
public void onDeviceProfileChanged(DeviceProfile dp) {
mNumPredictedAppsPerRow = dp.numShownAllAppsColumns;
removeAllViews();
applyPredictionApps();
}
下边学习下推荐应用数据哪里来的。
9.QuickstepModelDelegate
老代码可能是在ModelDelegate里,新代码是重写了,继承的ModelDelegate。
public class QuickstepModelDelegate extends ModelDelegate {
9.1.变量
final PredictorState mAllAppsState =
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
@VisibleForTesting
final PredictorState mHotseatState =
new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
@VisibleForTesting
final PredictorState mWidgetsRecommendationState =
new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
9.2.workspaceLoadComplete
LoaderTask里获取数据的时候会调用
public void workspaceLoadComplete() {
super.workspaceLoadComplete();
recreatePredictors();//见小节9.3
}
9.3.recreatePredictors
重新获取推荐应用。
private void recreatePredictors() {
destroyPredictors();//清空旧数据
if (!mActive) {
return;
}
Context context = mApp.getContext();
//数据是通过这个管理器获取的
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
//补充1,注册监听,这个是allApps页面的数据
registerPredictor(mAllAppsState, apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("home")
.setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
.build()));
//这个是hotseat的数据
registerHotseatPredictor(apm, context);
registerWidgetsPredictor(apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("widgets")
.setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
.build()));
}
>1.registerPredictor
private void registerPredictor(PredictorState state, AppPredictor predictor) {
state.setTargets(Collections.emptyList());//清除旧数据
state.predictor = predictor;
//注册推荐应用数据的变化
state.predictor.registerPredictionUpdates(
MODEL_EXECUTOR, t -> handleUpdate(state, t));//补充2,更新数据
state.predictor.requestPredictionUpdate();
}
>2.handleUpdate
task里最终交给BgDataModel.Callbacks回调方法bindExtraContainerItems处理了,直接搜哪里实现了这个方法即可。见9.4
private void handleUpdate(PredictorState state, List<AppTarget> targets) {
if (state.setTargets(targets)) {
// No diff, skip
return;
}
mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
}
9.4.bindExtraContainerItems
>1.launcher.java里
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(
PredictionRowView.class);
//见8.4
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
//hotseat是先交给controller里了
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getPopupDataProvider().setRecommendedWidgets(item.items);
}
}
>2.TaskbarModelCallbacks
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
//taskbar里的hotseat推荐应用数据
mPredictedItems = item.items;
commitItemsToUI();
} else if (item.containerId == Favorites.CONTAINER_PREDICTION) {
//这个是taskbar那个九宫格点击后弹的allapps页面的推荐应用
mControllers.taskbarAllAppsController.setPredictedApps(item.items);
}
}
10.总结
- allapps页面的布局学习
- 自定义容器LauncherAllAppsContainerView的学习,其他相关的child都是加载在这个相对布局里的
- 主要看下平板模式左右间距的设置,背景如何画的,底部横条如何画的。
- 推荐用用数据的获取。