最简单的自定义View

论坛 期权论坛 编程之家     
选择匿名的用户   2021-6-1 11:53   1180   0

为什么要自定义View

android提供了很多控件供我们使用 但有些功能是系统所提供的实现不
了的 这时候我们就需要自定义一个View来实现我们所需要的效果.
在Android中所有的控件都直接或间接的继承自View,分View和ViewGroup两部分.
我们常用的一些View比如TextView,ImageView都是继承自View并添加了一些各自的特性,ViewGroup也是继承View但是它可以包含子View也就是我们经常用到的各种布局比如LinearLayout,RelativeLayout等。

如何自定义布局

所以我们要是实现一个自己的View那就集成View并添加一些自己需要的特性即可.
以下是一个最简单的自定义View

public class MVIew extends View {
    /**
     * 创建一个继承View的class
     *View一共有四个构造器 这里先说两个
     * 第一个构造器的参数就是context,这是在Activity实例化的时候所需的,比如Button button = new Button(context);
     * 第二个构造器有两个参数 第一个同样是context 第二个参数AttributeSet放入了一些属性,这是我们在写XML文件时用到的比如
     * android:layout_width="match_parent"
     * android:layout_height="match_parent"如果不写函数的话是无法通过XML添加View
     */
    public MVIew(Context context) {
        super(context);
    }

