Android高级UI系列教程(二)

上期回顾 

Android高级UI系列教程(一)_我想月薪过万的博客-CSDN博客https://blog.csdn.net/qq_41885673/article/details/121870917

Android两种坐标系

FlowLayout布局效果展示

 

FlowLayout布局代码解析

根据我们上一节的测量算法分析可得:先测量子View 再测量并保存自己

  • 测量孩子
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //先度量孩子
        //第一步:先获取孩子总个数
        int childCount = getChildCount();
        //第二步:逐个测量
        for (int i = 0; i < childCount; i++) {
            //获取到当前的 子View
            View childView = getChildAt(i);
            //如果孩子不可见 则取消测量
            if (childView.getVisibility() != GONE) {
                LayoutParams childLP = childView.getLayoutParams();
                //将 LayoutParams 转变为 measureSpec
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
                //获取当前的 子View 的宽高
                childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                //获取子View的度量宽高
                int childMeasuredWidth = childView.getMeasuredWidth();
                int childMeasuredHeight = childView.getMeasuredHeight();
            }
        }
  • 计算并设置自己的宽高
    //度量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //这个不写 会导致 UI 混乱 因为父布局可能多次调用子 view.measure
        clearMeasureParams();

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的参考的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); //ViewGroup解析的父亲给我的参考高度

        List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
        int lineWidthUsed = 0; //记录这行已经使用了多宽的size
        int lineHeight = 0; //一行的行高

        int parentNeededWidth = 0; //measure过程中 子View要求的父ViewGroup的宽
        int parentNeededHeight = 0; //measure过程中 子View要求的父ViewGroup的高

        //先度量孩子
        //第一步:先获取孩子总个数
        int childCount = getChildCount();
        //第二步:逐个测量
        for (int i = 0; i < childCount; i++) {
            //获取到当前的 子View
            View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                LayoutParams childLP = childView.getLayoutParams();
                //将 LayoutParams 转变为 measureSpec
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
                //获取当前的 子View 的宽高
                childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                //获取子View的度量宽高
                int childMeasuredWidth = childView.getMeasuredWidth();
                int childMeasuredHeight = childView.getMeasuredHeight();

                //如果需要换行
                if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) {

                    //一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
                    allLines.add(lineViews);
                    lineHeights.add(lineHeight);

                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);

                    lineViews = new ArrayList<>();
                    lineWidthUsed = 0;
                    lineHeight = 0;
                }

                //View 是分行Layout的 所以要记录每一行有哪些View,这样可以方便layout布局
                lineViews.add(childView);
                //每行都会有自己的宽和高
                lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
                lineHeight = Math.max(lineHeight, childMeasuredHeight);

                //处理最后一行数据
                if (i == childCount - 1) {
                    allLines.add(lineViews);
                    lineHeights.add(lineHeight);

                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);
                }
            }
        }

        //再度量自己,并保存
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;

        //再度量自己,保存
        setMeasuredDimension(realWidth, realHeight);
    }
  • 布局代码编写
    //布局
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int lineCount = allLines.size();

        int curL = getPaddingLeft();
        int curT = getPaddingTop();

        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = allLines.get(i);

            int lineHeight = lineHeights.get(i);

            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);

                int top = curT;
                int left = curL;
                //不能使用 view.getWidth() 因为这个方法是 执行onLayout之后才会有值
                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();

                view.layout(left, top, right, bottom);

                curL = right + mHorizontalSpacing;
            }

            curT += lineHeight + mVerticalSpacing;
            curL = getPaddingLeft();
        }
    }
  • 完整代码展示
package com.wust.testalipay;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * ClassName: FlowLayout <br/>
 * Description: <br/>
 * date: 2021/12/14 16:16<br/>
 *
 * @author yiqi<br />
 * @QQ 1820762465
 * @微信 yiqiideallife
 * @技术交流QQ群 928023749
 */
public class FlowLayout extends ViewGroup {

    private int mHorizontalSpacing = 0;
    private int mVerticalSpacing = 0;

    private List<List<View>> allLines = new ArrayList<>(); // 记录所有的行,一行一行的存储,用于layout
    List<Integer> lineHeights = new ArrayList<>(); //记录每一行的行高,用于layout


    // new 一个对象的时候调用
    public FlowLayout(Context context) {
        this(context, null);
    }

    // 解析 xml 布局得时候调用
    public FlowLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // 主题style
    public FlowLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    // 我们在编写自定义布局得时候 一般重写这三个构造方法即可

    private void clearMeasureParams() {
        //这种写法会出现内存抖动
        //allLines = new ArrayList<>();
        //lineHeights = new ArrayList<>();
        allLines.clear();
        lineHeights.clear();
    }

    //度量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //这个不写 会导致 UI 混乱 因为父布局可能多次调用子 view.measure
        clearMeasureParams();

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的参考的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); //ViewGroup解析的父亲给我的参考高度

        List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
        int lineWidthUsed = 0; //记录这行已经使用了多宽的size
        int lineHeight = 0; //一行的行高

        int parentNeededWidth = 0; //measure过程中 子View要求的父ViewGroup的宽
        int parentNeededHeight = 0; //measure过程中 子View要求的父ViewGroup的高

        //先度量孩子
        //第一步:先获取孩子总个数
        int childCount = getChildCount();
        //第二步:逐个测量
        for (int i = 0; i < childCount; i++) {
            //获取到当前的 子View
            View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                LayoutParams childLP = childView.getLayoutParams();
                //将 LayoutParams 转变为 measureSpec
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
                //获取当前的 子View 的宽高
                childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                //获取子View的度量宽高
                int childMeasuredWidth = childView.getMeasuredWidth();
                int childMeasuredHeight = childView.getMeasuredHeight();

                //如果需要换行
                if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) {

                    //一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
                    allLines.add(lineViews);
                    lineHeights.add(lineHeight);

                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);

                    lineViews = new ArrayList<>();
                    lineWidthUsed = 0;
                    lineHeight = 0;
                }

                //View 是分行Layout的 所以要记录每一行有哪些View,这样可以方便layout布局
                lineViews.add(childView);
                //每行都会有自己的宽和高
                lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;
                lineHeight = Math.max(lineHeight, childMeasuredHeight);

                //处理最后一行数据
                if (i == childCount - 1) {
                    allLines.add(lineViews);
                    lineHeights.add(lineHeight);

                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);
                }
            }
        }

        //再度量自己,并保存
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;

        //再度量自己,保存
        setMeasuredDimension(realWidth, realHeight);
    }

    //布局
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int lineCount = allLines.size();

        int curL = getPaddingLeft();
        int curT = getPaddingTop();

        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = allLines.get(i);

            int lineHeight = lineHeights.get(i);

            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);

                int top = curT;
                int left = curL;
                //不能使用 view.getWidth() 因为这个方法是 执行onLayout之后才会有值
                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();

                view.layout(left, top, right, bottom);

                curL = right + mHorizontalSpacing;
            }

            curT += lineHeight + mVerticalSpacing;
            curL = getPaddingLeft();
        }
    }
}

易错点提示

1、onMeasure()可能会执行多次,所以得在 onMeasure() 方法中 初始化清空  allLines 和 lineHeights ,要不然你会发现布局会向下偏移。同时,初始化清空最好使用 clear() 方法,如果使用 new ArrayList<>() 就会导致内存抖动。

2、最后一行的处理不要忘记,处理逻辑如下:

                //处理最后一行数据
                if (i == childCount - 1) {
                    allLines.add(lineViews);
                    lineHeights.add(lineHeight);

                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);
                }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

super码王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值