当前位置:   article > 正文

最简单的自定义View_简单自定义view

简单自定义view

为什么要自定义View

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

如何自定义布局

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

  1. public class MVIew extends View {
  2. /**
  3. * 创建一个继承View的class
  4. *View一共有四个构造器 这里先说两个
  5. * 第一个构造器的参数就是context,这是在Activity实例化的时候所需的,比如Button button = new Button(context);
  6. * 第二个构造器有两个参数 第一个同样是context 第二个参数AttributeSet放入了一些属性,这是我们在写XML文件时用到的比如
  7. * android:layout_width="match_parent"
  8. * android:layout_height="match_parent"如果不写函数的话是无法通过XML添加View
  9. */
  10. public MVIew(Context context) {
  11. super(context);
  12. }
  13. public MVIew(Context context, @Nullable AttributeSet attrs) {
  14. super(context, attrs);
  15. }
  16. //重写onDraw方法
  17. @Override
  18. protected void onDraw(Canvas canvas) {
  19. super.onDraw(canvas);
  20. canvas.drawColor(Color.RED); //设置canvas的背景色
  21. float radius = 50; //给定半径
  22. //给定圆心的的坐标
  23. float cx = 50;
  24. float cy = 50;
  25. Paint paint = new Paint(); //实例化一个Paint对象
  26. paint.setColor(Color.BLUE); //设置圆的颜色
  27. //通过canvas的drawCircle方法画一个圆圈.
  28. canvas.drawCircle(cx, cy, radius, paint);
  29. }
  30. }
  • 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)来添加

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

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

  1. <cn.lipengfei.myview.MVIew
  2. android:layout_width="wrap_content"
  3. 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()这个方法.

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

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

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

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

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

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

这里写图片描述

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

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

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

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

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/759836
推荐阅读
相关标签
  

闽ICP备14008679号