android--自定义view

来源:互联网 发布:中国投资咨询公司 知乎 编辑:程序博客网 时间:2024/06/06 17:05

  我们为什么要自定义View ?

       已有的UI控件不能满足业务的需求。

自定义View的主要实现方式: 

1、直接继承已有控件

 2、继承ViewGroup实现组合控件 

3、继承View实现完全自定义的控件

自定义View的重要方法: 

1、onMeasure  控件的尺寸(需要对控件尺寸进行设置时实现) 

2、onLayout  对控件进行排版,排列子控件(继承ViewGroup的方式必须实 现)

  3、onDraw            在控件上进行图形绘制(需要自定义控件的显示效果时实现) 

   在这篇文章中,我主要解压介绍onMeasure和onDraw方法,在下一篇中,利用流式布局介绍onLayout方法。

在继承View时,会重写构造方法,我们一般选择两个构造方法。

构造方法的规则:

  自定义View必须实现构造方法: 

控件类名(Context  context)    使用代码创建控件对象时候使用 

控件类名(Context  context,AttributeSet  attrs)    在布局中使用时调用

onDraw方法 实现图形的绘制,

通过Canvas对象实现 

Canvas(画布)的主要方法:

  drawLine    画线

 drawRect    画矩形

 drawCircle  画圆形 

drawText    画文字 ..... 

Paint(画笔)的主要方法: 

setColor      设置颜色 

setStrokeWidth    设置描线的宽度 

