android framework13-launcher3【03allapps】

662 阅读16分钟

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 image.png

<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数据

image.png

<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

如下图,快速滑动条 image.png

<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);
        }
    }

平板模式,底部有个灰色的半透明的横条,我这里测试改成红色了,如下图

image.png

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

image.png

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"文字标签。

    public void setShowAllAppsLabel(boolean showAllAppsLabel) {
        if (showAllAppsLabel != mShowAllAppsLabel) {
            mShowAllAppsLabel = showAllAppsLabel;
            updateDividerType();
        }
    }

>2.显示line效果图

不同于2.2.4里的图,如果不需要显示lable的时候,效果如下:

image.png

>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都是加载在这个相对布局里的
  • 主要看下平板模式左右间距的设置,背景如何画的,底部横条如何画的。
  • 推荐用用数据的获取。