Android 自定义View基础(二)

来源:互联网 发布:自闭倾向 知乎 编辑:程序博客网 时间:2024/06/04 18:05

接着上一篇《Android 自定义View基础(一)》,这一篇我们真正的来实现一个文字自定义控件:

自定义控件的步骤:

自定义属性在自定义View的构造函数获取属性onMesure()onDraw()

1.自定义属性

在res/values/下创建一个attrs.xml文件,然后在创建需要的属性
代码如下:

<?xml version="1.0" encoding="utf-8"?><resources>    <attr name="titleText" format="string"></attr>    <attr name="titleTextColor" format="color"></attr>    <attr name="titleTextSize" format="dimension"></attr>    <declare-styleable name="CustomView01" >        <attr name="titleText"/>        <attr name="titleTextColor"/>        <attr name="titleTextSize"/>    </declare-styleable></resources>

上面的属性中,我们创建了文字,文字颜色,文字大小三个属性

 <declare-styleable name="CustomView01" >

名字也可以是你随便起,但是建议一般与你创建的自定义类名相同。方便查看和管理。

2.创建CustomView01

先创建一个CustomView01继承自View

public class CustomView01 extends View {    public CustomView01(Context context) {        super(context);    }    public CustomView01(Context context, AttributeSet attrs) {        super(context, attrs);    }}

上面的两个构造方法中,一个是我们动态创建控件时使用,一个是从xml布局文件中加载时调用。

3.将创建的控件写入到布局中

在activity_main.xml中加入我们创建的属性和控件

<?xml version="1.0" encoding="utf-8"?><RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:custom="http://schemas.android.com/apk/res-auto"    android:id="@+id/main_layout"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.aofei.myview.MainActivity">   <com.aofei.myview.CustomView01       android:layout_width="200dp"       android:layout_height="100dp"       custom:titleText="这是测试文字"       custom:titleTextColor="#ff0000"       custom:titleTextSize="40sp"       /></RelativeLayout>

此行代码就是将我们创建的自定义属性导入进当前布局,该代码不必记忆,只要输入app然后就会出现一个appNs的提示,确认后就是

 xmlns:app="http://schemas.android.com/apk/res-auto"

然后可以更改名字,为custom,我们的属性就是以custom 开头,

 xmlns:custom="http://schemas.android.com/apk/res-auto"

到这里我们运行,后发现,界面上并没有我们的创建的控件,为什么呢?这是因为我们的自定Custom中,并没有饮用我们的属性和调用onDraw()进行绘画。

然后我们实现我们调用属性和绘画

public class CustomView01 extends View {    private String mTitleText;//文本    private int mTitleTextColor;//文本的颜色    private int mTitleTextSize;//文本的大小    private Rect mBound;//绘制时控制文本绘制的范围    private Paint mPaint;  //绘制文本时的画笔    public CustomView01(Context context) {        this(context, null);    }    public CustomView01(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    /**     * 获取我们的自定义样式     *     * @param context     * @param attrs     * @param defStyleAttr     */    public CustomView01(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        //获取我们所定义的自定义样式属性        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView01, defStyleAttr, 0);        int n = a.getIndexCount();//获取多少个属性        //遍历所有属性        for (int i = 0; i < n; i++) {            int attr = a.getIndex(i);            //根据从xml对应的值对属性赋值            switch (attr) {                case R.styleable.CustomView01_titleText:                    mTitleText = a.getString(attr);                    break;                case R.styleable.CustomView01_titleTextColor:                    mTitleTextColor = a.getColor(attr, Color.BLACK);//默认颜色为黑色                    break;                case R.styleable.CustomView01_titleTextSize:                    //給文字尺寸赋值,默认设置为16sp,TypeValue也可以把sp转化为px                    mTitleTextSize = a.getDimensionPixelSize(attr,                            (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,                                    getResources().getDisplayMetrics()));                    break;            }        }        a.recycle();//释放资源,这个一定不要忘记!        //获取绘制文本的宽和高        mPaint=new Paint();        mPaint.setTextSize(mTitleTextSize);        mBound= new Rect();        mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);    }}

我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

4.我们重写onDraw,onMesure

