【01】自定义View与高级UI

(1)什么是自定义View?什么是高级UI?
(2)LayoutParams解析原理
(3)MeasureSpec原理解析
(4)自定义流式布局项目实战
(5)坐标系介绍
(6)XXLayout布局源码解析

【01】自定义View与高级UI

1.Android程序员给外界的感觉是什么

(1)实现UI
(2)自定义View
(3)自定义View决定了做的APP是否漂亮,做的APP的效果怎么样。
(4)Java与Kotlin是语言基础
(5)自定义View就是Android基础。对于Android工程师来说,绘制自定义View只是入门功夫。

2.什么是自定义View

(1)一个效果只要它能够在手机上面实现你就应该具备实现它的能力。
(2)大量的实践、练习。

2.1自定义View包含什么?

2.1.1布局

(1)onLayout onMeasure
(2)布局用的最多的是ViewGroup,在ViewGroup中用的最多的又是Layout,无论用的是什么Layout布局,最终都属于布局。Layout继承自ViewGroup。

2.1.2显示

(1)onDraw

(2)onDraw是绘制,里面包含了

  • 画布(canvas)
  • 画笔(paint)
  • 矩阵(matrix)
  • 扣图(clip)
  • MeasureRect
  • 动画(Animation)
  • 路径(path),贝塞尔曲线
  • 画线(line)

(3)用的最多的是View里面

2.1.3交互

(1)onTouchEvent(事件分发)
(2)用在组合的ViewGroup中。

2.2自定义控件如何分类?

2.2.1自定义view

(1)只需要重写onMeasure()和onDraw()

(2)在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View.

2.2.2自定义ViewGroup

(1)只需要重写onMeasure()和onLayout()

(2)自定义ViewGroup一般利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout.

3.自定义view的绘制流程

在这里插入图片描述

3.1.流式布局

(1)FlowLayout,流式布局,这个概念在移动端或者前端开发中很常见,特别是在多标签展示中,往往起到了关键的作用。然而Android官方,并没有为开发者提供这样的一个布局。

在这里插入图片描述

在这里插入图片描述

3.2用房子装修理解自定义绘制流程

(1)首先要测量,有多大多高.要测量每一个小房间的房子,然后再得到大房子的面积。

(2)布局,如果连通,摆放

(3)onDraw是对每一个小房间的装饰

(4)自定义View主要是实现 onMeasure + onDraw(房间装修)

(5)自定义ViewGroup主要是实现onMeasure + onLayout(整套房子装修及布局)

3.3流式布局各构造函数含义

	/**
     * 1.此构造函数在Java代码中去new的时候调用
     * @param context
     */
    public FlowLayout(Context context) {
        super(context);
    }

    /**
     * 1.在XML布局中使用的时候调用。
     * (1)XML以序列化的方式去创建对象
     * (2)解析的函数是在LayoutInflater中去解析XML的内容。
     *
     * 2.序列化
     * (1)可以自定义序列化解析,在IOT中使用的最多.
     * (2)IOT协议,序列化是一套数据交换的规则
     * (3)物联网:蓝牙 传递的数据,串口
     * (4)NFC:射频
     *
     * 3.通过反射去hook到函数去构造View
     * @param context
     * @param attrs
     */
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 1.自定义Style时调用
     * (1)有黑白主题时就使用此构造创建对象
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 1.自定义属性
     * @param context
     * @param attrs
     * @param defStyleAttr
     * @param defStyleRes
     */
    /*@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }*/

3.4View的层级结构

在这里插入图片描述

3.5测量过程

在这里插入图片描述

(1)测量的过程就是将xml中的宽高属性转变成具体dp或dip值的过程。

3.5.1LayoutParams是什么?与MeasureSpec有关系吗?

(1)LayoutParams是ViewGroup中的一个值。
(2)在自定义View的过程中有可能需要自己定义LayoutParams,比如ViewPager,可以根据自己的需求来定义。
(3)自定义LayoutParams,是为了增加自定义的相关属性,根据自己的需求定义属性。
(4)如何将LayoutParams转变成具体的值dp,dip.

