基本原理 Window 的实现类是 PhoneWindow,它是视图系统的核心,也是一切事件的起源。
Window 系统是通过 IPC 机制实现的,使用 WindowManager 来添加和移除控件,其实现类是 WindowManagerGlobal,其内部维持着以下四个集合
1 2 3 4 ArrayList<View> mViews = new ArrayList<View>(); ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); ArrayList<WindowManager.LayoutParams> mParams; ArraySet<View> mDyingViews = new ArraySet<View>();
在 Window 上添加 View 的逻辑就是更新这几个集合
1 2 3 4 5 6 7 8 9 10 11 12 13 public void addView (View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; ViewRootImpl root; View panelParentView = null ; synchronized (mLock) { root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } root.setView(view, wparams, panelParentView); }
实际的工作交给了 ViewRootImpl。
与 Activity 的关系 Window 与 Activity 实际是两个独立的系统,只不过二者有交集,Window 需要在 Activity 启动时展示其设置的布局而已 。
在启动活动前,要进行 WindowManager 的初始化
1 2 WindowManagerGlobal.initialize(); Activity a = performLaunchActivity(r, customIntent);
使用反射创建 Activity 对象后,将执行 attach 方法,此时将创建 PhoneWindow 并配置 WindowManager
1 2 3 4 5 6 7 8 9 10 final void attach (Context context, ActivityThread aThread ,...) { attachBaseContext(context); mFragments.attachHost(null ); mWindow = new PhoneWindow(this ); mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0 ); mWindowManager = mWindow.getWindowManager(); }
Activity 的onCreate方法也在这一步进行,到此 Window 中的 decorView 将被设置好,只是没有添加到 Window 上去。
而后是显示Activity 的 handleResumeActivity 方法,这里才利用 WindowManager 将 DecorView 添加到Window上去,参数全部由WindowManager.LayoutParams 决定。
1 2 3 4 5 View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); wm.addView(decor, l);
DecorView 和 ContentParent PhoneWindow 中会创建 DecorView 和 ContentParent 两个控件
1 2 3 4 5 6 7 8 private void installDecor () { if (mDecor == null ) { mDecor = new DecorView(getContext(), -1 ); } if (mContentParent == null ) { mContentParent = generateLayout(mDecor); } }
mContentParent 的创建较为复杂,它会根据theme配置有所不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 protected ViewGroup generateLayout (DecorView decor) { TypedArray a = getWindowStyle(); mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false ); int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) & (~getForcedWindowFlags()); if (mIsFloating) { setLayout(WRAP_CONTENT, WRAP_CONTENT); setFlags(0 , flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); } if (a.getBoolean(R.styleable.Window_windowNoTitle, false )) { requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.Window_windowActionBar, false )) { requestFeature(FEATURE_ACTION_BAR); } if (a.getBoolean(R.styleable.Window_windowFullscreen, false )) { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); } if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, false )) { setFlags(FLAG_TRANSLUCENT_STATUS, ...); } WindowManager.LayoutParams params = getAttributes(); int layoutResource = R.layout.screen_simple; View in = mLayoutInflater.inflate(layoutResource, null ); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null ) { throw new RuntimeException("Window couldn't find content container view" ); } return contentParent; }
到此为止内容控件只是一个占位控件,如在 R.layout.screen_simple 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:fitsSystemWindows ="true" android:orientation ="vertical" > <ViewStub android:id ="@+id/action_mode_bar_stub" android:inflatedId ="@+id/action_mode_bar" android:layout ="@layout/action_mode_bar" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:theme ="?attr/actionBarTheme" /> <FrameLayout android:id ="@android:id/content" android:layout_width ="match_parent" android:layout_height ="match_parent" android:foregroundInsidePadding ="false" android:foregroundGravity ="fill_horizontal|top" android:foreground ="?android:attr/windowContentOverlay" /> </LinearLayout >
当活动启动时最终调用 setContentView 方法时将替换这个内容布局,这里将提供的布局加载到 @android:id/content 中去,并处理了设置 FEATURE_CONTENT_TRANSITIONS 的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void setContentView (int layoutResID) { if (mContentParent == null ) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
WindowManager.LayoutParams 对Window窗口的改变都是通过 WindowManager.LayoutParams 参数来实现的,最突出了莫过于 WindowInset 了。
1 2 3 4 5 6 7 8 @Override public WindowInsets onApplyWindowInsets (WindowInsets insets) { mFrameOffsets.set(insets.getSystemWindowInsets()); insets = updateColorViews(insets, true ); insets = updateStatusGuard(insets); updateNavigationGuard(insets); return insets; }
updateColorViews 方法的作用主要是处理横竖屏情况下的 WindowInsets ,并通过更新 statusBar 和 navigationBar 的布局参数来处理 WindowInsets事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private WindowInsets updateColorViews (WindowInsets insets, boolean animate) { WindowManager.LayoutParams attrs = getAttributes(); int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); boolean disallowAnimate = (mLastWindowFlags ^ attrs.flags) & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS != 0 ; mLastWindowFlags = attrs.flags; mLastTopInset = Math.min(insets.getStableInsetTop(), insets.getSystemWindowInsetTop()); mLastBottomInset = Math.min(insets.getStableInsetBottom(), insets.getSystemWindowInsetBottom()); mLastRightInset = Math.min(insets.getStableInsetRight(), insets.getSystemWindowInsetRight()); boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0 ; int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset; updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor, navBarSize, navBarToRightEdge, 0 , animate && !disallowAnimate); boolean statusBarNeedsRightInset = navBarToRightEdge && mNavigationColorViewState.present; int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0 ; updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor, mLastTopInset, false , statusBarRightInset, animate && !disallowAnimate); return insets; }
默认情况下 ColorViewState 配置了一个参数 systemUiHideFlag, 取值为 SYSTEM_UI_FLAG_FULLSCREEN,如果设置为改制,更新方法 updateColorViewInt 无效。
与 Dialog 的关系 Dialog 的构造方法如下
1 2 3 4 5 6 7 8 9 10 11 Dialog(@NonNull Context context, @StyleRes int themeResId) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true ); themeResId = outValue.resourceId; mContext = new ContextThemeWrapper(context, themeResId); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); w.setGravity(Gravity.CENTER); }
这里明白的显示了一个 Dialog 就是一个 Window,显示什么内容也遵循 Window 的规律,使用 setContentView 来添加自定义布局。
至于显示则是利用 WindowManager 将准备好的 DecorView 添加到 Window 上去。
1 2 3 4 5 6 public void show () { mDecor = mWindow.getDecorView(); mWindowManager.addView(mDecor, l); mShowing = true ; sendShowMessage(); }
同理 hide() 方法实际就是隐藏 DecorView,dismiss 方法就是使用 WindowManager 移除 DecorView。
Dialog 的若干子类不过是配置它的布局而已,此外 Dilog 没有生命周期。
状态栏染色 我们已经知道状态栏的染色是通过添加View来完成的,其必须满足条件
1 2 3 <item name ="android:windowDrawsSystemBarBackgrounds" > true</item > <item name ="android:windowTranslucentStatus" > false</item > <item name ="android:statusBarColor" > #FF786312</item > //非必须
动态染色可以采用如下办法
1 getWindow().setStatusBarColor(Color.MAGENTA);
实际上这个 View 是这样定义的
1 2 3 4 5 6 7 private final ColorViewState mStatusColorViewState = new ColorViewState( SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, Gravity.TOP, Gravity.LEFT, STATUS_BAR_BACKGROUND_TRANSITION_NAME, com.android.internal.R.id.statusBarBackground, FLAG_FULLSCREEN);
我们也完全可以这样做
1 2 View view = findViewById(android.R.id.statusBarBackground); view.setBackgroundColor(Color.YELLOW);
如果播放一个动画,就能完全看清楚这个 View
1 v.animate().setDuration(2000 ).setInterpolator(new CycleInterpolator(0.5f )).translationY(200 ).start();
这一过程的实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 private void updateColorViewInt (final ColorViewState state, int sysUiVis, int color, int size, boolean verticalBar, int rightMargin, boolean animate) { state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0 && (getAttributes().flags & state.hideWindowFlag) == 0 && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ; boolean show = state.present && (color & Color.BLACK) != 0 && (getAttributes().flags & state.translucentFlag) == 0 ; View view = state.view; int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity; if (view == null ) { state.view = view = new View(mContext); view.setBackgroundColor(color); view.setVisibility(INVISIBLE); LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight, resolvedGravity); lp.rightMargin = rightMargin; addView(view, lp); updateColorViewTranslations(); } else { LayoutParams lp = (LayoutParams) view.getLayoutParams(); lp.height = resolvedHeight; lp.width = resolvedWidth; lp.gravity = resolvedGravity; lp.rightMargin = rightMargin; view.setLayoutParams(lp); view.setBackgroundColor(color); } if (animate) { view.animate().alpha(1.0f ).setInterpolator(mShowInterpolator). setDuration(mBarEnterExitDuration); } }
这里 windowDrawsSystemBarBackgrounds 和 windowTranslucentStatus 是互相排斥的,想要改变状态栏颜色,必须使用前者,禁止后者。后者一旦生效,上述方法将无效,View 不会被创建。
WindowSystemUiVisibility SystemUiVisibility 能够完成的动作完全可以通过设置 Window 来完成,但好处在于随时可以清除效果。
SystemUiVisibility 的设置必染色要复杂,涉及状态栏和布局的变化,不过在回调中也会执行更新方法
1 2 3 public void onWindowSystemUiVisibilityChanged (int visible) { updateColorViews(null , true ); }
View.SYSTEM_UI_FLAG_FULLSCREEN 这种情况下,状态栏将彻底消失,内容布局扩展到全屏模式
SYSTEM_UI_FLAG_IMMERSIVE 默认情况下,触摸下拉会出现通知栏,必须手动清除才能取消 SYSTEM_UI_FLAG_FULLSCREEN 的效果;配合该标记能够下拉出状态栏,且立即清除效果
SYSTEM_UI_FLAG_IMMERSIVE_STICKY 下拉出原生未染色的的状态栏,且不清除效果
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 内容布局到全屏,且给ActionBar设置一个padding显示状态栏,一旦清除,布局回收,该Padding还在
View.SYSTEM_UI_FLAG_LAYOUT_STABLE 配合上述设置,使得更改永久生效,清除之后布局不回收
最后在说下 ActionBar 和 Toolbar 的关系
设置 Toolbar 的方法如下,首先要判断是否已经存在 ActionBar,通常如果使用 FEATURE_ACTION_BAR 则会初始化 ActionBar, 实现类是 WindowDecorActionBar
1 2 3 4 5 6 7 @Nullable public ActionBar getActionBar () { Window window = getWindow(); if (!window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null ) return ; mActionBar = new WindowDecorActionBar(this ); return mActionBar; }
如果没有,可以正常进行,新建的实现类是 ToolbarActionBar。
1 2 3 4 5 6 7 8 9 public void setActionBar (@Nullable Toolbar toolbar) { if (getActionBar() instanceof WindowDecorActionBar) { throw new IllegalStateException("This Activity already has an action bar supplied " + "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + "android:windowActionBar to false in your theme to use a Toolbar instead." ); } ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(), this ); mActionBar = tbab; mActionBar.invalidateOptionsMenu(); }
可见 ActionBar 和 Toolbar 是表里的关系。你可以处理菜单相关的事项
1 2 3 4 5 Menu getMenu () ;inflateMenu(R.menu.menu_main); boolean showOverflowMenu () ; boolean hideOverflowMenu () ;void setOnMenuItemClickListener (OnMenuItemClickListener listener) ;
也可以使用动画
1 ViewPropertyAnimator toolbarAnimator = toolbar.animate();