自定义ViewGroup-第五大布局TagLayout
目录
View的绘制流程
View的绘制流程都是无非就是onMeasure()、onDraw()、onLayout()三个方法,现在具体记录下这三个方法的作用。
面试【View的绘制流程】
第一步 performMeasure():用于指定和测量layout中所有控件的宽高,对于ViewGroup,先去测量里面的子孩子,根据子孩子的宽高再来计算和指定自己的宽高
对于View,它的宽高是由自己和父布局决定的。这句话的意思是其MeasureSpec由父容器的MeasureSpec和自身的layoutParams来共同决定的,当View是固定宽/高,父容器的MeasureSpec是什么,它的SpecMode的值都是EXACTLY。当view宽/高是Match_parent,父容器specMode的值是什么它就是啥,且大小不会超过父容器的剩余空间。当View宽/高是wrap_content,view的specMode总是AT_MOST,且大小不会超过父容器的剩余空间。
第二步 performLayout():用于摆放子布局,for循环所有子view,用child.layout()摆放ChildView,前提是控件不是GONE。View->layout()->onLayout()
第三步 PerformDraw():用于绘制自己还有子View,对于ViewGroup首先绘制自己的背景,for循环绘制子view调用子view的draw()方法,对于view绘制自己的背景,绘制自己现实的内容(TextView设置 background)
View->draw()->drawBackground();//画背景 onDraw(canvas);// 画自己 ViewGroup 默认情况下不会调用
dispatchDraw(canvas);// 画子View 不断的循环调用子View的 draw()
Tips
1.如果要获取Vie的高度,前提肯定需要调用测量方法,测量完毕之后才能获取宽高。
2.View的绘制流程是在onResume()之后才开始。
3.addView()、setVisibility()等等会调用requestLayout();重新走一遍View的绘制流程。
4.在onDraw()不要布局嵌套。
自定义ViewGroup - 流式布局 TagLayout
2.1 onMeasure()根据子View指定ViewGroup的宽高
private List<List<View>> mChildViews=new ArrayList<>();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//清空集合
mChildViews.clear();
//1.1.1 for循环测量子view
int childCount=getChildCount();
//获取手机屏幕宽度
int width=MeasureSpec.getSize(widthMeasureSpec);
//ViewGroup的高度不需要占满屏幕 根据子View来就行
int height=getPaddingTop()+getPaddingBottom();
//paddingLeft内边距 marginleft外边距
//lineWidth表示在一行中所占的宽度 该变量主要为了控制换行
int lineWidth=getPaddingLeft();
//子view高度不一致的情况下
int maxheight=0;
//有几个ArrayList说明有几行 换行拿到paddingLeft
//在拿到不同的Arraylist存的childView进行摆放
//这个ArrayList放不需要换行的控件
ArrayList<View> childViews=new ArrayList<>();
mChildViews.add(childViews);
for(int i=0;i<childCount;i++){
View childView=getChildAt(i);
//这段话执行之后就可以获取子view的宽高,因为会调用子view的onMeasure
measureChild(childView,widthMeasureSpec,heightMeasureSpec);
//只有通过系统的MarginLayoutParams才能得到LeftMargin和RightMargin
//ViewGroup.LayoutParams得不到LeftMargin和RightMargin
//LinearLayout.LayoutParams有所有需要的margin值
ViewGroup.MarginLayoutParams params=(MarginLayoutParams) childView.getLayoutParams();
//得到宽高
if(lineWidth+(childView.getMeasuredWidth()+params.leftMargin+params.rightMargin)>width){
//换行,累加高度,加上上一行条目中最大的高度
height+=childView.getMeasuredHeight()+params.bottomMargin+params.topMargin;
lineWidth=childView.getMeasuredWidth()+params.rightMargin+params.leftMargin;
//这个ArrayList放需要换行的控件
childViews=new ArrayList<>();
mChildViews.add(childViews);
}else{
//如果控件宽度没有超过屏幕宽度,说明可以放多个控件
//高度应该取多个控件中最高的
lineWidth+=childView.getMeasuredWidth()+params.rightMargin+params.leftMargin;
maxheight=Math.max((childView.getMeasuredHeight()+params.topMargin+params.bottomMargin),maxheight);
}
childViews.add(childView);
}
//高度等于不断换行增加的高度以及没有换行时的一排View控件中的最大高度
height+=maxheight;
//1.1.2 根据子view计算和指定自己的宽高
setMeasuredDimension(width,height);
}
2.2 onLayout()
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//在onMeasure()中我们主要做的是得到要画的ViewGroup的宽高
//在onLayout()中我们要做的是设置子View在什么位置摆放
//不断调用此方法摆放子view
int left,top=getPaddingTop(),right,bottom;
for (List<View> childViews : mChildViews) {
left = getPaddingLeft();
for (View childView : childViews) {
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
left += params.leftMargin;
right = left + childView.getMeasuredWidth();
int childTop = top + childView.getMeasuredHeight();
bottom = childTop + childView.getMeasuredHeight();
childView.layout(left, childTop, right, bottom);
//left在摆放的过程中在不断叠加
left += childView.getMeasuredWidth() + params.rightMargin;
}
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childViews.get(0).getLayoutParams();
top += childViews.get(0).getMeasuredHeight() + params.topMargin + params.bottomMargin;
}
}