android framework13-launcher3【04taskbar】

1,448 阅读25分钟

1.简介

这里是讲的是给平板用的导航栏,手机模式的话不会用到。

  • home页,是和hotseat平行显示的

image.png

  • 其他页面都是在底部的

image.png

  • 打开app的情况,有个9宫格,hotseat图标,导航图标

image.png

2. 代码流程相关

2.1.QuickstepLauncher.java

protected void setupViews() {
//
        mTISBindHelper = new TISBindHelper(this, this::onTISConnected);

2.2.TISBindHelper.java

    public TISBindHelper(Context context, Consumer<TISBinder> connectionCallback) {
        mContext = context;
        mConnectionCallback = connectionCallback;
        internalBindToTIS();
    }
    
private void internalBindToTIS() {
//绑定服务,最后的flag是0说明不会自动启动服务,那就说明有别的地方启动了服务,见2.6补充说明
    mTisServiceBound = mContext.bindService(new Intent(mContext, TouchInteractionService.class),
            this, 0);
    if (mTisServiceBound) {
        resetServiceBindRetryState();
        return;
    }

    final long timeoutMs = (long) Math.min(
            Math.scalb(BACKOFF_MILLIS, mConnectionAttempts), MAX_BACKOFF_MILLIS);
    mHandler.postDelayed(mConnectionRunnable, timeoutMs);
    mConnectionAttempts++;
}

2.3.TouchInteractionService.java

    public void onCreate() {
        super.onCreate();
//...
        mTaskbarManager = new TaskbarManager(this);
//...
    }

2.4.TaskbarManager

构造方法以及其他配置改变的回调里会调用recreateTaskBar方法

    public TaskbarManager(TouchInteractionService service) {
    //...
    recreateTaskbar();
}

>recreateTaskbar

    public void recreateTaskbar() {
        DeviceProfile dp = mUserUnlocked ?
                LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;

        destroyExistingTaskbar();
        //判断taskbar是否可用,
        boolean isTaskBarEnabled = dp != null && isTaskbarPresent(dp);
        if (!isTaskBarEnabled) {
            SystemUiProxy.INSTANCE.get(mContext)
                    .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
            return;
        }
        //走到这里说明是taskbar模式了
        if (mTaskbarActivityContext == null) {
            mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
                    mUnfoldProgressProvider);
        } else {
            mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
        }
        mTaskbarActivityContext.init(mSharedState);

        if (mActivity != null) {
            mTaskbarActivityContext.setUIController(
                    createTaskbarUIControllerForActivity(mActivity));
        }
    }

2.5.TaskbarActivityContext

    public TaskbarActivityContext(Context windowContext, DeviceProfile launcherDp,
            TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
            unfoldTransitionProgressProvider) {
        super(windowContext);
        final Resources resources = getResources();

        matchDeviceProfile(launcherDp, getResources());

        mNavMode = DisplayController.getNavigationMode(windowContext);
        mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                () -> getPackageManager().isSafeMode());
        SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
        mIsUserSetupComplete = settingsCache.getValue(
                Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
        mIsNavBarForceVisible = settingsCache.getValue(
                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);

        mIsNavBarKidsMode = settingsCache.getValue(
                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
    //...
        // 这里加载布局了
        int taskbarLayout = DisplayController.isTransientTaskbar(this)
                ? R.layout.transient_taskbar
                : R.layout.taskbar;
        mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
        TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
        TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
        FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
        StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);

        mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);

        final boolean isDesktopMode = getPackageManager().hasSystemFeature(FEATURE_PC);
    //可以看到,上边的view在下边都能找到对应的Controller来处理。
        // Construct controllers.
        mControllers = new TaskbarControllers(this,
                new TaskbarDragController(this),
                buttonController,
                isDesktopMode
                        ? new DesktopNavbarButtonsViewController(this, navButtonsView)
                        : new NavbarButtonsViewController(this, navButtonsView),
                new RotationButtonController(this,
                        c.getColor(R.color.taskbar_nav_icon_light_color),
                        c.getColor(R.color.taskbar_nav_icon_dark_color),
                        R.drawable.ic_sysbar_rotate_button_ccw_start_0,
                        R.drawable.ic_sysbar_rotate_button_ccw_start_90,
                        R.drawable.ic_sysbar_rotate_button_cw_start_0,
                        R.drawable.ic_sysbar_rotate_button_cw_start_90,
                        () -> getDisplay().getRotation()),
                new TaskbarDragLayerController(this, mDragLayer),
                new TaskbarViewController(this, taskbarView),
                new TaskbarScrimViewController(this, taskbarScrimView),
                new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
                    mWindowManager,
                    new RotationChangeProvider(WindowManagerGlobal.getWindowManagerService(), this,
                        getMainExecutor())),
                new TaskbarKeyguardController(this),
                new StashedHandleViewController(this, stashedHandleView),
                new TaskbarStashController(this),
                new TaskbarEduController(this),
                new TaskbarAutohideSuspendController(this),
                new TaskbarPopupController(this),
                new TaskbarForceVisibleImmersiveController(this),
                new TaskbarOverlayController(this, launcherDp),
                new TaskbarAllAppsController(),
                new TaskbarInsetsController(this),
                new VoiceInteractionWindowController(this),
                new TaskbarTranslationController(this),
                isDesktopMode
                        ? new DesktopTaskbarRecentAppsController(this)
                        : TaskbarRecentAppsController.DEFAULT);
    }
    //可以看到,是通过windowManger添加的
    public void init(@NonNull TaskbarSharedState sharedState) {
        mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
        mWindowLayoutParams = createDefaultWindowLayoutParams();

        // Initialize controllers after all are constructed.
        mControllers.init(sharedState);
        updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);

        if (!mAddedWindow) {
            mWindowManager.addView(mDragLayer, mWindowLayoutParams);
            mAddedWindow = true;
        } else {
            mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
        }
    }

>1.onTaskbarIconClicked

    protected void onTaskbarIconClicked(View view) {
        Object tag = view.getTag();
        if (tag instanceof Task) {
            Task task = (Task) tag;
            ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
                    ActivityOptions.makeBasic());
            mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
        } else if (tag instanceof FolderInfo) {
        //...点击文件夹会打开,taskbar高度为match,具体见图1
            setTaskbarWindowFullscreen(true);
        //...
        } else if (tag instanceof WorkspaceItemInfo) {
        //...底部导航栏上的普通的icon
        } else if (tag instanceof AppInfo) {
        //...
        } else if (tag instanceof ItemClickProxy) {
            ((ItemClickProxy) tag).onItemClicked(view);
        } else {
            Log.e(TAG, "Unknown type clicked: " + tag);
        }

        AbstractFloatingView.closeAllOpenViews(this);
    }

下图对应上边的FolderInfo image.png 下图就是底部导航栏的默认情况,点击快捷图标或文件夹点开里边的图标,走的是上边的WorkspaceItemInfo image.png

2.6.OverviewProxyService.java

2.3 小节里的服务是在这个服务里启动的

>1.internalConnectToCurrentUser

  • 在这个方法里通过action启动的TouchInteractionService
  • 见2.7 方法被调用2次,所以服务启动了2次
    private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";

    private void internalConnectToCurrentUser() {
        //关闭已打开的服务
        disconnectFromLauncherService();

        // If user has not setup yet or already connected, do not try to connect
        if (!isEnabled()) {
            Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled());
            return;
        }
        mHandler.removeCallbacks(mConnectionRunnable);
        Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
                .setPackage(mRecentsComponentName.getPackageName());
        try {
        //这里flag是auto_create所以会startservice
            mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                    mOverviewServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.of(mUserTracker.getUserId()));
        } catch (SecurityException e) {
            Log.e(TAG_OPS, "Unable to bind because of security error", e);
        }
        if (mBound) {
            // Ensure that connection has been established even if it thinks it is bound
            //延迟5秒执行runnable,确保服务bind成功,如果没有就重新执行
            mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
        } else {
            // Retry after exponential backoff timeout
            retryConnectionWithBackoff();
        }
    }

>2.服务的清单文件

在launcher3的清单文件里

        <service android:name="com.android.quickstep.TouchInteractionService"
             android:permission="android.permission.STATUS_BAR_SERVICE"
             android:directBootAware="true"
             android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.QUICKSTEP_SERVICE"/>
            </intent-filter>
        </service>

2.7.internalConnectToCurrentUser方法哪里被调用

>1.构造方法

对象是注解生成的,它的构造方法里会调用上述方法启动服务,至于这个对象用的地方比较多,就不研究了。

