Android顯示框架:Android應用視圖的管理者Window

關於做者java

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android

第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄git

文章目錄程序員

  • 一 窗口類型
  • 二 窗口參數
  • 三 窗口模式
  • 四 窗口回調
  • 五 窗口實現

從這篇文章開始,咱們來分析和Window以及WindowManager相關的內容,github

Abstract base class for a top-level window look and behavior policy.架構

Window在Android是一個窗口的概念,平常開發中咱們和它接觸的很少,咱們更多接觸的是View,可是View都是經過Window來呈現的,Window是View的直接管理者。
而WindowManager承擔者管理Window的責任。app

一 窗口類型

Window在Android中有三種類型:ide

  • 應用Window:z-index在1~99之間,它每每對應着一個Activity。
  • 子Window:z-index在1000~1999之間,它每每不能獨立存在,須要依附在父Window上,例如Dialog等。
  • 系統Window:z-index在2000~2999之間,它每每須要聲明權限才能建立,例如Toast、狀態欄、系統音量條、錯誤提示框都是系統Window。

z-index是Android窗口的層級的概念,z-index越大的窗口越居於頂層,函數

z-index對應着WindowManager.LayoutParams裏的type參數,具體說來。工具

應用Window

  • public static final int FIRST_APPLICATION_WINDOW = 1;//1
  • public static final int TYPE_BASE_APPLICATION = 1;//窗口的基礎值,其餘的窗口值要大於這個值
  • public static final int TYPE_APPLICATION = 2;//普通的應用程序窗口類型
  • public static final int TYPE_APPLICATION_STARTING = 3;//應用程序啓動窗口類型,用於系統在應用程序窗口啓動前顯示的窗口。
  • public static final int TYPE_DRAWN_APPLICATION = 4;
  • public static final int LAST_APPLICATION_WINDOW = 99;//2

子Window

  • public static final int FIRST_SUB_WINDOW = 1000;//子窗口類型初始值
  • public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
  • public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
  • public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
  • public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
  • public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
  • public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
  • public static final int LAST_SUB_WINDOW = 1999;//子窗口類型結束值

系統Window

  • public static final int FIRST_SYSTEM_WINDOW = 2000;//系統窗口類型初始值
  • public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;//系統狀態欄窗口
  • public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;//搜索條窗口
  • public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;//通話窗口
  • public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;//系統ALERT窗口
  • public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;//鎖屏窗口
  • public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//TOAST窗口

二 窗口參數

在WindowManager裏定義了一個LayoutParams內部類,它描述了窗口的參數信息,主要包括:

  • public int x:窗口x軸座標
  • public int y:窗口y軸座標
  • public int type:窗口類型
  • public int flags:窗口屬性
  • public int softInputMode:輸入法鍵盤模式

關於窗口屬性,它控制着窗口的行爲,舉幾個常見的:

  • FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 只要窗口可見,就容許在開啓狀態的屏幕上鎖屏
  • FLAG_NOT_FOCUSABLE 窗口不能得到輸入焦點,設置該標誌的同時,FLAG_NOT_TOUCH_MODAL也會被設置
  • FLAG_NOT_TOUCHABLE 窗口不接收任何觸摸事件
  • FLAG_NOT_TOUCH_MODAL 在該窗口區域外的觸摸事件傳遞給其餘的Window,而本身只會處理窗口區域內的觸摸事件
  • FLAG_KEEP_SCREEN_ON 只要窗口可見,屏幕就會一直亮着
  • FLAG_LAYOUT_NO_LIMITS 容許窗口超過屏幕以外
  • FLAG_FULLSCREEN 隱藏全部的屏幕裝飾窗口,好比在遊戲、播放器中的全屏顯示
  • FLAG_SHOW_WHEN_LOCKED 窗口能夠在鎖屏的窗口之上顯示
  • FLAG_IGNORE_CHEEK_PRESSES 當用戶的臉貼近屏幕時(好比打電話),不會去響應此事件
  • FLAG_TURN_SCREEN_ON 窗口顯示時將屏幕點亮

關於窗口類型,它對應着窗口的層級,上面咱們也提到過了。

它的構造函數也主要是針對這幾個參數的。

