美文网首页安卓开发博客Android开发经验谈
Android布局其实很简单,就两步

Android布局其实很简单,就两步

作者: 我叫陆大旭 | 来源:发表于2018-07-25 14:47 被阅读211次
Android布局其实很简单,就两步

最近做了几个自定义布局,就想扯扯Android布局的这些内容

Android布局其实只有两个步骤

第一步,测量各个View的大小。
第二步,把View布局到指定的位置。

测量


先记住测量的起点是View的measure()方法,然后在measure()方法调用onMeasure()方法来自己测量。
所以主要牵涉的二个方法,measure()和onMeasure()。

我们先看一下FrameLayout代码。假设FrameLayout要求被自我测量,那么它就会调用onMeasure()。

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

        ......
//获取子View,告诉他们“你们要调用自己测量了。调用方法measureChildWithMargins()”
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ....
            }
        }
......
//等到所有的子View测量完毕,就进行设置自己的大小;如果有需要也可能会进行二次测量
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    }
//ViewGroup.class
//
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        ......
//父亲让子类测量时,调用的就是View的measure()。这里传的两个参数就是父类安排子类的大小
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
//View.class
//
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);

        if (forceLayout || needsLayout) {
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
......
//子类进行自我测量中...
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {       
            }
        }
    }

这里的FrameLayout是继承了ViewGroup。

  • View中的onMeasure()主要是用来计算自己的尺寸。
  • ViewGroup中的onMeasure()主要是用来调用子类的measure()进行自我测量。同时计算自己的尺寸。

在onMeasure()自己计算尺寸的时候一定要注意是是需要要符合父类的限制的,这个限制就是onMeasure()方法中传入的widthMeasureSpec和heightMeasureSpec。这两个参数中存储着两个信息,大小和限制的类型。

获取大小和限制类型的方式

final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);

限制的类型一个是三个

MeasureSpec.AT_MOST:老爸告诉你,你只能这么大
MeasureSpec.EXACTLY:老爸告诉你,你就这么大,不能讨教还价
MeasureSpec.UNSPECIFIED:老爸告诉你,你想这么着就怎么着

如何设置当前View的尺寸

一般计算完当前View的尺寸后可以通过调用resolveSize()或resolveSizeAndState()方法计算出当前的大小用setMeasuredDimension

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

来,我们举几个例子

1.如果我希望ImageView一直是方

public class SquareImageView extends ImageView {
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //先测量出大小
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //然后根据测量的结果重新设置大小
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        if (width > height)
            setMeasuredDimension(width,width);
        else
            setMeasuredDimension(height,height);
    }
}

第一步,在调用super.onMeasure()方法后其实已经测量出当前View的大小。
第二步,为了使ImageView变成方的又进行了高宽。
第三步,调用setMeasuredDimension(),重新设置了测量的大小。

2.如果就是想自己计算

public class CalculationView extends View {
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //去掉super.onMeasure()方法
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //通过一顿计算得出当前View的大小
        int width = ......
        int height = ......
        width = resolveSize(width,widthMeasureSpec);
        height = resolveSize(height,heightMeasureSpec);
        setMeasuredDimension(width,height);
    }
}

如果是纯粹的自己重新计算大小,就不需要调用super.onMeasure()。
第一步,计算自己应该有的大小。
第二步,用resolveSize方法结合父类的限制获取大小。
第三步,调用setMeasuredDimension()设置大小。

3.如果是对ViewGroup进行计算一般的流程

public class CalculationView extends ViewGroup {
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for (int i = 0; i < count; i++){
            View view  = getChildAt(i);
            LayoutParams lp =  view.getLayoutParams();
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            /**
             * 进行一顿就算操作得出各种结论
             * 比如,最宽的是多宽,最高的是多高等信息
             */
        }

        /**
         * 设置当前View的大小
         */
        setMeasuredDimension(measuredWidth,measuredHeight);

        for (int i=0;i<count;i++) {
            /**
             *
             * 然后通过自己的widthMode,widthSize,heightMode,heightSize和lp进行各种计算
             *
             * 比如最终确定高是height,宽是width。
             *
             */
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);
            int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);

            view.measure(childWidthMeasureSpec,childHeightMeasureSpec);
        }
    }
}

