Android Layout inflate分析(2) - ViewGroup对象的构造

简介: inflate系列第2章出炉,工具已经齐备,下面开始介绍材料。首先我们从ViewGroup中的LayoutParams开始打牢基础,这是个只有宽和高两个参数的简单类,通过它我们把TypedArray,Theme之类的概念搞清楚,为将来进入更复杂的布局做准备。

UI控件

ViewGroup

ViewGroup实现了ViewManager和ViewParent两个接口。

@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

ViewManager

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

在inflate的过程中,主要用到的是构造和addView。第一个View参数不用说了,要加入的子View。另外一个重要的参数是ViewGroup.LayoutParams. 这个参数的主要用途是指定子View的位置。

ViewGroup.LayoutParams

ViewGroup.LayoutParams的基本属性

作为一个基本类,它的主要作用是指定子View的宽和高。
除了直接指定大小之外,它还接受两个值:MATCH_PARENT(老的名字叫FILL_PARENT)和WRAP_CONTENT. 这大家都太熟悉了,就不多说了。

下面抽象一下,常量和宽高,是我们熟悉的部分。

6770    public static class LayoutParams {
...
6777        @SuppressWarnings({"UnusedDeclaration"})
6778        @Deprecated
6779        public static final int FILL_PARENT = -1;
...
6786        public static final int MATCH_PARENT = -1;
...
6793        public static final int WRAP_CONTENT = -2;
...
6804        public int width;
6815        public int height;

下面是布局动画的,先放在这里,用到再说。

        /**
         * Used to animate layouts.
         */
        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;

ViewGroup.LayoutParams的构造方法

别看下面都是又是主题,又是绕来绕去的高大上方法。本质上,ViewGroup.LayoutParams就是宽和高两个域。这两个值赋正确了,其它的就都不用管。值可以是具体的pixel值,也可以是MATCH_PARENT或者WRAP_CONTENT两个常量。

我们把代码中的几个构造方法的顺序调整一下,先看说人话的。
第一个是最正宗的赋值型构造,两个值一赋就OK。

public LayoutParams(int width, int height) {
    this.width = width;
    this.height = height;
}

再看下拷贝构造方法:

/**
 * Copy constructor. Clones the width and height values of the source.
 *
 * @param source The layout params to copy from.
 */
public LayoutParams(LayoutParams source) {
    this.width = source.width;
    this.height = source.height;
}

然后再看说文言的,这个得转几道手,看几个其它类的方法:

6840        public LayoutParams(Context c, AttributeSet attrs) {
6841            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
6842            setBaseAttributes(a,
6843                    R.styleable.ViewGroup_Layout_layout_width,
6844                    R.styleable.ViewGroup_Layout_layout_height);
6845            a.recycle();
6846        }

首先来看这个Context.obtainStyledAttributes,从主题中读取值。先获取当前上下文的主题,然后调用主题类的obtainStyledAttributes.

530    public final TypedArray obtainStyledAttributes(
531            AttributeSet set, @StyleableRes int[] attrs) {
532        return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
533    }

我们移步/frameworks/base/core/java/android/content/res/Resources.java,看看Theme中的obtainStyledAttributes的实现,我们删节一下,一共也没几句逻辑:

1593        public TypedArray obtainStyledAttributes(AttributeSet set,
1594                @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
1595            final int len = attrs.length;
1596            final TypedArray array = TypedArray.obtain(Resources.this, len);
1597
...
1602            final XmlBlock.Parser parser = (XmlBlock.Parser)set;
1603            AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1604                    parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
1605
1606            array.mTheme = this;
1607            array.mXml = parser;
...
1638            return array;
1639        }

然后我们转战TypedArray.obtain:

43    static TypedArray obtain(Resources res, int len) {
44        final TypedArray attrs = res.mTypedArrayPool.acquire();
45        if (attrs != null) {
46            attrs.mLength = len;
47            attrs.mRecycled = false;
48
49            final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
50            if (attrs.mData.length >= fullLen) {
51                return attrs;
52            }
53
54            attrs.mData = new int[fullLen];
55            attrs.mIndices = new int[1 + len];
56            return attrs;
57        }
58
59        return new TypedArray(res,
60                new int[len*AssetManager.STYLE_NUM_ENTRIES],
61                new int[1+len], len);
62    }

得到了TypedArray结果之后,再通过setBaseAttributes将值设置好。上面已经反复强调了,在ViewGroup.LayoutParams一共就只有宽和高两个参数,不管怎么复杂地折腾,最终落实的一定是这两个值。

6881        /**
6882         * Extracts the layout parameters from the supplied attributes.
6883         *
6884         * @param a the style attributes to extract the parameters from
6885         * @param widthAttr the identifier of the width attribute
6886         * @param heightAttr the identifier of the height attribute
6887         */
6888        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
6889            width = a.getLayoutDimension(widthAttr, "layout_width");
6890            height = a.getLayoutDimension(heightAttr, "layout_height");
6891        }

