高级UI<第十一篇>:视图的摆放(onLayout)
视图摆放,即自定义视图
onLayout
的实现,当自定义一个视图时,基本都会重写onMeasure
、onLayout
以及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)
的知识点已经讲完了,下面将使用onMeasure
和onLayout
实现横向布局
和纵向布局
吧。
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();
}
}
}
[本章完...]