public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
            public LayoutParams() {
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                type = TYPE_APPLICATION;
                format = PixelFormat.OPAQUE;
            }

            public LayoutParams(int _type) {
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                type = _type;
                format = PixelFormat.OPAQUE;
            }

            public LayoutParams(int _type, int _flags) {
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                type = _type;
                flags = _flags;
                format = PixelFormat.OPAQUE;
            }

            public LayoutParams(int _type, int _flags, int _format) {
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                type = _type;
                flags = _flags;
                format = _format;
            }

            public LayoutParams(int w, int h, int _type, int _flags, int _format) {
                super(w, h);
                type = _type;
                flags = _flags;
                format = _format;
            }

            public LayoutParams(int w, int h, int xpos, int ypos, int _type, int _flags, int _format) {
                super(w, h);
                x = xpos;
                y = ypos;
                type = _type;
                flags = _flags;
                format = _format;
            }
 }複製代碼

三 窗口模式

關於窗口模式咱們就比較熟悉了,咱們會在AndroidManifest.xml裏Activity的標籤下設置android:windowSoftInputMode="adjustNothing",來控制輸入鍵盤顯示行爲。

可選的有6個參數,源碼裏也有6個值與之對應:

  • SOFT_INPUT_STATE_UNSPECIFIED:沒有指定軟鍵盤輸入區域的顯示狀態。
  • SOFT_INPUT_STATE_UNCHANGED:不要改變軟鍵盤輸入區域的顯示狀態。
  • SOFT_INPUT_STATE_HIDDEN:在合適的時候隱藏軟鍵盤輸入區域,例如,當用戶導航到當前窗口時。
  • SOFT_INPUT_STATE_ALWAYS_HIDDEN:當窗口得到焦點時,老是隱藏軟鍵盤輸入區域。
  • SOFT_INPUT_STATE_VISIBLE:在合適的時候顯示軟鍵盤輸入區域,例如,當用戶導航到當前窗口時。
  • SOFT_INPUT_STATE_ALWAYS_VISIBLE:當窗口得到焦點時,老是顯示軟鍵盤輸入區域。

固然,咱們也能夠經過代碼設置鍵盤模式。

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);複製代碼

四 窗口回調

Window裏定義了一個Callback接口,Activity實現了Window.Callback接口,將Activity關聯給Window,Window就能夠將一些事件交由Activity處理。

public interface Callback {

        //鍵盤事件分發
        public boolean dispatchKeyEvent(KeyEvent event);

        //觸摸事件分發
        public boolean dispatchTouchEvent(MotionEvent event);

        //軌跡球事件分發
        public boolean dispatchTrackballEvent(MotionEvent event);

        //可見性事件分發
        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);

        //建立Panel View
        public View onCreatePanelView(int featureId);

        //建立menu
        public boolean onCreatePanelMenu(int featureId, Menu menu);

        //畫板準備好時回調
        public boolean onPreparePanel(int featureId, View view, Menu menu);

        //menu打開時回調
        public boolean onMenuOpened(int featureId, Menu menu);

        //menu item被選擇時回調
        public boolean onMenuItemSelected(int featureId, MenuItem item);

        //Window Attributes發生變化時回調
        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);

        //Content View發生變化時回調
        public void onContentChanged();

        //窗口焦點發生變化時回調
        public void onWindowFocusChanged(boolean hasFocus);

        //Window被添加到WIndowManager時回調
        public void onAttachedToWindow();

        //Window被從WIndowManager中移除時回調
        public void onDetachedFromWindow();

         */
        //畫板關閉時回調
        public void onPanelClosed(int featureId, Menu menu);

        //用戶開始執行搜索操做時回調
        public boolean onSearchRequested();
    }複製代碼

五 窗口實現

Window是一個抽象類,它的惟一實現類是PhoneWindow,PhoneWindow裏包含了如下內容:

  • private DecorView mDecor:DecorView是Activity中的頂級View,它本質上是一個FrameLayout,通常說來它內部包含標題欄和內容欄(com.android.internal.R.id.content)。
  • ViewGroup mContentParent:窗口內容視圖,它是mDecor自己或者是它的子View。
  • private ImageView mLeftIconView:左上角圖標
  • private ImageView mRightIconView:右上角圖標
  • private ProgressBar mCircularProgressBar:圓形loading條
  • private ProgressBar mHorizontalProgressBar:水平loading條
  • 其餘的一些和轉場動畫相關的Transition與listener

看到這些,你們有沒有以爲很熟悉,這就是咱們平常開發中常常見到的東西,它在PhoneWindow裏被建立。另外,咱們在Activity裏常常調用的方法,它的實際實現也是
在PhoneWindow裏,咱們分別來看一看。

