自定义 View 之探索 onLayout()、onMeasure() 方法及作用

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

  • 介绍

    onLayout()、onMeasure()这两个方法是我们自定义View的关键,也许你知道它是怎么使用,但不知道它为什么要这样使用?我们在看一些书籍和源码的时候,经常会看到它的出现,今天,我们就来讲解它在自定义View中起到的真正作用。

一、onLayout()

        

    原意:当此视图应该为其每个子视图分配大小和位置时,从布局调用。带有子类的派生类应该重写此方法,并在每个子类上调用布局。

    那么我通过一个简单的例子,来看看它起到哪些作用。我们新建一个MyViewGroup继承ViewGroup,然后默认不做任何处理。

/**
 * @Created by xww.
 * @Creation time 2018/8/15.
 */

public class MyViewGroup extends ViewGroup {

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }

    /**
     * 切换页面
     */
    public void scrollPage(int index) {
        scrollTo(index * getWidth(), 0);
    }
}

    我们在layout中引用我们的MyViewGroup控件,并对它进行视图添加。这里我们添加了三个子视图,分别是View1、View2和View3:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@mipmap/ic_launcher" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_bright">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher" />
</FrameLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="textview测试1"
        android:textSize="50sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="textview测试2"
        android:textSize="50sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="textview测试3"
        android:textSize="50sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="textview测试4"
        android:textSize="50sp" />
</LinearLayout>

    然后分别将View1、View2、View3添加进MyViewGroup,代码如下:

public class MainActivity extends AppCompatActivity {

    private MyViewGroup myView;
    private RadioGroup rg;

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

    private void initView() {
        myView = findViewById(R.id.myView);
        rg = findViewById(R.id.rg);

        View view1 = LayoutInflater.from(this).inflate(R.layout.view1, null);
        View view2 = LayoutInflater.from(this).inflate(R.layout.view2, null);
        View view3 = LayoutInflater.from(this).inflate(R.layout.view3, null);
        myView.addView(view1);
        myView.addView(view2);
        myView.addView(view3);

        for (int i = 0; i < 3; i++) {
            RadioButton radioButton = new RadioButton(this);
            if (i == 0) {
                radioButton.setChecked(true);
            }
            radioButton.setId(i);
            rg.addView(radioButton);
        }
        rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                myView.scrollPage(checkedId);
            }
        });
    }
}

    我们的主布局layout我做了一些处理,为了显得更加鲜明的对比,布局代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.x.earthquakeclient.MainActivity">

    <RadioGroup
        android:id="@+id/rg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal" />

    <com.example.x.earthquakeclient.MyViewGroup
        android:id="@+id/myView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

    那么运行一下项目,可以看到这样的效果:

    可以看到什么都没有,我们明明添加了三个子视图给它的,怎么跑哪去了?原因就是因为我们虽然重写onLayout()方法,但是没有做任何处理,相当于没做任何对子视图布局的操作,它当然显示不出来了。

    那么我修改MyViewGroup中的代码,在onLayout()方法中对子视图进行横向的一个布局操作:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
        }
    }

    那再次运行看看我们的效果吧,为了区别我给它设置了背景颜色!

    可以看到我们的子视图显示出来了,第一个页面ImageView显示的是没错了,因为我们只放了一个控件。但是问题来了,第二、第三个页面中的图片和文字跑哪去啦?原因是这样,在onLayout()方法中,它所布局的是最外层的一个父容器,所以我们的每一个父容器都可以显示出来,但是父容器里面的子视图就无法显示。既然这样,我们先放一边,来看看我们的另一个主角onMeasure()方法。

二、onMeasure()

    原意:测量视图及其内容,以确定测量的宽度和测量的高度。此方法由measure(int, int)调用,并且应该由子类覆盖,以提供其内容的准确和高效的度量。

    onMeasure()即测量视图和视图里的内容,我们用这个方法循环遍历出视图内容,进行依次测量来设置它的宽和高,所以我修改MyViewGroup的代码,重写一下onMeasure()这个方法,代码如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            childView.measure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    我再次运行项目,再来看看效果:

    现在,效果和我们预想的一样了。通过这个例子,我们进一步理解了onLayout()、onMeasure()这两个方法在自定义View中起到的真正作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值