@SysUISingleton
public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
//..
    @Inject
    public OverviewProxyService(Context context,

//贴下日志,简单整理下

  • systemui启动的时候,SystemUIApplication里会启动一堆继承CoreStartable的类
  • OverviewProxyRecentsImpl的构造方法里需要OverviewProxyService对象,都是注解生成的
  • 其他地方又需要OverviewProxyRecentsImpl这个对象
  • 一层层的注解生成的对象太吓人了,具体哪个类先用的不重要了。
08:58:18.329 com.android.systemui    
 java.lang.Exception
	at com.android.systemui.recents.OverviewProxyService.internalConnectToCurrentUser(OverviewProxyService.java:19)
	at com.android.systemui.recents.OverviewProxyService.startConnectionToCurrentUser(OverviewProxyService.java:19)
	at com.android.systemui.recents.OverviewProxyService.<init>(OverviewProxyService.java:319)
	at com.android.systemui.recents.OverviewProxyService_Factory.get(OverviewProxyService_Factory.java:137)
	at dagger.internal.DoubleCheck.get(DoubleCheck.java:14)
	at dagger.internal.DelegateFactory.get(DelegateFactory.java:5)
	at com.android.systemui.recents.OverviewProxyRecentsImpl_Factory.get(OverviewProxyRecentsImpl_Factory.java:9)
	at dagger.internal.DoubleCheck.get(DoubleCheck.java:14)
	at com.android.systemui.dagger.ContextComponentResolver.resolve(ContextComponentResolver.java:14)
	at com.android.systemui.dagger.ContextComponentResolver.resolveRecents(ContextComponentResolver.java:3)
	at com.android.systemui.recents.RecentsModule_ProvideRecentsImplFactory.get(RecentsModule_ProvideRecentsImplFactory.java:32)
	at com.android.systemui.dagger.ReferenceSystemUIModule_ProvideRecentsFactory.get(ReferenceSystemUIModule_ProvideRecentsFactory.java:11)
	at dagger.internal.DoubleCheck.get(DoubleCheck.java:14)
	at com.android.systemui.dagger.DaggerReferenceGlobalRootComponent$PresentJdkOptionalInstanceProvider.get(DaggerReferenceGlobalRootComponent.java:2)
	at com.android.systemui.dagger.DaggerReferenceGlobalRootComponent$PresentJdkOptionalInstanceProvider.get(DaggerReferenceGlobalRootComponent.java:1)
	at com.android.systemui.accessibility.SystemActions_Factory.get(SystemActions_Factory.java:36)
	at dagger.internal.DoubleCheck.get(DoubleCheck.java:14)
	at com.android.systemui.SystemUIApplication$$ExternalSyntheticLambda2.run(R8$$SyntheticClass:47)
	at com.android.systemui.SystemUIApplication.timeInitialization(SystemUIApplication.java:28)
	at com.android.systemui.SystemUIApplication.startServicesIfNeeded(SystemUIApplication.java:20)
	at com.android.systemui.SystemUIApplication.startServicesIfNeeded(SystemUIApplication.java:6)
	at com.android.systemui.SystemUIService.onCreate(SystemUIService.java:10)
	at android.app.ActivityThread.handleCreateService(ActivityThread.java:4499)
	at android.app.ActivityThread.-$$Nest$mhandleCreateService(Unknown Source:0)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2165)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:7920)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)

>2.NotificationLockscreenUserManagerImpl

这里监听广播调用对应的方法,第2次调用了

                case Intent.ACTION_USER_UNLOCKED:
                    // Start the overview connection to the launcher service
                    Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
                    break;

3. layout

3.1.taskbar.xml

#2.5用到

<com.android.launcher3.taskbar.TaskbarDragLayer
    android:id="@+id/taskbar_container"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clipChildren="false">

    <com.android.launcher3.taskbar.TaskbarView
        android:id="@+id/taskbar_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:forceHasOverlappingRendering="false"
        android:layout_gravity="bottom"
        android:clipChildren="false" />

    <com.android.launcher3.taskbar.TaskbarScrimView
        android:id="@+id/taskbar_scrim"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:id="@+id/navbuttons_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" >

        <FrameLayout
            android:id="@+id/start_contextual_buttons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingStart="@dimen/taskbar_contextual_button_padding"
            android:paddingEnd="@dimen/taskbar_contextual_button_padding"
            android:paddingTop="@dimen/taskbar_contextual_padding_top"
            android:gravity="center_vertical"
            android:layout_gravity="start"/>

        <LinearLayout
            android:id="@+id/end_nav_buttons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:gravity="center_vertical"
            android:layout_gravity="end"/>

        <FrameLayout
            android:id="@+id/end_contextual_buttons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingTop="@dimen/taskbar_contextual_padding_top"
            android:gravity="center_vertical"
            android:layout_gravity="end"/>
    </FrameLayout>

    <com.android.launcher3.taskbar.StashedHandleView
        android:id="@+id/stashed_handle"
        tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/taskbar_stashed_handle_dark_color"
        android:clipToOutline="true"
        android:layout_gravity="bottom"/>

</com.android.launcher3.taskbar.TaskbarDragLayer>

>id:start_contextual_buttons

帧布局,显示在左侧,看下里边都可能添加啥东西?NavbarButtonsViewController.java

mIsImeRenderingNavButtons:控制IME是否呈现后退和IME切换按钮,可以理解为输入法是否自己控制后退,切换输入法功能,如果没有的话,我们导航栏上就会加个按钮用来切换输入法

配置里读取,默认false

//三个按钮添加

        if (!mIsImeRenderingNavButtons) {
            // IME switcher
            View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
                    isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
                    mControllers.navButtonController, R.id.ime_switcher);

//非3个导航按钮的情况

boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
    if (alwaysShowButtons) {
    } else {
  
        if (!mIsImeRenderingNavButtons) {
            View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
                    mStartContextualContainer, mControllers.navButtonController, R.id.back);
            imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);

>id:end_contextual_buttons

帧布局,显示在右侧,

//第一个地方 上边贴的,非3个按钮的话,ime的添加

    if (alwaysShowButtons) {
            // Rotation button
            RotationButton rotationButton = new RotationButtonImpl(
                    addButton(mEndContextualContainer, R.id.rotate_suggestion,
                            R.layout.taskbar_contextual_button));
            rotationButton.hide();
            mControllers.rotationButtonController.setRotationButton(rotationButton, null);

//第二个地方,旋转按钮

//第三个,无障碍模式

        // A11y button
        mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
                endContainer, navButtonController, R.id.accessibility_button,
                R.layout.taskbar_contextual_button);

>id:end_nav_buttons

线性布局,end

这个就是back,home,recents 3个按钮添加的容器。

3.2.taskbar_nav_button.xml

back,home,recent 按钮用到的

<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="@dimen/taskbar_nav_buttons_size"
    android:layout_height="@dimen/taskbar_nav_buttons_size"
    android:background="@drawable/taskbar_icon_click_feedback_roundrect"
    android:scaleType="center"
    android:tint="@color/taskbar_nav_icon_light_color"
    tools:ignore="UseAppTint" />

3.3.taskbar_all_apps_button.xml

导航栏底部那个9宫格图标

<com.android.launcher3.views.IconButtonView
    android:layout_width="@dimen/taskbar_icon_min_touch_size"
    android:layout_height="@dimen/taskbar_icon_min_touch_size"
    android:contentDescription="@string/all_apps_button_label"
    android:backgroundTint="@android:color/transparent"
    android:icon="@drawable/ic_all_apps_button"
    />

3.4.taskbar_all_apps.xml

点击导航栏那个9宫格图标,弹出的allapps页面用到的布局

<com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:accessibilityPaneTitle="@string/all_apps_label">

    <com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView
        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" />
</com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView>

4.TaskbarView

  • 绘制9宫格以及hotseat按钮,只有打开某个app的时候,才会在底部出现,其他时候隐藏的。
  • 托管任务栏内容,如Hotseat和最近的应用程序。绘制在其他应用程序的顶部
    public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
    //...

4.1 构造方法

根据配置决定是否加载9宫格图标

        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
            mAllAppsButton = LayoutInflater.from(context)
                    .inflate(R.layout.taskbar_all_apps_button, this, false);
            mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
            mAllAppsButton.setScaleX(mIsRtl ? -1 : 1);
            if (mActivityContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) {
                mAllAppsButton.setVisibility(GONE);
            }
        }

4.2.点击/长按事件

protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
    mControllerCallbacks = callbacks;
    mIconClickListener = mControllerCallbacks.getIconOnClickListener();
    //见补充1
    mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();

    setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());

    if (mAllAppsButton != null) {
        mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
    }
}