3.5.2View的结构
android:layout_width="10dp"
android:layout_width="match_parent"(-1)
android:layout_width="wrap_content"(-2)

(1)View的结构是树形结构
(2)ViewGroup是父亲,它有孩子View.
(3)从继承关系上来说,View是父类,而ViewGroup是子类
(4)ViewGroup可以包含各种View
(5)每一个View中存在三种情况

  • 具体的dp
  • match_parent
  • wrap_content

(1)match_parent受到父亲的限制
(2)wrap_content:受到子View布局方式的限制。
(3)根据View的绘制流程与树形结构来看,在调用onMeasure()函数度量的时候,方法的参数就来自于当前View的父亲,它是一种递归测量。
(4)onMeasure()没有将测量到的值返回,测量的值是通过getMeasuredWidth()与getMeasuredHeight()去获取到的。
(5)setMeasuredDimension(width,height)保存测量值,就是为了能够获取。

3.5.3MeasureSpec是什么?

(1)测量的过程中使用
(2)是View中的内部类,基本都是二进制运算.由于int是32位的,用高两位表示mode,低30位表示size,MODE_SHIFT=30的作用是移位。

(3)具体测量模式

  • UNSPECIFIED:不对View大小做限制,系统使用
  • EXACTLY:确切的大小,如:100dp
  • AT_MOST:大小不可超过某数值,如:matchParent, 最大不能超过你爸爸
3.5.4MeasureSpec具体的算法是怎样的?

在这里插入图片描述

(1)是getChildMeasureSpec()方法的具体算法
(2)作为子View要将控件大小转变为布局的大小
(3)getChildMeasureSpec(int spec, int padding, int childDimension)

  • 第一个参数spec来自于父级布局指定的大小,父亲的大小是父亲给的。
  • 第二个参数padding.要在父亲的空间大小上分配一块区域作为孩子的大小空间,至少要与父亲之间有一定的间隔。所以父亲要减去这一个padding才是当前孩子的大小空间。
  • 第三个参数childDimension指的是孩子需要的大小空间。
    /**
     * 1.为什么要实现onMeasure?
     * (1)测量
     * (2)先测量子,再测量自己。(先测量小房间,再测量整套房子)多数情况是这样测量
     * (3)ViewPager测量比较特殊,只需要测自己。
     *
     * 2.需要解决widthMeasureSpec,heightMeasureSpec从哪里来的问题?
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**
         * 1.1先度量孩子
         * (1)度量孩子主要是去解析layout_width与layout_height属性
         * (2)要将layout_width与layout_height属性变成具体的大小,dp或dip值
         */
        int childCount = getChildCount();

        /**
         * 1.2getChildMeasureSpec(int spec, int padding, int childDimension)
         *
         * - 第一个参数spec来自于父级布局指定的大小,父亲的大小是父亲给的。
         * - 第二个参数padding.要在父亲的空间大小上分配一块区域作为孩子的大小空间,至少要与父亲之间有一定的间隔。所以父亲要减去这一个padding才是当前孩子的大小空间。
         * - 第三个参数childDimension指的是孩子需要的大小空间。
         *
         * 1.3以下步骤拿到的是距离父亲的左右上下的padding
         *
         * 1.4childLP.width、childLP.height指的是xml的宽与高,也就是孩子需要的大小空间。
         * 
         * 1.5通过getChildMeasureSpec的计算就得到了具体的值
         */
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        for(int i = 0 ; i < childCount;i++){
            View childView = getChildAt(i);

            //1.1.1将layoutParams转变为measureSpec
            LayoutParams childLP = childView.getLayoutParams();
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                    paddingLeft+paddingRight,childLP.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                    paddingTop+paddingBottom,childLP.height);
            childView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
        }
3.5.5为什么要measure?

在这里插入图片描述

在这里插入图片描述

