自定义view主要分为组合式和继承View或者ViewGroup重写两种方式,流式布局是第二种继承ViewGroup的方式实现的。
一般这种自定义View主要是通过重写onMeasure和onLayout方法实现的,在onMeasure测量的时候首先要是区分父布局MeasureSpec.EXACTLY或者是MeasureSpec.AT_MOST,然后通过MeasureSpec.getSize的方法测量自己宽高,通过measureChild()测量子view的宽高。最后在onLayout中根据具体位置绘制view的
-
EXACTLY(明确)
父view决定子view的确切大小,子被限定在给定的边界里,忽略本身想要的大小。当设置width或height为100dp时候,子view最多只能100dp, 当设置width或height为match_parent时,模式为EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。 -
AT_MOST(至少)
子view最大可以达到的指定大小 ,当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。 -
UNSPECIFIED(未指定)
父view不限制子view大小,如系统的scrollview,自定义使用较少。
一些用的变量
private List<View> curLineViews;//存在每行的view
private List<List<View>> totalLineViews;//存储所有行的view
private int curWidth//当前一行的宽度
,curHeigh//当前一行的高度
,mostWidth//记录当前的最宽值,如果父类不是EXACTLY模式,则宽度以我们最大宽度为准
,mostHigth//同上
;
下面我们看一下onMesure方法里,注释写的很清楚了,核心的方法就是在for循环里遍历子view,计算每个view相加的宽度是否超过限定的宽度,然后把子view存起来在onLayout方法里计算具体布局的位置,要注意的是单行view不满足换行的时候需要单独处理一下。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int heighMode=MeasureSpec.getMode(heightMeasureSpec);
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
int heighSize=MeasureSpec.getSize(heightMeasureSpec);
curLineViews=new ArrayList<>();
totalLineViews=new ArrayList<>();
everyLinesHeigh=new ArrayList<>();
//需要通过遍历子view 计算出一共需要的宽和高
for (int i = 0; i <getChildCount() ; i++) {
measureChild(getChildAt(i),widthMeasureSpec,heightMeasureSpec);
//子控件的宽高
int childWidth = getChildAt(i).getMeasuredWidth();
int childHeight = getChildAt(i).getMeasuredHeight();
//如果当前一行的宽度超过了最大宽度,则需要进行换行
if(curWidth+childWidth>widthSize){
totalLineViews.add(curLineViews);//存储当前这一行的数据
curLineViews=new ArrayList<>();//清空备用行集合
mostWidth=Math.max(mostWidth,curWidth);//获取当前行的宽度,如果比之前的宽 则取出备用
mostHigth+=curHeigh;
curWidth=0;
everyLinesHeigh.add(curHeigh);
curHeigh=0;
}
curWidth+=childWidth;
curLineViews.add(getChildAt(i));//存储当前这行中的view
curHeigh=Math.max(curHeigh,childHeight);//获取当前最高的一个元素作为当前的高度值
if(i==getChildCount()-1){//所有子view都加上只有一行的情况
totalLineViews.add(curLineViews);//存储当前这一行的数据
curLineViews=new ArrayList<>();//清空备用行集合
mostWidth=Math.max(mostWidth,curWidth);//获取当前行的宽度,如果比之前的宽 则取出备用
mostHigth+=curHeigh;
everyLinesHeigh.add(curHeigh);
}
}
setMeasuredDimension(widthMode==MeasureSpec.EXACTLY?widthSize:mostWidth,
heighMode==MeasureSpec.EXACTLY?heighSize:mostHigth);
}
再来看一下onLayout里的方法,主要是计算每个子view放置的具体位置,核心方法在两个for循环,遍历每行的views,然后在每行的views里继续遍历每个view,具体完成每个childView的layout布局。
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
int curX= getPaddingLeft();
int curY= getPaddingTop();
for (int j = 0; j <totalLineViews.size() ; j++) {
for (int k = 0; k <totalLineViews.get(j).size() ; k++) {// 遍历每一行的数据,分别赋值位置
View child =totalLineViews.get(j).get(k);
child.layout(
curX,
curY,
curX+child.getMeasuredWidth(),
curY+child.getMeasuredHeight()
);
curX+=child.getMeasuredWidth(); //下一个位置x累加
}
curY+=everyLinesHeigh.get(j);//换行Y累加
curX=0; //换行清空x
}
}
以上只是简单的实现了效果,具体如margin等间距,可以在此基础上自己加上。我们看下效果
image.png
网友评论