>1.图标长按事件

          public View.OnLongClickListener getIconOnLongClickListener() {
          //具体参考4.6
              return mControllers.taskbarDragController::startDragOnLongClick;
          }

>2.九宫格点击事件

        public View.OnClickListener getAllAppsButtonClickListener() {
            return v -> {
                mControllers.taskbarAllAppsController.show();
            };
        }

//TaskbarAllAppsController.java

    public void show() {
        show(true);
    }

    private void show(boolean animate) {
            //..
                //加载布局
        mSlideInView = (TaskbarAllAppsSlideInView) overlayContext.getLayoutInflater().inflate(
                R.layout.taskbar_all_apps, overlayContext.getDragLayer(), false);
        mSlideInView.addOnCloseListener(() -> {
        //..
        });
        TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
                overlayContext, mSlideInView, mControllers);
        //这里会加入到容器里
        viewController.show(animate);
        //..
    }

//下边简单贴下相关的代码

    void show(boolean animate) {
        mSlideInView.show(animate);
    }
    //...
     void show(boolean animate) {
        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
            return;
        }
        mIsOpen = true;
        //这个添加到容器里
        attachToContainer();
    }        
//
protected void attachToContainer() {
    if (mColorScrim != null) {
        getPopupContainer().addView(mColorScrim);
    }
    getPopupContainer().addView(this);
}    
//
protected BaseDragLayer getPopupContainer() {
    return mActivityContext.getDragLayer();
}    

4.3.updateHotseatItems

      protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
        //...
        for (int i = 0; i < hotseatItemInfos.length; i++) {
            ItemInfo hotseatItemInfo = hotseatItemInfos[i];
            //获取对应类型的布局
            if (hotseatItemInfo.isPredictedItem()) {
                expectedLayoutResId = R.layout.taskbar_predicted_app_icon;//补充1
            } else if (hotseatItemInfo instanceof FolderInfo) {
                expectedLayoutResId = R.layout.folder_icon;//补充2
                isFolder = true;
            } else {
                expectedLayoutResId = R.layout.taskbar_app_icon;//补充3
            }

            View hotseatView = null;
            while (nextViewIndex < getChildCount()) {
                hotseatView = getChildAt(nextViewIndex);

                // see if the view can be reused
            if (hotseatView == null) {
                if (isFolder) {
                    FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                    FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
                            mActivityContext, this, folderInfo);
                    folderIcon.setTextVisible(false);
                    hotseatView = folderIcon;
                } else {
                    hotseatView = inflate(expectedLayoutResId);
                }
                LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
                hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
                //添加hotseat图标到容器里
                addView(hotseatView, nextViewIndex, lp);
            }

            //设置点击长按事件
            setClickAndLongClickListenersForIcon(hotseatView);
            nextViewIndex++;
        }
        // Remove remaining views
        while (nextViewIndex < getChildCount()) {
            removeAndRecycle(getChildAt(nextViewIndex));
        }
        //添加9宫格图标
        if (mAllAppsButton != null) {
            int index = mIsRtl ? getChildCount() : 0;
            addView(mAllAppsButton, index);
        }
        if (mActivityContext.getDeviceProfile().isQsbInline) {
            addView(mQsb, mIsRtl ? getChildCount() : 0);
            // Always set QSB to invisible after re-adding.
            mQsb.setVisibility(View.INVISIBLE);
        }

        mThemeIconsBackground = calculateThemeIconsBackground();
        //给所有图标染色
        setThemedIconsBackgroundColor(mThemeIconsBackground);
    }  

>1.taskbar_predicted_app_icon.xml

  • PredictedAppIcon继承的也是BubbleTextView
<com.android.launcher3.uioverrides.PredictedAppIcon style="@style/BaseIcon.Workspace.Taskbar" />
  • 不同的iconDisplay,有不同的size,具体看iconDisplay
    <style name="BaseIcon.Workspace.Taskbar" >
         <item name="iconDisplay">taskbar</item>
     </style>

>2.folder_icon.xml

<com.android.launcher3.folder.FolderIcon
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:focusable="true" >
    <com.android.launcher3.views.DoubleShadowBubbleTextView
        style="@style/BaseIcon.Workspace"
        android:id="@+id/folder_icon_name"
        android:focusable="false"
        android:layout_gravity="top"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</com.android.launcher3.folder.FolderIcon>

style里并没有设置iconDisplay,那么就会使用默认是workspace

    <!-- Icon displayed on the workspace -->
    <style name="BaseIcon.Workspace.Shadows" parent="BaseIcon">
        <item name="android:shadowRadius">2.0</item>
        <item name="android:shadowColor">?attr/workspaceShadowColor</item>
        <item name="ambientShadowColor">?attr/workspaceAmbientShadowColor</item>
        <item name="ambientShadowBlur">1.5dp</item>
        <item name="keyShadowColor">?attr/workspaceKeyShadowColor</item>
        <item name="keyShadowBlur">.5dp</item>
        <item name="keyShadowOffsetX">.5dp</item>
        <item name="keyShadowOffsetY">.5dp</item>
    </style>

    <!-- Intentionally empty so we can override -->
    <style name="BaseIcon.Workspace" parent="BaseIcon.Workspace.Shadows">
    </style>

>3.taskbar_app_icon.xml

看下style,参考补充1,尺寸用的也是taskbar的

<com.android.launcher3.views.DoubleShadowBubbleTextView style="@style/BaseIcon.Workspace.Taskbar" />

4.4.onLayout

根据 计算的逻辑,默认icon是居中显示的,可是右侧还有3个导航按钮,所以如果会挡住右侧导航按钮的话,会动态往左侧偏移。

   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int count = getChildCount();
     
       //计算icon 最右侧的位置,右侧有3个导航按钮,会处理对应的偏移量
        // Layout the children
        mIconLayoutBounds.right = iconEnd;
        mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
        mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
        //倒着来,从右往左布局
        for (int i = count; i > 0; i--) {
            View child = getChildAt(i - 1);
            if (child == mQsb) {
            } else {
                iconEnd -= mItemMarginLeftRight;
                int iconStart = iconEnd - mIconTouchSize;
                child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
                iconEnd = iconStart - mItemMarginLeftRight;
            }
        }
        mIconLayoutBounds.left = iconEnd;
    }

4.5.TaskbarViewController.java

看下TaskbarView可见性的控制逻辑

        mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 8);
        mTaskbarIconAlpha.setUpdateVisibility(true);

>1.输入法切换按钮可见,或者recents功能disabled,透明度为0

    /**
     * Should be called when the IME switcher visibility changes.
     */
    public void setIsImeSwitcherVisible(boolean isImeSwitcherVisible) {
        mTaskbarIconAlpha.get(ALPHA_INDEX_IME_BUTTON_NAV).setValue(
                isImeSwitcherVisible ? 0 : 1);
    }

    /**
     * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
     */
    public void setRecentsButtonDisabled(boolean isDisabled) {
        // TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
        mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
    }

>2.NavbarButtonsViewController.java

非锁屏状态并且非固定屏幕 或者 非小屏幕

        mPropertyHolders.add(new StatePropertyHolder(
                mControllers.taskbarViewController.getTaskbarIconAlpha()
                        .get(ALPHA_INDEX_KEYGUARD),
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
                        && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0));

        mPropertyHolders.add(new StatePropertyHolder(
                mControllers.taskbarViewController.getTaskbarIconAlpha()
                        .get(ALPHA_INDEX_SMALL_SCREEN),
                flags -> (flags & FLAG_SMALL_SCREEN) == 0));

>3.下拉状态栏的时候隐藏

    private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
        float alpha = isExpanded ? 0 : 1;
        AnimatorSet anim = new AnimatorSet();
        anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get(
                TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));

>4.TaskbarLauncherStateController.java

        mIconAlphaForHome = mControllers.taskbarViewController
                .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);

//

    private void onIconAlignmentRatioChanged() {
        boolean taskbarWillBeVisible = mIconAlignment.value < 1;
    //...
        // Switch taskbar and hotseat in last frame
        updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0);
    //..
        }
    }

    private void updateIconAlphaForHome(float alpha) {
        mIconAlphaForHome.setValue(alpha);
        //..
    }

>5.TaskbarStashController.java

其他的就不看了,这里根据是否是is stash决定是否修改值。

        mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().get(
                TaskbarViewController.ALPHA_INDEX_STASH);

stash的条件

    private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
            flags -> {
                boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
                boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
                boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
                boolean stashedInTaskbarAllApps =
                        hasAnyFlag(flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS);
                boolean stashedForSmallScreen = hasAnyFlag(flags, FLAG_STASHED_SMALL_SCREEN);
                return (inApp && stashedInApp) || (!inApp && stashedLauncherState)
                        || stashedInTaskbarAllApps || stashedForSmallScreen;
            });

