自定义View(1)构造函数、自定义属性

本篇主要是讲解自定义View的基本操作,暂时无运行效果,主要涉及自定义View构造函数、onMeasure、onDraw、监听与时事件分发、自定义View属性等。最终效果需要结合《自定义View(2)onMeasure、onDraw》一齐食用。

1. 什么是自定义View?

自定义View可以理解为,除了安卓提供的原生继承View的API之外的,自定义的继承View或者ViewGroup的控件。

2. 自定义TextView

自定义TextView继承自View,并重写里面的三个构造方法
布局为什么会显示加载到我们的Activity,LayoutInflate解析的时候(实例化View是通过反射)。

public class TextView extends View {
    
    

    // 该构造函数在代码里面new的时候调用。TextView textView = new TextView(this);
    public TextView(Context context) {
    
    
        super(context);
    }

    // 该构造函数在布局layout中使用。<com.example.view1.TextView/>
    public TextView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
    }

    /***
     * 该构造函数在布局layout中调用,但是是在使用style时。
     * <com.example.view1.TextView
     *     style="@style/Test_View_Test"
     *     android:text="Hello World!" />
     */
    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
    }
}

2.2 自定义View的测量方法onMeasure

    /**
     * 自定义View的测量方法,布局、控件的宽高由这个方法指定,需要测量。
     *
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取宽高的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // MeasureSpec.AT_MOST : 在布局中指定为wrap_content
        // MeasureSpec.EXACTLY : 在布局中指定为确切的值  xxdp、match_content、fill_content
        // MeasureSpec.UNSPECIFIED : 在布局尽可能的大   很少用到,一般ScrollView、ListView在测量子布局的时候用到
        if (widthMode == MeasureSpec.AT_MOST) {
    
    

        }
    }

ScrollView嵌套ListView会显示不全(只显示一项)
是因为ScrollView传给ListView的heightMode为UNSPECIFIED,走如下代码:

// /Android/Sdk/sources/android-33/android/widget/ListView.java
        if (heightMode == MeasureSpec.UNSPECIFIED) {
    
    
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

要解决这个问题,那么就需要heightMode为AT_MOST,我们自定义一个MyListView继承ListView。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        // Integer.MAX_VALUE是防止进入 if (returnedHeight >= maxHeight) 而不是返回return returnedHeight;
        //  >> 2 是因为MAX_VALUE是2^31,>> 2就是30位。(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST)就是32位,
        // 满足makeMeasureSpec的需要。
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
---------------------
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 获取前2位
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);      // 获取后30位

2.3 自定义View的绘制方法onDraw

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        super.onDraw(canvas);
        // 画文本
        canvas.drawText();
        // 画弧
        canvas.drawArc();
        //画圆
        canvas.drawCircle();
    }

2.4 自定义View的事件分发处理

手指触摸,处理事件分发和拦截,用于用户交互

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
    
        switch (event.getAction()) {
    
    
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onTouchEvent(event);
    }

2.5 自定义属性

2.5.1 在布局中自定义属性

<!-- 新建 /CustomView/View1/app/src/main/res/values/attrs.xml -->

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- name最好是自定义View的名字 -->
    <declare-styleable name="TextView">
        <!-- name是属性名称;format是格式:dimension表示宽高、字体大小 -->
        <attr name="text" format="string"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize" format="dimension"/>
        <attr name="android:maxLength" format="integer"/>
        <attr name="android:background" format="reference|color"/>

        <!-- 枚举 -->
        <attr name="android:inputType">
            <enum name="number" value="1"/>
            <enum name="text" value="2"/>
            <enum name="password" value="3"/>
        </attr>
    </declare-styleable>
</resources>

2.5.2 在布局中使用自定义属性

申明命名空间,然后在自定义View布局中使用。

<!-- /CustomView/View1/app/src/main/res/layout/activity_main.xml -->

<!-- 这个是必须要添加的,app也可以改成其他名字,不然Android不认识,如下的android,在编译时就会报错。-->
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
        <com.example.view1.TextView
            app:text="你好"
            app:textColor="@color/yellow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />

2.5.3 在自定义View中获取这些属性

// /CustomView/View1/app/src/main/java/com/example/view1/TextView.java

    public TextView(Context context) {
    
    
        // 第一个构造函数可以直接调用第二个构造函数
        this(context, null);
    }

    // 该构造函数在布局layout中使用。<com.example.view1.TextView/>
    public TextView(Context context, @Nullable AttributeSet attrs) {
    
    
        // 让第二个构造函数调用第三个构造函数
        this(context, attrs, 0);
    }

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        // 保证不管使用哪个购置函数,都是用的第三个构造函数
        super(context, attrs, defStyleAttr);
        
        // 获取自定义属性,包括刚刚自定义的Text、TextColor、TextSize
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
        mText = array.getString(R.styleable.TextView_text);
        mTextColor = array.getColor(R.styleable.TextView_textColor, mTextColor);
        mTextSize = array.getDimensionPixelSize(R.styleable.TextView_textSize, mTextSize);
        
        // 回收
        array.recycle();
    }

做完这些,此时还是什么都不会显示的,因为没有指定宽高和进行绘制。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xunyan6234/article/details/139261823