欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 自定义View简单归纳

程序员文章站 2022-05-30 20:26:28
...

什么是自定义View


自定义View即继承与View和ViewGroup的自定义控件,可以实现系统控件以外的功能,也可以继承与系统控件,对系统控件进行所需要的修改。我也做了一个简单的自定义TextView,但是并没有完成其OnTouchEvent事件。这是个人学习归纳,如有错误请留言,谢谢大家。

在这里我将自定义View分成五步


第一步创建自定属性


在res文件夹下创建一个attrs文件。然后通过declare-styleable创建一个自定义的控件,写下所需要实现的自定义属性即可。

<resources>
    <!--name 最好是你自定义View的名字-->
    <declare-styleable name="mTextView">
        <!--
        name属性名称
        format输入格式:
        string文字  color颜色  dimension宽高(字体大小) integer数字 reference资源文件
        -->
        <attr name="text" format="string"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize" format="dimension"/>
        <attr name="maxLength" format="integer"/>
        <!-- background 自定义View都是继承自View , 背景是由View管理的-->
        <!--   <attr name="background" format="reference|color"/>-->
        <!--枚举-->
        <attr name="inputType">
            <enum name="number" value="1"/>
            <enum name="text" value="2"/>
            <enum name="password" value="3"/>
        </attr>
    </declare-styleable>
</resources>

第二步完成其三个构造方法OnCreat


在完成构造方法前,我们需要创建所需要的参数并对必要参数设置默认值,且必须创建一个画笔Paint。完成构造方法后,需要在构造方法中获取自定义属性的值,建议三个构造方法都依次调用含有3个参数的构造方法,保证了代码的复用性。

在这里讲解一下每一个构造方法什么时候会被调用

一个参数的构造方法将在是在代码中new的时候会被调用的

二个参数的构造方法将在xml文件中添加控件时被调用

三个参数的构造方法则是在需要用到自定义属性的时候被调用

  public mTextView(Context context) {
        this(context,null);
    }

    public mTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public mTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.mTextView);

        mText = array.getString(R.styleable.mTextView_text);
        mTextSize = array.getDimensionPixelSize(R.styleable.mTextView_textSize,SptoPx(mTextSize));//第二个参数为默认值
        mTextColor = array.getColor(R.styleable.mTextView_textColor,mTextColor);//第二个参数为默认值

        //回收  避免浪费资源及内存溢出
        array.recycle();

        mPaint = new Paint();
        //抗锯齿
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        //  默认给一个背景
        // setBackgroundColor(Color.TRANSPARENT);
    }

第三步完成其测量方法OnMeasure


三种测量模式 :
AT_MOST(wrap_content,需要计算所占长宽)
EXACTLY(确定的值,不用计算,包括macth_parent也是一个固定值)
UNSPECIFIED(尽可能大,一般只用于系统组件)

widthMeasureSpec 和 heightMeasureSpec介绍

widthMeasureSpec 和 heightMeasureSpec都是一个int值且都包含32位数字  前2位表示信息模式  后面30位表示其具体值 (系统将这两个信息自动拼接到了一块)

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //1.确定的值,这个时候不用计算  获取宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //2.wrap_content需要计算
        if(widthMode == MeasureSpec.AT_MOST)
        {
            //计算宽度 用画笔测量
            Rect bounds = new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            width = bounds.width()+getPaddingLeft()+getPaddingRight();
        }

        //1.确定的值,这个时候不用计算  获取高度
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //2.wrap_content需要计算
        if(heightMode == MeasureSpec.AT_MOST)
        {
            //计算宽度 用画笔测量
            Rect bounds = new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            height = bounds.height()+getPaddingTop()+getPaddingBottom();
        }
    }


第四步完成其OnDraw绘画方法


这里涉及到了一个绘画位置的问题,x表示的是绘画其实位子,而y并非表示绘画的y轴位置而是表示基线位子baseLine(需要我们自己算)

在这里有一个问题一定要考虑,关于添加了padding以后基线的位子也会随之改变。由于继承与View所以margin则不需要我们修改。

在这里我们还要关注一个问题即如果自定义控件继承与ViewGroup其Ondraw方法在设置背景前不会被调用,因为ViewGroup在内部已经设置了flags使其不会调用

ondraw方法,而在设置background后会调用其ondraw方法。那么怎么解决了,我们可以在其dispatchDraw方法中完成绘画功能。这样我们即使不设置背景依然

可以做绘画。

关于基线问题:这幅图详细介绍了基线和bottom、top的含义

Android 自定义View简单归纳

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // x 就是开始的位置   0    y 基线 baseLine 需要运算
        //dy 代表的是:高度的一半到 baseLine的距离
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        // top 是一个负值  bottom 是一个正值    top,bttom的值代表是  bottom是baseLine到文字底部的距离(正值)
        // 必须要清楚的,可以自己打印就好
        int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
        int baseLine = getHeight()/2 + dy+getPaddingTop()/2-getPaddingBottom()/2;
        int x = getPaddingLeft();

        canvas.drawText(mText,x,baseLine,mPaint);
    }



第五步、完成其OnTouchEvent


我们需要在OnTouchEvent中做的事情比较多,包括事件的分发和事件的拦截。由于我只是写了一个简单的自定义TextView所以OnTouchEvent中没有做什么处理

    /**
     * 处理跟用户交互的,手指触摸等等
     * @param event 事件分发事件拦截
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                // 手指按下
                Log.e("TAG","手指按下");
                break;

            case MotionEvent.ACTION_MOVE:
                // 手指移动
                Log.e("TAG","手指移动");
                break;

            case MotionEvent.ACTION_UP:
                // 手指抬起
                Log.e("TAG","手指抬起");
                break;
        }

        invalidate();

        return super.onTouchEvent(event);
    }

如果我们的自定义控件是继承于ViewGroup则我们还需要完成其OnLayout的方法。


mTextView(自定义TextView)

Github地址: mTextView