看完这篇还不会自定义 View,我跪搓衣板

作者: _yuanhao

自定义View

在实际使用的过程中,我们经常会接到这样一些需求,比如环形计步器,柱状图表,圆形头像等等,这时我们通常的思路是去Google 一下,看看 github 上是否有我们需要的这些控件,但是如果网上收不到这样的控件呢?这时我们经常需要自定义 View 来满足需求。

看完这篇还不会自定义 View,我跪搓衣板_第1张图片

自定义控件之路

关于自定义控件,一般辉遵循以下几个套路

  • 首先重写 onMeasure() 方法
  • 其次重写 onDraw() 方法
  • 总所周知 onMeasure()

方法是用来重新测量,并设定控件的大小,我们知道控件的大小是用 width 和 height 两个标签来设定的。通常有三种赋值情况 :

  • 首先直接赋值,比如直接给定 15dp 这样确切的大小
  • 其次 match_parent
  • 当然还有 wrap_parent

这时也许你就会有疑问,既然都已经有了这些属性,那还重写 onMeasure 干嘛,直接调用 View 的方法不就行了吗?但是你想想,比如你设计了一个圆形控件,用户在 width 和 height 都设置了 wrap_parent 属性,同时又给你传了一张长方形的图片,那结果会怎么样?必然得让你“方”啊。。所以这时就需要重写 onMeasure 方法,设定其宽高相等。


如何重写OnMeasure()
首先把 onMeasure() 打出来

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }

这时大家不眠会好奇,明明是重绘大小,那么给我提供宽高就行了呀?这个 int widthMeasureSpec, int heightMeasureSpec ,是个什么鬼?其实很好理解,大家都知道计算机中数据是已二进制存储的。同时,就像我之前讲的 View 的大小赋值形式有三种,那么在计算机中,要存储二进制数,需要几位二进制呢,答案很明了 -> 两位。同时大家也发现,这两个参数都是 int 型的。int 型数据在计算机中用 32 位存储。所以聪明的 Google 就把这 30 位划分为两部分。第一部分两位拿来存类型,后面 28 位拿来存数据大小。


开始重写OnMeasure()方法
首先,无论是 width 还是 height ,我们都得先判断类型,再去计算大小,so~ 咱先写个方法专门用于计算并返回大小。

看完这篇还不会自定义 View,我跪搓衣板_第2张图片
private int getMySize(int defaultSize, int measureSpec) {
 // 设定一个默认大小 defaultSize
 int mySize = defaultSize;
 // 获得类型
 int mode = MeasureSpec.getMode(measureSpec);
 // 获得大小
 int size = MeasureSpec.getSize(measureSpec);

 switch (mode) {
 case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
 mySize = defaultSize;
 break;
 }
 case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
 //我们将大小取最大值,你也可以取其他值
 mySize = size;
 break;
 }
 case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
 mySize = size;
 break;
 }
 }
 return mySize;
 }

然后,我们再从 onMeasure() 中调用它

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 // 分别获得长宽大小
 int width = getMySize(100, widthMeasureSpec);
 int height = getMySize(100, heightMeasureSpec);

 // 这里我已圆形控件举例
 // 所以设定长宽相等
 if (width < height) {
 height = width;
 } else {
 width = height;
 }
 // 设置大小
 setMeasuredDimension(width, height);
 }

在 xml 中应用试试效果




 


到这里图就已经重绘出来了,让我们运行一下下

看完这篇还不会自定义 View,我跪搓衣板_第3张图片

我们惊呆了,说好的控件呢??!别急,咱还没给他上色呢,所以它自然是透明的。所以现在重写 onDraw() 方法,在 onDraw() 方法中 (这里我为了写的方便,在 onDraw 方法中直接 new 了对象 { 嗷我没有对象} 但这是一种很容易导致内存泄露的行为)

我们通过 canvas (安卓的一个绘图类对象进行图形的绘制)

@Override
 protected void onDraw(Canvas canvas) {
 // 调用父View的onDraw函数,因为View这个类帮我们实现了一些
 // 基本的而绘制功能,比如绘制背景颜色、背景图片等
 super.onDraw(canvas);
 int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
 Log.d(TAG, r + "");
 // 圆心的横坐标为当前的View的左边起始位置+半径
 int centerX = r;
 // 圆心的纵坐标为当前的View的顶部起始位置+半径
 int centerY = r;
 // 定义灰色画笔,绘制圆形
 Paint bacPaint = new Paint();
 bacPaint.setColor(Color.GRAY);
 canvas.drawCircle(centerX, centerY, r, bacPaint);
 // 定义蓝色画笔,绘制文字
 Paint paint = new Paint();
 paint.setColor(Color.BLUE);
 paint.setTextSize(60);
 canvas.drawText("大傻瓜", 0, r+paint.getTextSize()/2, paint);
 }

