ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Android UI

2022-01-24 18:34:16  阅读:136  来源: 互联网

标签:MeasureSpec int UI child Android view public View


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()

标签:MeasureSpec,int,UI,child,Android,view,public,View
来源: https://blog.csdn.net/haicuiyanan/article/details/122614796

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有