MarginLayoutParams

ViewGroup.LayoutParams只有宽和高两个参数,简单是极简了。下面我们给它周围加个白边。一共6个变量,上下左右4个边距,加上起始和结束2个边距。

6969    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
6970        /**
6971         * The left margin in pixels of the child. Margin values should be positive.
6972         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6973         * to this field.
6974         */
6975        @ViewDebug.ExportedProperty(category = "layout")
6976        public int leftMargin;
6977
6978        /**
6979         * The top margin in pixels of the child. Margin values should be positive.
6980         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6981         * to this field.
6982         */
6983        @ViewDebug.ExportedProperty(category = "layout")
6984        public int topMargin;
6985
6986        /**
6987         * The right margin in pixels of the child. Margin values should be positive.
6988         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6989         * to this field.
6990         */
6991        @ViewDebug.ExportedProperty(category = "layout")
6992        public int rightMargin;
6993
6994        /**
6995         * The bottom margin in pixels of the child. Margin values should be positive.
6996         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6997         * to this field.
6998         */
6999        @ViewDebug.ExportedProperty(category = "layout")
7000        public int bottomMargin;
7001
7002        /**
7003         * The start margin in pixels of the child. Margin values should be positive.
7004         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
7005         * to this field.
7006         */
7007        @ViewDebug.ExportedProperty(category = "layout")
7008        private int startMargin = DEFAULT_MARGIN_RELATIVE;
7009
7010        /**
7011         * The end margin in pixels of the child. Margin values should be positive.
7012         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
7013         * to this field.
7014         */
7015        @ViewDebug.ExportedProperty(category = "layout")
7016        private int endMargin = DEFAULT_MARGIN_RELATIVE;

ViewGroup的构造

前三个都是陪太子读书的,一共是4个参数,前三个是给1个参数,2个参数,3个参数时其它给空参数时的调用。

560    public ViewGroup(Context context) {
561        this(context, null);
562    }
563
564    public ViewGroup(Context context, AttributeSet attrs) {
565        this(context, attrs, 0);
566    }
567
568    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
569        this(context, attrs, defStyleAttr, 0);
570    }

其余就下面这一个,它一共有3步,我们分别分析。

572    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
573        super(context, attrs, defStyleAttr, defStyleRes);
574        initViewGroup();
575        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
576    }

initViewGroup

这个好,基本上都是设一些属性

582    private void initViewGroup() {
583        // ViewGroup doesn't draw by default
584        if (!debugDraw()) {
585            setFlags(WILL_NOT_DRAW, DRAW_MASK);
586        }
587        mGroupFlags |= FLAG_CLIP_CHILDREN;
588        mGroupFlags |= FLAG_CLIP_TO_PADDING;
589        mGroupFlags |= FLAG_ANIMATION_DONE;
590        mGroupFlags |= FLAG_ANIMATION_CACHE;
591        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
592
593        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
594            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
595        }
596
597        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
598
599        mChildren = new View[ARRAY_INITIAL_CAPACITY];
600        mChildrenCount = 0;
601
602        mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
603    }

