android--自定义view

  我们为什么要自定义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方法的使用。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值