//FLAG_IN_APP就是字面意思,打开一个app的时候

    private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;

4.6.TaskbarDragController.java

>1.startDragOnLongClick

//走这里
    public boolean startDragOnLongClick(View view) {
        return startDragOnLongClick(view, null, null);
    }

    protected boolean startDragOnLongClick(
            DeepShortcutView shortcutView, Point iconShift) {
        return startDragOnLongClick(
                shortcutView.getBubbleText(),
                new ShortcutDragPreviewProvider(shortcutView.getIconView(), iconShift),
                iconShift);
    }

    private boolean startDragOnLongClick(
            View view,
            @Nullable DragPreviewProvider dragPreviewProvider,
            @Nullable Point iconShift) {
        if (!(view instanceof BubbleTextView)) {
            return false;
        }

        BubbleTextView btv = (BubbleTextView) view;
        mActivity.onDragStart();
        btv.post(() -> {
        //长按逻辑在这里,见补充2
            DragView dragView = startInternalDrag(btv, dragPreviewProvider);
            if (iconShift != null) {
                dragView.animateShift(-iconShift.x, -iconShift.y);
            }
            btv.getIcon().setIsDisabled(true);
            mControllers.taskbarAutohideSuspendController.updateFlag(
                    TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true);
        });
        return true;
    }

>2.startInternalDrag

    private DragView startInternalDrag(
            BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider) {
        float iconScale = btv.getIcon().getAnimatedScale();

        // Clear the pressed state if necessary
        btv.clearFocus();
        btv.setPressed(false);
        btv.clearPressedBackground();

        final DragPreviewProvider previewProvider = dragPreviewProvider == null
                ? new DragPreviewProvider(btv) : dragPreviewProvider;
        final Drawable drawable = previewProvider.createDrawable();
        final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
        int dragLayerX = mTempXY[0];
        int dragLayerY = mTempXY[1];

        Rect dragRect = new Rect();
        btv.getSourceVisualDragBounds(dragRect);
        dragLayerY += dragRect.top;

        DragOptions dragOptions = new DragOptions();
        dragOptions.preDragCondition = null;
        //是否允许显示menu菜单,默认是true
        if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
        //这个就是那个menu的显示逻辑
            PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
                    mControllers.taskbarPopupController.showForIcon(btv);
            if (popupContainer != null) {
                dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
            }
        }
        if (dragOptions.preDragCondition == null) {
            dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
                private DragView mDragView;

                @Override
                public boolean shouldStartDrag(double distanceDragged) {
                    return mDragView != null && mDragView.isAnimationFinished();
                }

                @Override
                public void onPreDragStart(DropTarget.DragObject dragObject) {
                    mDragView = dragObject.dragView;

                    if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()
                            && !shouldStartDrag(0)) {
                        // Immediately close the popup menu.
                        mDragView.setOnAnimationEndCallback(() -> callOnDragStart());
                    }
                }

                @Override
                public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
                    mDragView = null;
                }
            };
        }
//后续就是拖拽的逻辑了.见补充3
        return startDrag(
                drawable,
                /* view = */ null,
                /* originalView = */ btv,
                dragLayerX,
                dragLayerY,
                (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
                (ItemInfo) btv.getTag(),
                /* dragVisualizeOffset = */ null,
                dragRect,
                scale * iconScale,
                scale,
                dragOptions);
    }

>3.startDrag

taskview里的icon能看到说明已经打开了一个页面了,这时候的拖拽也只能实现分屏功能

    protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
            DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
            ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
            float dragViewScaleOnDrop, DragOptions options) {
        mOptions = options;

        mRegistrationX = mMotionDown.x - dragLayerX;
        mRegistrationY = mMotionDown.y - dragLayerY;

        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;

        mLastDropTarget = null;
//创建拖拽对象
        mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
        mDragObject.originalView = originalView;
        mDragObject.deferDragViewCleanupPostAnimation = false;
//是否开启拖拽
        mIsInPreDrag = mOptions.preDragCondition != null
                && !mOptions.preDragCondition.shouldStartDrag(0);

        float scalePx = mDragIconSize - dragRegion.width();
        //创建拖拽控件
        final DragView dragView = mDragObject.dragView = new TaskbarDragView(
                mActivity,
                drawable,
                mRegistrationX,
                mRegistrationY,
                initialDragViewScale,
                dragViewScaleOnDrop,
                scalePx);
        dragView.setItemInfo(dragInfo);
        mDragObject.dragComplete = false;

        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);

        mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
        if (!mOptions.isAccessibleDrag) {
            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
        }

        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;
        mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();

        if (dragRegion != null) {
            dragView.setDragRegion(new Rect(dragRegion));
        }
//拖拽视图显示
        dragView.show(mLastTouch.x, mLastTouch.y);
        mDistanceSinceScroll = 0;

        if (!mIsInPreDrag) {
            callOnDragStart();
        } else if (mOptions.preDragCondition != null) {
            mOptions.preDragCondition.onPreDragStart(mDragObject);
        }
//父类处理,见4.7.1
        handleMoveEvent(mLastTouch.x, mLastTouch.y);

        return dragView;
    }

>4.callOnDragStart

    protected void callOnDragStart() {
        super.callOnDragStart();
        // Pre-drag has ended, start the global system drag.
        //结束预拖拽,开启全局拖拽
        AbstractFloatingView.closeAllOpenViews(mActivity);
        startSystemDrag((BubbleTextView) mDragObject.originalView);
    }

>5.startSystemDrag

    private void startSystemDrag(BubbleTextView btv) {
        View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {

            @Override
            public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
                int iconSize = Math.max(mDragIconSize, btv.getWidth());
                shadowSize.set(iconSize, iconSize);
                // The registration point was taken before the icon scaled to mDragIconSize, so
                // offset the registration to where the touch is on the new size.
                int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2;
                int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2;
                shadowTouchPoint.set(mRegistrationX + offsetX, mRegistrationY + offsetY);
            }

            @Override
            public void onDrawShadow(Canvas canvas) {
                canvas.save();
                if (DEBUG_DRAG_SHADOW_SURFACE) {
                    canvas.drawColor(0xffff0000);
                }
                float scale = mDragObject.dragView.getScaleX();
                canvas.scale(scale, scale);
                mDragObject.dragView.draw(canvas);
                canvas.restore();
            }
        };

        Object tag = btv.getTag();
        ClipDescription clipDescription = null;
        Intent intent = null;
        if (tag instanceof ItemInfo) {
            ItemInfo item = (ItemInfo) tag;
            LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
            clipDescription = new ClipDescription(item.title,
                    new String[] {
                            item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
                                    ? ClipDescription.MIMETYPE_APPLICATION_SHORTCUT
                                    : ClipDescription.MIMETYPE_APPLICATION_ACTIVITY
                    });
            intent = new Intent();
            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                String deepShortcutId = ((WorkspaceItemInfo) item).getDeepShortcutId();
                intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
                        launcherApps.getShortcutIntent(
                                item.getIntent().getPackage(),
                                deepShortcutId,
                                null,
                                item.user));
                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
                intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
            } else {
                intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
                        launcherApps.getMainActivityLaunchIntent(item.getIntent().getComponent(),
                                null, item.user));
            }
            intent.putExtra(Intent.EXTRA_USER, item.user);
        } else if (tag instanceof Task) {
            Task task = (Task) tag;
            clipDescription = new ClipDescription(task.titleDescription,
                    new String[] {
                            ClipDescription.MIMETYPE_APPLICATION_TASK
                    });
            intent = new Intent();
            intent.putExtra(Intent.EXTRA_TASK_ID, task.key.id);
            intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId));
        }

        if (clipDescription != null && intent != null) {
            // Need to share the same InstanceId between launcher3 and WM Shell (internal).
            InstanceId internalInstanceId = new InstanceIdSequence(
                    com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId();
            com.android.launcher3.logging.InstanceId launcherInstanceId =
                    new com.android.launcher3.logging.InstanceId(internalInstanceId.getId());

            intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);

            ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
            //这个是View里的方法
            if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE
                            | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION)) {
                onSystemDragStarted(btv);

            }
        }
    }

4.7.DragController

