这是自定义View的第一篇文章,通过制作简单的自定义View来了解自定义View的流程。

自定义View是Android学习和开发中必不可少的一部分。通过自定义View我们可以制作丰富绚丽的控件,自定义View主要有三种方式,具体如下:

  1. 继承已有的View,来扩展我们的View
  2. 组合多个View来实现一个复合的View
  3. 完全重写View,来实现制作全新的控件

这里,我们讲第三种方法来了解自定义View的流程。

自定义View主要依赖的方法

自定义VIew中,我们主要重写onMeasureonDraw这两种方法来展现一个View。

onMeasure:主要工作是对我们要绘制的View进行测量,因为不进行测量的话,系统不知道要绘制的View有多大,无法绘制出我们需要的样子。

onDraw: 主要工作就是绘制我们需要的图形,这个是自定义View中定制性最强也是最主要的工作。

下面我们先了解一下这两个方法具体能做什么,然后我们通过一个实例来学习一下具体的用法。

onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

我们从上面的方法可以看出onMeasure 方法中有两个参数widthMeasureSpecheightMeasureSpec这两个参数每一个参数中都包含了测量值的大小和测量的模式。

测量模式有一下三种:

  1. EXACTLY

    当我们把控layout_width或者layout_height属性设为match_parent或者是个100dp那样的精确值时就会用这种测量模式。
  2. AT_MOST

    即最大值模式,当控件的layout_width属性或者layout_height指定为wrap_content时,空间大小一般随着控件的子控件或者内容的变化而变化,这是控件的尺寸只要不超过父控件允许的最大尺寸就行了。
  3. UNSPECIFIED

    这个是我们按照自身的想法,想绘制多大就绘制多大,没有任何限制。

具体的做法一般是,我们先根据参数,得到具体的测量模式与测量值,在根据测试的模式不同,计算不同的宽度和高度。最后通过setMeasuredDimension(int measuredWidth,int measuredHeight)将我们计算过的宽和高设置进去,完成测量工作。

onDraw

onDraw(Canvas canvas)是我们展现自定义View的主要方法,他的参数是一个canvas也就是说一个画布,为了绘制图案,我们有了画布以外,我们还需要一个画笔Paint。这个画笔来决定你画图案的颜色,线条的粗细,是否抗锯齿,图案的风格等等。而画什么图案就交由canvas对象,调用canvas.drawXXX()来实现想要的图案,具体的文档,参见Canvas官方文档

自制圆形载入View

上面说了这么多,都是在YY,我们具体通过一个例子来走一遍自定义View的流程。效果如下:

首先我们新建一个CircleLoadingView.java文件。该类继承View,生成构造器,显式调用父类的构造器,并初始化我们的Paint对象,覆写onMeasure,onDraw方法,实现自定义View.

第一步——初始化

在构造方法中,我们调用自己写的initView方法来初始化Paint对象

private void initView(){
paint = new Paint();
//设置画笔的颜色
paint.setColor(circleColor);
//设置抗锯齿,让图像更清晰
paint.setAntiAlias(true);
//设置画笔的风格,有三种属性FILL,STROLE,FILL_AND_STROKE,我们不需要填充,所以设置为STROKE
paint.setStyle(Paint.Style.STROKE);
//设置画笔的粗细
paint.setStrokeWidth(circleStrokewidth);
}

第二步——onMeasure

接下来,我们需要测量我们的View的大小,重写onMeasure()方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置一个默认的宽和高,AT_MOST模式需要
int result = 0;
//通过MeasureSpec.getMode与getSize方法获取宽和高的测量方式与测量大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//保存最后的测量值,优化代码的话可以不用这个变量的。
int width = 0,height = 0;
//对测量模式进行判断,如果是EXACTLY的话则最后的测量值就是系统帮我们测量的结果。
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
}else{
//如果是UNSPECIFIED 则使用我们的默认值作为最后的测量值
result = 300;
//如果是AT_MOST 则就要用系统测量结果与我们默认结果取最小值来决定最后的测量结果
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(result,widthSize);
}
}
//高度和宽度的过程是一致的。
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
}else {
result = 300;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(result, heightSize);
}
}
//把我们最后的宽和高设置进去
setMeasuredDimension(width,height); }

这就是onMeasure方法的代码,可以看出来,这个都是可以放到其他地方复用的模板代码。

第三步——onDraw