(1)要解决的问题就是孩子节点大小分配的问题,父亲的大小是多少,能给多少的问题。
(2)这也就是决定孩子大小计算的算法。

  • 是具体值(EXACTLY)的时候,采用什么样的算法计算孩子的大小。
  • 是未指定(UNSPECIFIED)值时,采用什么样的算法计算孩子的大小。
  • 是AT_MOST时,采用什么样的算法计算孩子的大小。

(3)父亲的大小也有三种情况

  • 有一个具体的大小
  • 自己不知道自己的大小
  • AT_MOST模式:大小不可以超过父亲

(4)正因为如此,就会有9种算法获得孩子的大小。

在这里插入图片描述

(1)根据不同的测量模式,通过判断值,判断match_parent、wrap_parent的方式,计算出不同的孩子的大小。
(2)计算后得到的是一个理论的值。
(3)只有计算完所有孩子的大小之后,才能得到确切的值。

3.5.6流式布局的宽高如何确定?

(1)宽度是每一行中最宽的一个组合项(取决于几个标签的组合后最宽的那一组的宽度)的宽度。
(2)流式布局的高度是所有行数的高度之和。

List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
int lineWidthUsed = 0; //记录这行已经使用了多宽的size
int lineHeight = 0; // 一行的行高
3.5.7放一个节点时做记录
//1.1.2获取子View的度量宽高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();

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

(1)添加是否需要换行判断

if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {

}

(2)解析父亲能够给我的参考大小

/**
* 1.6解析父亲能够给我的参考大小
* (1)这个大小是根据MeasureSpec去计算出来的
*/
int selfWidth = MeasureSpec.getSize(widthMeasureSpec);  //ViewGroup解析的父亲给我的宽度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度

(3)换行先清空每一行记录的相关值

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

(4)子View要求分配的大小宽高

//(2)换行时子View需要的大小空间(宽高)
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);

3.6通过父亲的测量模式Mode计算自己的测量模式

(1)父亲的宽高仍旧是一个ViewGroup,它的宽高值只是一个参考值,不是确切值。必须考虑父亲的测量模式Mode.

(2)根据子View的度量结果,来重新度量自己ViewGroup

/**
* 1.再度量自己,保存
* (1)为什么要保存?
* (2)测量完之后保存是为了通过getMeasuredWidth()与getMeasuredHeight()方法获取到值。
* (3)自己的宽高取决于子View的宽高情况
* (4)根据子View的度量结果,来重新度量自己ViewGroup
* (5)作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
*/
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);

4.布局

4.1Android的两种坐标系

4.1.1Android屏幕坐标系

在这里插入图片描述

4.1.2Android视图坐标系

在这里插入图片描述

4.1.3摆放

(1)getLeft、getTop、getRight、getBottom针对的是视图坐标系。
(2)主要是因为其是针对父View做布局。
(3)布局第一个节点,当把第一个布局放在父View上面的时候,第一个节点的位置取决于与父View的左边与顶部距离。右边距离是子View宽度+子View与左边的距离。高度是子View高度+与父View顶部的距离。
(4)问题的关键是计算第一个节点的左边界与上边界。

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

(5)第二行的View如何摆放?

  • 需要在测量过程中记录每一行的行高,以及每一行的控件
allLines.add(lineViews);
lineHeights.add(lineHeight);
  • 这样记录有一个问题,就是会缺少一行,因为在最后行一是不会走换行的逻辑的。因为没有满一行,就不会换行。因此需要加一个处理最后一行的逻辑。
/**
* 3.处理最后一行数据
* (1)原因是最后一行不会走换行的逻辑,也就记录不了最后一行的子View与行数
* (2)所以单独做处理
*/
if (i == childCount - 1) {
allLines.add(lineViews);
lineHeights.add(lineHeight);
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
            }

(6)一行一行摆放

	/**
     * 1.布局摆放
     * (1)子View到底在父View的哪个位置
     * (2)获取第一个子View摆放在父View中的左边距与顶边距
     * (3)一行一行进行布局
     */
    @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 left = curL;
                int top =  curT;

                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();
                view.layout(left,top,right,bottom);
                curL = right + mHorizontalSpacing;
            }

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