>1.handleMoveEvent

    protected void handleMoveEvent(int x, int y) {
        mDragObject.dragView.move(x, y);

        // Drop on someone?
        final int[] coordinates = mCoordinatesTemp;
        DropTarget dropTarget = findDropTarget(x, y, coordinates);
        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        checkTouchMove(dropTarget);

        // Check if we are hovering over the scroll areas
        mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
        mLastTouch.set(x, y);

        int distanceDragged = mDistanceSinceScroll;
        if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
            distanceDragged /= DEEP_PRESS_DISTANCE_FACTOR;
        }
        if (mIsInPreDrag && mOptions.preDragCondition != null
                && mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
             //见补充2,以及子类4.6.4
            callOnDragStart();
        }
    }

>2.callOnDragStart

    protected void callOnDragStart() {
        if (mOptions.preDragCondition != null) {
            mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
        }
        mIsInPreDrag = false;
        mDragObject.dragView.onDragStart();
        for (DragListener listener : new ArrayList<>(mListeners)) {
            listener.onDragStart(mDragObject, mOptions);
        }
    }

5.TaskbarDragLayer.java

5.1.构造方法

这个容器的背景交给render类来处理了,根据高度画对应的背景,就是底部那个黑色的背景

    public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr, int defStyleRes) {
        super(context, attrs, 1 /* alphaChannelCount */);
        //taskbar的背景用这个画的
        mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
        mBackgroundRenderer.getPaint().setAlpha(0);
    }

5.2.dispatchDraw

    protected void dispatchDraw(Canvas canvas) {
        float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
                * (1f - mTaskbarBackgroundOffset);
        mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
        mBackgroundRenderer.draw(canvas);
        super.dispatchDraw(canvas);
    }

5.3.setTaskbarBackgroundAlpha

    //修改透明度,可以改变背景的可见性
    protected void setTaskbarBackgroundAlpha(float alpha) {
        mBackgroundRenderer.getPaint().setAlpha((int) (alpha * 255));
        invalidate();
    }

5.4.setTaskbarBackgroundOffset

设置平移,同步修改背景位置

    protected void setTaskbarBackgroundOffset(float offset) {
        mTaskbarBackgroundOffset = offset;
        invalidate();
    }    

5.5.背景透明度逻辑

>TaskbarDragLayerController.java

可以看到由6种变量的值来计算的。

    private void updateBackgroundAlpha() {
        final float bgNavbar = mBgNavbar.value;
        final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
                * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
                //mBgOverride的值比较重要。
        mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);

        mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);

        updateNavBarDarkIntensityMultiplier();
    }

看名字大概就能知道这些都是哪些控件的背景

    // Alpha properties for taskbar background.
    private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
    //导航按钮
    private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
    
    private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
    //状态栏下拉
    private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
    //输入法
    private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
    // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
    private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);

//默认值

   public void init(TaskbarControllers controllers) {
        mControllers = controllers;
        mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());

        mNavButtonDarkIntensityMultiplier = mControllers.navbarButtonsViewController
                .getNavButtonDarkIntensityMultiplier();

        mBgTaskbar.value = 1;
        mKeyguardBgTaskbar.value = 1;
        mNotificationShadeBgTaskbar.value = 1;
        mImeBgTaskbar.value = 1;
        mBgOverride.value = 1;
        updateBackgroundAlpha();
    }

下边一个个看下,他们的值都是由啥决定的

>1#mBgOverride

结论: 这个就用的初始化的值1,后边几个修改的地方都没调用。

翻译下注释,就是说如果有其他地方(比如scrimeView)正在处理这个背景,这里就直接hide也就是设置成0,

public AnimatedFloat getOverrideBackgroundAlpha() {
    return mBgOverride;
}

//LauncherTaskbarUIController.java

    /**
     * Sets whether the background behind the taskbar/nav bar should be hidden.
     */
    public void forceHideBackground(boolean forceHide) {
        mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
    }

// 调用的地方一

下边2个if条件都不满足。

    public void setSystemGestureInProgress(boolean inProgress) {
        super.setSystemGestureInProgress(inProgress);
        if (DisplayController.isTransientTaskbar(mLauncher)) {
            forceHideBackground(false);
            return;
        }
        if (!FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
            // Launcher's ScrimView will draw the background throughout the gesture. But once the
            // gesture ends, start drawing taskbar's background again since launcher might stop
            // drawing.
           
            forceHideBackground(inProgress);
        }
    }

//调用的地方二

QuickstepTransitionManager.java 点击桌面normal状态的app图标,或者长按选择壁纸,点击壁纸,这两种创建动画用的下边的方法

    private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
            int startDelay, boolean skipAllAppsScale) {
            //...
     if (mLauncher.isInState(ALL_APPS)) {            
     //...
    } else if (mLauncher.isInState(OVERVIEW)) {
        endListener = composeViewContentAnimator(launcherAnimator, alphas, scales);
    } else {
    //...---------->>
        final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get();
        //这个是false,也没走
        if (scrimEnabled) {
        //...---------->>
            if (scrimView.getBackground() instanceof ColorDrawable) {
            //...---------->>
                if (useTaskbarColor) {
                    // Hide the taskbar background color since it would duplicate the scrim.
                    scrim.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationStart(Animator animation) {
                        //---------->>
                            if (taskbarUIController != null) {
                                taskbarUIController.forceHideBackground(true);
                            }
                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                        //---------->>
                            if (taskbarUIController != null) {
                                taskbarUIController.forceHideBackground(false);
                            }
                        }
                    });
                }

>2#mBgNavbar

enable条件整理下:

  1. 非锁屏界面,下拉状态栏
  2. 屏保界面只显示后退键

NavbarButtonsViewController.java

public void init(TaskbarControllers controllers) {
//...
            // Animate taskbar background when either..
            // notification shade expanded AND not on keyguard
            // back is visible for bouncer
            mPropertyHolders.add(new StatePropertyHolder(
                    mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
                    flags -> ((flags & FLAG_NOTIFICATION_SHADE_EXPANDED) != 0
                                && (flags & FLAG_KEYGUARD_VISIBLE) == 0)
                            || (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
                 //...           
        applyState();
        mPropertyHolders.forEach(StatePropertyHolder::endAnimation);

StatePropertyHolder的用法后边会讲,上边理解下,flags后边的就是判断enable与否的条件,enable为ture,值为1,false的话值为0.

>3#mBgTaskbar

  • goingToLauncher:当前显示的是桌面,或者说没有打开其他app的情况。包括luancher状态home,background,allApps

  • isTaskbarAlignedWithHotseat taskbar是否与hotseat对齐,配置里true

  • 这里有个问题,上滑到allapps页面的时候,taskbar的背景透明度是0,而allapps那边又给底部画了个半透明的横条,导致导航键看不清。我们可以在上边2个条件上再加一个条件,那就是【当前不在allapps页面或者导航栏是隐藏状态】,并且得把allapps页面画的那个底部横条隐藏掉。

TaskbarLauncherStateController.java

    private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
        boolean goingToLauncher = isInLauncher();
    //...
    float backgroundAlpha =
            goingToLauncher && mLauncherState.isTaskbarAlignedWithHotseat(mLauncher)
                    ? 0 : 1;
    // 动画进行中,或者新旧值不一样的话
    if (mTaskbarBackgroundAlpha.isAnimating()
            || mTaskbarBackgroundAlpha.value != backgroundAlpha) {
        mTaskbarBackgroundAlpha.cancelAnimation();
        //修改为新的值
        animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(backgroundAlpha)
                .setDuration(duration));
    }        

修改后的代码,兼容隐藏导航栏的功能

        float backgroundAlpha =
                goingToLauncher && mLauncherState.isTaskbarAlignedWithHotseat(mLauncher)
                && (isInHotseatOnTopStates()|| mControllers.navbarButtonsViewController.mConfig4MIDM.isHideNavBar())
                        ? 0 : 1;

>4#mNotificationShadeBgTaskbar

TaskbarActivityContext.java

    private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
        float alpha = isExpanded ? 0 : 1;
        AnimatorSet anim = new AnimatorSet();
        anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get(
                TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));
                //限制条件是非3个导航按钮的情况,这里不满足
        if (!isThreeButtonNav()) {
            anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
                    .animateToValue(alpha));
        }
        anim.start();
        if (skipAnim) {
            anim.end();
        }
    }

>5#mKeyguardBgTaskbar

可以看到,enable的条件是非锁屏界面,也就是非锁屏界面是1,锁屏界面是0

        mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
                .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));

>6#mImeBgTaskbar

