一、onLayout布局出洞
在上一篇文章自定义控件那些事儿 ------ 三【量测】中,为实现图片和文字组合控件的正确显示,就已经引入了onLayout实现的整体过程。
onLayout具体实现中,主要执行在layout()方法上。分别为left,top,right,bottom。实则是确定了当前控件的左上角和右下角,从而确定了当前控件展示的区域。所以,对一个View强制使用layout()方法,可以修改当前view所在的位置。在之后的属性动画实现中,就应用了这一特点。
达到理解整个布局过程作用,重写ViewGroup作为布局文件,其中内部子View均采用已有控件。从而避免较多的实现onMeasure()方法,导致整个过程的复杂。
二、初步实现
1,布局控件ViewGroup的实现
public class LayoutView extends ViewGroup {
public LayoutView(Context context) {
super(context);
}
public LayoutView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean b, int left, int top, int right, int bottom) {
int tempHeight = 0;
int mViewGroupWidth = getMeasuredWidth(); //当前ViewGroup的总宽度
int mPainterPosX = left; //当前绘图光标横坐标位置
int mPainterPosY = top; //当前绘图光标纵坐标位置
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int width = childView.getMeasuredWidth();
int height = childView.getMeasuredHeight();
if (height > tempHeight) {
tempHeight = height;
}
//如果剩余的空间不够,则移到下一行开始位置
if (mPainterPosX + width > mViewGroupWidth) {
mPainterPosX = left;
mPainterPosY += tempHeight;
}
//执行ChildView的绘制
childView.layout(mPainterPosX, mPainterPosY, mPainterPosX + width, mPainterPosY + height);
//记录当前已经绘制到的横坐标位置
mPainterPosX += width;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
}
模仿LinearLayout的实现,做一个内部包含多个控件的控件。从左向右开始排列。当行距不够时,切换下一行显示。
注意点:
在类中使用tempHeight变量,用于记录当前行View的最高高度。换行以后的View只能在这个View之下,防止View之间的叠加。在换行中,要将tempHeight归零,第二行的高度不再以第一行的高度为最高值。
布局中使用:
<?xml version="1.0" encoding="utf-8"?>
<com.future.layoutdemo.view.LayoutView 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"
tools:context="com.future.layoutdemo.MainActivity">
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorAccent" />
<View
android:layout_width="180dp"
android:layout_height="130dp"
android:background="@color/colorPrimaryDark" />
<View
android:layout_width="30dp"
android:layout_height="80dp"
android:background="@color/colorPrimary" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent" />
<View
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@color/colorPrimary" />
</com.future.layoutdemo.view.LayoutView>
展示效果:
三、内容完善
在以上的实现中,添加margin值是没有效果的,当然不符合使用要求了!
1,修改控件实现,添加布局LayoutParams
在布局中优化布局计算,添加margin值影响。
public class Layout2View extends ViewGroup {
public Layout2View(Context context) {
super(context);
}
public Layout2View(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Layout2View(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean b, int left, int top, int right, int bottom) {
int tempHeight = 0;
int mViewGroupWidth = getMeasuredWidth(); //当前ViewGroup的总宽度
int mViewGroupHeight = getMeasuredHeight(); //当前ViewGroup的总高度
int mPainterPosX = left; //当前绘图光标横坐标位置
int mPainterPosY = top; //当前绘图光标纵坐标位置
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int width = childView.getMeasuredWidth();
int height = childView.getMeasuredHeight();
if (height > tempHeight) {
tempHeight = height;
}
Layout2View.LayoutParams margins = (Layout2View.LayoutParams) (childView.getLayoutParams());
//ChildView占用的width = width+leftMargin+rightMargin
//ChildView占用的height = height+topMargin+bottomMargin
//如果剩余的空间不够,则移到下一行开始位置
if (mPainterPosX + width + margins.leftMargin + margins.rightMargin > mViewGroupWidth) {
mPainterPosX = left;
// mPainterPosY += height + margins.topMargin + margins.bottomMargin;
mPainterPosY += tempHeight + margins.topMargin + margins.bottomMargin;
}
//执行ChildView的绘制
childView.layout(mPainterPosX + margins.leftMargin, mPainterPosY + margins.topMargin, mPainterPosX + margins.leftMargin + width, mPainterPosY + margins.topMargin + height);
mPainterPosX += width + margins.leftMargin + margins.rightMargin;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new Layout2View.LayoutParams(getContext(), attrs);
}
}
2,使用修改
<?xml version="1.0" encoding="utf-8"?>
<com.future.layoutdemo.view.Layout2View 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"
tools:context="com.future.layoutdemo.MainActivity">
<View
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="20dp"
android:background="@color/colorAccent" />
<View
android:layout_width="180dp"
android:layout_height="130dp"
android:background="@color/colorPrimaryDark" />
<View
android:layout_width="30dp"
android:layout_height="80dp"
android:background="@color/colorPrimary" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:background="@color/colorAccent" />
<View
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@color/colorPrimary" />
</com.future.layoutdemo.view.Layout2View>
布局中添加margin值,展示效果如下:
慢慢的都会成长起来的,不管经历什么,不管有多少伤痛,你都能习惯的展示云淡风轻的微笑。
天塌下来,塞满食物的嘴里,再加一点鸡腿!