4.2getMeasureWidth与getWidth的区别

4.2.1getMeasureWidth

(1)在measure()过程结束后就可以获取到对应的值;

(2)通过setMeasuredDimension()方法来进行设置的.

4.2.2getWidth

(1)在layout()过程结束后才能获取到;

(2)通过视图右边的坐标减去左边的坐标计算出来的.

4.2.3注意绘制流程的生命周期。

(1)在度量onMeasure之后,布局onLayout()之前,要获取到一个View的宽与高,都要使用度量之后的值,而不要使用度量之前的值。

4.2.4onMeasure为什么会调用多次?

(1)因为父View会度量子View,而子View又要度量子View.所以如果子View度量多次,就会导致onMeasure执行多次。这是由它的父View决定的。
(2)所以在onMeasure测量的时候,一些测量记录也需要清零

    /**
     * 清空测量参数,避免内存抖动
     */
    private void clearMeasureParams() {
        allLines.clear();
        lineHeights.clear();
    }
4.2.5内存抖动

(1)如果在onMeasure中去初始化集合,在onMeasure多次调用时会出现内存抖动

    private void initMeasureParams(){
        allLines = new ArrayList<>();
        lineHeights = new ArrayList<>();
    }

(2)因为多次初始化集合,会在内存中分配空间,导致内存产生不连续存放空间,如果某一时刻需要申请大量的内存,需要连续内存空间时,就会由GC触发内存回收,从而引发内存抖动。即内存一直处于申请回收申请回收过程。

5.FlowLayout源码

package com.gdc.knowledge.highui.flowlayout;

import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

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


/**
 * @author XiongJie
 * @version appVer
 * @Package com.gdc.knowledge.highui.flowlayout
 * @file
 * @Description:流式布局
 * @date 2021-6-7 14:26
 * @since appVer
 */

public class FlowLayout extends ViewGroup {

    private static final String TAG = "FlowLayout";
    private int mHorizontalSpacing = dp2px(16); //每个item横向间距
    private int mVerticalSpacing = dp2px(8); //每个item纵向间距

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

    /**
     * 1.此构造函数在Java代码中去new的时候调用
     * @param context
     */
    public FlowLayout(Context context) {
        super(context);
    }

    /**
     * 1.在XML布局中使用的时候调用。
     * (1)XML以序列化的方式去创建对象
     * (2)解析的函数是在LayoutInflater中去解析XML的内容。
     *
     * 2.序列化
     * (1)可以自定义序列化解析,在IOT中使用的最多.
     * (2)IOT协议,序列化是一套数据交换的规则
     * (3)物联网:蓝牙 传递的数据,串口
     * (4)NFC:射频
     *
     * 3.通过反射去hook到函数去构造View
     * @param context
     * @param attrs
     */
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 1.自定义Style时调用
     * (1)有黑白主题时就使用此构造创建对象
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 清空测量参数,避免内存抖动
     */
    private void clearMeasureParams() {
        allLines.clear();
        lineHeights.clear();
    }

    /**
     * 1.自定义属性
     * @param context
     * @param attrs
     * @param defStyleAttr
     * @param defStyleRes
     */
    /*@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }*/



    /**
     * 1.为什么要实现onMeasure?
     * (1)测量
     * (2)先测量子,再测量自己。(先测量小房间,再测量整套房子)多数情况是这样测量
     * (3)ViewPager测量比较特殊,只需要测自己。
     *
     * 2.需要解决widthMeasureSpec,heightMeasureSpec从哪里来的问题?
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //解决内存抖动
        clearMeasureParams();

        /**
         * 1.1先度量孩子
         * (1)度量孩子主要是去解析layout_width与layout_height属性
         * (2)要将layout_width与layout_height属性变成具体的大小,dp或dip值
         */
        int childCount = getChildCount();

