Android轶事之View要去大保健?View大小自己决定?

-“爹,我要吃糖”
-“好哒儿子”
-“爹,我要吃包包”
- “好哒儿子”
- “爹,我要吃串串”
- “好哒儿子”
- “爹,我要大保健”
- (啪啪啪,三耳光)

儿子是一定要听爹话的,那么在Android世界里,是不是这样呢? 今天就来和大家讨论一下 View 父子之间的琐事。

大家都知道,儿子肯定有自己想做的事情,也就是有自己的思想。那么转换到Android的View上面呢,就是测量啦。View自己会测量自己,告诉父布局他自身有多大,要占多大空间。可儿子就能胆大妄为,想怎么样就怎么样吗,答案是否定的。

View的最终大小不是由自己决定的,而是由layout决定。

这里我们来做一个实验。

比如,新写一个矩形自定义view:

public class RectView extends View {
    private Paint mPaint;
    private int mWidth;
    private int mHeight;

    public RectView(Context context) {
        this(context, null);
    }

    public RectView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);

    }

    public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if(widthMode == MeasureSpec.AT_MOST){
            mWidth = 200;
        }


        if(heightMode == MeasureSpec.AT_MOST){
            mHeight = 200;

        }
        setMeasuredDimension(mWidth,mHeight);
        }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0,0,mWidth,mHeight,mPaint);
        super.onDraw(canvas);
    }
}

这里简单处理了一下 这个view的wrap_content时候的大小,写死为200px(注意是px不是dp)。
放到一个linearLayout下,看看效果

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.wingsofts.father.MainActivity">

   <com.wingsofts.father.RectView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content" />

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    <com.wingsofts.father.RectView

        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

这里写图片描述
可以看到,在wrap_content的情况下,两个view的大小跟自己预期的一样。(这种情况就是:“老爹,我想吃包子” “好哒儿子”)。

其实这篇文章的起源来与一个layout函数,大家都知道layout函数决定了一个view在什么位置。但是大家有没有想过,既然是先测量再layout的,为什么layout函数需要四个参数?既然测量知道view的大小了,那么只需要左上角x,y两个坐标不就好了吗?

layout(int l,int t,int r,int b);

难道测量的大小并不能真正决定view自己的大小吗?测量出来的值只是一个期望值,而不是最终的值吗,最终还是要听老爹话吗?我们来实验一下就知道了。(“老爹,我要大保健”)

自己新建一个viewgroup,继承自LinearLayout

public class MyLinear extends LinearLayout {
    public MyLinear(Context context) {
        this(context, null);
    }

    public MyLinear(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyLinear(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //调用父类方法
        super.onLayout(changed, l, t, r, b);
        //将第一个view布局到0,0,50,50位置
        View v = getChildAt(0);
        v.layout(0,0,50,50);
    }
}

这时候布局只是把LinearLayout改成MyLinear。
这里写图片描述
(儿贼,听说你要大保健,先来尝尝爹的大宝剑!咔咔咔嚓,给削小了)
这里写图片描述

呃呃呃。。。看来在android世界中,儿子也是要听老子的啊。一般的要求绝对满足,有非分之想,过不了老子那关啊。

  • 再来个彩蛋吧。。

话说,大家一定有过一个拿空白View(其实用Space标签比较好)去占一定大小的经历(损招,但是有时候很好用)。。

比如通常希望一个空白view占据50dp的宽度,高度写为wrap_content 。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wingsofts.father.MainActivity">


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <View
        android:layout_width="50dp"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

那么你就会发现。。
这里写图片描述

WTF??? 为毛是全屏的高度。我写的是wrap_content啊。。难道View的wrap_content就是全屏??

不要急。。我们来read the fxxking source code ..

直接定位到view的onMeasure方法

   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

额。。没啥东西呀,继续跳到getDefaultSize()

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:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

呃呃。。 问:小白为什么长得像他哥哥。

这下真像大白了:原来,在View自身wrap_content(也就是AT_MOST模式),switch的case下面是没有break的,也就是说,

如果View没有处理AT_MOST模式,那么wrap_content自动当成match_parent处理。

呃呃。。跑题了。。。 好吧,没啥说的了,写这篇文章就是告诉大家一个道理。儿子就得听爹的话。。。 拜拜。如果你喜欢我的博客,记得关注我。

posted on 2016-02-01 13:23  木鱼哥  阅读(141)  评论(0编辑  收藏  举报

导航