图片.png本文将结合
onMeasure
和onLayout
两个方法手写瀑布流布局。onMeasure
主要是测量自己本身的大小和子视图的大小,和位置无关。onLayout
主要负责视图的摆放,和位置有关。
如图所示,这就是瀑布流布局。
手写瀑布流步骤如下:
【第一步】
:创建MyCustomView类,继承ViewGroup,由于MyCustomView是要写在xml中的,所以必须构造两个参数的构造方法,另外,事先测量好当前视图和所有子视图。代码如下:
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) {
}
}
xml中表现形式如下,其中MyCustomView
的大小暂定为match_parent
,它所有的额子视图的大小暂定为wrap_content
。
<com.vrv.viewdemo.MyCustomView
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="床"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="床前明月光"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="疑似地上写两个双"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="举头"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="忘明月"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="低"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="头思故乡"
android:textSize="26sp"/>
</com.vrv.viewdemo.MyCustomView>
【第二步】
:在onLayout
方法中摆放子视图,首先横向摆放,当水平方向的剩余空间不足以摆放下一个视图时,则换行摆放,直到最后一个视图摆放完成。
代码如下:
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) {
//在当前视图测量完毕之后,通过getMeasuredWidth可以精准的获取宽度
int measuredWidth = getMeasuredWidth();
//计算当前行视图的最大高度
int curMaxHeight = 0;
//当前摆放的宽度
int curWidth = 0;
//当前摆放的高度
int curHeight = 0;
View child;
//遍历所有的子视图
for(int i=0;i<getChildCount();i++){
//获取子视图
child = getChildAt(i);
//当前行视图最大高度
if(curMaxHeight < child.getMeasuredHeight()){
curMaxHeight = child.getMeasuredHeight();
}
//如果下一个子视图摆放之后超出当前视图的最大宽度,则换行
if(curWidth + child.getMeasuredWidth() > measuredWidth){
curWidth = 0;
curHeight = curHeight + curMaxHeight;
curMaxHeight = 0;
}
//摆放
child.layout(curWidth, curHeight, curWidth + child.getMeasuredWidth(), curHeight + child.getMeasuredHeight());
//计算下一个子视图摆放的水平位置
curWidth = curWidth + child.getMeasuredWidth();
}
}
}
效果如下:
图片.png但是,如果将MyCustomView的大小和背景修改一下,如下:
<com.vrv.viewdemo.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark">
这时再来看一下效果,如下:
图片.png显然,MyCustomView在大小设置为wrap_content
的条件下依然沾满整个屏幕,这时一个严重的问题,这时需要重新测量MyCustomView视图了。
【第三步】
:解决当前视图大小设置为wrap_content
不准确的问题
首先,让所有子视图测量完毕,最终计算出最大当前宽度和高度,为了计算宽度和高度,显然以下测量子视图的代码并不合适
//测量所有的子视图
measureChildren(widthMeasureSpec, heightMeasureSpec);//测量所有的子布局
我们现在要做的事情就是:遍历所有子视图,子视图一个一个的测量,顺便计算出包裹所有子视图的宽度和高度,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取宽度模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//获取高度模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取当前视图的宽度
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
//获取当前视图的高度
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
//当前视图的宽度测量规格
int widthNewMeasureSpec = widthMeasureSpec;
//当前视图的高度测量规格
int heightNewMeasureSpec = heightMeasureSpec;
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY){
//测量父视图
setMeasuredDimension(widthNewMeasureSpec, heightNewMeasureSpec);
//测量所有的子布局
measureChildren(widthNewMeasureSpec, heightNewMeasureSpec);
}else {
//计算当前行视图的最大高度
int curMaxHeight = 0;
//当前摆放的宽度
int curWidth = 0;
//当前摆放的高度
int curHeight = 0;
//最大总宽度
int maxWidth = 0;
//最大总高度
int maxHeight = 0;
//遍历子视图
for (int index=0;index<getChildCount();index++){
//获取子视图
View subview = getChildAt(index);
//测量子视图
measureChild(subview, widthMeasureSpec, heightMeasureSpec);
//当前行视图最大高度
if(curMaxHeight < subview.getMeasuredHeight()){
curMaxHeight = subview.getMeasuredHeight();
}
//计算最大宽度
if(maxWidth < curWidth){
maxWidth = curWidth;
}
//计算最大高度
maxHeight = curHeight + curMaxHeight;
//如果下一个子视图摆放之后超出当前视图的最大宽度,则换行
if(curWidth + subview.getMeasuredWidth() > measuredWidth){
curWidth = 0;
curHeight = curHeight + curMaxHeight;
curMaxHeight = 0;
}
//计算下一个子视图摆放的水平位置
curWidth = curWidth + subview.getMeasuredWidth();
}
//计算当前视图新的宽度测量规格
if(widthMode == MeasureSpec.EXACTLY){
widthNewMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, widthMode);
}else if(widthMode == MeasureSpec.AT_MOST){
widthNewMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, widthMode);
}else{
widthNewMeasureSpec = 0;
}
//计算当前视图新的高度测量规格
if(heightMode == MeasureSpec.EXACTLY){
heightNewMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, heightMode);
}else if(heightMode == MeasureSpec.AT_MOST){
heightNewMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}else{
heightNewMeasureSpec = 0;
}
setMeasuredDimension(widthNewMeasureSpec, heightNewMeasureSpec);
}
}
效果如下:
图片.png【第四步】
:把padding的情况考虑进去
<com.vrv.viewdemo.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="30dp"
android:background="@color/colorPrimaryDark">
如果在MyCustomView中天假padding,那么视图就变成了这样,如下:
图片.png试问,这个效果谁能忍受?
那么,该怎么去解决这个问题呢?
通过以下方式可以获得padding值,如下:
//获取左padding
int paddingLeft = getPaddingLeft();
//获取右padding
int paddingRight = getPaddingRight();
//获取上padding
int paddingTop = getPaddingTop();
//获取下padding
int paddingBottom = getPaddingBottom();
将padding考虑在内,调整代码:
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) {
//获取宽度模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//获取高度模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取当前视图的宽度
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
//获取当前视图的高度
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
//当前视图的宽度测量规格
int widthNewMeasureSpec = widthMeasureSpec;
//当前视图的高度测量规格
int heightNewMeasureSpec = heightMeasureSpec;
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY){
//测量父视图
setMeasuredDimension(widthNewMeasureSpec, heightNewMeasureSpec);
//测量所有的子布局
measureChildren(widthNewMeasureSpec, heightNewMeasureSpec);
}else {
//计算当前行视图的最大高度
int curMaxHeight = 0;
//最大总宽度
int maxWidth = 0;
//最大总高度
int maxHeight = 0;
//当前摆放的横向位置
int curWidth = getPaddingLeft();
//当前摆放的纵向位置
int curHeight = getPaddingTop();
//遍历子视图
for (int index=0;index<getChildCount();index++){
//获取子视图
View subview = getChildAt(index);
//测量子视图
measureChild(subview, widthMeasureSpec, heightMeasureSpec);
//当前行视图最大高度
if(curMaxHeight < subview.getMeasuredHeight()){
curMaxHeight = subview.getMeasuredHeight();
}
//计算最大高度
maxHeight = curHeight + curMaxHeight;
if(index == getChildCount() - 1){
maxHeight += getPaddingBottom();
}
//如果下一个子视图摆放之后超出当前视图的最大宽度,则换行
if(curWidth + subview.getMeasuredWidth() + getPaddingRight()> measuredWidth){
//计算最大宽度
if(maxWidth < curWidth){
maxWidth = curWidth + getPaddingRight();
}
curWidth = getPaddingLeft();
curHeight = curHeight + curMaxHeight;
curMaxHeight = 0;
}else{
//计算最大宽度
if(maxWidth < curWidth){
maxWidth = curWidth;
}
}
//计算下一个子视图摆放的水平位置
curWidth = curWidth + subview.getMeasuredWidth();
}
//计算当前视图新的宽度测量规格
if(widthMode == MeasureSpec.EXACTLY){
widthNewMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, widthMode);
}else if(widthMode == MeasureSpec.AT_MOST){
widthNewMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, widthMode);
}else{
widthNewMeasureSpec = 0;
}
//计算当前视图新的高度测量规格
if(heightMode == MeasureSpec.EXACTLY){
heightNewMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, heightMode);
}else if(heightMode == MeasureSpec.AT_MOST){
heightNewMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}else{
heightNewMeasureSpec = 0;
}
setMeasuredDimension(widthNewMeasureSpec, heightNewMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//在当前视图测量完毕之后,通过getMeasuredWidth可以精准的获取宽度
int measuredWidth = getMeasuredWidth();
//计算当前行视图的最大高度
int curMaxHeight = 0;
//当前摆放的宽度
int curWidth = getPaddingLeft();
//当前摆放的高度
int curHeight = getPaddingTop();
View child;
//遍历所有的子视图
for(int i=0;i<getChildCount();i++){
//获取子视图
child = getChildAt(i);
//如果下一个子视图摆放之后超出当前视图的最大宽度,则换行
if(curWidth + child.getMeasuredWidth() + getPaddingRight()> measuredWidth){
curWidth = getPaddingLeft();
curHeight = curHeight + curMaxHeight;
curMaxHeight = 0;
}
//摆放
child.layout(curWidth, curHeight, curWidth + child.getMeasuredWidth(), curHeight + child.getMeasuredHeight());
//当前行视图最大高度
if(curMaxHeight < child.getMeasuredHeight()){
curMaxHeight = child.getMeasuredHeight();
}
//计算下一个子视图摆放的水平位置
curWidth = curWidth + child.getMeasuredWidth();
}
}
}
效果如下:
图片.png当然,如果子视图的高度不一致时,以上代码也支持,如图:
图片.png【第五步】
:把marign的情况考虑进去
如果在子视图中添加marign是无效的,比如:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="床"
android:layout_marginLeft="10dp"
android:layout_marginTop="20dp"
android:textSize="26sp"/>
为了让marign有效,还需要做一些处理,使用以下代码可以获取margin值
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) subview.getLayoutParams();
因为使用了MarginLayoutParams
,所以当前自定义视图必须重写generateLayoutParams
方法
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet params) {
return new MarginLayoutParams(getContext(), params);
}
将获取到的margin加入代码,修改后的代码最终为:
public class MyCustomView extends ViewGroup {
public MyCustomView(Context context) {
super(context);
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet params) {
return new MarginLayoutParams(getContext(), params);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取宽度模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//获取高度模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取当前视图的宽度
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
//获取当前视图的高度
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
//当前视图的宽度测量规格
int widthNewMeasureSpec = widthMeasureSpec;
//当前视图的高度测量规格
int heightNewMeasureSpec = heightMeasureSpec;
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY){
//测量父视图
setMeasuredDimension(widthNewMeasureSpec, heightNewMeasureSpec);
//测量所有的子布局
measureChildren(widthNewMeasureSpec, heightNewMeasureSpec);
}else {
//计算当前行视图的最大高度
int curMaxHeight = 0;
//最大总宽度
int maxWidth = 0;
//最大总高度
int maxHeight = 0;
//当前摆放的横向位置
int curWidth = getPaddingLeft();
//当前摆放的纵向位置
int curHeight = getPaddingTop();
//遍历子视图
for (int index=0;index<getChildCount();index++){
//获取子视图
View subview = getChildAt(index);
//测量子视图
measureChild(subview, widthMeasureSpec, heightMeasureSpec);
//获取布局参数,可以获取margin
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) subview.getLayoutParams();
curWidth += marginLayoutParams.leftMargin;
//当前行视图最大高度
if(curMaxHeight < subview.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin){
curMaxHeight = subview.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
}
//如果下一个子视图摆放之后超出当前视图的最大宽度,则换行
if(curWidth + subview.getMeasuredWidth() + marginLayoutParams.rightMargin + getPaddingRight()> measuredWidth){
//计算最大宽度
if(maxWidth < curWidth){
maxWidth = curWidth + marginLayoutParams.rightMargin + getPaddingRight();
}
curWidth = getPaddingLeft();
curHeight = curHeight + curMaxHeight;
maxHeight = curHeight + marginLayoutParams.bottomMargin + subview.getMeasuredHeight();
curMaxHeight = 0;
}else{
//计算最大宽度
if(maxWidth < curWidth){
maxWidth = curWidth + marginLayoutParams.rightMargin;
}
//计算最大高度
maxHeight = curHeight + curMaxHeight;
if(index == getChildCount() - 1){
maxHeight += getPaddingBottom();
}
}
//计算下一个子视图摆放的水平位置
curWidth = curWidth + subview.getMeasuredWidth() + marginLayoutParams.rightMargin;
}
//计算当前视图新的宽度测量规格
if(widthMode == MeasureSpec.EXACTLY){
widthNewMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, widthMode);
}else if(widthMode == MeasureSpec.AT_MOST){
widthNewMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, widthMode);
}else{
widthNewMeasureSpec = 0;
}
//计算当前视图新的高度测量规格
if(heightMode == MeasureSpec.EXACTLY){
heightNewMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight, heightMode);
}else if(heightMode == MeasureSpec.AT_MOST){
heightNewMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}else{
heightNewMeasureSpec = 0;
}
setMeasuredDimension(widthNewMeasureSpec, heightNewMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//在当前视图测量完毕之后,通过getMeasuredWidth可以精准的获取宽度
int measuredWidth = getMeasuredWidth();
//计算当前行视图的最大高度
int curMaxHeight = 0;
//当前摆放的宽度
int curWidth = getPaddingLeft();
//当前摆放的高度
int curHeight = getPaddingTop();
View child;
//遍历所有的子视图
for(int i=0;i<getChildCount();i++){
//获取子视图
child = getChildAt(i);
//获取布局参数,可以获取margin
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
curWidth += marginLayoutParams.leftMargin;
//如果下一个子视图摆放之后超出当前视图的最大宽度,则换行
if(curWidth + child.getMeasuredWidth() + marginLayoutParams.rightMargin + getPaddingRight()> measuredWidth){
curWidth = getPaddingLeft();
curHeight = curHeight + curMaxHeight;
curMaxHeight = 0;
}
//摆放
child.layout(curWidth, curHeight + marginLayoutParams.topMargin, curWidth + child.getMeasuredWidth(), curHeight + marginLayoutParams.topMargin + child.getMeasuredHeight());
//当前行视图最大高度
if(curMaxHeight < child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin){
curMaxHeight = child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
}
//计算下一个子视图摆放的水平位置
curWidth = curWidth + child.getMeasuredWidth() + marginLayoutParams.rightMargin;
}
}
}
在布局中设置padding和margin,如下:
<com.vrv.viewdemo.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:background="@color/colorPrimaryDark">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="床"
android:layout_margin="20dp"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="床前明月光"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="疑"
android:layout_marginTop="30dp"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="似地上写两个双"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="举头"
android:layout_marginBottom="20dp"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="忘明月"
android:layout_margin="40dp"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="低"
android:textSize="26sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="头思故乡"
android:layout_marginTop="10dp"
android:textSize="26sp"/>
</com.vrv.viewdemo.MyCustomView>
最终效果为:
图片.png声明:以上代码逻辑是按照本人的思维编写的,不建议直接抄袭,如果想要学习的话,强烈建议跟我一样自己动手,手写一个瀑布流布局。
[本章完...]
网友评论