setContentView()

這是一個咱們很是熟悉的方法,只不過咱們一般是在Activity裏進行調用,可是它的實際實現是在PhoneWindow裏。

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //1. 若是沒有DecorView則建立它,並將建立好的DecorView賦值給mContentParent
            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 {
            //2. 將Activity傳入的佈局文件生成View並添加到mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //3. 回調Window.Callback裏的onContentChanged()方法,這個Callback也被Activity
            //所持有,所以它實際回調的是Activity裏的onContentChanged()方法,通知Activity
            //視圖已經發生改變。
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }    
}複製代碼

這個方法主要作了兩件事情:

  1. 若是沒有DecorView則建立它,並將建立好的DecorView賦值給mContentParent
  2. 將Activity傳入的佈局文件生成View並添加到mContentParent中
  3. 回調Window.Callback裏的onContentChanged()方法,這個Callback也被Activity所持有,所以它實際回調的是Activity裏的onContentChanged()方法,通知Activity視圖已經發生改變。

建立DecorView是經過installDecor()方法完成的,它的邏輯也很是簡單,就是建立了一個ViewGroup而後返回給了mDecor和mContentParent。

public class PhoneWindow extends Window implements MenuBuilder.Callback {

 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

  private void installDecor() {
         mForceDecorInstall = false;
         if (mDecor == null) {
             //生成DecorView
             mDecor = generateDecor(-1);
             mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
             mDecor.setIsRootNamespace(true);
             if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
             }
         } else {
             mDecor.setWindow(this);
         }
         if (mContentParent == null) {
             mContentParent = generateLayout(mDecor);
             ...
             } else {
                ...
             }
            ...
         }
     }

 protected ViewGroup generateLayout(DecorView decor) {
        //讀取並設置主題顏色、狀態欄顏色等信息
        ...
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        //設置窗口參數等信息
        ...
        return contentParent;
    }    
}複製代碼

經過以上這些流程,DecorView已經被建立並初始化完畢,Activity裏的佈局文件也被成功的添加到PhoneWindow的mContentParent(實際上就是DecorView,它是Activity的頂層View)
中,可是這個時候DecorView尚未真正的被WindowManager添加到Window中,它還沒法接受用戶的輸入信息和焦點事件,這個時候就至關於走到了Activity的onCreate()流程,界面還
未展現給用戶。

直到走到Activity的onResume()方法,它會調用Activity的makeVisiable()方法,DecorView才真正的被用戶所看到。

public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, WindowControllerCallback {

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
}複製代碼

一般以上的分析,咱們理解了setContentView的工做原理,另外還有addContentView、clearContentView,正如它們的名字那樣,setContentView是替換View,addContentView是添加View。實現原理相同。

好了,以上即是本篇文章的所有內容,下一篇文章咱們來分析WindowManager的內容,分析Window的添加、移除和更新的流程。

附錄

文章末尾給你們提供一個WindowUtils工具類。

import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.view.Surface;
import android.view.Window;
import android.view.WindowManager;

public final class WindowUtils {

    /** * Don't let anyone instantiate this class. */
    private WindowUtils() {
        throw new Error("Do not need instantiate!");
    }

    /** * 獲取當前窗口的旋轉角度 * * @param activity activity * @return int */
    public static int getDisplayRotation(Activity activity) {
        switch (activity.getWindowManager().getDefaultDisplay().getRotation()) {
            case Surface.ROTATION_0:
                return 0;
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
            default:
                return 0;
        }
    }

    /** * 當前是不是橫屏 * * @param context context * @return boolean */
    public static final boolean isLandscape(Context context) {
        return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
    }

    /** * 當前是不是豎屏 * * @param context context * @return boolean */
    public static final boolean isPortrait(Context context) {
        return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
    }
    /** * 調整窗口的透明度 1.0f,0.5f 變暗 * @param from from>=0&&from<=1.0f * @param to to>=0&&to<=1.0f * @param context 當前的activity */
    public static void dimBackground(final float from, final float to, Activity context) {
        final Window window = context.getWindow();
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(
                new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        WindowManager.LayoutParams params
                                = window.getAttributes();
                        params.alpha = (Float) animation.getAnimatedValue();
                        window.setAttributes(params);
                    }
                });
        valueAnimator.start();
    }
}複製代碼
相關文章
相關標籤/搜索