Android 自定义View学习之文字绘制

自定义组件通常来说有三种定义方式:

  1. 组件类继承自View,这是定义一个自己想要的效果的组件;

  2. 从已有组件扩展,比如,从ImageView扩展出功能更强、更有个性化的组件;

  3. 将多个已有组件合成一个新的组件,比如侧边带字母索引的ListView ;

我们先从第一种说起:

我们需要知道的第一件事 是View的生命周期。

这里写图片描述

继承一个View

在自定义组件的时候,先要继承现有的View,并为其提供三个构造方法:

FirstView.java :

public class MyFirstView extends View {
public MyFirstView(Context context) {

        super(context);

    }

    public MyFirstView(Context context, AttributeSet attrs) {

        super(context, attrs);

    }

    public MyFirstView(Context context, AttributeSet attrs, int defStyleAttr) {

        super(context, attrs, defStyleAttr);

    }
}

定义自定义属性

为了将我们需要的View加载到我们的UI上,我们需要通过XML属性来指定它的样式与行为

res/values/attrs.xml :

<resources>
    <declare-styleable name="MyFirstView">
        <attr name="labelColor" format="color"/>
        <attr name="labelHeight" format="dimension"/>
        <attr name="labelPosition" format="enum">
            <enum name="left" value="0"/>
            <enum name="right" value="1"/>
        </attr>
        <attr name="labelWidth" format="dimension"/>
        <attr name="mShowText" format="boolean"/>
    </declare-styleable>
</resources>

注意: 因为这个参数要求整体唯一,所以如果你定义的一些attr在你引入的某些库中已经存在,会出现错误。

上面的代码声明了5个自设的属性:

  • mShowText——是否显示文本
  • labelPosition——文本显示位置
  • labelHeight——文本高度
  • labelWidth——文本宽度
  • labelColor——文本颜色

一旦你定义了自设的属性,你可以在layout XML文件中使用它们,就像内置属性一样。唯一不同的是你自设的属性是归属于不同的命名空间。不是属于http://schemas.android.com/apk/res/android的命名空间,它们归属于http://schemas.android.com/apk/[your package name]。

activity_main.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:custom="http://schemas.android.com/apk/com.example.view.myexample"
    tools:context="com.example.view.myexample.MainActivity">

   <com.example.view.myexample.MyFirstView
       android:id="@+id/myfirstView"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:padding="10dp"
       android:layout_weight="100"
       custom:showText="true"
       custom:labelHeight="20dp"
       custom:labelWidth="110dp"
       custom:labelPosition="left"
       android:background="@color/colorPrimaryDark"
       custom:labelColor="@android:color/black"
       />

    <Button
        android:id="@+id/Reset"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="按钮"
        android:background="@color/colorAccent"/>
</LinearLayout>

应用自定义属性

定义好了可选参数之后,不着急如何在XML布局中使用,先要知道如何在类中使用他们,我们在构造方法中获取并赋给一些自定义View中变量。按照官方文档的做法,需要在两个参数的构造方法中获取他们:

public MyFirstView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyFirstView,0,0);
        try{
            mShowText = a.getBoolean(R.styleable.MyFirstView_mShowText,false);
            mTextWidth = a.getDimension(R.styleable.MyFirstView_labelWidth,10.0f);
            mTextHeight = a.getDimension(R.styleable.MyFirstView_labelHeight,100.0f);
            mTextPos = a.getInteger(R.styleable.MyFirstView_labelPosition,TEXTPOS_LEFT);
            mTextColor = a.getColor(R.styleable.MyFirstView_labelColor,0xff000000);
        }finally {
            a.recycle();//TypedArray对象是一个共享资源,必须被在使用后进行回收
        }

    }

我们在这里获取参数值,比如TextHeight为我们所设置的100;

创建绘图对象

重绘一个自定义的view的最重要的步骤是重写onDraw()方法。onDraw()的参数是一个Canvas对象。Canvas类定义了绘制文本,线条,图像与许多其他图形的方法。你可以在onDraw方法里面使用那些方法来创建你的UI。

在你调用任何绘制方法之前,你需要创建一个Paint对象。

android.graphics framework把绘制定义为下面两类:

  • 绘制什么,由Canvas处理
  • 如何绘制,由Paint处理

