自定义控件高级UI

高级UI<第十一篇>:视图的摆放(onLayout)

2019-11-25  本文已影响0人  NoBugException

视图摆放,即自定义视图onLayout的实现,当自定义一个视图时,基本都会重写onMeasureonLayout以及onDraw这三个方法,本文的重点是onLayout

代码如下:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}

从源码角度上来说,当测量完毕之后,就开始视图的摆放操作,源码如下:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {

        //...(省略)

        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        //...(省略)

}
public void layout(int l, int t, int r, int b) {

        //...(省略)

        onLayout(changed, l, t, r, b);//最终执行视图的onLayout方法

        //...(省略)

}

最终执行View或者ViewGroup中的onLayout方法。

在源码中,获取view的宽高使用了以下两个方法

int measuredWidth = view.getMeasuredWidth()
int measuredHeight = view.getMeasuredHeight()

那么,为什么不建议在onMeasure中使用这种方法获取宽和高?在onLayout中又建议使用这种方法获取宽和高了呢?

这里需要理解三种获取view宽度和高度的方法,以及区别?

【方法一】

    MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
    宽:lp.width;
    高:lp.height;

getLayoutParams获取到数字是-1时,代表MATCH_PARENT,当getLayoutParams获取到的数字是-2时,代表WRAP_CONTENT,当getLayoutParams获取到的数据是固定值时,说明视图的宽或高也是固定值。

这种方法建议在onMeasure中使用,因为在执行setMeasuredDimension之前,还不清楚视图的大小。

【方法二】

    宽:getMeasuredWidth()
    高:getMeasuredHeight()

只有执行setMeasuredDimension之后,才能通过getMeasuredWidth()getMeasuredHeight()获取视图大小,所以,这种方法只能在setMeasuredDimension之后使用,在执行setMeasuredDimension方法之前只能使用【方法一】

这种方法,可以在onMeasure中使用,但需要注意的是,一定要在setMeasuredDimension之后使用;当然,这种方法可以直接在onLayout方法中使用,因为这时肯定已经测量完毕了。

【方法三】

    宽:getWidth()
    高:getHeight()

这两个方法我想大家都不会陌生,这种方式不能在onMeasure中去使用,因为只有视图摆放之后才能使用,在onLayout使用这个方法获取当前视图的大小是可以获取到的,因为当前视图早已被父视图摆放,但是,如果通过这个方法获取子视图的大小,就必须保证子视图已经被摆放。

那么,子视图该如何摆放呢?

    childView.layout(left, top, right, bottom);

以上代码就是摆放的核心代码了,如果在摆放前获取宽和高,获取的宽高必然为0,示例代码如下:

    childView.getWidth()
    childView.getHeight()
    childView.layout(left, top, right, bottom);

所以,使用【方法三】必须在摆放之后使用,示例代码如下:

    childView.layout(left, top, right, bottom);
    childView.getWidth()
    childView.getHeight()

说到这里,有关视图的摆放(onLayout)的知识点已经讲完了,下面将使用onMeasureonLayout实现横向布局纵向布局吧。

xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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:orientation="vertical"
    tools:context=".MainActivity">


    <com.vrv.viewdemo.MyCustomView
        android:id="@+id/ll_customview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_marginLeft="20dp"
        android:layout_centerHorizontal="true">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="11111"
            android:textSize="20sp"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮1"
            android:textSize="20sp"
            android:layout_marginTop="50dp"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮2"
            android:textSize="20sp"
            android:layout_marginTop="50dp"/>

    </com.vrv.viewdemo.MyCustomView>

</RelativeLayout>

【横向布局】

效果如下:

图片.png

代码如下:

public class MyCustomView extends ViewGroup {

    public MyCustomView(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //测量当前视图
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        //测量所有的子视图
        measureChildren(widthMeasureSpec, heightMeasureSpec);//测量所有的子布局

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        int width = 0;
        View child;
        //遍历所有的子视图
        for(int i=0;i<getChildCount();i++){
            //获取子视图
            child = getChildAt(i);
            //摆放
            child.layout(width, 0, width + child.getMeasuredWidth(), child.getMeasuredHeight());
            //计算下一个子视图摆放的高度
            width = width + child.getMeasuredWidth();
        }

    }
}

【纵向布局】

效果如下:

图片.png

代码如下:

public class MyCustomView extends ViewGroup {

    public MyCustomView(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //测量当前视图
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        //测量所有的子视图
        measureChildren(widthMeasureSpec, heightMeasureSpec);//测量所有的子布局

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        int height = 0;
        View child;
        //遍历所有的子视图
        for(int i=0;i<getChildCount();i++){
            //获取子视图
            child = getChildAt(i);
            //摆放
            child.layout(0, height, child.getMeasuredWidth(), height + child.getMeasuredHeight());
            //计算下一个子视图摆放的高度
            height = height + child.getMeasuredHeight();
        }

    }
}

[本章完...]

上一篇 下一篇

猜你喜欢

热点阅读