运行一下

看完这篇还不会自定义 View,我跪搓衣板_第4张图片

大功告成!但是善于思考的可能会发现:使用这种方式,我们只能使用父类控件的属性,但是我们有时需要更多的功能,比如:图片控件需要改变透明度,卡片控件需要设定阴影值等等,那么父类控件的属性显然不够用了,这时我们就要开始实现自定义布局。

自定义布局属性xml属性

由于自定义布局属性一般只需要对 onDraw() 进行操作。所以 onMeasure() 等方法的重写我就不再啰嗦了,这里我打算继承字 view 实现一个类似 TextView 的控件。

首先,让我们现在 res/values/styles 文件中增加一个自定义布局属性。



 
 

 
 
 
 
 
 
 


这些标签都是什么意思呢?

首先:

MyView 是自定义布局属性的名字,也就是标签也就是入口,在 onDraw 中,用 context.obtainStyledAttributes(attrs, R.styleable.MyView); 获得自定义布局属性的全部子项。

其次:

attr 中的 name 便是你属性的名字,比如说这个 text_size 、text_color 、text_text 这三个属性,在 布局文件中就是:


最后:

format 标签,format 标签指定的是数据类型,具体可以看这篇,我在这里就不重复了 ->


解析和引用

上面我们先定义了属性,又在布局中对其赋值,那么实际中,我们如何在自定义控件里,获得它的实际值呢?让我们先写下构造方法,在构造方法中获得这些值的大小:

private int textSize;
 private String textText;
 private int textColor;

 public MyView(Context context) {
 super(context);
 }

 public MyView(Context context, AttributeSet attrs) {
 super(context, attrs);

 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);

 textSize = array.getDimensionPixelSize(R.styleable.MyView_text_size, 15);
 textText = array.getString(R.styleable.MyView_text_text);
 textColor = array.getColor(R.styleable.MyView_text_color,Color.BLACK);

 array.recycle();
 }
  • 建立一个 TypeArray 对象,用于存储自定义属性所传入的的值。obtainStyledAttributes 方法又两个参数,第二个参数就是我们在styles.xml文件中的 标签,即属性集合的标签,在R文件中名称为R.styleable+name
  • 然后根据 array 对象,获取传入的值。一般来说,它的方法有两个属性,第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称,第二个参数为,如果没有设置这个属性,则设置的默认的值
  • 最后记得将TypedArray对象回收

重写onDraw()

由于在构造方法中,我们已经获得基本的值,所以在 onDraw() 中,将这些东西绘制出来就行了,这里直接上代码:

@Override
 protected void onDraw(Canvas canvas) {
 // 调用父View的onDraw函数,因为View这个类帮我们实现了一些
 // 基本的而绘制功能,比如绘制背景颜色、背景图片等
 super.onDraw(canvas);
 int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
 // 圆心的横坐标为当前的View的左边起始位置+半径
 int centerX = r;
 // 圆心的纵坐标为当前的View的顶部起始位置+半径
 int centerY = r;
 // 定义灰色画笔,绘制圆形
 Paint bacPaint = new Paint();
 bacPaint.setColor(Color.GRAY);
 canvas.drawCircle(centerX, centerY, r, bacPaint);
 // 定义蓝色画笔,绘制文字
 Paint paint = new Paint();
 paint.setColor(textColor);
 paint.setTextSize(textSize);
 canvas.drawText(textText, 0, r+paint.getTextSize()/2, paint);
 }

运行:

看完这篇还不会自定义 View,我跪搓衣板_第5张图片

本文是作者对个人学习过程的总结,如果大家有发现错误,希望能在评论区指出,谢谢

Android学习PDF+架构视频+面试文档+源码笔记


感谢大家能耐着性子看完

在这里小编也分享一份自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

如果你有需要的话,可以点赞+评论关注我,然后 加我VX:15388039515 我发给你
(或关注微信公众号“Android开发之家”回复【资料】免费领取)

看完这篇还不会自定义 View,我跪搓衣板_第6张图片

看完这篇还不会自定义 View,我跪搓衣板_第7张图片
看完这篇还不会自定义 View,我跪搓衣板_第8张图片

你可能感兴趣的:(看完这篇还不会自定义 View,我跪搓衣板)