技术是永无止境的,如果真的爱技术,那就勇敢的坚持下去。我很喜欢这句话,当我在遇到问题的时候、当我觉得代码枯燥的时候,我就会问自己,到底是不是真的热爱技术,这个时候,我心里总是起着波澜,我的答案是肯定的,我深深的爱着这门技术。
今天我们继续聊聊Android的自定义View系列。先看看效果吧:
这个是我手机杀毒软件的一个动画效果,类似于雷达搜索,所以用途还是很广泛的,特别是先了解一下这里的具体逻辑和写法,对技术的进步一定很有用。
先简单的分析一下这里的元素,主要有四个圆、一个扇形、还有八条虚线。当知道这些以后,代码就可以开始写了。
先看看这里用到了哪些变量。
private float mWidth;
private float mHeight;
private Paint mPaint, mPaint2, mPaint3, mPaint4, mLinePaint;
private RectF mRectF;
private float startAngle = 360;
private float radius1, radius2, radius3, radius4;
private Path path;
不是很多,有画笔6个,然后是宽与高,还有就是角度以及四个圆的半径以及一个矩形、当然还有绘制虚线的Path类。接下来是初始化的操作。
// 初始化画笔操作
private void initData() {
mPaint = new Paint();
mPaint.setStrokeWidth(1);
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.STROKE);
mPaint.setColor(Color.parseColor("#ffffff"));
mPaint2 = new Paint();
mPaint2.setStrokeWidth(5);
mPaint2.setColor(Color.parseColor("#00ff00"));
mPaint3 = new Paint();
mPaint3.setStrokeWidth(3);
mPaint3.setAntiAlias(true);
mPaint3.setStyle(Style.STROKE);
mPaint3.setColor(Color.parseColor("#ffffff"));
mPaint4 = new Paint();
mPaint4.setStrokeWidth(2);
mPaint4.setAntiAlias(true);
mPaint4.setStyle(Style.STROKE);
mPaint4.setColor(Color.parseColor("#ffffff"));
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStrokeWidth(1);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(Color.parseColor("#ffffff"));
path = new Path();
}
这里包括了五个画笔的初始化操作、一个路径的初始化操作。注意每个画笔的具体样式是不一样的,这样方便实现不同的效果。
初始化的操作完了之后,就是给变量赋值了,还是一样的,我们选择在onSizeChange()里面对变量进行赋值。代码如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();
mHeight = getHeight();
mRectF = new RectF((float) (mWidth * 0.1), (float) (mWidth * 0.1),
(float) (mWidth * 0.9), (float) (mWidth * 0.9));
// 绘制渐变效果
LinearGradient gradient = new LinearGradient((float) (mWidth * 0.3),
(float) (mWidth * 0.9), (float) (mWidth * 0.1),
(float) (mWidth * 0.5), new int[] {
Color.parseColor("#458EFD"), Color.GREEN,
Color.parseColor("#458EFD"), Color.WHITE,
Color.parseColor("#458EFD") }, null,
Shader.TileMode.CLAMP);
mPaint2.setShader(gradient);
// 四个圆的半径
radius1 = (float) (mWidth * 0.4);
radius2 = (float) (mWidth * 0.3);
radius3 = (float) (mWidth * 0.2);
radius4 = (float) (mWidth * 0.1);
}
其实很明显啊,我们在前面讲的几个自定义的View中,几乎所有的变量初值都是在这个方法里面写的,这个方法究竟有什么特点呢?其实这个是系统回调方法,是系统调用的,它的方法名已经告诉我们了,这个方法会在这个view的大小发生改变是被系统调用,我们要记住的就是view大小变化,这个方法就被执行就可以了。最主要的是,它还在onDraw方法之前调用。
还记得之前的效果吗?里面有一个渐变,这个渐变就是代码里LinearGradient 类的操作结果了,具体的用法,我在后面会专门去解释它。
到了这里,基本上所有的准备工作都完成了,接下来进行真真的绘图。
先看看onDraw方法里面做的操作:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvasArc(canvas);
canvasArc2(canvas);
canvasCircle(canvas);
canvasLine(canvas);
}
这里有画圆的,有画扇形的,也有画线的,所以可以发现,我们所有看到的效果,都是在onDraw方法里面实现的。我们具体看看每一个方法:
第一个绘制扇形:
// 绘制旋转的扇形
private void canvasArc(Canvas canvas) {
canvas.drawArc(mRectF, startAngle, 60, true, mPaint2);
}
只有两行代码,也很容易理解,那么第二个扇形也是差不多:
// 绘制旋转的扇形
private void canvasArc2(Canvas canvas) {
canvas.drawArc(mRectF, startAngle, 1, true, mPaint3);
}
值得注意的是,第二个的扇形的角度是1,为什么是1呢,原来我这里只是想要它旋转角度的效果,并不需要它有多宽,所以在具体实现自定义View的时候,也要学会活学活用。
两个扇形已经画好了,那就看看四个圆怎么画:
// 绘制四个圆
private void canvasCircle(Canvas canvas) {
canvas.drawCircle(mWidth / 2, mHeight / 2, radius1, mPaint3);
canvas.drawCircle(mWidth / 2, mHeight / 2, radius2, mPaint);
canvas.drawCircle(mWidth / 2, mHeight / 2, radius3, mPaint);
canvas.drawCircle(mWidth / 2, mHeight / 2, radius4, mPaint4);
}
四个圆就是四行代码,怎么样,很简单吧?
注意他们所用的画笔是不太一样的,这里是比较容易忽视的地方。
最后来看看我纠结很久的画虚线的操作,这里真是把我卡了好一会儿,先看看代码:
// 绘制虚线
private void canvasLine(Canvas canvas) {
int lineCount = 8;
for (int i = 0; i < lineCount; i++) {
path.moveTo(mWidth / 2, mHeight / 2);
path.lineTo(radius1, radius4);
PathEffect effects = new DashPathEffect(new float[] {
(float) (mWidth * 0.005), (float) (mWidth * 0.02),
(float) (mWidth * 0.005), (float) (mWidth * 0.02) }, 0);
mLinePaint.setPathEffect(effects);
canvas.drawPath(path, mLinePaint);
canvas.rotate(45, mWidth / 2, mHeight / 2);
}
}
按理说,绘制虚线,本质也是画线,但是我一开始是用canvas.drawLine方法,但是没有效果,网上查找了资料,才知道可能是版本的问题,于是我换用了path。
到了这个时候,全部效果已经出来了,那么为了让她动起来,我们还是要加点逻辑,我的思路是这样的:
定义一个线程,然后通过改变扇形的开始角度来实现动画的效果。
代码如下:
class MyThread extends Thread {
@Override
public void run() {
while (true) {
if (running) {
SystemClock.sleep(200);
handler.sendEmptyMessage(2);
}
}
}
}
private boolean running = true;
public Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
synchronized (this) {
if (startAngle < 1) {
startAngle = 360;
} else {
startAngle--;
invalidate();
}
}
};
};
到了这里,基本可以实现动画效果了,为了让控件更好的被外面所用,我们还像外面暴露了一些方法。
// 开启动画
public void setStartAngle() {
thread = new MyThread();
thread.start();
}
// 重新开启动画
public void startAnge() {
running = true;
}
// 暂停动画
public void stopAnge() {
running = false;
}
// 是否在运动
public boolean isRunning() {
return running;
}
最后看看我在MainActivity.java的操作:
/**
* 小瓶盖 2016年7月12日16:35:26
*
* @author 自定义View——仿Vivo i管家病毒扫描动画效果
*
* 博客地址:http://blog.csdn.net/qq_25193681
*/
public class MainActivity extends ActionBarActivity {
private VirusKilling mVirusKilling;
float mVirusKillingValue = 360;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
mVirusKilling = (VirusKilling) findViewById(R.id.Virus_Killing);
tv = (TextView) findViewById(R.id.tv);
// 传入TextView,改变下方的文字的时候用到
mVirusKilling.setTextView(tv);
// 第一次打开的时候就开始更新动画
mVirusKilling.setStartAngle();
}
/**
* 点击事件的处理
*
* @param v
*/
public void onStop(View v) {
TextView tv = (TextView) v;
if (mVirusKilling.isRunning()) {
tv.setText("继续扫描");
mVirusKilling.stopAnge();
} else {
tv.setText("停止扫描");
mVirusKilling.startAnge();
}
}
@Override
protected void onPause() {
mVirusKilling.stopAnge();
super.onPause();
};
@Override
protected void onStart() {
mVirusKilling.startAnge();
super.onStart();
}
}
还有布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ltl.vivoviruskilling.MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#458EFD"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dip"
android:padding="8dip" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/back"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="病毒查杀"
android:textColor="#ffffff"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="设置"
android:textColor="#ffffff"
android:textSize="20sp" />
RelativeLayout>
<com.ltl.vivoviruskilling.view.VirusKilling
android:id="@+id/Virus_Killing"
android:layout_width="200dip"
android:layout_height="200dip"
android:layout_gravity="center" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:onClick="onStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="停止扫描"
android:paddingTop="5dip"
android:paddingBottom="5dip"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:background="@drawable/btn_bg"
android:textColor="#ffffff"
android:textSize="18sp" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="正在扫描:NETWORKSTATE"
android:layout_margin="20sp"
android:textColor="@android:color/holo_blue_bright"
android:textSize="16sp" />
LinearLayout>
LinearLayout>
RelativeLayout>
一个背景效果的代码:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<corners android:radius="30dip"/>
<solid android:color="#00000000"/>
<stroke
android:width="1dip"
android:color="#ffffff" />
shape>
到现在,所有的效果都出来了,可能讲的不是很详细,但是代码是没有问题的,代码也不多,多看几次也很容易理解。
最后我把源码贴上来:源码地址
现在还是开始写博客,技术可能不是很高端,但是日子还长,期待我们一起进步。