我们基本已经完成了80%的工作了,接下来只需要重写onDraw()绘制我们需要的一个弧形就可以了。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//决定我们的弧形外接的矩形的宽和高
float wdistance = (float)(wlength * 0.6);
float hdistance = (float)(hlength * 0.6);
//定义外接矩形
RectF rectF = new RectF(
wlength / 2 - wdistance / 2,
hlength / 2 - hdistance / 2,
wlength / 2 + wdistance / 2,
hlength / 2 + hdistance / 2);
//画弧形
canvas.drawArc(
rectF,//外接矩形
270,//起始角度
(float)(240),//划过的度数
false,//是否是扇形
paint//画笔
); }

看了上面的代码,可能有些糊涂,因为里面多了很多没有提到的变量。首先的变量就是wlength,与hlength这个指的是我们onMeasure测量以后的宽和高,我们保存下来。在onMeasure方法中,我们加入下面代码。

 wlength = width;
hlength = height;

同时我们定义了外接矩形的宽和高就是我们View的0.6倍,上,下, 左,右各留了0.2倍的内边距。然后通过new Rectf()方法来生成矩形对象,4个参数分别为矩形的左边距Y轴的距离(也就是说X轴的坐标),上边距X轴的距离(也就是说Y轴的坐标),右边距Y轴的距离(也就是说X轴的坐标),下边距Y轴的距离(也就是说Y轴的坐标)。具体见下图

canvas 实际上就是这个坐标轴。

定义好外接矩形之后,我们开始调用drawArc方法开始绘制弧形,这个方法接受5个参数,第二个参数是弧起始的角度,这个接受一个整数代表角度,他是这样确定起始的角度的。以我们的手表的3点开始,顺时针转过我们定义的角度,这时的位置就是我们开始的位置,比如我们现在定义的是270,那就是手表3点的位置转过270度为我们弧形开始的位置。就是12点的位置。然后第三个参数,代表弧形划过的角度。也是顺时针。第四个参数则代表是否用扇形,我们这里不用,也就是说只是一个两个端点不连接圆心的弧,第五个是我们初始化过的paint。这样我们的自定义的弧形就出来了。

我们就可以把我们的控件放到xml中,当成普通的view去引用了。直接看代码:

<com.example.byhieg.circleloadingview.CircleLoadingView
android:id="@+id/loading"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
/>
这里的引用View一定要用全名。我们就可以直接运行程序。

第四步——动起来

这个弧形其实并没有卵用,不能动。我们接下来让他动起来,这里我们就用绘图自带的方法postInvalidate()来实现重绘。要让一个图像动起来最简单的办法就是让他在每一个坐标点就绘制一下,然后让他在每个坐标点都出现一次就实现了动画的效果。就像小时候有那种很多页的漫画书,我们快速翻阅漫画书就感觉漫画书中的漫画动起来了,道理是一样的。

那这次我们只需要在onDraw方法中,不断修改弧形的第二个参数,让他每次不同就可以了。具体的我们看代码:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float wdistance = (float)(wlength * 0.6);
float hdistance = (float)(hlength * 0.6); RectF rectF = new RectF(
wlength / 2 - wdistance / 2,
hlength / 2 - hdistance / 2,
wlength / 2 + wdistance / 2,
hlength / 2 + hdistance / 2);
canvas.drawArc(
rectF,
//每次重绘,起始的角度就会多5度。
270 + 5 * i,
(float)(240),
false,
paint
);
i++;
//调用重绘,来实现图像不断绘制
postInvalidate();
}

这样,我们再次运行程序的时候,我们的弧形就动起来。当我们在接触动画概念的时候,又会发现有很多方法实现弧形旋转。

第五步——完善

前面4步,我们已经实现了简单的自定义View,并且有一个可观的效果。但是我们还要完善一下,比如我们可以在XML中指定弧形的颜色,弧形的粗细。这个时候,我们就需要在values文件下新建一个attrs.xml。在里面制定我们的自定义属性,然后在我们的View文件中读取这些属性。

我们先看一下attrs.xml中,我们的代码:

<resources>
<declare-styleable name="CircleLoadingView">
<attr name="circleColor" format="color" />
<attr name="circleStrokewidth" format="float" />
</declare-styleable>
</resources>

这里,我们就定义了2个属性,一个是弧形的颜色,一个是弧形的粗细,格式分别是color和float。然后我们在view中的initView代码中,获取那些属性。代码如下:

TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleLoadingView);
circleStrokewidth = ta.getFloat(R.styleable.CircleLoadingView_circleStrokewidth, 0);
circleColor = ta.getColor(R.styleable.CircleLoadingView_circleColor, 0);

然后我们就可以在我们的xml中引用这些属性了。

<com.example.byhieg.circleloadingview.CircleLoadingView
android:id="@+id/loading"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
circleview:circleColor="@android:color/holo_blue_dark"
circleview:circleStrokewidth="15"/>

这里的circleview是要在根布局的设置中声明的 xmlns:circleview="http://schemas.android.com/apk/res-auto"

因为,这个主要出现在加载过程中,所以他需要一个方法来让他显示和隐藏。我们在View的文件中,设置一个方法叫做setViewVisable

当为true的时候,就可以显示,当为false就可以隐藏。这样,这个自定义View就比较完善了。

隐藏方法代码如下:

public void setViewVisable(boolean choose) {
if (choose) {
this.setVisibility(View.VISIBLE);
}else{
this.setVisibility(View.GONE);
}
}

PS

这个小控件的源码我放到网上了,大家可以对照参考下

CircleLoadingView的源码

通过圆形载入View了解自定义View的更多相关文章

  1. Android圆形图片不求人,自定义View实现(BitmapShader使用)

    在很多APP当中,圆形的图片是必不可少的元素,美观大方.本文将带领读者去实现一个圆形图片自定View,力求只用一个Java类来完成这件事情. 一.先上效果图 二.实现思路 在定义View 的onMea ...

  2. 手把手带你做一个超炫酷loading成功动画view Android自定义view

    写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...

  3. 手把手带你画一个漂亮蜂窝view Android自定义view

    上一篇做了一个水波纹view  不知道大家有没有动手试试呢点击打开链接 这个效果做起来好像没什么意义,如果不加监听回调 图片就能直接替代.写这篇博客的目的是锻炼一下思维能力,以更好的面多各种自定义vi ...

  4. 手把手教你打造一个心电图效果View Android自定义View

    大家好,看我像不像蘑菇-因为我在学校呆的发霉了. 思而不学则殆 丽丽说得对,我有奇怪的疑问,大都是思而不学造成的,在我书读不够的情况下想太多,大多等于白想,所以革命没成功,同志仍需努力. 好了废话不说 ...

  5. Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)

      Android 高手进阶(21)  版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明地址:http://blog.csdn.net/xiaanming/article/detail ...

  6. Android 自定义view --圆形百分比(进度条)

    转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50334595 注:本文由于是在学习过程中写的,存在大量问题(overdraw onDr ...

  7. 自定义View之一圆形图片

    自定义View的方法 对现有控件进行扩展 通过组合来实现新的控件 重写View来实现全新的控件 本篇文章主要讲对现有控件的扩展 1.圆形图片控件 自定义View,对ImageView的扩展 重写onD ...

  8. 自定义View和ViewGroup

    为了扫除学习中的盲点,尽可能多的覆盖Android知识的边边角角,决定对自定义View做一个稍微全面一点的使用方法总结,在内容上面并没有什么独特的地方,其他大神们的博客上面基本上都有讲这方面的内容,如 ...

  9. Android查缺补漏(View篇)--自定义 View 的基本流程

    View是Android很重要的一部分,常用的View有Button.TextView.EditView.ListView.GridView.各种layout等等,开发者通过对这些View的各种组合以 ...

随机推荐

  1. Maven的pom.xml 配置详解

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  2. 《DSP using MATLAB》示例Example6.1

    今天是2016年最后一天了,看到其他博友都写年终总结,做了这个,做了那个,收获满满,再看看自己, 恍恍惚惚一年,不知道干了些什么,惭愧.刚才接到老妈远方的电话,弟弟就在一小时前做爸爸了,我在 这里祝福 ...

  3. JS应用,表单上的一些东西

    例: <body> <form>我的生日是哪一年? <input type="text" value="" id="t1 ...

  4. LeetCode 292. Nim Game

    Problem: You are playing the following Nim Game with your friend: There to stones. The one who remov ...

  5. 图解HTTP

    1.返回结果的HTTP状态码 a. 2xx 成功: 200 ok 204 No Content  206 Partial Content b. 3XX重定向:301 Moved Permanently ...

  6. Java RMI之HelloWorld篇

    Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方 ...

  7. Difference between WCF and Web API and WCF REST and Web Service

    The .Net framework has a number of technologies that allow you to create HTTP services such as Web S ...

  8. 安装zabbix-3.0.3+nginx-1.10.1+php-5.6.22

    好久没有接触监控类的软件了,今天抽空搭建了下最新的版本 首先系统环境 zabbix-server-1 192.168.11.11   centos6.7 mysql-server    192.168 ...

  9. smack 4.1创建群聊

    smack 4.1.1版本对群聊修改了很多,MultUserChat的构造函数修改成了私有,以前通过new MultUserChat创建聊天室,现在通过MultUserChatMananger先通过r ...

  10. ACCEPTANCE CRITERIA FOR USER STORIES

    One of the teams I have recently coached quickly got a grasp of how to phrase user stories but found ...