    public MVIew(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    //重写onDraw方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.RED);      //设置canvas的背景色
        float radius = 50;                //给定半径
        //给定圆心的的坐标
        float cx = 50;
        float cy = 50;
        Paint paint = new Paint();       //实例化一个Paint对象
        paint.setColor(Color.BLUE);      //设置圆的颜色
        //通过canvas的drawCircle方法画一个圆圈.
        canvas.drawCircle(cx, cy, radius, paint);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

这个自定义的View就是画了一个圆圈,接下来就是让这个view加载到activity上了.第一种就是直接在Activity上实例化一个并通过addView(View)来添加

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MVIew mvIew = new MVIew(getApplicationContext());                                //实例化自定义的View
        RelativeLayout main_layout = (RelativeLayout) findViewById(R.id.main_layout);   //获取布局的对象
        main_layout.addView(mvIew);                                                     //向布局文件添加View
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第二种就是最常用的通过XML文件去添加

<cn.lipengfei.myview.MVIew
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  • 1
  • 2
  • 3

要注意的是标签名要写自定义的View的完整类名既包名.类名
以下是效果
这里写图片描述
效果很简单 就是一个红色的画板上有一个蓝色的半径为50的圆圈.
这里有一个问题
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
明明width和height都是wrap_content为什么出来的辣么大.

测量

这涉及到View的测量.
Android由一个MeasureSpec的类,可以通过它来说实现测量.
MeasureSpec是一个int型的值,并且采用了位运算,高两位为测量的模式,低30位是具体的值.
由三种测量模式

  • EXACTLY 精确模式 例如layout_height=”50dp”或是=”match_parent”
  • AT_MOST 最大值模式 就是warp_content
  • UNSPECIFIED 通过字面意思就是没有指定尺寸

Android通过onMeasure()测量View的大小,默认情况下是EXACTLY模式,所以刚才的例子虽然写了warp_concent依然没有起作用,如果想实现AT_MOST模式那就需要我们重写onMeasure()这个方法.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //先声明两个int值来表示最终的width和height并给定一个默认的大小
        int width_size  = 100;
        int height_size = 100;
        //使用MeasureSpec分别获取width和height的MODE和SIZE
        int HEIGHT_MODE = MeasureSpec.getMode(heightMeasureSpec);
        int HEIGHT_SIZE = MeasureSpec.getSize(heightMeasureSpec);
        int WIDTH_MODE = MeasureSpec.getMode(widthMeasureSpec);
        int WIDTH_SIZE = MeasureSpec.getSize(widthMeasureSpec);
        if (HEIGHT_MODE == MeasureSpec.EXACTLY) {
            height_size = HEIGHT_SIZE;       //如果测量模式是精确的话 那么就直接使用获取到的值就好了
        }else if (HEIGHT_MODE == MeasureSpec.AT_MOST){  //如果是最大值模式的话 那么久比较获取的和设定的默认值那个小就使用那个.ps:Math.min(int a,int b)是比较数值大小的.
            height_size = Math.min(HEIGHT_SIZE,height_size);
        }
        if (WIDTH_MODE == MeasureSpec.EXACTLY){
            width_size = WIDTH_SIZE;
        }else if (WIDTH_MODE == MeasureSpec.AT_MOST){
            width_size = Math.min(WIDTH_SIZE,width_size);
        }
        //测量完毕后得到的了width和height通过setMeasuredDimension()设置width和height,真正决定具体大小的是setMeasuredDimension()的两个参数.
        setMeasuredDimension(width_size,height_size);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

效果如下
这里写图片描述
可以看到这个时候控件的大小已经变成所设定的默认值了.

onDraw(Canvas canvas)

测量完成后就有了大小接下来就是内容了,我们需要在view上显示什么就重写onDraw来实现,以上例子是通过onDraw(Canvas canvas)绘制了一个圆圈.
首先要说的是Canvas,刚才的例子就是通过 canvas.drawCircle(cx, cy, radius, paint);这样一个方法来画了一个圆.这里不对Canvas展开来说,只是说一些Canvas的简单用法.
Canvas就是一个画板,可以在这个画板上话各种各样的东西

Paint paint = new Paint();       //实例化一个Paint对象
      paint.setColor(Color.BLACK);
      paint.setStrokeWidth(10);//因为默认实在是太细了 设定了一个宽度值
      canvas.drawLine(0,0,100,100,paint);
  • 1
  • 2
  • 3
  • 4

这就是在画了一条宽度为10的黑色的线drawLine()的前两个参数为开始点的坐标后两个为结束点的坐标,最后一个参数就是paint;
这里写图片描述
还有很多方法可以调这里就不一一列举了,可以根据canvas.的提示来试试
晚些时候我会把我总结的一些方法写出来方便初学者来看看.
除了Canvas外还有一个Paint的对象也总用到 这个是画笔的意思,比如化园的例子就是通过这个画笔来设定的园的颜色,还有画线的例子也是通过画笔来设定宽度值,如果我们吧之前的代码改一下

canvas.drawColor(Color.RED);      //设置canvas的背景色
        float radius = 50;                //给定半径
        //给定圆心的的坐标
        float cx = 50;
        float cy = 50;
        Paint paint = new Paint();       //实例化一个Paint对象
        paint.setColor(Color.BLACK);     //设置圆的颜色
        paint.setAntiAlias(true);        //设置抗锯齿
        paint.setStyle(Paint.Style.STROKE);  //设置样式
        paint.setStrokeWidth(3);          //设置宽度
        //通过canvas的drawCircle方法画一个圆圈.
        canvas.drawCircle(cx, cy, radius, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

其他的都没有变 只是添加了三个

        paint.setAntiAlias(true);        //设置抗锯齿
        paint.setStyle(Paint.Style.STROKE);  //设置样式
        paint.setStrokeWidth(3);          //设置宽度
  • 1
  • 2
  • 3

这里写图片描述

效果就是这个样子 在样式里面设置了STROKE表示只描边也就是空心效果与之相对的还有两个属性分别是FILL_AND_STROKE,FILL,刚开始有些人搞不清这俩有啥区别,譬如我.FILL就是填充上也就是一个实心的圆圈,FILL_AND_STROKE是不仅填充成一个实心圆而且还有边框,Paint并没有单独给STROKE设置颜色的方法(至少我没发现)
这俩的区别我举个例子就很清楚了

paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(50); 
  • 1
  • 2

这是设置成FILL并添加边框出来的效果

这里写图片描述
大家可以试试无论setStrokeWidth(50)填入多少其效果是没有任何区别的因为他就没有边框.

paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setStrokeWidth(50);  
  • 1
  • 2

改成FILL_AND_STROKE后
这里写图片描述
这样的效果圆已经超出去一部分了,设定了填充+边框 不仅由填充效果还有边框,给边框设定了宽度值,再加上我设定的圆心坐标正好是半径值 所以边框的部分就出去了.但如果我们不设setStrokeWidth的话 这两者其实是没有什么区别的.另外要注意的是StrokeWidth的值是在园外面的,也就是说它并不占用园的实际大小,比如园的半径是100,这个半径指的是填充的部分,当把StrokeWidth设定为100时 这个圆会变大
刚才我提到Paint并没有设置STROKE的颜色的方法,所以我是通过两个画笔来实现的,通过多个画笔多个画布叠加图层来实现我们想要的各种效果
比如我现在想要一个边框为蓝色的填充为黑色的圆圈.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.RED);     
        float radius = 200;       
        float cx = 500;
        float cy = 500;
        Paint paint = new Paint();       //实例化Paint
        paint.setColor(Color.BLACK);     //设置圆的颜色
        paint.setAntiAlias(true);        //设置抗锯齿
        paint.setStyle(Paint.Style.FILL_AND_STROKE);  //设置样式
        Paint paint2 = new Paint();      //实例化第二个paint对象
        paint2.setColor(Color.BLUE);     //设置颜色为蓝色
        paint2.setStyle(Paint.Style.STROKE);//设置样式
        paint2.setStrokeWidth(30);          //设定边框宽度
        //通过canvas的drawCircle方法画一个圆圈.
        canvas.drawCircle(cx, cy, radius, paint);
        canvas.drawCircle(cx, cy, radius, paint2);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里写图片描述
需要注意的是canvas.draw的先后顺序 因为这两个圆是一样大小的 只是一个多一个边框而已 所以先后是无所谓的

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP