Android UI

1、为什么要自定义view

Android系统内置View无法实现我们的需求;

出于性能考虑。

2、自定义viewgroup步骤

自定义viewGroup: 只需重写onMeasure()和onLayout()

onMeasure:

1、确定自身的大小;2、确定子view的大小

onMeasure流程:

1、ViewGroup开始测量自己的尺寸

2、为每个子View计算测量的限制信息

3、把上一步确定的限制信息,传递给每一个子view,然后子view开始measure自己的尺寸

4、获取子view测量完成后的尺寸

5、ViewGroup根据自身的情况,计算自己的尺寸

6、保存自身的尺寸

onLayout 最终是根据top left right bottom确定位置的

onMeasure()方法中常用的方法
1、getChildCount():获取子View的数量;
2、getChildAt(i):获取第i个子控件;
3、subView.getLayoutParams().width/height:设置或获取子控件的宽或高;
4、measureChild(child, widthMeasureSpec, heightMeasureSpec):测量子View的宽高;
5、child.getMeasuredHeight/width():执行完measureChild()方法后就可以通过这种方式获取子view的宽高值;
6、getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;
7、setMeasuredDimension(width, height):重新设置控件的宽高。

onLayout

1、根据规则确定子view位置

onLayout流程:

// 1、遍历子View
// 2、确定自己的规则
// 3、子View的测量尺寸
// 4、left,top,right,bottom
// 5、child.layout
示例一:
package com.huawei.javauidemo.widget;

import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.material.bottomappbar.BottomAppBar;

/**
 * < 自定义viewGroup> <功能详细描述>
 * 下一行比上一行向右偏移100
 * 
 * @author CYN
 * @version [版本号, 2022/1/21]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class CustomViewGroup extends ViewGroup {

    private static final int OFFSET = 100; // 表示缩进的尺寸

    public CustomViewGroup(Context context) {
        super(context);
    }

    public CustomViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 1、测量自身
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 2、为每个子View计算测量的限制信息
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        // 3、把上一步确定的限制信息,传递给每一个子view,然后子view开始measure自己的尺寸
        int childCount = getChildCount();
        for(int i = 0; i < childCount; i ++) {
            View child = getChildAt(i);
            ViewGroup.LayoutParams lp = child.getLayoutParams();
            int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
            int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
            child.measure(childWidthSpec, childHeightSpec);

            int width = 0;
            int height = 0;
            // 4、获取子view测量完成后的尺寸
            // 5、ViewGroup根据自身的情况,计算自己的尺寸
            switch(widthMode) {
                case MeasureSpec.EXACTLY:
                    width = widthSize;
                    break;
                case MeasureSpec.AT_MOST:
                case MeasureSpec.UNSPECIFIED:
                    for(int i = 0; i < childCount; i ++) {
                        View child = getChildAt(i);
                        int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
                        width = Math.max(width, widthAddOffset); // 取最大的宽度
                    }
                    break;
                default:
                    break;
            }

            switch(heightMode) {
                case MeasureSpec.EXACTLY:
                    height = heightSize;
                    break;
                case MeasureSpec.AT_MOST:
                case MeasureSpec.UNSPECIFIED:
                    for(int i = 0; i < childCount; i ++) {
                        View child = getChildAt(i);
                        height += child.getMeasuredHeight();
                    }
                    break;
                default:
                    break;
            }
            // 6、保存自身的尺寸
            setMeasuredDimension(width, height);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 1、遍历子View
        // 2、确定自己的规则
        // 3、子View的测量尺寸
        // 4、left,top,right,bottom
        // 5、child.layout
        int left = 0;
        int top = 0;
        int right = 0;
        int bottom = 0;

        int childCount = getChildCount();
        for(int i = 0; i < childCount; i ++) {
            View child = getChildAt(i);
            left = i * OFFSET;
            right = left + child.getMeasuredWidth();
            bottom = top + child.getMeasuredHeight();
            child.layout(left, top, right, bottom);
            top += child.getMeasuredHeight();// 不考虑padding margin gravity ...
        }
    }
}
示例:
package com.huawei.javauidemo.widget;

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

import com.huawei.javauidemo.R;

import java.util.ArrayList;

/**
 * < 自定义viewGroup> <把子控件从左到右排放,如果一行放不下,那么自动换行>
 *
 * 自定义布局流程
 * 1、自定义属性:声明,设置,解析获取自定义值
 * 在 attr.xml 声明
 * 2、测量:在onMeasure MeasureSpec.AT_MOST/EXACTLY
 * 自身的宽高/child的宽高
 * 3、布局:在onLayout方法里面根据自己规则来确定children的位置
 * 4、绘制:onDraw
 * 5、处理LayoutParams
 * 6、触摸反馈:滑动事件
 *
 * @author CYN
 * @version [版本号, 2022/1/21]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class CustomFlowLayout extends ViewGroup {

    private static final String TAG = "CustomFlowLayout";

    private List<View> lineViews; // 每一行的子view
    private list<List<View>> views; // 所有的行 一行一行的存储
    private List<Integer> heights; // 存储每一行的高度

    public CustomFlowLayout(Context context) {
        super(context, null);
    }

    public CustomFlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
    }

    public CustomFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void init() {
        views = new ArrayList<>();
        lineViews = new ArrayList<>();
        heights = new ArrayList<>();
    }

    public static class LayoutParams extends MarginLayoutParams {

        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomFlowLayout_Layout);
            try {
                gravity = a.getInt(R.styleable.CustomFlowLayout_android_gravity, -1);
            } finally {
                a.recycle();
            }
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        @Override
        public String toString() {
            return "LayoutParams{" +
                    "gravity=" + gravity +
                    ", bottomMargin=" + bottomMargin +
                    ", leftMargin=" + leftMargin +
                    ", rightMargin=" + rightMargin +
                    ", topMargin=" + topMargin +
                    ", height=" + height +
                    ", width=" + width +
                    "} ";
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 1、测量自身
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 2、为每个子View计算测量的限制信息
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 记录当前行的宽度和高度
        int linewidth = 0; // 宽度是当前行的子view的宽度之和
        int lineHeight = 0; // 高度是当前行所有子view中高度的最大值

        // 记录整个流式布局的宽度和高度
        int flowlayoutWidth = 0; // 所有行中宽度的最大值
        int flowlayoutHeight = 0; // 所有行的高度的累加

        // 初始化参数列表
        init();

        // 3、遍历所有的子view,对子view进行测量,分配到具体的行
        int childCount = getChildCount();
        for(int i = 0; i < childCount; i ++) {
            View child = getChildAt(i);
            // 测量子view 获取到当前子view的测量的宽度/高度
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 获取到当前子view的测量的高度/宽度
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            LayoutParams lp = child.getLayoutParams();
            // 看下当前的行的剩余的宽度是否可以容纳下一个子view,如果放不下,换行
            // 保存当前行的所有子view,累加行高,当前的宽度,高度 置零
            if (linewidth + childWidth > widthSize) { // 换行
                views.add(lineViews);
                lineViews = new ArrayList<>(); // 创建新的一行
                flowlayoutWidth = Math.max(flowlayoutWidth, linewidth);
                flowlayoutHeight += lineHeight;
                heights.add(lineHeight);
                linewidth = 0;
                lineHeight = 0;
            }
            lineViews.add(child);
            linewidth += childWidth;
            lineHeight = Math.max(lineHeight, childHeight);

            if (i == childCount - 1) { // 最后一行
                flowlayoutHeight += lineHeight;
                flowlayoutWidth = Math.max(flowlayoutWidth, linewidth);
                heights.add(lineHeight);
                views.add(lineViews);
            }
        }
        // FlowLayout最终宽高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowlayoutWidth,
                heightMode == MeasureSpec.EXACTLY ? heightSize : flowlayoutHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int lineCount = views.size();
        int currX = 0;
        int currY = 0;

        for(int i = 0; i < lineCount; i ++) { // 大循环,所有的子view 一行一行的布局
            List<View> lineViews = views.get(i); // 取出一行
            int lineHeight = heights.get(i); // 取出这一行的高度值
            // 遍历当前行的子view
            int size = lineViews.size();
            for(int j = 0; j < size; j ++) { // 布局当前行的每一个view
                View child = lineViews.get(j);
                int left = currX;
                int top = currY;
                int right = left + child.getMeasuredWidth();
                int bottom = top + child.getMeasuredHeight();
                child.layout(left, top, right, bottom);
                // 确定下一个view的left
                currX = right;
            }
            currY +=lineHeight;
            currX = 0;
        }
    }
}

3、自定义view步骤

自定义view: 只需重写onMeasure()和onDraw()

相关文章

Android性能优化——之控件的优化 前面讲了图像的优化,接下...
前言 上一篇已经讲了如何实现textView中粗字体效果,里面主要...
最近项目重构,涉及到了数据库和文件下载,发现GreenDao这个...
WebView加载页面的两种方式 一、加载网络页面 加载网络页面,...
给APP全局设置字体主要分为两个方面来介绍 一、给原生界面设...
前言 最近UI大牛出了一版新的效果图,按照IOS的效果做的,页...