Android高级UI(一),UI绘制流程及原理

会讲到自定义View的时候为什么要重写onMeasure()方法。

一  用户的R.layout.activty_xxx.xml加载到window的流程

首先系统会创建一个顶层容器DecorView,是继承framelayout的viewgroup。 DecorView是Phonewindow.java持有的一个实例。

在系统内部帮我们初始化好了DecorView对象, 然后根据activity主题的不同而加载不同的screen_xxx.xml, 比如NoActionBar, DarkActionBar等。

不管是哪个screen_xxx.xml, 都会有一个一样的Framelayout, id是 com.android.internal.R.id.content

在顶层布局中加载基础布局Framelayout,

开发者通过activity的setContentView(), 把activity_xxx_layout.xml添加到ContentView中。再将ContentView添加到基础布局中的Framelayout中。

在这个路径下面:

C:\Users\xxx\AppData\Local\Android\sdk\platforms\android-28\data\res\layout\

有几个screen的xml文件,分别是:

screen.xml

screen_action_bar.xml

screen_custom_title.xml

screen_progress.xml

screen_simple.xml

screen_simple_overlay_action_mode.xml

screen_swipe_dismiss.xml

screen_title.xml

screen_title_icons.xml

screen_toolbar.xml

最终加载哪里一个screen文件,是根据Activty的theme来的。

以screen_simple为例:

<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/layout/screen_simple.xml

This is an optimized layout for a screen, with the minimum set of features
enabled.
-->

<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>

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

protected ViewGroup generateLayout(DecorView decor) {

...

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

...

}

我们的acitvity_xxx_layout.xml是加载到srceen.xml的FrameLayout里面的。

@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) {
        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();
    }
    mContentParentExplicitlySet = true;
}

对window的颜色,背景等修改,其实际上就是对DecorView修改。而且是通过设置Window Style方法, 在 PhoneWindow会专门读取style的值,并且相应的设置。

二, 绘制view的流程:

1. WindowManagerGlobal的作用域是整个App。 它缓存了整个App的Activity的DecorView对象,以及ViewRootImpl对象。

2. WindowManagerImpl是对WindowManagerGlobal操作的封装。它的addView RemoveView UpdateView 都是针对WindowManagerGlobal缓存的Activity DecorView对象

3. 在主线程ActivityThread中, 接到resume acticty的hanlder message, 也就是 RESUME_ACTIVITY。

4. 在ActivityThread.handleResumeActivity()方法中调用, 

ViewManager wm = a.getWindowManager();
wm.addView(decor, l);

5. 最终又调用到windowManagerGlobal.addView();

6. 在windowManagerGlobal里面生产ViewRootImpl. 添加顶层view, 也就是decor view。

7. 最后在ViewRootImpl里进行三大步骤。

  1. onMeasure()

关于onMeasure(), 必须搞清楚MeasureSpec的含义。

View = 模式 + 尺寸->MeasureSpec  32位int值

00000000000000000000000000000000

MODE_MASK : 11000000000000000000000000000000

~MODE_MASK: 00111111111111111111111111111111

SpecMode(前2位)   +  SpecSize(后30)


    public static final int UNSPECIFIED = 0 << MODE_SHIFT; 00000000000000000000000000000000

父容器不对View做任何限制,系统内部使用

       
    public static final int EXACTLY     = 1 << MODE_SHIFT; 01000000000000000000000000000000
父容器检测出View的大小,Vew的大小就是SpecSize LayoutPamras match_parent 固定大小

    
    public static final int AT_MOST     = 2 << MODE_SHIFT; 10000000000000000000000000000000
父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras wrap_content


mode + size --> MeasureSpec
MeasureSpec = mode + size


 

以FrameLayout的onMeasure为例。如果FrameLayout的layout_width和layout_height都是wrap_content.

那么FrameLayout需要知道子控件的最大宽和最大高, 才能确定FrameLayout自身的宽高。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;

//确定子控件的最大宽和最大高

  for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        

//还有一些padding的设置
        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

//以及最大最小宽高的设置       

// Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

//最后通过setMeasuredDimension确定自己的真实宽高

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
                        
    }
 

很重要的一点:

ViewGroup  measure --> onMeasure(测量子控件的宽高)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
View measure --> onMeasure --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)


在View的默认OnMeasure()方法中,getDefaultSize()获取到的值就是自身的宽高,match_parent == wrap_content 的情况是一样的,所以自定义View时需要重写onMeasure()。以动态确定你想要的Size.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST://match_parent == wrap_content 的情况是一样的,所以自定义View时需要重写onMeasure()。
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值