欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

最简单的自定义View

程序员文章站 2022-05-30 20:26:16
...

为什么要自定义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的完整类名既包名.类名 
以下是效果 
最简单的自定义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

效果如下 
最简单的自定义View 
可以看到这个时候控件的大小已经变成所设定的默认值了.

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; 
最简单的自定义View 
还有很多方法可以调这里就不一一列举了,可以根据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

最简单的自定义View

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

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

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

最简单的自定义View 
大家可以试试无论setStrokeWidth(50)填入多少其效果是没有任何区别的因为他就没有边框.

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

改成FILL_AND_STROKE后 
最简单的自定义View 
这样的效果圆已经超出去一部分了,设定了填充+边框 不仅由填充效果还有边框,给边框设定了宽度值,再加上我设定的圆心坐标正好是半径值 所以边框的部分就出去了.但如果我们不设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

最简单的自定义View 
需要注意的是canvas.draw的先后顺序 因为这两个圆是一样大小的 只是一个多一个边框而已 所以先后是无所谓的

相关标签: Android