今天写这篇博客的目的,主要是帮助自己总结一下今天学习到的自定义View的相关知识,如果顺便能够帮助大家一点点,那我也感觉很开心。
首先,自定义的一般步骤是:
1.创建自定义View,继承系统自带的View,并重写其相关构造方法;
2.在Values下面新建attrs文件,写好相关的属性名称和格式;
3.在自定义View中获取相关的属性,重写View的onDraw方法, 来完成视图的绘制;
4.在XML文件中使用自定义的View。
按照以上步骤,我来给大家分享一下我今天写的这个自定义View。先给大家看一下效果图:
主要就是里面一个实心的圆,外面一个实心的圆,然后还有两个空心的圆在不断地改变半径,产生波动的效果,由于是图片是静态的,所以看不出来波动的效果,如果需要了解的,可以下载demo来看一下,demo的下载地址在文章的末尾。
首先,要创建一个自定义View,来继承系统自带的View,代码如下:
public class WaveView extends View {
public WaveView(Context context) {
this(context, null);
}
public WaveView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
然后要在values目录下新建一个attrs文件,来把自定义View相关的属性提取出来。这里主要提取的属性有:
1.内圆的颜色;
2.外圆的颜色;
3.内圆的半径;
4.文本的内容;
5.文本的颜色;
6.文本的大小;
7.波动的频率。
看attrs文件的内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="firstColor" format="color"/>
<attr name="secondColor" format="color"/>
<attr name="wavingInterval" format="integer"/>
<attr name="circleRadius" format="dimension"/>
<attr name="centerText" format="string"/>
<attr name="centerTextColor" format="color"/>
<attr name="centerTextSize" format="dimension"/>
<declare-styleable name="WaveView">
<attr name="firstColor"/>
<attr name="secondColor"/>
<attr name="circleRadius"/>
<attr name="centerText"/>
<attr name="centerTextColor"/>
<attr name="centerTextSize"/>
<attr name="wavingInterval"/>
</declare-styleable>
</resources>
然后要在自定义View的构造方法当中获取自定义的属性,代码如下:
public WaveView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.WaveView,
defStyleAttr,
0);
int n = a.getIndexCount();
for(int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.WaveView_firstColor:
firstColor = a.getColor(
attr, Color.parseColor("#1992f7"));
break;
case R.styleable.WaveView_secondColor:
secondColor = a.getColor(
attr, Color.parseColor("#661992f7"));
break;
case R.styleable.WaveView_wavingInterval:
wavingInterval = a.getInteger(attr, 50);
break;
case R.styleable.WaveView_circleRadius:
circleRadius = a.getDimensionPixelSize(
attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
50,
getResources().getDisplayMetrics()));
break;
case R.styleable.WaveView_centerText:
centerText = (String) a.getText(attr);
break;
case R.styleable.WaveView_centerTextColor:
centerTextColor = a.getColor(
attr, Color.WHITE);
break;
case R.styleable.WaveView_centerTextSize:
centerTextSize = a.getDimensionPixelSize(
attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,16,
getResources().getDisplayMetrics()));
break;
default:
break;
}
}
a.recycle();
}
并且要在自定义View的onDraw方法中绘制相关的视图,代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(255);
mPaint.setColor(firstColor);
canvas.drawCircle(
width / 2, height / 2,
circleRadius , mPaint
);
mPaint.setColor(secondColor);
canvas.drawCircle(
width / 2, height / 2,
circleRadius + dip2px(15),
mPaint
);
mPaint.setColor(centerTextColor);
canvas.drawText(
centerText,
width / 2 - centerTextBound.width() / 2,
height / 2 + centerTextBound.height() / 2,
mPaint
);
mPaint.setColor(secondColor);
if(isDrawFirst) {
mPaint.setAlpha(alpha1);
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(
width / 2 - radius1,
height / 2 - radius1,
width / 2 + radius1,
height / 2 + radius1
);
canvas.drawOval(rectF, mPaint);
}
if(isDrawSecond) {
mPaint.setAlpha(alpha2);
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(
width / 2 - radius2,
height / 2 - radius2,
width / 2 + radius2,
height / 2 + radius2
);
canvas.drawOval(rectF, mPaint);
}
}
完成了以上的相关工作之后,就可以在XML文件中使用这个自定义的View。使用如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yulin.customviewset.customview.WaveView
android:id="@+id/id_wave_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
app:firstColor="#1992f7"
app:secondColor="#661992f7"
app:circleRadius="75dp"
app:wavingInterval="80"
app:centerText="Waving"
app:centerTextColor="#ffffff"
app:centerTextSize="20sp"
/>
</RelativeLayout>
大致介绍以后,我们在来分析一下这个自定义View的一些关键点。
首先是自定义属性的获取。自定义属性的获取的关键部分有一下几个:
1.在attrs文件中正确声明相关的属性名和属性对应的数据格式;
2.在attrs文件中还需要声明declare-styleable,并把已经声明的相关属性加入进来;
3.在自定义View的构造方法中以正确的方式获取相关的属性。比如上面的自定义View要获取圆的半径这个属性,方式如下:
circleRadius = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()));
由于半径这个属性的格式是dimension,所以用TypedArray的getDimensionPixelSize来获取,其中的TypedVale可以把dp或者sp转换成px,50是默认值。
然后是onDraw方法中视图的绘制,主要包括这么几个部分:
1.内部实心圆;
2.外部实心圆;
3.中间的文字;
4.两个不断变动的圆环。
对于两个静态的圆,绘制方法很简单,核心代码如下:
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(255);
mPaint.setColor(firstColor);
canvas.drawCircle(
width / 2,
height / 2,
circleRadius,
mPaint
);
mPaint.setColor(secondColor);
canvas.drawCircle(
width / 2,
height / 2,
circleRadius + dip2px(15),
mPaint
);
Paint的setStyle是设置画笔的类型,FILL表示填充,setAlpha表示设置画笔的透明度,值是0到255,setColor是设置画笔的颜色,drawCircle表示绘制一个圆,需要指定圆心和半径以及画笔。
还需要绘制显示在内部实心圆中心的文字,绘制一段文字很简单,关键的难点在于如何把这个文字绘制在圆的中心,关键步骤如下:
1.要根据文字的长度和文字的大小来得到文字的边界范围;
centerTextBound = new Rect();
mPaint.setTextSize(centerTextSize);
mPaint.getTextBounds(
centerText,
0,
centerText.length(),
centerTextBound
);
2.计算出绘制文字的起始位置,绘制文字。
mPaint.setColor(centerTextColor);
canvas.drawText(
centerText,
width / 2 - centerTextBound.width() / 2,
height / 2 + centerTextBound.height() / 2,
mPaint
);
剩下的内容就是如何改变两个圆环的半径和透明度来让实现波动的效果了。首先要把两个圆环绘制出来,如下:
if(isDrawFirst) {
mPaint.setAlpha(alpha1);
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(
width / 2 - radius1,
height / 2 - radius1,
width / 2 + radius1,
height / 2 + radius1
);
canvas.drawOval(rectF, mPaint);
}
if(isDrawSecond) {
mPaint.setAlpha(alpha2);
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(
width / 2 - radius2,
height / 2 - radius2,
width / 2 + radius2,
height / 2 + radius2
);
canvas.drawOval(rectF, mPaint);
}
在onDraw方法中绘制好着两个圆环之后,我们就可以通过改变isDrawFirst和isDrawSecond这两个变量的值来控制绘制哪一个圆环,以及通过改变radius1和radius2的值来控制圆环1和圆环2的半径,来产生动态波动的效果,因此我们可以开启一个线程来循环改变这些参数,以实现不停地波动的效果。这里我们使用线程池来改变这些变量的值,代码如下:
@Override
public void startWaving() {
isWaving = true;
if(wavingService == null) {
wavingService = Executors
.newSingleThreadScheduledExecutor();
}
wavingService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if(isWaving) {
isDrawFirst = true;
if(radius1 < maxRadius) radius1 += 2;
if(radius1 >= maxRadius) {
radius1 = circleRadius;
alpha1 = 255;
}
if(radius2 < maxRadius) radius2 += 2;
if(radius2 >= maxRadius) {
radius2 = circleRadius;
alpha2 = 255;
}
if((radius1 - height / 4) >
(maxRadius - height/4) / 2 &&
!isDrawSecond) {
isDrawSecond = true;
alpha2 = 255;
radius2 = circleRadius;
}
if(alpha1 == 0) alpha1 = 255;
if(alpha2 == 0) alpha2 = 255;
alpha1 -= 2;
alpha2 -= 2;
postInvalidate();
}
}
},0, wavingInterval, TimeUnit.MILLISECONDS);
}
以上代码表示,每隔wavingInterval毫秒来进行一次视图的重绘操作,postInvalidate用于在非UI线程中通知View调用onDraw方法进行视图的重绘。
需要说明的是,由于线程池显示的调用shutdown或shutdownNow方法,再次进行操作的时候会出现java.util.concurrent.RejectedExecutionException异常,因此这里通过改变isWaving的值来决定是否需要进行视图的重绘,以节省资源。
在Activity中的具体使用方法如下:
package com.yulin.customviewset;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.yulin.customviewset.customview.WaveView;
public class MainActivity extends AppCompatActivity {
private WaveView mWaveView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWaveView = (WaveView) findViewById(R.id.id_wave_view);
}
@Override
protected void onResume() {
super.onResume();
mWaveView.startWaving();
}
@Override
protected void onPause() {
super.onPause();
mWaveView.pauseWaving();
}
@Override
protected void onDestroy() {
super.onDestroy();
mWaveView.stopWaving();
}
public void startWaving(View v) {
if(!mWaveView.isWaving()) mWaveView.startWaving();
}
public void pauseWaving(View v) {
if(mWaveView.isWaving()) mWaveView.pauseWaving();
}
}
这里为了节省资源,做了以下处理,在onResume中开始波动,在onPause方法中暂停波动,在onDestroy中停止波动,释放相关的资源。最后附上Demo下载地址: