• <html>


    前言:在Android 关于屏幕的一些事儿》中的最后提到了希望做一个屏幕适配的帮助类。这段时间我们内核组一个IOS的哥们在封装3D跨平台内核,也问起我一些关于Android屏幕的事情,身边都是大牛,越来越感觉自己小白了。

    一、 闲扯


        下面几篇博客想写写关于View的一些事情,为什么会有这个想法呢?是我之前想的一个简单的解决屏幕适配的方法,由xml布局填充为View终于都会通过layoutInflater的inflater方法:
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        ... ...
    }
        那么我给LayoutInflater类设置一个动态代理,然后监视到要运行inflate方法的时候把xml里面的数据更改一下就能够了。

    可是LayoutInflater是直接继承自Object,那么基于接口的动态代理肯定是没戏了,那么仅仅有通过基于子类的动态代理,于是把Java Web框架中经常使用的CGLib搞了过来:

    final LayoutInflater inflater = LayoutInflater.from(this);
    Enhancer.create(LayoutInflater.class, new MethodInterceptor() {
    
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable {
            Log.i("MainActivity", "LayoutInflaterde运行的方法名称:" + method.getName());
            return method.invoke(inflater, args);
    }
    
    });
        没想到已执行程序挂了,一查原因。Android生成的类文件和JDK生成的类文件不同。突然想起来非常初学Android的时候确实看到过。那这条路是走不通啦!

    所以还是好好缕缕View的思绪吧~

    二、Hello World


    简单回想下创建一个Activity的过程:

        1. 创建一个类直接或间接继承Activity

    public class MainActivity extends Activity {
    
    }

        2. 书写布局文件

    <?

    xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </RelativeLayout>

    3. 在Acticity的onCreate()方法中设置布局

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    4. 在Manifest清单文件里注冊该Activity

    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    执行效果例如以下:


    三、setContentView 分析


        对于以上几个步骤。我们早就烂熟于心,并且相信大家也肯定好奇过,为什么通过setContentView就能够显示布局文件呢?那么我们就从源代码的角度分析下:

    1. setContentView()显然是父类中的方法,它是怎样定义的呢?
    在Activity类中。我们看到该方法的定义:
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    2. 这里是调用的getWindow()返回对象的setContentView()方法,那么getWindow()返回的是什么呢?
    public Window getWindow() {
        return mWindow;
    }
    可见Window是Activity中的一个成员变量。

    3. Window是怎么初始化的?
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    
        ... ...
        mWindow = new PhoneWindow(this);
        ... ...
    }
    4. PhoneWindow 源代码中 setContentView
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
        首先推断 mContentParent是否为空,假设为空则初始化 decor。通过以往经验知道DecoredView是Activity View结构的根View,这里的installDecor()应该就是初始化了mContentParent;假设mContentParent不为空则删除全部子View;
        然后通过LayoutInflater将xml布局填充为View并载入到mContentView,关于LayoutInflater会在下一篇博客来详细分析。

        至此,Activity的setContentView() 方法的分析也就告一段落,大致总结下就是:
     1. 我们定义的Activity的setContentView()调用Activity的setContentView()方法。
     2. Activity的setContentView调用 PhoneWindow类中的setContentView()方法;
     3. PhoneWindow类中的setContentView() 将xml布局通过LayoutInflater填充为View布局并载入到根布局。

    四、installDecor 分析

     
       通过以上了解了xml布局填充为View并载入到Activity的根View上,那么这个根View是怎样创建的呢?
    1. PhoneWindow 两个重要成员变量
    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;
    通过凝视能够看出, mDecor即Activity的根View,mContentView就是我们放置Activity布局的父View,而且它是mDecor或者mDecor的子View。

    2. installDecor() 分析
    private void installDecor() {
        if (mDecor == null) {
            // 初始化 mDecor
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
        // 初始化 mContentView
        mContentParent = generateLayout(mDecor);
    
        // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
        mDecor.makeOptionalFitsSystemWindows();
    
        mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    // 设置 mTitleView显示内容
                    ... ...
                } else {
                    // 设置 mTitleView显示内容
                    ... ...
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                    // 设置 ActionBar
                    ... ...
                }
            }
        }
    }
    installDecor()主要进行了两个重要的事情,就是把mDecor和mContentView进行了初始化。

    (1)、通过generateDecor()初始化mDecor;(2)、通过generateLayout初始化mContentView。


    3. generateDecor 分析
    protected DecorView generateDecor() {
    	return new DecorView(getContext(), -1);
    }
    DecorView是PhoneWindow的一个继承自FrameLayout的内部类,这里创建了一个DecorView并赋值给了mDecor。

    4. generateLayout 分析

    protected ViewGroup generateLayout(DecorView decor) {
        TypedArray a = getWindowStyle();
    
        // 设置 当前Activity配置的主题theme
    	// 窗体是否是浮动的
    	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)) {
    		// Don't allow an action bar if there is no title.
    		requestFeature(FEATURE_ACTION_BAR);
    	}
    	// 窗体中ActionBar是否在布局空间内
    	if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
    		requestFeature(FEATURE_ACTION_BAR_OVERLAY);
    	}
    
    	if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
    		requestFeature(FEATURE_ACTION_MODE_OVERLAY);
    	}
    
    	if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
    		requestFeature(FEATURE_SWIPE_TO_DISMISS);
    	}
    	// 窗体是否全屏显示
    	if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
    		setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
    	}
    
    	... ...
       
    	//窗体状态栏颜色配置
    	if (!mForcedStatusBarColor) {
    		mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
    	}
    	//窗体导航栏颜色配置
    	if (!mForcedNavigationBarColor) {
    		mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
    	}
    
    	if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
    			>= android.os.Build.VERSION_CODES.HONEYCOMB) {
    		if (a.getBoolean(
    				R.styleable.Window_windowCloseOnTouchOutside,false)) {
    			setCloseOnTouchOutsideIfNotSet(true);
    		}
    	}
    
    	WindowManager.LayoutParams params = getAttributes();
    	//输入法配置
    	if (!hasSoftInputMode()) {
    		params.softInputMode = a.getInt(
    				R.styleable.Window_windowSoftInputMode,
    				params.softInputMode);
    	}
     
        if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, mIsFloating)) {
                /* All dialogs should have the window dimmed */
                if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                    params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
                }
                if (!haveDimAmount()) {
                    params.dimAmount = a.getFloat(
                            android.R.styleable.Window_backgroundDimAmount, 0.5f);
                }
            }
            //设置当前Activity的出现动画效果
            if (params.windowAnimations == 0) {
                params.windowAnimations = a.getResourceId(
                        R.styleable.Window_windowAnimationStyle, 0);
            }
     
            // The rest are only done if this window is not embedded; otherwise,
            // the values are inherited from our container.
            if (getContainer() == null) {
                if (mBackgroundDrawable == null) {
                    if (mBackgroundResource == 0) {
                        mBackgroundResource = a.getResourceId(
                                R.styleable.Window_windowBackground, 0);
                    }
                    if (mFrameResource == 0) {
                        mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                    }
                    mBackgroundFallbackResource = a.getResourceId(
                            R.styleable.Window_windowBackgroundFallback, 0);
                    if (false) {
                        System.out.println("Background: "
                                + Integer.toHexString(mBackgroundResource) + " Frame: "
                                + Integer.toHexString(mFrameResource));
                    }
                }
                mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
                mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
                mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
            }
    	}
    
    	// 为窗体加入 decor根布局
        // Inflate the window decor.
    
        int layoutResource;
        int features = getLocalFeatures();
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = com.android.internal.R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
             // If no other features and not embedded, only need a title.
             // If the window is floating, we need a dialog layout
             if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
             } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                 if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
                    layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
                 } else {
                    layoutResource = com.android.internal.R.layout.screen_action_bar;
                 }
             } else {
                layoutResource = com.android.internal.R.layout.screen_title;
             }
             // System.out.println("Title!");
             } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
                 layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
             } else {
                 layoutResource = com.android.internal.R.layout.screen_simple;
             }
    
             mDecor.startChanging();
    
    		 // 通过布局填充器LayoutInflater将layoutResource填充为布局。载入到decor上
             View in = mLayoutInflater.inflate(layoutResource, null);
             decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    
    		 // 对contentParent进行赋值
             ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
             if (contentParent == null) {
                 throw new RuntimeException("Window couldn't find content container view");
             }
    
             if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                ProgressBar progress = getCircularProgressBar(false);
                if (progress != null) {
                    progress.setIndeterminate(true);
                }
            }
    
        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
    	// 设置窗体的背景标题等
        if (getContainer() == null) {
                Drawable drawable = mBackgroundDrawable;
            if (mBackgroundResource != 0) {
                drawable = getContext().getResources().getDrawable(mBackgroundResource);
            }
            mDecor.setWindowBackground(drawable);
            drawable = null;
            if (mFrameResource != 0) {
                drawable = getContext().getResources().getDrawable(mFrameResource);
            }
            mDecor.setWindowFrame(drawable);
    
            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
    
            if (mTitle != null) {
                setTitle(mTitle);
            }
            setTitleColor(mTitleColor);
        }
    
        mDecor.finishChanging();
    
        return contentParent;
    }

    依据代码中的凝视大家能够清楚看到,主要是读取Activity的theme/feature配置。设置窗体,然后依据窗体的属性来选择相应的窗体修饰并填充为View载入到mDecor中,并对 contentParent赋值。

    依据配置选择layoutResource布局com.android.internal.R.layout.xxx,比方:com.android.internal.R.layout.screen_action_bar;

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="?android:attr/actionBarStyle">
            <com.android.internal.widget.ActionBarView
                android:id="@+id/action_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                style="?android:attr/actionBarStyle" />
            <com.android.internal.widget.ActionBarContextView
                android:id="@+id/action_context_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                style="?android:attr/actionModeStyle" />
        </com.android.internal.widget.ActionBarContainer>
        <FrameLayout android:id="@android:id/content"
            android:layout_width="match_parent" 
            android:layout_height="0dip"
            android:layout_weight="1"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?

    android:attr/windowContentOverlay" /> <LinearLayout android:id="@+id/lower_action_context_bar" android:layout_width="match_parent" android:layout_height="wrap_content" style="?

    android:attr/actionBarStyle" android:visibility="gone" /> </LinearLayout>

    布局主要分为了ActionBar和FrameLayout的布局。是不是有点熟悉这个FrameLayout的id为content。对头,我们在上面分析的mContentParent就是这个FrameLayout。例如以下图所看到的:


    五、总结


        依据以上分析。流程即例如以下图所看到的:
        
        下一篇《Android LayoutInflater源代码解析》博客将对LayoutInflater进行源代码分析。

img

xuehuayous

等级:

排名:千里之外

我的GitHub
文章分类
阅读排行
文章存档
评论排行
最新评论
img
  • 相关阅读:
    n皇后问题
    hdu 4911 Inversion and poj2299 [树状数组+离散化]
    离散化
    汉诺塔
    hdu 4027 Can you answer these queries?[线段树]
    开根号
    hdu 1069 Monkey and Banana 【动态规划】
    Linux系统下安装rz/sz命令及使用说明
    PHP获得指定日期所在月的第一天和最后一天
    PHP获得指定日期所在星期的第一天和最后一天
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7242138.html
  • Copyright © 2020-2023  润新知