        /**
         * 1.2getChildMeasureSpec(int spec, int padding, int childDimension)
         *
         * - 第一个参数spec来自于父级布局指定的大小,父亲的大小是父亲给的。
         * - 第二个参数padding.要在父亲的空间大小上分配一块区域作为孩子的大小空间,至少要与父亲之间有一定的间隔。所以父亲要减去这一个padding才是当前孩子的大小空间。
         * - 第三个参数childDimension指的是孩子需要的大小空间。
         *
         * 1.3以下步骤拿到的是距离父亲的左右上下的padding
         *
         * 1.4childLP.width、childLP.height指的是xml的宽与高,也就是孩子需要的大小空间。
         *
         * 1.5通过getChildMeasureSpec的计算就得到了具体的值
         */
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        /**
         * 1.6解析父亲能够给我的参考大小
         * (1)这个大小是根据MeasureSpec去计算出来的
         */
        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);  //ViewGroup解析的父亲给我的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度

        /**
         * 2.计算流式布局的宽高
         * 2.1宽度是每一行中最宽的一个组合项(取决于几个标签的组合后最宽的那一组的宽度)的宽度。
         * 2.2流式布局的高度是所有行数的高度之和。
         */
        List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
        int lineWidthUsed = 0; //记录这行已经使用了多宽的size
        int lineHeight = 0; // 一行的行高

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

        for(int i = 0 ; i < childCount; i++){
            View childView = getChildAt(i);

            //1.1.1将layoutParams转变为measureSpec
            LayoutParams childLP = childView.getLayoutParams();

            if (childView.getVisibility() != View.GONE) {
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
                childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

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

                //2.3.3判断是否需要换行
                if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {

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

                    /**
                     * (2)换行时子View需要的大小空间(宽高)
                     * 高度=向父亲要求分配的高度+单个view行高+纵向间距
                     * 宽度=最大值(向父亲要求分配的宽度+单个view宽度+横向间距)
                     */
                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);

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

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

                /**
                 * 3.处理最后一行数据
                 * (1)原因是最后一行不会走换行的逻辑,也就记录不了最后一行的子View与行数
                 * (2)所以单独做处理
                 */
                if (i == childCount - 1) {
                    allLines.add(lineViews);
                    lineHeights.add(lineHeight);
                    parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                    parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
                }
            }

        }

        /**
         * 1.再度量自己,保存
         * (1)为什么要保存?
         * (2)测量完之后保存是为了通过getMeasuredWidth()与getMeasuredHeight()方法获取到值。
         * (3)自己的宽高取决于子View的宽高情况
         * (4)根据子View的度量结果,来重新度量自己ViewGroup
         * (5)作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

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

        /**
         * 1.记录的是子View真正需要的宽和高
         * (1)不能按如下方式保存是因为父级ViewGroup测量模式的大小值只是一个参考值,需要根据子View的度量
         * 结果,来重新度量自己的大小。然后保存。
         * setMeasuredDimension(parentNeededWidth,parentNeededHeight);
         */
        setMeasuredDimension(realWidth,realHeight);
    }

    /**
     * 1.布局摆放
     * (1)子View到底在父View的哪个位置
     * (2)获取第一个子View摆放在父View中的左边距与顶边距
     * (3)一行一行进行布局
     */
    @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);

            // @TODO 要考虑gravity属性的情况计算宽高
            for (int j = 0; j < lineViews.size(); j++){
                View view = lineViews.get(j);
                int left = curL;
                int top =  curT;

                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();
                view.layout(left,top,right,bottom);
                curL = right + mHorizontalSpacing;
            }

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

    public static int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources
                .getSystem().getDisplayMetrics());
    }
}

6.查漏补缺

6.1padding与margin

(1)盒子套盒子

(2)如果有margin是相对于它的父view来说的

6.2lastLine遗漏

6.3visibleState遗漏

(1)需要判断子View是否可见的情况

if (childView.getVisibility() != View.GONE) {}

6.4gravity属性控制layout

6.5参考flexBox Layout

FlexboxLayout——实现灵活多变的瀑布流

7.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

7.1微信打赏

在这里插入图片描述

7.2支付宝打赏

在这里插入图片描述

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值