在继承ViewGroup的类中比继承View的类多一个让子类调用measure()的步骤
第一步,获取子类的大小,结合自己布局的规则进行测量和保存必要的数据。
第二步,通过第一步的计算已经知道了自己的大小,用setMeasuredDimension()方法进行设置。
第三步,对各个子类进行限制并且调用view.measure()方法。


黄金分割线

就上面所有的内容只是测量,仅仅是测量,还没有到真正布局的步骤。
黄金分割线下就是布局了。


布局

先记住布局的起点是View的layout()方法,然后layout()方法中会调用onLayout()方法。

我们先看看FrameLayout代码,假设要执行布局就会调用onLayout()方法。

//FrameLayout.class
//
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
......
//获取测量出来的大小
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
......
         //对各子View进行布局操作
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
//View.class
//
public void layout(int l, int t, int r, int b) {

//设置布局的位置和大小
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//对子类布局进行计算
        onLayout(changed, l, t, r, b);
    }
}
//View.class
//
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {

//设置View的位置大小
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        
    }
    return changed;
}

布局的逻辑其实很简单,就是递归调用onLayout()和layout()方法
第一步,在onLayout方法中进行必要的计算,主要是针对当前布局。
第二步,获取每一个View的LayoutParams的设置参数和大小,一顿计算和操作。
第三步,调用子View的layout()方法,调用对View位置起作用的就是setFrame()方法
第四步,继续调用子类的onLayout()方法。

在View的onLayout()方法是空的,ViewGroup的onLayout是一个抽象方法。

总结


测量

  • 测量涉及measure()和onMeasure()方法。
  • measure()主要的作用就是优化和调用onMeasure()方法。
  • 重要的是onMeasure()方法。根据不同需求在方法中一顿计算、测量、设置测量后的大小并且可能会调用子类的measure()方法。
  • 记住MeasureSpec类和里面的方法、参数。记住View.resolveSize()。

布局

  • 布局涉及layout()和onLayout()方法。
  • layout()主要作用就是设置当前View的位置和调用onLayout()方法。
  • 重要的是onLayout()方法。根据不同布局需求利用子类的LayoutParams参数和大小在方法中一顿计算,然后调用子类的layout()方法。

我叫陆大旭。

一个懂点心理学的无聊程序员大叔。
看完文章无论有没有收获,记得打赏、关注和点赞!

相关文章

  • Android布局其实很简单,就两步

    最近做了几个自定义布局,就想扯扯Android布局的这些内容 Android布局其实只有两个步骤 第一步,测量各个...

  • Android性能优化(二)布局优化

    布局优化的主要思想其实很简单,就是尽量减少布局文件的层级,布局层级少了Android绘制时的工作量就少了,程序运行...

  • Android 安卓手机及平板虚拟键盘遮住底部导航栏问题

    其实很简单,一行代码就搞定了,在你的父布局里加这样一行代码 android:fitsSystemWindows="...

  • Android 布局优化

    Android 的布局优化其实是很简单的事情,不要因为看到优化两个字就觉得是很高级的内容,其实只要你熟悉之后,就会...

  • Android性能优化

    布局优化 尽量减少布局文件的层级,这个道理很简单,布局中的层级少了,Android绘制的工作量就少了,程序的性能自...

  • Android的布局优化

    Android的布局优化思想很简单,就是尽可能减少布局文件的层级,布局层级少了意味着 Android 绘制时工作量...

  • Android事件分发机制和触摸反馈

    前言 首先,Android系统为什么会定义一套事件分发机制?其实很简单,因为我们的布局里面View,ViewGro...

  • 用这三个方法手把手带你了解何为 Android 性能优化?

    Android 性能优化的三个方法 布局优化 布局优化的思想很简单,就是尽量减少布局文件的层级,布局中的层级少了,...

  • Android性能优化-布局优化

    一、删除无用控件和层级 布局优化的思想很简单,就是尽量减少布局文件的层级,布局层级少了,那么Android绘制时的...

  • 布局优化

    布局优化的思想很简单,就是尽量减少布局文件的层级,这个道理很浅显的,布局中的层级少了,这就意味着Android绘制...

网友评论

    本文标题:Android布局其实很简单,就两步

    本文链接:https://www.haomeiwen.com/subject/oxsjmftx.html