• 动画学习之WIFI图形绘制


    Android原生动画概述:

    对于APP开发中涉及到的一些动画基本上都可以用Android提供的各种原生动画类来实现,所以在学习自定义动画之前首先来对原生动画进行一个基本的了解,这里不详细对每一个原生动画进行深入学习,因为重点是学会如何自定义动画,其Android支持的原生动画主要有以下三类:

    ①、补间动画【View Animation】:

    • 平移动画:TranslateAnimation
    • 旋转动画:RotateAnimation
    • 缩放动画:ScaleAnimation
    • 渐变动画:AlphaAnimation

    ②、属性动画【Property Animation】:

    这个动画在实际中用得比较多的是这两个类:ObjectAnimator和ValueAnimator,而ObjectAnimator是继承自ValueAnimator,如下:

    其中ValueAnimator在之前的学习中也已经用过了,它可以对值进行变化。

    对于上面两种动画其实是有一个比较大的区别的,下面来做一个小实验来直观的感受一下两者的区别:

    新建一个工程,先准备布局:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
        android:orientation="vertical"
        tools:context="com.animationdemo.test.MainActivity">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="traslate1"
            android:text="点我平移补间动画" />
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="traslate2"
            android:text="点我平移属性动画" />
    </LinearLayout>

    先看一下补间动画的效果:

    public class MainActivity extends AppCompatActivity {
    
        private boolean isTranslate;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        //补间动画平移
        public void traslate1(View view) {
            if (!isTranslate) {
                TranslateAnimation translateAnimation = new TranslateAnimation(TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 100, TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 0);
                translateAnimation.setDuration(1000);
                translateAnimation.setFillAfter(true);
                view.startAnimation(translateAnimation);
                isTranslate = true;
            } else {
                Toast.makeText(this, "点我了", Toast.LENGTH_SHORT).show();
            }
        }
    
        //属性动画平移
        public void traslate2(View view) {
    
        }
    }

    主要是看一下事件的点击效果,编译运行:

    从运行的结果来看,补间动画的平移并非真正的将View给移动了,也就是本尊未动,只是它的影子动了,接着再来看一下属性动画对于同样效果的实现,如下:

    public class MainActivity extends AppCompatActivity {
    
        private boolean isTranslate;
        private boolean isTranslate2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        //补间动画平移
        public void traslate1(View view) {
            if (!isTranslate) {
                TranslateAnimation translateAnimation = new TranslateAnimation(TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 100, TranslateAnimation.ABSOLUTE, 0, TranslateAnimation.ABSOLUTE, 0);
                translateAnimation.setDuration(1000);
                translateAnimation.setFillAfter(true);
                view.startAnimation(translateAnimation);
                isTranslate = true;
            } else {
                Toast.makeText(this, "点我了", Toast.LENGTH_SHORT).show();
            }
        }
    
        //属性动画平移
        @SuppressLint("ObjectAnimatorBinding")
        public void traslate2(View view) {
            if (!isTranslate2) {
                ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", 0, 100);
                objectAnimator.setDuration(1000);
                objectAnimator.start();
                isTranslate2 = true;
            } else {
                Toast.makeText(this, "点我了2", Toast.LENGTH_SHORT).show();
            }
        }
    }

    编译运行:

    很明显这次本尊和影子都一起动了,也就是真正的移动,这也就是两者之间的一个很明显的区别。

    ③、帧动画【Drawable Animation】:

    这个就比较简单了,其实也就是动画是由系列的图片按一定的速度进行切换而实现滴,而在Android中叫AnimationDrawable。

    自定义动画引入:

    在上面已经介绍了Android提供的三种原生动画,这个在之后还会具体去应用到的,但是现实应用中可能会碰到原生动画满足不需求的情况,所以此时自定义动画就派上用场了,先来看一下如下系列动画效果:

    它是来自于https://github.com/81813780/AVLoadingIndicatorView这个开源项目,接下来会挑几个效果进行学习,会搭配的Android的原生动画最终实现特定的效果,首先咱们要实现一个WIFI效果,如下:

    而这个是通过单纯的canvas的绘制功能来实现动画效果而并未采用Android的原生动画,所以下面开始来一步步实现这个WIFI动画效果吧! 

    WIFI思路整理:

    在正式编码实现之前,先来对效果进行一个思路整理, 先拿一个静态图来分析:

    很明显该动画是由一个扇形和三个弧形组成,而且共用一个圆心,如下:

    而且该圆心并非是自定义的中心点,另外需要控制绘制格数,当绘制满格信号的时候,又得回到一格信号,所以这里存在一上计数的逻辑控制,所以大概了解了之后接下来咱们就可以具体来动手将它一一实现啦。

    WIFI图形绘制:

    在加入动画之前先来在咱们自定义的View上绘制出一个WIFI的静态图形,所以先新建一个自定义View:

    首先先画一个一格信号的扇形,而怎么绘制扇形在之前已经学习过了【http://www.cnblogs.com/webor2006/p/7341697.html】,可以用canvas.drawArc()进行,而扇形绘制需要一个外切矩形,外切矩形的大小决定了扇形的大小,如下:

    而总共有四格信号,每格信号其实就都一个弧形,所以需要对应四个外切矩形,到底对应几个可以将其定义成一个常量,如下:

    接着问题的焦点就回到了如何来确定每个RectF中的left、top、right、bottom的值了,那怎么确定呢?先来分析一下效果图:

    再详细一点说:

    所以说,咱们首先得要计算出这个总的半径长度,而这个长度应该是依赖于整个View的长宽,直径应该是取View宽高的较小值,所以:

    有了直径之后,半径不就除以2嘛,这就是最大的半径,最后还得将其均分,所以:

    所以每循环一次,则将这个基本半径进行成倍增加,如下:

    这样就可以来确定外切距形的左、上、右、下的值啦,如下:

    关于这四个参数为啥这样传,先不用过脑想,等绘出来之后再来理解,所以下面将这些矩形绘制出来看一下效果:

    为了看到效果,在布局中定义这个View,如下:

    编译运行:

    呃~~乌黑一片~~什么鬼~~原因是得设置一下画笔的样式,如下:

    编译运行:

    嗯~~为了更进一步理解这个外切矩形,咱们拆解一个个来绘制,如下:

    运行:

    运行:

    运行:

    编译运行:

    理解了这个外切矩形之后,下面再将这个rectF传给画布的drawArc方法中进行弧形的绘制,如下:

    那为啥startAngle是-135,sweepAngle是90呢? 其中startAngle表示弧的起始角度,而我们想要绘制的起始角度应该是在坐标系中的如下位置:

    而sweepAngle表示弧的弧度,那很明显咱们要绘制的弧度的结束位置应该在坐标系的如下位置:

    所以这两个值为啥这样写的原因已经说明,接下来则开始运行看一下效果:

    呃~未啥是长这个样子呢?其实是因为画笔样式的原因,下面修改如下:

    编译运行:

    嗯~~样子有了,但是弧条不够粗,所以改下画笔的粗度呗:

    编译运行:

    嗯~~够粗了,但是!!目前内容显示贴着顶边的,不太好看,此时应该将其往下移动一点,具体如下:

    编译运行:

    但是!!关于WIFI图形的绘制这块还差最后一个细节:第一格的应该是一个扇形,其它格数都是弧,而扇形与弧的决定是由这个参数来决定的:

    所以此时需要加入条件判断来着情处理,如下:

    编译运行:

    WIFI动画实现:

    有了静态图之后,接下来就是想办法将它动起来了,这里不采用任何Android提供的原生动画来弄,而是通过纯代码,所以需要解决两个问题:

    1、不断的让界面重绘,那很简单,直接用invalidate();

    2、得按一定的规律一格格的往上绘制,达到最大格数之后则需要又回到第一格,很显然需要用一个变量来控制当前绘制的格数,如下:

    而每次绘制都是有一个for循环,所以需要通过一定的算法来控制咱们的绘制,具体如何做呢?下面直接给出:

    另外每次绘制还得更改一下shouldExistSigalSize的大小,所以:

    接下来得用一个定时器不断去让View重绘,有很多方法,这里直接用Handler来弄,可以这样做,如下:

    此时编译运行:

    嗯~~貌似不错~~但是还是存在一个小BUG的,第一次进来就绘制了两格,所以解决它很解决,只要改变初始化的格数既可,如下:

    为何要将其改成4既可呢?因为:

    所以,此时再编译运行就木问题了:

    嗯~~不过差完美还有一步,资源的回收,此时如果将Activity退出,其刷新线程还在不断进行,如下:

    所以接下来对资源进行回收,比较简单:

    /**
     * WIFI动画:采用自定义View的方式来实现,而非用Android提供的原生动画
     */
    public class WiFi extends View {
    
        //constants
        /* 总共有几格信号 */
        private static final float SIGNAL_SIZE = 4F;
    
        //variables
        private Paint paint;
        /* 绘制的基准长度:取屏幕宽高的较少值 */
        private int baseLength;
        /* 当前信号存在的数量,默认从1格信号开始 */
        private float shouldExistSinalSize = 4;
        private Handler handler = new Handler();
        private boolean isExit;
    
        public WiFi(Context context) {
            this(context, null);
        }
    
        public WiFi(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, -1);
        }
    
        public WiFi(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            paint = new Paint();
            paint.setColor(Color.BLACK);
            paint.setAntiAlias(true);
            paint.setStrokeWidth(4);
            //不断的进行绘制
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (isExit)
                        return;
                    Log.e("cexo", "handler run()...");
                    invalidate();
                    handler.postDelayed(this, 1000);
                }
            }, 1000);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            //绘制应该是在屏幕较小的长度来进行,所以先取出最小值,在绘制时需要参考该值
            baseLength = Math.min(w, h);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            shouldExistSinalSize++;
            if (shouldExistSinalSize > 4) {
                shouldExistSinalSize = 1;//如果已经绘制四格信号了,那么下一次则又回到一格信号状态
            }
            canvas.save();
            canvas.translate(0, baseLength / SIGNAL_SIZE);//将画布往下移动一点,以防绘制太贴边
            //根据信号的格数来进行相应的绘制
            RectF rectF;
            //计算出一个基准圆半径
            float baseRadius = baseLength / 2 / SIGNAL_SIZE;//算出平均的半径
            for (int i = 0; i < SIGNAL_SIZE; i++) {
                if (i >= SIGNAL_SIZE - shouldExistSinalSize) {
                    float radius = baseRadius * i;
                    rectF = new RectF(radius, radius, baseLength - radius, baseLength - radius);
                    if (i < SIGNAL_SIZE - 1) {//此时需绘制一个弧形
                        paint.setStyle(Paint.Style.STROKE);//设置画笔样式为空心样式
                        canvas.drawArc(rectF, -135, 90, false, paint);
                    } else {//最下面则需要绘制一个扇形
                        paint.setStyle(Paint.Style.FILL);//设置画笔样式为实心样式
                        canvas.drawArc(rectF, -135, 90, true, paint);
                    }
                }
            }
            canvas.restore();
        }
    
        public void onDestroy() {
            isExit = true;
        }
    }
    public class MainActivity extends AppCompatActivity {
    
        private WiFi wifi;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            wifi = findViewById(R.id.wifi);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            wifi.onDestroy();
        }
    }

    至此wifi信号的效果就完美实现,看似简单的效果其实现并非简单,可见也花的篇幅也不小。

  • 相关阅读:
    PostgreSQL锁级别及什么操作获取什么锁
    python类和实例
    使用@property
    python3基础笔记(六)模块与包
    【转载】Python装饰器-专题笔记
    python3基础笔记(五)迭代器与生成器
    python3基础笔记(四)文件处理
    python3基础笔记(三)函数与全局、局部变量
    python3基础笔记(二)python的基本数据类型与运算符
    python3基础笔记(一)
  • 原文地址:https://www.cnblogs.com/webor2006/p/8322387.html
Copyright © 2020-2023  润新知