1.简介
这里是讲的是给平板用的导航栏,手机模式的话不会用到。
- home页,是和hotseat平行显示的
- 其他页面都是在底部的
- 打开app的情况,有个9宫格,hotseat图标,导航图标
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
下图就是底部导航栏的默认情况,点击快捷图标或文件夹点开里边的图标,走的是上边的WorkspaceItemInfo
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条件整理下:
- 非锁屏界面,下拉状态栏
- 屏保界面只显示后退键
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度,如下图所示。
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(透明度由多个值决定)