Android自定义View——仿vivo i管家病毒扫描动画效果

技术是永无止境的,如果真的爱技术,那就勇敢的坚持下去。我很喜欢这句话,当我在遇到问题的时候、当我觉得代码枯燥的时候,我就会问自己,到底是不是真的热爱技术,这个时候,我心里总是起着波澜,我的答案是肯定的,我深深的爱着这门技术。

今天我们继续聊聊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>

一个背景效果的代码:

<?xml version="1.0" encoding="utf-8"?>
<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>

到现在,所有的效果都出来了,可能讲的不是很详细,但是代码是没有问题的,代码也不多,多看几次也很容易理解。

最后我把源码贴上来:源码地址

现在还是开始写博客,技术可能不是很高端,但是日子还长,期待我们一起进步。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值