   @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    @Override    protected void onDraw(Canvas canvas) {//        super.onDraw(canvas);   //我们要重写,不必调用继承的View的方法        mPaint.setColor(Color.YELLOW);     //背景矩形     canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);        mPaint.setColor(mTitleTextColor);//画文字        canvas.drawText(mTitleText,getWidth()/2-mBound.width()/2,getHeight()/2+mBound.height()/2,mPaint);    }

运行后的结果如下:

到这里,我们就实现一个类似于TextView的控件
我们在onDraw()中打印一下几个测量的值

  @Override    protected void onDraw(Canvas canvas) {//        super.onDraw(canvas);   //我们要重写,不必调用继承的View的方法        mPaint.setColor(Color.YELLOW);        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);        mPaint.setColor(mTitleTextColor);        mPaint.setAntiAlias(true);//抗锯齿        canvas.drawText(mTitleText,getWidth()/2-mBound.width()/2,getHeight()/2+mBound.height()/2,mPaint);        Log.e(TAG,"getMeasuredWidth()="+getMeasuredWidth()+", getMeasuredHeight()="+getMeasuredHeight());        Log.e(TAG,"getWidth()="+getWidth()+" ,getHeight="+getHeight());        Log.e(TAG,"mBound.width()="+mBound.width()+" ,mBound.height()="+mBound.height());    }

打印结果如下
这里写图片描述

03-09 14:37:47.172 22448-22448/com.aofei.myview E/CustomView01: getMeasuredWidth()=600, getMeasuredHeight()=30003-09 14:37:47.172 22448-22448/com.aofei.myview E/CustomView01: getWidth()=600 ,getHeight=30003-09 14:37:47.172 22448-22448/com.aofei.myview E/CustomView01: mBound.width()=354 ,mBound.height()=58

这个就是我们控件的大小。

如果我们将我们的布局文件中的属性更改为wrap_content,

<com.aofei.myview.CustomView01       android:layout_width="wrap_content"       android:layout_height="wrap_content"       custom:titleText="这是测试文字"       custom:titleTextColor="#ff0000"       custom:titleTextSize="20sp"       />

运行后的结果如下:


为什么会出现这样的结果呢,看下我们打印的测量尺寸。

03-09 14:44:32.548 22448-22448/com.aofei.myview E/CustomView01: getMeasuredWidth()=1080, getMeasuredHeight()=153603-09 14:44:32.548 22448-22448/com.aofei.myview E/CustomView01: getWidth()=1080 ,getHeight=153603-09 14:44:32.548 22448-22448/com.aofei.myview E/CustomView01: mBound.width()=354 ,mBound.height()=58

此时的宽高发生了变化,这是系统帮我们测量的尺寸,都是match_parent,是手机的屏幕尺寸了,getWidth()=1080 ,getHeight=1536
getMeasuredWidth()=1080, getMeasuredHeight()=1536,显然,这不是我们想要的效果。所以当我们设置了wrap_content时,我们需要自己进行测量,即重写onMeasure.

MeasureSpec的SpecMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为wrap_content
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:

   @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //重写onMeasure        int widthMode=MeasureSpec.getMode(widthMeasureSpec);        int widthSize=MeasureSpec.getSize(widthMeasureSpec);        int heightMode=MeasureSpec.getMode(heightMeasureSpec);        int heightSize=MeasureSpec.getSize(heightMeasureSpec);        int width;        int height;        if (widthMode==MeasureSpec.EXACTLY){            width=widthSize;        }else {            //如果不是给出精确的值,我们通过计算文字的长度和距离控件左右的值来确定,我们画多大            mPaint.setTextSize(mTitleTextSize);        mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);            float textWidth=mBound.width();            int desired = (int) (getPaddingLeft()+textWidth+getPaddingRight());            width=desired;        }        if (heightMode==MeasureSpec.EXACTLY){            height=heightSize;        }else {            mPaint.setTextSize(mTitleTextSize);            mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);            float textHeight=mBound.height();            int desired = (int) (getPaddingTop()+textHeight+getPaddingBottom());            height=desired;        }        setMeasuredDimension(width,height);//设置计算后的宽高    }

我们修改一下布局文件,如下

<?xml version="1.0" encoding="utf-8"?><RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:custom="http://schemas.android.com/apk/res-auto"    android:id="@+id/main_layout"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.aofei.myview.MainActivity">   <com.aofei.myview.CustomView01       android:layout_width="wrap_content"       android:layout_height="wrap_content"       custom:titleText="3388"       custom:titleTextColor="#ff0000"       custom:titleTextSize="20sp"       android:padding="10dp"       /></RelativeLayout>

运行后的结果是:


这样的效果,就是我们想要的,我们可以对高度,宽度随便设置,基本可以满足我们的需求。

接下来我们稍微扩展一下,給这个View添加一个点击事件。在onDraw()最后面添加一个点击事件。

        //再画完后我们給这个控件添加一个点击事件        this.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                mTitleText=randomText();                postInvalidate();//重写绘制            }        });
 /**     * 获取一个随机的四个数字的字符串     * @return     */  private  String randomText(){      Random random = new Random();      Set<Integer> set=new HashSet<>();      while (set.size()<4){          int randomInt=random.nextInt(10);          set.add(randomInt);      }      StringBuffer sb=new StringBuffer();      for (Integer i:set) {          sb.append(""+i);      }      return sb.toString();  }

运行效果如下:

,这是不是很像我们登陆一些网站的时候验证码的更换。写到这里,就简单实现了一个类似于TextView的自定义控件,不管看博客还是视频,都需要自己动手,永远不要认为自己看懂了,不用写了。

0 0