不研究了,这个看起来有点杂乱。

    private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay,
            boolean animateBg, int changedFlags) {
        //...
        if (!supportsVisualStashing()) {
            // Just hide/show the icons and background instead of stashing into a handle.
            //...
            mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
                    hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
            //...
            return;
        }

看下和这个FLAG_STASHED_IN_APP_IME相关的代码

    // If we're in an app and any of these flags are enabled, taskbar should be stashed.
    private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
            | FLAG_STASHED_IN_SYSUI_STATE | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
            | FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;

    private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
            FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;

//

    public void setSystemGestureInProgress(boolean inProgress) {
        //...
        // Only update the following flags when system gesture is not in progress.
        boolean shouldStashForIme = shouldStashForIme();
        if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
        //...
            updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
        } else {
            applyState(mControllers.taskbarOverlayController.getCloseDuration());
        }
    }
    
    /**
     * We stash when IME or IME switcher is showing AND NOT
     *  * in small screen AND
     *  * 3 button nav AND
     *  * landscape (or seascape)
     * We do not stash if taskbar is transient
     */
    private boolean shouldStashForIme() {
       
        if (DisplayController.isTransientTaskbar(mActivity)) {//false
            return false;
        }
        return (mIsImeShowing || mIsImeSwitcherShowing) &&
                !(isPhoneMode() && mActivity.isThreeButtonNav()
                        && mActivity.getDeviceProfile().isLandscape);
    }

6.NavbarButtonsViewController

管理导航按钮的,就是back,home,recent按钮,还有个输入法的,布局结构见 小节3.1

    public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
        mContext = context;
        mNavButtonsView = navButtonsView;
        mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
        mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
        mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);

    }

6.1.init

添加需要的按钮到上边3个对应的容器里,另外代码里的mPropertyHolders集合相关的都先不看。

    public void init(TaskbarControllers controllers) {
    //...
        mNavButtonsView.getLayoutParams().height = p.y;

        mIsImeRenderingNavButtons =false;//读取配置,这里为false
        if (!mIsImeRenderingNavButtons) {
            // 添加输入法切换按钮,IME switcher
            View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
                    isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
                    mControllers.navButtonController, R.id.ime_switcher);
        //...
        }
        //...
        boolean isInSetup = !mContext.isUserSetupComplete();
        boolean isInKidsMode = mContext.isNavBarKidsModeActive();
        boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
        //..
        if (alwaysShowButtons) {
        //这个就是添加导航按钮的方法
            initButtons(mNavButtonContainer, mEndContextualContainer,
                    mControllers.navButtonController);
            updateButtonLayoutSpacing();
            //修改flag
            updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));

            // Rotation button
            RotationButton rotationButton = new RotationButtonImpl(
                    addButton(mEndContextualContainer, R.id.rotate_suggestion,
                            R.layout.taskbar_contextual_button));
            rotationButton.hide();
            mControllers.rotationButtonController.setRotationButton(rotationButton, null);
        } else {
        //..非3个导航按钮的暂时不看
        }
        //state应用到所有的集合元素里
        applyState();
        mPropertyHolders.forEach(StatePropertyHolder::endAnimation);

        // Initialize things needed to move nav buttons to separate window.
        //新的图层,用来显示导航按钮的,具体见6.5
        mSeparateWindowParent = new BaseDragLayer<TaskbarActivityContext>(mContext, null, 0) {
            @Override
            public void recreateControllers() {
                mControllers = new TouchController[0];
            }

            @Override
            protected boolean canFindActiveController() {
                // We don't have any controllers, but we don't want any floating views such as
                // folder to intercept, either. This ensures nav buttons can always be pressed.
                return false;
            }
        };
        mSeparateWindowParent.recreateControllers();
    }

6.2.initButtons

    private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
            TaskbarNavButtonController navButtonController) {
        //back button
        mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
                mNavButtonContainer, mControllers.navButtonController, R.id.back);
        mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);
        mBackButtonAlpha.setUpdateVisibility(true);
    //...
        // home button
        mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
                navButtonController, R.id.home);
        mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
        mHomeButtonAlpha.setUpdateVisibility(true);

        // Recents button
        mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
                navContainer, navButtonController, R.id.recent_apps);
        //重新设置点击事件
        mRecentsButton.setOnClickListener(v -> {
            navButtonController.onButtonClick(BUTTON_RECENTS, v);
            mHitboxExtender.onRecentsButtonClicked();
        });

        // A11y button,小人图标,辅助功能用的,不研究
        mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
                endContainer, navButtonController, R.id.accessibility_button,
                R.layout.taskbar_contextual_button);

    }

6.3.addButton

    protected ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
            ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
        return addButton(drawableId, buttonType, parent, navButtonController, id,
                R.layout.taskbar_nav_button);
    }

    private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
            ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
            @LayoutRes int layoutId) {
        ImageView buttonView = addButton(parent, id, layoutId);
        buttonView.setImageResource(drawableId);
        buttonView.setContentDescription(parent.getContext().getString(
                navButtonController.getButtonContentDescription(buttonType)));
                //设置点击长按事件
        buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType, view));
        buttonView.setOnLongClickListener(view ->
                navButtonController.onButtonLongClick(buttonType, view));
        return buttonView;
    }

    private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
        ImageView buttonView = (ImageView) mContext.getLayoutInflater()
                .inflate(layoutId, parent, false);
        buttonView.setId(id);
        //添加到容器里
        parent.addView(buttonView);
        mAllButtons.add(buttonView);
        return buttonView;
    }

6.4.updateStateForSysuiFlags

   public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
        if (systemUiStateFlags == mSysuiStateFlags) {
            return;
        }
        //把系统的flag解析成自定义的
        parseSystemUiFlags(systemUiStateFlags);
        applyState();//见补充2,更新state
        if (skipAnim) {
            mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
        }
    }

>1.parseSystemUiFlags

就是解析系统的flag,然后设置给我们自己的flag

    private void parseSystemUiFlags(int sysUiStateFlags) {

    mSysuiStateFlags = sysUiStateFlags;
    boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
    boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
    boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
    boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
    boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
    boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
    int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
            | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
    boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
    boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
    boolean isVoiceInteractionWindowShowing =
            (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;

    //见补充2
    updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
    updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
    updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
    updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
    updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
    updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
    updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
    updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
    updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);

    if (mA11yButton != null) {
        // Only used in 3 button
        boolean a11yLongClickable =
                (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
        mA11yButton.setLongClickable(a11yLongClickable);
        updateButtonLayoutSpacing();
    }
} 

>2.updateStateForFlag

    private void updateStateForFlag(int flag, boolean enabled) {
        if (enabled) {
            mState |= flag;
        } else {
            mState &= ~flag;
        }
    }

    private void applyState() {
        int count = mPropertyHolders.size();
        for (int i = 0; i < count; i++) {
        //循环所有的property,更新state
            mPropertyHolders.get(i).setState(mState);
        }
    }

6.5. 导航按钮图层变化

这个目前就点击打开hotseat里的文件夹的时候用到

>1.moveNavButtonsToNewWindow

把导航按钮移动到新的窗口

    /**
     * Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on a new window.
     */
    public void moveNavButtonsToNewWindow() {
        if (mAreNavButtonsInSeparateWindow) {
            return;
        }

        if (mIsImeRenderingNavButtons) {
            // IME is rendering the nav buttons, so we don't need to create a new layer for them.
            return;
        }

        mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View view) {
                mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener(
                        mSeparateWindowInsetsComputer);
            }

            @Override
            public void onViewDetachedFromWindow(View view) {
                mSeparateWindowParent.removeOnAttachStateChangeListener(this);
                mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                        mSeparateWindowInsetsComputer);
            }
        });
        mAreNavButtonsInSeparateWindow = true;
        //移除导航容器,并添加到新的容器里
        mContext.getDragLayer().removeView(mNavButtonsView);
        mSeparateWindowParent.addView(mNavButtonsView);
        WindowManager.LayoutParams windowLayoutParams = mContext.createDefaultWindowLayoutParams();
        windowLayoutParams.setTitle(NAV_BUTTONS_SEPARATE_WINDOW_TITLE);
        //把新的容器添加到窗口
        mContext.addWindowView(mSeparateWindowParent, windowLayoutParams);

    }

>2.moveNavButtonsBackToTaskbarWindow

还原回去

    /**
     * Moves mNavButtonsView from its temporary window and reattaches it to TaskbarDragLayer.
     */
    public void moveNavButtonsBackToTaskbarWindow() {
        if (!mAreNavButtonsInSeparateWindow) {
            return;
        }

        mAreNavButtonsInSeparateWindow = false;
        mContext.removeWindowView(mSeparateWindowParent);
        mSeparateWindowParent.removeView(mNavButtonsView);
        //又添加回原本的TaskbarDragLayer容器里了
        mContext.getDragLayer().addView(mNavButtonsView);
    }

>3.变化的条件