注意:尽量不要在onDraw中创建对象,onDraw会被频繁调用(500ms左右

这里写一个倒计时的例子。我们一步一步来完成。

先写出一个圆中有数数字的样式。

布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"    android:layout_height="match_parent" tools:context="com.example.my_view.MainActivity"><com.example.my_view.Counter    android:layout_width="match_parent"    android:layout_height="wrap_content" /></LinearLayout>
Counter.java

package com.example.my_view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;/** * 自定义控件 */public class Counter extends View {    private Paint paint;    public Counter(Context context) {        super(context);    }    public Counter(Context context,  AttributeSet attrs) {        super(context, attrs);        //因为自定义布局是写在xml文件中 ,所有会调用该方法。        //初始化画笔        init();    }    private void init(){        //创建画笔        paint = new Paint();        //设置粗细        paint.setStrokeWidth(2);        //设置平滑度        paint.setAntiAlias(true);        //设置文字大小        paint.setTextSize(40);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //画笔颜色        paint.setColor(Color.RED);        canvas.drawCircle(100,100,50,paint);        //重置颜色        paint.setColor(Color.GREEN);        canvas.drawText("10",75,110,paint);    }}
这里MainActivity.java中我什么都没有写,所有这里就不去看代码了。

效果图:



   我们发现,画笔的颜色和一些基本设置都在自定义布局中来设置的。我们都知道,我们这些数据,都是在xml布局文件中设置的。那应该怎么去自定义属性呢?这里分几步去完成。

自定义属性 

1、在values下添加attrs.xml自定义属性的资源文件 

     2、在attrs.xml中添加属性:

 <?xml  version="1.0"  encoding="utf-8"?> 

<resources>       

 <!--自定义属性的集合  name为自定义控件的类名-->        

<declare-styleable  name="Notepad">                

<!--自定义属性  name是属性名,format为属性值类型-->               

 <attr  name="lineColor"  format="color"/>              

  <attr  name="lineHeight"  format="integer"/>     

    </declare-styleable> 

</resources> 
3、在布局中的控件上添加属性: 

在根布局上添加appNs 

在控件上使用自定义属性:app:属性名  =  "值"

  4、在自定义View中读取自定义属性

 //从布局文件中读取自定义属性

 //obtainStyledAttributes获得属性集合,

  //第一个参数是AttributeSet属性集合,第二个参数是attrs中定义的属性集 合名称 

TypedArray  typedArray  =  context.obtainStyledAttributes(attrs,   R.styleable.Notepad); 

//再从TypedArray中获得属性值

 int  color  =  typedArray.getColor(R.styleable.Notepad_lineColor,   Color.BLUE);

  int  line  =  typedArray.getInteger(R.styleable.Notepad_lineHeight,  3);

    //将TypedArray回收

     typedArray.recycle();

实现用户触摸的监听的方式:

 1、重写onTouchEvent方法      (自定义视图一般使用这种) 

2、调用setOnTouchEventListener方法  (调用者去实现)

实现代码:

改变布局文件:


资源文件:attrs.xml文件

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="Counter">        <attr name="bgColor" format="color"/>        <attr name="textColor" format="color"/>        <attr name="number" format="integer"/>    </declare-styleable></resources>
Counter.java

package com.example.my_view;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.os.SystemClock;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * 自定义控件 */public class Counter extends View {    private Paint paint;    private int bgColor;    private int number;    private int textColor;    private int count;    //该变量用来倒计时    private Thread thread; //利用线程来让数字跑起来    public Counter(Context context) {        super(context);    }    public Counter(Context context, AttributeSet attrs) {        super(context, attrs);        //因为自定义布局是写在xml文件中 ,所有会调用该方法。        //初始化画笔        init(attrs);    }    private void init(AttributeSet attrs) {        if (attrs != null) {            //获取属性文件中 属性的集合            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.Counter);            //获取背景颜色 默认值:Color.YELLOW            bgColor = typedArray.getColor(R.styleable.Counter_bgColor, Color.YELLOW);            //获取字体颜色 默认值: Color.RED            textColor = typedArray.getColor(R.styleable.Counter_textColor, Color.RED);            //获取倒计时的数据 默认值:5            number = typedArray.getInteger(R.styleable.Counter_number, 5);            count = number;            //回收集合            typedArray.recycle();        }        //创建画笔        paint = new Paint();        //设置粗细        paint.setStrokeWidth(2);        //设置平滑度        paint.setAntiAlias(true);        //设置文字大小        paint.setTextSize(40);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //画笔颜色        paint.setColor(bgColor);        canvas.drawCircle(100, 100, 50, paint);        //重置颜色        paint.setColor(textColor);        canvas.drawText("" + count, 75, 110, paint);    }    /**     * 实现触摸事件,让数字开始动起来     * @param event     * @return     */    @Override    public boolean onTouchEvent(MotionEvent event) {        if (event.getAction() == MotionEvent.ACTION_DOWN) {            //启动线程倒计时            if (thread == null) {                thread = new Thread(new Runnable() {                    @Override                    public void run() {                        while (true) {                            //重绘                            postInvalidate();                            if (count > 0) {                                count--;                            } else {                                break;                            }                            //让线程沉睡一秒,                            //这里没有用线程的sleep方法,因为用线程的sleep方法,还要处理异常                            SystemClock.sleep(1000);                        }                    }                });                //启动线程                thread.start();            }        }        return super.onTouchEvent(event);    }}

效果图:

通过这个例子,应该可以了解onDraw方法的使用,下面看下onMeasure方法介绍。

我们在布局文件中添加一个控件:

运行后,你会发现,这个控件根本就没有显示出来,在上面应该也可以看到,我自定义控件设置的高是wrap_content,利用的布局是LinearLayout,那为什么没有显示出该控件呢?


    因为我们在自定义控件时,根本就没有给控件设置大小,现在我们可以把onMeasure方法加上,让TextView控件显示出来。

在Counter.java添加如下代码:

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //设置宽和高,默认值:200        this.setMeasuredDimension(getSize(widthMeasureSpec,200),                getSize(heightMeasureSpec,200));    }    /**     * 获取 控件的大小     * @param spec  实际值     * @param defaultSize  默认值     */    private int getSize(int spec, int defaultSize){        /*//获取尺寸模式          模式有三种模式              EXACTLY:一般是设置了明确的值或者是MATCH_PARENT              AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT               UNSPECIFIED:表示子布局想要多大就多大,很少使用*/        int mode = MeasureSpec.getMode(spec);        //获取尺寸大小        int size = MeasureSpec.getSize(spec);        // n 表示最后控件的大小  先等于默认值        int n = defaultSize;        //如果是精准模式,则大小与size相同        if(mode == MeasureSpec.EXACTLY){            n = size;        }else{            //吐过是AT_MOST模式,就选默认值和size中小的哪一个            if(mode == MeasureSpec.AT_MOST){                n = Math.min(size,defaultSize);            }        }        //返回尺寸大小        return n;    }

运行后:


onDraw方法和onMeasure两个方法就介绍到这里。下次利用流布局来介绍一下onLayout方法的使用。


原创粉丝点击