第8单元 自定义View与Touch事件分发

前言

原生View不满足我们的业务需求,我们要自定义View

自定义View基础

类型 定义
自定义组合控件 多个控件组合成为一个新的控件,方便多处复用
继承系统View控件 继承自TextView等系统控件,在系统控件的基础功能上进行扩展
继承View 不复用系统控件逻辑,继承View进行功能定义
继承系统ViewGroup 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展

View绘制流程

onMeasure

测量View的宽高
setMeasuredDimension() 设置View测量的高度

onLayout

计算当前View以及子View的位置

onDraw

视图的绘制工作

3、getMeasureWidth()与getWidth()区别

一个View 的大小是由View本身和父View两个共同决定
getMeasureWidth:measure阶段确定,是xml中的原始值
getWidth:layout阶段确定,是最终显示的大小
两个值可能相等,可能不相等

如何在onCreate中获取View的高度?

postDelay延迟获取一下
ViewTreeObserver方式获取
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    
    
@Override
public void onGlobalLayout() {
    
    
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.i(TAG, "width: " + view.getWidth());
Log.i(TAG, "height: " + view.getHeight());
}
});

4、View,ViewGroup绘制区别

onMeasure:
View只需要确定自己的大小就可,ViewGroup需要先确定子View的大小

onLayout:
View不需要处理,ViewGroup必须实现onLayout来安排子view的位置

onDraw:
View需要实现自己的onDraw就好,ViewGroup通过dispatchDraw以实现对各个子view的绘制

5 例子

自定义一个文本控件

//自定义文件控件
public class FentTextView extends View {
    
    
    //new用
    public FentTextView(Context context) {
    
    
        super(context);
    }

    //xml里面用
    public FentTextView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
    }
    
}

此时可以在xml中引入,但是没有具体的属性.
那么就可以自定义属性了
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FentTextView">
        <attr name="android:text" format="string"></attr>
<!--        自定义name-->
        <attr name="text" format="string"></attr>
    </declare-styleable>
</resources>

属性值的类型format

(1). reference:参考某一资源ID
(2). color:颜色值
(3). boolean:布尔值
(4). dimension:尺寸值
(5). float:浮点值
(6). integer:整型值
(7). string:字符串
(8). fraction:百分数
(9). enum:枚举值
(10). flag:位或运算

此时xml设置的文件内容.和绘制的并不统一.
需要继续修改view中的内容.

package com.fenghongzhang.day004;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

//自定义文件控件
public class FentTextView extends View {
    
    
    
    private String text;

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //new用
    public FentTextView(Context context) {
    
    
        super(context);
    }

    //xml里面用
    public FentTextView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        /**
         * 第一个参数attrs
         * 第二个参数是自定义view的名称
         */
        //取到所有属性值
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.FentTextView);
        //取到文本值
        text = typedArray.getString(R.styleable.FentTextView_android_text);
        
    }

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        super.onDraw(canvas);
        canvas.drawText(text,200,200,paint);
    }
}

修改字体大小

<attr name="size" format="dimension"></attr>
package com.fenghongzhang.day004;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

//自定义文件控件
public class FentTextView extends View {
    
    

    private String text;
    private float size;

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //new用
    public FentTextView(Context context) {
    
    
        super(context);
    }

    //xml里面用
    public FentTextView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        /**
         * 第一个参数attrs
         * 第二个参数是自定义view的名称
         */
        //取到所有属性值
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.FentTextView);
        //取到文本值
        text = typedArray.getString(R.styleable.FentTextView_android_text);
        //获取大小
        size = typedArray.getDimension(R.styleable.FentTextView_size, 20);

    }

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        super.onDraw(canvas);
        //设置字体大小
        paint.setTextSize(size);
        canvas.drawText(text,200,200,paint);
    }
}

此时view的宽高还不起作用.

android:layout_height="wrap_content"   //自适应大小  
android:layout_height="match_parent"   //与父视图等高  
android:layout_height="fill_parent"    //与父视图等高  
android:layout_height="100dip"         //精确设置高度值为 100dip 

测量规格(MeasureSpec) = 测量模式(mode) + 测量大小(size)

在这里插入图片描述

2、三种测量模式

MeasureSpec 表示的是一个 32 位的整数值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小 SpecSize。

三种测量模式

EXACTLY : 精确测量模式
当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。

AT_MOST最大值模式.
当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。

UNSPECIFIED不指定测量模式,
父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。比如listview就是这个模式

    //设置最重要的属性之一
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
//                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

        //100为宽度默认值
        measureWidth = getMyViewSize(300, widthMeasureSpec);
        //200为高度默认值
        measureHeight = getMyViewSize(300, heightMeasureSpec);
        //记录子子布局确认后的尺寸
        setMeasuredDimension(measureWidth, measureHeight);

//        int widthModel = MeasureSpec.getMode(widthMeasureSpec);
//        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//
//        int heightModel = MeasureSpec.getMode(heightMeasureSpec);
//        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//        setMeasuredDimension(width,height);

    }
   private int getMyViewSize(int defaultSize, int measureSpec) {
    
    
        int result = defaultSize;
        //解封模式和尺寸值
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
    
    
            case MeasureSpec.UNSPECIFIED:
                result = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                //指定一个值
                result = defaultSize;
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

参考链接

https://www.jianshu.com/p/23519665ff32

https://www.jianshu.com/p/146e5cec4863

猜你喜欢

转载自blog.csdn.net/shuai_ge_feng/article/details/114101996
今日推荐