TaskbarActivityContext.java

    public void setTaskbarWindowFocusableForIme(boolean focusable) {
        if (focusable) {
            mControllers.navbarButtonsViewController.moveNavButtonsToNewWindow();
        } else {
            mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow();
        }
        setTaskbarWindowFocusable(focusable);
    }

//可以看到,folder打开关闭的时候会调用上边的方法

            folder.setOnFolderStateChangedListener(newState -> {
                if (newState == Folder.STATE_OPEN) {
                    setTaskbarWindowFocusableForIme(true);
                } else if (newState == Folder.STATE_CLOSED) {
                    // Defer by a frame to ensure we're no longer fullscreen and thus won't jump.
                    getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false));
                    folder.setOnFolderStateChangedListener(null);
                }
            });

6.6.backButton可见性的控制

透明度为0就不可见了,具体逻辑可以看 章节 7,8里相关类的介绍

首先用的是MultiValueAlpha,里边数组长度是3,所以有3个value来最终控制结果,MultiValueAlpha的合并规则就是value相乘。

        mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);
        mBackButtonAlpha.setUpdateVisibility(true);
        mPropertyHolders.add(new StatePropertyHolder(
                mBackButtonAlpha.get(ALPHA_INDEX_KEYGUARD_OR_DISABLE),
                flags -> {
                    // Show only if not disabled, and if not on the keyguard or otherwise only when
                    // the bouncer or a lockscreen app is showing above the keyguard
                    boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
                            (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
                            (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
                    return (flags & FLAG_DISABLE_BACK) == 0
                            && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
                }));

另外两个数组的变化逻辑,TaskbarForceVisibleImmersiveController.java

    private void updateIconDimmingAlpha() {
        if (mControllers == null || mControllers.navbarButtonsViewController == null) {
            return;
        }

        MultiPropertyFactory<View> ba =
                mControllers.navbarButtonsViewController.getBackButtonAlpha();
        if (ba != null) {
            ba.get(ALPHA_INDEX_IMMERSIVE_MODE).setValue(mIconAlphaForDimming.value);
        }
    //..
    }

//还在启动中

        if (isInSetup) {
            handleSetupUi();

            // Hide back button in SUW if keyboard is showing (IME draws its own back).
            mPropertyHolders.add(new StatePropertyHolder(
                    mBackButtonAlpha.get(ALPHA_INDEX_SUW),
                    flags -> (flags & FLAG_IME_VISIBLE) == 0));

顺到这里还有backbutton旋转角度的控制逻辑,看下enable条件,可以看到输入法切换按钮可见的时候,back角度会旋转90度,不可见的时候恢复0度,如下图所示。

image.png

        mPropertyHolders.add(new StatePropertyHolder(mBackButton,
                flags -> (flags & FLAG_IME_VISIBLE) != 0 && !mContext.isNavBarKidsModeActive(),
                View.ROTATION, isRtl ? 90 : -90, 0));
        // Translate back button to be at end/start of

6.7.homeButton可见性

看下enable条件,非锁屏状态,并且flag是disable_home

       mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
        mHomeButtonAlpha.setUpdateVisibility(true);
        mPropertyHolders.add(
                new StatePropertyHolder(mHomeButtonAlpha.get(
                        ALPHA_INDEX_KEYGUARD_OR_DISABLE),
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
                        (flags & FLAG_DISABLE_HOME) == 0));

数组2

    private void updateIconDimmingAlpha() {
        if (mControllers == null || mControllers.navbarButtonsViewController == null) {
            return;
        }
        //...
        MultiPropertyFactory<View> ha =
                mControllers.navbarButtonsViewController.getHomeButtonAlpha();
        if (ba != null) {
            ha.get(ALPHA_INDEX_IMMERSIVE_MODE).setValue(mIconAlphaForDimming.value);
        }
    }

数组3,没有处理,那就是默认值1了。

6.8.recentsButton可见性

看下enable条件,非锁屏状态,并且flag是disable_recents

        mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
                        && !mContext.isNavBarKidsModeActive()));

6.9.updateNavButtonTranslationY

导航按钮的位置:

  • 在默认桌面是和大个的hotseat平行的,taskbar背景透明
  • 其他状态下是和taskbar里小个的hotseat图标平行的,正常taskbar都带背景颜色
  • 目前看到是由3个值决定的,后边看下这3个值都是啥
    private void updateNavButtonTranslationY() {
        if (isPhoneButtonNavMode(mContext)) {
            return;
        }
        final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
        final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
        TaskbarUIController uiController = mControllers.uiController;
        final float inAppDisplayAdjustmentTranslationY =
                (uiController instanceof LauncherTaskbarUIController
                        && ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
                        ? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
        mNavButtonsView.setTranslationY(normalTranslationY
                + imeAdjustmentTranslationY
                + inAppDisplayAdjustmentTranslationY);
    }

>1.相关变量

控制上边的平移的就是下边3个变量,后边查下这几个值都是哪里改变的

    private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
            this::updateNavButtonTranslationY);
    private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
            this::updateNavButtonTranslationY);
    private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
            this::updateNavButtonTranslationY);

7.MultiValueAlpha

可以理解为,view的透明度由多个数组里的值决定。

private final MultiValueAlpha mTaskbarIconAlpha;
//
    mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS/*8*/);
    mTaskbarIconAlpha.setUpdateVisibility(true);

7.1.MultiValueAlpha

最终的值是由里边的数组里对象的value相乘得来的。

public class MultiValueAlpha extends MultiPropertyFactory<View> {

//value的混合模式,这里是相乘
    private static final FloatBiFunction ALPHA_AGGREGATOR = (a, b) -> a * b;

    //决定透明度是否影响可见性,为真的话,透明度为0的时候会隐藏view
    private boolean mUpdateVisibility;
    //默认值是1
    public MultiValueAlpha(View view, int size) {
        super(view, VIEW_ALPHA, size, ALPHA_AGGREGATOR, 1f);
    }

  
    public void setUpdateVisibility(boolean updateVisibility) {
        mUpdateVisibility = updateVisibility;
    }

    @Override
    protected void apply(float value) {
        super.apply(value);
        if (mUpdateVisibility) {
            AlphaUpdateListener.updateVisibility(mTarget);
        }
    }
}

7.2.MultiPropertyFactory

public class MultiPropertyFactory<T> {

public static final FloatProperty<MultiPropertyFactory<?>.MultiProperty> MULTI_PROPERTY_VALUE =
        new FloatProperty<MultiPropertyFactory<?>.MultiProperty>("value") {

            @Override
            public Float get(MultiPropertyFactory<?>.MultiProperty property) {
                return property.mValue;
            }

            @Override
            public void setValue(MultiPropertyFactory<?>.MultiProperty property, float value) {
                property.setValue(value);
            }
        };

    public MultiPropertyFactory(T target, FloatProperty<T> property, int size,
            FloatBiFunction aggregator, float defaultPropertyValue) {
        mTarget = target;
        mProperty = property;
        mAggregator = aggregator;
        //创建一个数组
        mProperties = new MultiPropertyFactory<?>.MultiProperty[size];
        for (int i = 0; i < size; i++) {
            mProperties[i] = new MultiProperty(i, defaultPropertyValue);
        }
    }
    //获取对应index的值
    public MultiProperty get(int index) {
        return (MultiProperty) mProperties[index];
    }
    //....
    //看下这个内部类,可以看出,它的属性修改最终会调用外部value的修改
    public class MultiProperty {

    private final int mInx;
    private final float mDefaultValue;
    private float mValue;

    MultiProperty(int inx, float defaultValue) {
        mInx = inx;
        mDefaultValue = defaultValue;
        mValue = defaultValue;
    }
    //mLastIndexSet是上次修改的index,默认是-1
    public void setValue(float newValue) {
    //if条件满足,会根据合并规则mAggregator合并所有其他数组里的值
        if (mLastIndexSet != mInx) {
            mAggregationOfOthers = mDefaultValue;
            for (MultiPropertyFactory<?>.MultiProperty other : mProperties) {
                if (other.mInx != mInx) {
                    mAggregationOfOthers =
                            mAggregator.apply(mAggregationOfOthers, other.mValue);
                }
            }

            mLastIndexSet = mInx;
        }
        //上边if是合并数组里其他的值,这里在把自己最新的值合并进来
        float lastAggregatedValue = mAggregator.apply(mAggregationOfOthers, newValue);
        //更新自己的值
        mValue = newValue;
        //把最终合并的值给到目标处理
        apply(lastAggregatedValue);
    }

    public float getValue() {
        return mValue;
    }

    public Animator animateToValue(float value) {
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, MULTI_PROPERTY_VALUE, value);
        animator.setAutoCancel(true);
        return animator;
    }
}

