Android中自定义View

120 阅读2分钟

1,自定义View的分类

自定义View:没有现成的View,需要我们自己实现,就需要创建自定义View,自定义View一般继承View,SurfaceView或者其他View。

自定义ViewGroup:一般是利用现有的组件根据特定的方式组合成新的组件,实现需要的功能,一般继承自ViewGroup或者layout。

2,自定义View的绘制流程

onMeasure(),onlayout(),onDraw()

onMeasure()测量View的大小

onlayout()确定子View的布局

onDraw()实际进行绘制

自定义View主要实现onMeasure()和onDraw方法

自定义ViewGroup主要实现onMeasure和onlayout()

QQ截图20230112103338.png

MeasureSpace是View的内部类,基本都是二进制运算,由于int是32位,用高两位表示mode,低30位表示size,MODE_SHIFT = 30的作用是移位,Mode的类型有三种:UPSPACEFILED,EXACTLY,AT_MOST。

UPSPACEFILED:不对view的大小做限制,系统中用

EXACTLY:父容器已经得到自己View确切的大小

AT_MOST:父容器指定一个大小,子View最大不能超过指定大小,eg:machParent,最大不能超过machParent

MeasureSpace和dp之间的转换

QQ截图20230112114323.png

所有的View测量子View时最终都会调用

MeasureSpec.makeMeasureSpec(.......)

getMeasureWith和getWith

getMeasure在measure过程结束后就可以获取对应的值,通过setMeasureDimension方法进行设置

getWith在layout结束后才能获取到,通过视图右边的坐标减去左边的坐标计算出来

3,View的层级关系

QQ截图20230112105556.png

4,Android中的两种坐标系 android中的屏幕坐标

QQ截图20230112155858.png

视图坐标系

QQ截图20230112160001.png

5,实现一个自定义空间

想要实现的效果

QQ截图20230112155641.png

实现代码

package com.example.gshg;

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;

public class FlowLayout extends ViewGroup {

    private static final String Tag  = "FlowLayout";
    private int mHorizontalSpacing = dp2px(16);
    private int mVerticalSpacing = dp2px(16);

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

    //在代码中new FlowLayout()的时候调用
    public FlowLayout(Context context) {
        super(context);
    }

    //在xml中使用时调用
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //设置主题时会调用这个构造方法
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //布局
    @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 height = 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();
                curL = right + mHorizontalSpacing;
            }
            curL = getPaddingLeft();
            curT = curT + height + mVerticalSpacing;

        }
    }

    //度量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        initMeasure();
        //度量子View的大小
        int childCount = getChildCount();
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        List<View> lineViews = new ArrayList<>(); //保存所有的子view
        int mLineWithUsed = 0; //记录已经使用的行宽
        int mLineHeightUsed = 0;

        int selfWith = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup中父控件提供的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec);  //viewGroup中父空间提供的宽度

        int parentNeedWith = 0; // measure 过程中子View要求父View的宽
        int parentNeedHeight = 0;  // measure 过程中子View要求父View的高



        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            LayoutParams childLp = childView.getLayoutParams();
            int childWithMeasureSpace = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLp.width);
            int childHeightMeasureSpace = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLp.height);

            childView.measure(childWithMeasureSpace, childHeightMeasureSpace);

            //View是分行layout的,所以要记录一行有那些View,方便布局layout
            lineViews.add(childView);
            //获取子View的宽和高
            int childMeasuredWith = childView.getMeasuredWidth();
            int childMeasureHeight = childView.getMeasuredHeight();

            //计算每一行的宽高
            mLineWithUsed = mLineWithUsed + childMeasuredWith + mHorizontalSpacing;
            mLineHeightUsed = Math.max(mLineHeightUsed, childMeasureHeight);

            //通过宽度确定是否需要换行,通过换行后没每行的行高来获取整个viewGroup的行高
            if (childMeasuredWith + mLineWithUsed + mHorizontalSpacing > selfWith) {

                //一旦换行后,我们就可以判断当前行需要的宽和高,因此可以记录下来
                parentNeedWith = Math.max(parentNeedWith, mLineWithUsed + mHorizontalSpacing);
                parentNeedHeight = parentNeedHeight + mLineHeightUsed + mVerticalSpacing;

                lineViews = new ArrayList<>();
                mLineWithUsed = 0;
                mLineHeightUsed = 0;
            }
        }
        //度量自己的大小
        //根据子View的宽高,重新度量自己ViewGroup
        //作为一个ViewGroup他也是一个View,它的宽高也需要根据它父亲给他提供的宽高
        int withMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int realWith = withMode == MeasureSpec.EXACTLY ? selfWith : parentNeedWith;
        int realHeight = heightMode == MeasureSpec.EXACTLY ? selfHeight: parentNeedHeight;

        setMeasuredDimension(realWith, realHeight);
    }

    private static int dp2px(int dp){

        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());

    }

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