initFromAttributes

605    private void initFromAttributes(
606            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

又到了我们熟悉的context.obtainStyledAttributes,下面就是分门别类放东西,就不多说了。

607        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
608                defStyleRes);
609
610        final int N = a.getIndexCount();
611        for (int i = 0; i < N; i++) {
612            int attr = a.getIndex(i);
613            switch (attr) {
614                case R.styleable.ViewGroup_clipChildren:
615                    setClipChildren(a.getBoolean(attr, true));
616                    break;
617                case R.styleable.ViewGroup_clipToPadding:
618                    setClipToPadding(a.getBoolean(attr, true));
619                    break;
620                case R.styleable.ViewGroup_animationCache:
621                    setAnimationCacheEnabled(a.getBoolean(attr, true));
622                    break;
623                case R.styleable.ViewGroup_persistentDrawingCache:
624                    setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
625                    break;
626                case R.styleable.ViewGroup_addStatesFromChildren:
627                    setAddStatesFromChildren(a.getBoolean(attr, false));
628                    break;
629                case R.styleable.ViewGroup_alwaysDrawnWithCache:
630                    setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
631                    break;
632                case R.styleable.ViewGroup_layoutAnimation:
633                    int id = a.getResourceId(attr, -1);
634                    if (id > 0) {
635                        setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
636                    }
637                    break;
638                case R.styleable.ViewGroup_descendantFocusability:
639                    setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
640                    break;
641                case R.styleable.ViewGroup_splitMotionEvents:
642                    setMotionEventSplittingEnabled(a.getBoolean(attr, false));
643                    break;
644                case R.styleable.ViewGroup_animateLayoutChanges:
645                    boolean animateLayoutChanges = a.getBoolean(attr, false);
646                    if (animateLayoutChanges) {
647                        setLayoutTransition(new LayoutTransition());
648                    }
649                    break;
650                case R.styleable.ViewGroup_layoutMode:
651                    setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
652                    break;
653                case R.styleable.ViewGroup_transitionGroup:
654                    setTransitionGroup(a.getBoolean(attr, false));
655                    break;
656                case R.styleable.ViewGroup_touchscreenBlocksFocus:
657                    setTouchscreenBlocksFocus(a.getBoolean(attr, false));
658                    break;
659            }
660        }
661
662        a.recycle();
663    }

ViewGroup的父类 - View类

@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

这个类又是一个要求UiThread的类.

View类的构造方法,就是被ViewGroup类用super调用的那个,我们省略一些细节,看看它的结构:

3897    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
3898        this(context);
3899
3900        final TypedArray a = context.obtainStyledAttributes(
3901                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
3902
...
3948
3949        final int N = a.getIndexCount();
3950        for (int i = 0; i < N; i++) {
3951            int attr = a.getIndex(i);
3952            switch (attr) {
3953                case com.android.internal.R.styleable.View_background:
3954                    background = a.getDrawable(attr);
3955                    break;
...

我们把细节省略掉之后,跟ViewGroup刚才我们看到的构造函数已经非常像了。还是获取一个TypedArray对象,然后分门别类处理这些属性。

目录
相关文章
|
2月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
203 4
|
2月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
1月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
32 8
|
3月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
93 15
Android 系统缓存扫描与清理方法分析
|
2月前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
44 1
|
JavaScript 前端开发 Java
《Android游戏开发详解》一3.1 构造方法
通过回顾第2章中的重要概念并且做一些小的修改,我们可以更容易地进入较为复杂的主题。首先创建一个名为Constructors的项目,并且创建一个World类,如程序清单3.1所示。
1299 0
|
2月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
1月前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
53 19
|
1月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
60 14