protected void apply(float value) {
    mProperty.set(mTarget, value);
}
}

7.3.具体对象分析

以上边的mTaskbarIconAlpha为例,合并规则是数组里的value相乘,所以有一个是0的话那么结果也就是0了。 它里边创建的数组长度是8,数组元素MultiProperty的默认值是1f

下边是修改它的数组索引为6和3的MultiProperty的值。根据上边分析的结果,view的透明度其实就是数组里8个对象的value值相乘,那么可以知道,输入法切换按钮可见的话,结果是0,recent按钮不可见的话,结果也是0,反正有一个是0,最终的结果就是0,也就是透明度为0.

//index 6,3

    public void setIsImeSwitcherVisible(boolean isImeSwitcherVisible) {
        mTaskbarIconAlpha.get(6).setValue(
                isImeSwitcherVisible ? 0 : 1);
    }

    public void setRecentsButtonDisabled(boolean isDisabled) {
        mTaskbarIconAlpha.get(3).setValue(isDisabled ? 0 : 1);
    }

//indx=4 状态栏下拉的时候,下边这个用的是动画

    private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
        float alpha = isExpanded ? 0 : 1;
        AnimatorSet anim = new AnimatorSet();
        anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get(4).animateToValue(alpha));

//index 1和7

        mPropertyHolders.add(new StatePropertyHolder(
                mControllers.taskbarViewController.getTaskbarIconAlpha()
                        .get(1),
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
                        && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0));

        mPropertyHolders.add(new StatePropertyHolder(
                mControllers.taskbarViewController.getTaskbarIconAlpha()
                        .get(7),
                flags -> (flags & FLAG_SMALL_SCREEN) == 0));

//还有其他几个就不贴了,反正最终的透明度就是这些值的乘积,那么要完全透明,就都得是1了。

8.StatePropertyHolder

可以理解为一个用来保存状态属性的类,

        StatePropertyHolder(View view, IntPredicate enableCondition) {
            this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
            mAnimator.addListener(new AlphaUpdateListener(view));
        }

        StatePropertyHolder(MultiProperty alphaProperty,
                IntPredicate enableCondition) {
            this(alphaProperty, enableCondition, MULTI_PROPERTY_VALUE, 1, 0);
        }
        //我们研究下这个
        StatePropertyHolder(AnimatedFloat animatedFloat, IntPredicate enableCondition) {
            this(animatedFloat, enableCondition, AnimatedFloat.VALUE, 1, 0);
        }

        <T> StatePropertyHolder(T target, IntPredicate enabledCondition,
                Property<T, Float> property, float enabledValue, float disabledValue) {
            mEnableCondition = enabledCondition;//enable和disable的判断逻辑
            mEnabledValue = enabledValue; //enable的值
            mDisabledValue = disabledValue;//disable的值
            //new一个animator,动态改变target的值,
            mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
        }
        
    public void setState(int flags) {
        boolean isEnabled = mEnableCondition.test(flags);
        if (mIsEnabled != isEnabled) {
            mIsEnabled = isEnabled;
            mAnimator.cancel();
            mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
            mAnimator.start();
        }
    }

    public void endAnimation() {
        if (mAnimator.isRunning()) {
            mAnimator.end();
        }
    }

8.1.AnimatedFloat

NavbarButtonsViewController.java

脑袋有点晕,这里确认下,0是透明,1是不透明,开始弄反了。

            // Animate taskbar background when either..
            // notification shade expanded AND not on keyguard
            // back is visible for bouncer
            mPropertyHolders.add(new StatePropertyHolder(
                    mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
                    //enable的条件:状态栏下拉并且非锁屏
                    flags -> ((flags & FLAG_NOTIFICATION_SHADE_EXPANDED) != 0
                                && (flags & FLAG_KEYGUARD_VISIBLE) == 0)
                                //或者 好像是屏保界面显示后退键?
                            || (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));

8.2MultiProperty

这个可以参考 【章节7】MultiValueAlpha

8.3. view

        mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
                        && !mContext.isNavBarKidsModeActive()));

构造方法可以看到,是修改的view的alpha

            StatePropertyHolder(View view, IntPredicate enableCondition) {
                this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
                //这里的listener是处理view的可见性,根据alpha变化修改
                mAnimator.addListener(new AlphaUpdateListener(view));
            }

9.TaskbarAllAppsContainerView.java

打开一个app,底部导航栏会出现9宫格以及hotseat按钮,点击9宫格按钮,弹出的页面用到的就是这个控件,和那个带search框的不是一个view,虽然大家都继承的一个view

public class TaskbarAllAppsContainerView extends
        ActivityAllAppsContainerView<TaskbarOverlayContext> {

9.1 隐藏search控件

    protected View inflateSearchBox() {
        // Remove top padding of header, since we do not have any search
        mHeader.setPadding(mHeader.getPaddingLeft(), 0,
                mHeader.getPaddingRight(), mHeader.getPaddingBottom());

        TaskbarAllAppsFallbackSearchContainer searchView =
                new TaskbarAllAppsFallbackSearchContainer(getContext(), null);
        searchView.setId(R.id.search_container_all_apps);
        searchView.setVisibility(GONE);
        return searchView;
    }

9.2.长按事件

需要注意的是,taskbar弹出来的这个allapps页面,里边icon的长按事件重写了,不是那个默认的了

   private void setUpIconLongClick() {
        mAppsView.setOnIconLongClickListener(
                mContext.getDragController()::startDragOnLongClick);
    }

//TaskbarDragController.java

    private boolean startDragOnLongClick(
            View view,
            @Nullable DragPreviewProvider dragPreviewProvider,
            @Nullable Point iconShift) {
         //非bubbleTextView或者disable了长按事件,不做处理,返回。
        if (!(view instanceof BubbleTextView) || mDisallowLongClick) {
            return false;
        }
        BubbleTextView btv = (BubbleTextView) view;
        mActivity.onDragStart();
        btv.post(() -> {
        //具体的拖拽逻辑
            DragView dragView = startInternalDrag(btv, dragPreviewProvider);
            if (iconShift != null) {
                dragView.animateShift(-iconShift.x, -iconShift.y);
            }
            btv.getIcon().setIsDisabled(true);
            mControllers.taskbarAutohideSuspendController.updateFlag(
                    TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true);
        });
        return true;
    }

10.TaskbarViewController

    private AnimatedFloat mTaskbarNavButtonTranslationY;
    private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
    

10.1.init

    public void init(TaskbarControllers controllers) {
//..
//可以看到,下边两个变量 和6.9.1里用的是一个
        mTaskbarNavButtonTranslationY =
                controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
        mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
                .getTaskbarNavButtonTranslationYForInAppDisplay();
                

10.2.createIconAlignmentController

  • 创建一个动画,用提供的设备配置文件将任务栏图标对齐
    private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
            PendingAnimation setter = new PendingAnimation(100);
    //..
        int offsetY = launcherDp.getTaskbarOffsetY();
        setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator);
        //如下,更新transY的值
        setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
        setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
        

上述方法调用的地方

>1.setLauncherIconAlignment

    public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
        boolean isHotseatIconOnTopWhenAligned =
                mControllers.uiController.isHotseatIconOnTopWhenAligned();
        // When mIsHotseatIconOnTopWhenAligned changes, animation needs to be re-created.
        //动画控制器为空 或者 mIsHotseatIconOnTopWhenAligned标志状态改变,重新创建控制器
        if (mIconAlignControllerLazy == null
                || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned) {
            mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
            //这里
            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
        }
        mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
        if (alignmentRatio <= 0 || alignmentRatio >= 1) {
            // Cleanup lazy controller so that it is created again in next animation
            mIconAlignControllerLazy = null;
        }
    }

10.3.onRotationChanged

    public void onRotationChanged(DeviceProfile deviceProfile) {
        if (!mControllers.uiController.isIconAlignedWithHotseat()) {
            // We only translate on rotation when icon is aligned with hotseat
            return;
        }
        mActivity.setTaskbarWindowHeight(
                deviceProfile.taskbarSize + deviceProfile.getTaskbarOffsetY());
        mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
    }

11.总结

  • 大屏加载的不是systemUI应用里的navgationBar,而是launcher3里的taskBar
  • 学习下taskBar相关view加载的逻辑
  • taskBar用到的核心view学习,TaskbarDragLayer主要负责画个背景 ,taskbarView是画hotseat相关的icon以及9宫格图标。NavbarButtonsView就是导航按钮以及输入法切换按钮
  • 简单学习下各种view的状态控制器MultiValueAlpha(透明度由多个值决定)