例如Canvas提供绘制一条直线的方法,Paint提供直线颜色。Canvas提供绘制矩形的方法,Paint定义是否使用颜色填充。简单来说:Canvas定义你在屏幕上画的图形,而Paint定义颜色,样式,字体,所以在绘制之前,你需要创建一个或者多个Paint对象。在这个例子中,是在init()方法实现的,由constructor调用。

 private void init() {
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        /*
         *绘制文本的宽高
         */
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setColor(mTextColor);

        if(mTextHeight==0){
            mTextHeight = mTextPaint.getTextSize();
        }else{
            mTextPaint.setTextSize(mTextHeight);
            int width = DensityUtil.px2dip(getContext(),mTextWidth);
        }


    }

定义一个init()方法,并在三个构造方法中都调用此方法。

刚开始就创建对象是一个重要的优化技巧。Views会被频繁的重新绘制,初始化许多绘制对象需要花费昂贵的代价。在onDraw方法里面创建绘制对象会严重影响到性能并使得你的UI显得卡顿。

处理布局事件

为了正确的绘制你的view,你需要知道view的大小。复杂的自定义view通常需要根据在屏幕上的大小与形状执行多次layout计算。

如果你想更加精确的控制你的view的大小,需要重写onMeasure())方法。这个方法的参数是View.MeasureSpec,它会告诉你的view的父控件的大小。那些值被包装成int类型,你可以使用静态方法来获取其中的信息。

这里是一个实现onMeasure()的例子。

FirstView.java :

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//1440px 获得父控件的宽度,这里单位是px,可以转换为dp进行查看

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//1940px  获得父控件的高度,单位为px


        int minW = getPaddingLeft()+getPaddingRight()+getSuggestedMinimumWidth();//70px (20dp) //获得padding总的宽度,单位为px

        int w = resolveSizeAndState(minW,widthMeasureSpec,1);//1440px

        int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();//1500px     1440px-10px+70px
        int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);//1430px


        int measuredHeight, measuredWidth;

        if (widthMode == MeasureSpec.EXACTLY) {//判断父容器宽是否为MATCH_PARENT
            measuredWidth = widthSize;
        } else {
            measuredWidth = SIZE;
        }

        if (heightMode == MeasureSpec.EXACTLY) {//父容器高度是否为MATCH_PARENT,因为我们的高度是WRAP_CONTENT,所以不会进入if
            measuredHeight = heightSize;
        } else {
            measuredHeight = SIZE;//15px
        }

        setMeasuredDimension(w, h);


    }

对这个方法的理解可以看看这个

上面的代码有三个重要的事情需要注意:

  1. 计算的过程有把view的padding考虑进去。这个在后面会提到,这部分是view所控制的。

  2. 帮助方法resolveSizeAndState()是用来创建最终的宽高值的。这个方法比较 view 的期望值与传递给 onMeasure
    方法的 spec 值,然后返回一个合适的View.MeasureSpec值。

  3. onMeasure()没有返回值。它通过调用setMeasuredDimension()来获取结果。调用这个方法是强制执行的,如果你遗漏了这个方法,会出现运行时异常。

绘图

每个view的onDraw都是不同的,但是有下面一些常见的操作:

  • 绘制文字使用drawText()。指定字体通过调用setTypeface(), 通过setColor()来设置文字颜色.

  • 绘制基本图形使用drawRect(), drawOval(), drawArc().
    通过setStyle()来指定形状是否需要filled, outlined.

  • 绘制一些复杂的图形,使用Path类. 通过给Path对象添加直线与曲线, 然后使用drawPath()来绘制图形.
    和基本图形一样,paths也可以通过setStyle来设置是outlined, filled, both.

  • 通过创建LinearGradient对象来定义渐变。调用setShader()来使用LinearGradient。

  • 通过使用drawBitmap来绘制图片.

 protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mTextPaint.setColor(Color.GREEN);

        /*
         *文字的X轴坐标
         */
        float stringWidth = mTextPaint.measureText(mData.get(mCurrentItem).getmLabel());//439px  (125dp)
        float width =getWidth();//  1440px (411dp)
        float x =(getWidth()-stringWidth)/2;//500.5px

        /*
         *文字的Y轴坐标
         */

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float y = getHeight() / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
        canvas.drawText(mData.get(mCurrentItem).mLabel, x,y, mTextPaint);

        Log.i(TAG,"onDraw:" + ""+ x + ", " + y);

    }

在此之前,我们为其提供文本:

 private class Item {
        public String mLabel;

        public String getmLabel() {
            return mLabel;
        }

        public void setmLabel(String mLabel) {
            this.mLabel = mLabel;
        }
    }
 public void addItem(String label){
        Item it = new Item();
       it.setmLabel(label);
        mData.add(it);
    }
    ...
            addItem("Annabelle");
            addItem("Brunhilde");
            addItem("Carolina");
            addItem("Dahlia");
            addItem("Ekaterina");
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值