Android 自定义View简单归纳

什么是自定义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的含义

这里写图片描述

  @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


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值