zoukankan      html  css  js  c++  java
  • android高级UI之PathMeasure<二>Path测量实战(各种Loading效果)

    在上一次https://www.cnblogs.com/webor2006/p/15488224.html已经学习上PathMeasure的基础了,这次则针对它进行一些实际效果的操练加以巩固。

    实战:各种Loading效果:

    Loading一:让箭头图片沿圆轨迹走

    效果:

    首先来实现在上篇开头所展示的这个效果:

     

    如果对于上一篇的整体基础都了解之后,实现这样沿路径轨迹的效果那就很轻松了。

    实现:

    1、新建View:

    这里还是基于上一次学习的工程往上继续垒代码:

    2、 画园:

    首先先准备一个中心的圆,待会它是就图片需要沿着运转的轨迹:

    package com.cexo.pathmeasurestudy;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.View;
    
    import androidx.annotation.Nullable;
    
    /**
     * Loading效果一:箭头图片沿圆路径旋转
     */
    public class LoadingView extends View {
    
        private int viewWidth;
        private int viewHeight;
        private Paint circlePaint;
    
        public LoadingView(Context context) {
            this(context, null);
        }
    
        public LoadingView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, -1);
        }
    
        public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
    
        private void init(Context context) {
            circlePaint = new Paint();
            circlePaint.setColor(Color.RED);
            circlePaint.setStrokeWidth(5);
            circlePaint.setStyle(Paint.Style.STROKE);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.WHITE);
    
            canvas.translate(viewWidth / 2, viewHeight / 2);
    
            Path path = new Path();
            //Path.Direction.CW顺时针方向画圆
            path.addCircle(0, 0, 200, Path.Direction.CCW);
            canvas.drawPath(path, circlePaint);
        }
    
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            viewWidth = w;
            viewHeight = h;
        }
    }

    比较简单,效果如下:

    3、导入图片:

    接下来则将需要沿这个圆轨迹移动的图片导进来:

    4、将图片绘制在圆轨迹上:

    此时的效果:

    此时图片的绘制点则是在圆心上,不是咱们所预期的效果,应该是绘制在圆的轨迹上才行,此时很自然的就可以借助上一次学习的Path的测量,可以得到这俩值:

     

    其中测量时的forceClosed参数的含义就不过多说明了,可以参考上一个基础篇,接下来的重点就是如何借助这个测量得到的pos和tan来将咱们的图片绘制在圆轨迹上了,这里可以将图片绘制的left和top改为它试试:

    此时运行看一下:

    嗯,基本上是已经在圆轨迹上了,但是应该是让整个图片的中心在轨迹上,而不是图片的(0,0)坐标点:

     

    这个简单,减去掉图片的宽高的一半既可:

    此时再运行:

    5、让图片进行角度旋转:

    接下来重点就是要来处理图片的角度旋转了,很明显目前图片的角度是不对的,需要让箭头沿着圆的轨迹走才可以,那首先得要算出圆指定位置的角度才行对吧,这个简单,在上一次基础篇中已经打好这块的基础了:

    这里就不过多说明,先来得到角度:

    而要想让图片进行指定角度的旋转,此时在绘制图片时就得用另一个API了,这个也在上一篇有介绍过:

    这里就直接上代码了:

    此时运行:

     

    貌似平移的效果木有了,原来咱们平移不是用的它么?

    而目前绘制图片用的是matrix的api了,如何对它进行平移呢?其实matrix里有专门的api了,如下: 

    不过,发现貌似这个箭头是要往逆时针旋转呀,这是因为我们在绘制圆时就是指定的逆时针:

     

    所以,咱们改一下它,用逆时针旋转貌似更加自然一些:

    6、让箭头沿轨迹动起来:

    那接下来还差最后一步,如何让箭头动起来,这块就不难了,因为目前我们已经处理好圆的起始位置了:

     

    我们只要让这个位置不断的进行增加,那不就可以实现一个动态的效果么?具体做法如下:

    运行效果:

    至此,整个效果完成,是不是在打好基础之后,实现这样的效果非常之顺其自然?

    Loading二:

    效果:

    接下来实现第二个Loading效果,也是上一篇中提到过的:

    而要实现这样的效果,就得用这个API了:

    掌握基础之后其实现也不难。

    实现:

    1、新建View:

    2、绘制圆:

    跟上一个Loading效果一样,先在View的中心绘一个一模一样的圆:

    package com.cexo.pathmeasurestudy;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.View;
    
    import androidx.annotation.Nullable;
    
    /**
     * Loading效果一:利用getSegment() api实现Path的截取
     */
    public class LoadingView2 extends View {
    
        private int viewWidth;
        private int viewHeight;
        private Paint circlePaint;
    
        //region 构造
        public LoadingView2(Context context) {
            this(context, null);
        }
    
        public LoadingView2(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, -1);
        }
    
        public LoadingView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
        //endregion
    
        private void init(Context context) {
            circlePaint = new Paint();
            circlePaint.setColor(Color.RED);
            circlePaint.setStrokeWidth(5);
            circlePaint.setStyle(Paint.Style.STROKE);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.WHITE);
    
            canvas.translate(viewWidth / 2, viewHeight / 2);
    
            Path path = new Path();
            //Path.Direction.CW顺时针方向画圆
            path.addCircle(0, 0, 200, Path.Direction.CW);
            canvas.drawPath(path, circlePaint);
        }
    
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            viewWidth = w;
            viewHeight = h;
        }
    }

    效果就不贴出来了。

    2、截取Path指定位置进行显示:

    这里先实现一个Path截取的静态效果:

     

    运行看一下:

    看到了么,是不是显示的就是其圆中截取的从100到200长度的位置? 

    3、将截段动态化:

    接下来要实现目标动态的效果,则就需要来动态控制从Path中哪截取对吧,也就是:

    那这里可以使用一个属性动画ValueAnimator来弄一个系数,然后再不断进行界面的刷新,如下:

    接下来则就根据这个系数来动态算一下开始位置和结束位置,首先咱们结束位置这么来定:

    这个容易理解,那。。对于start这个参数如何确定呢?它肯定是要小于stop值的,所以它肯定是这样的:

    其中标问号的这个值的计算就是最关键的,这里先来看一下预期的效果,等于是路径先走一半圆:

    接着再超过一半圆的场景下,则开始让整个圆路径变短直到消失为止:

    这个用文字可能不太好描述,还是以一个慢动作再来体会一下这个效果背后的逻辑:

    具体这块的公式就不推导了,一句代码,如下:

    此时运行看一下效果:

    看着有点卡对吧,是因为录屏软件的原因。。

    4、调试理解代码:

    对于这个效果的实现,其实最难理解的就是这个Path开始点位置的确定,也就是:

    难理解也得想办法理解它,通常当你有段代码死活都无法理解时,此时如果知道答案时最好的方式就是自己通过调试一点点来理解,这里以四个条件来进行分析,为了便于计算,这里假设整个圆Path测量的总长度为100。

    a、animatorValue=0.3:

    stop=100 * 0.3 = 30;

    start=stop - ((0.5 - 0.2) * 100) = 30 - 30 = 0;

    所以此时绘制的路径区域为:

    b、animatorValue=0.5:

    stop=100 * 0.5 = 50;

    start=stop - ((0.5 - 0) * 100) = 50 - 50 = 0;

    所以此时绘制的路径区域为:

    c、animatorValue=0.7:

    stop=100 * 0.7 = 70;

    start=stop - ((0.5 - 0.2) * 100) = 70 - 30 = 40;

    所以此时绘制的路径区域为:

    也就是,当走过半圆的路径之后,start的开始截取的位置也开始变化了。

    d、animatorValue=1.0:

    stop=100 * 1.0 = 100;

    start=stop - ((0.5 - 0.5) * 100) = 100 - 0 = 100;

    所以此时绘制的路径区域为:

    通过这么几个条件的分析,是不是对这句代码的理解就会更加的简单了?

    5、优化代码:

    对于目前的代码实现上其实有一些不太好的地方,就是:

    对于整个圆的测量其实只要测一次就可以了,没必要放在onDraw()中每次都测量,这样性能也不是太好,所以这里抽离一下:

     

    此时运行,你会发现出bug了。。

    卡住了。。另外你再点back键时,发现有时还会anr:

    这是为啥呢?其实是我们目标的这个Path在绘制时木有进行重置造成,修改如下:

    为啥呢?通过Android Studio的Profile app发现,内存爆增。。

    而看一下Path.reset()方法的说明:

    哦,大致明白了,也就是由于onDraw()方法执行非常之频繁,而Path如果你不主动reset()的话,那么你之前绘制的元素肯定是还在的,那么当onDraw()函数不断执行很明显内存只会爆增的,最终达到界面卡死从而有ANR的情况出现了,这一点必须要注意。另外这里还有一个细节就是关于硬件加速的场景需要加这么一句话:

    关于这块我木有测出来,先这么写吧,待之前遇到了这种问题到时再回头来理解它。

    Loading三:

    效果:

    接下来再来实现这样的一个Loading效果:

    有了上一个Loading的基础,是不是实现这个就不难了。

    实现:

    1、新建View:

    2、画个圆:

    package com.cexo.pathmeasurestudy;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.View;
    
    /**
     * Loading效果三:继续利用getSegment() api实现Path的截取
     */
    public class LoadingView3 extends View {
    
        private int viewWidth;
        private int viewHeight;
        private Paint circlePaint;
    
        public LoadingView3(Context context) {
            this(context, null);
        }
    
        public LoadingView3(Context context, AttributeSet attrs) {
            this(context, attrs, -1);
        }
    
        public LoadingView3(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
    
        private void init() {
            circlePaint = new Paint();
            circlePaint.setColor(Color.RED);
            circlePaint.setStrokeWidth(5);
            circlePaint.setStyle(Paint.Style.STROKE);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.WHITE);
    
            canvas.translate(viewWidth / 2, viewHeight / 2);
    
            Path path = new Path();
            //Path.Direction.CW顺时针方向画圆
            path.addCircle(0, 0, 200, Path.Direction.CW);
            canvas.drawPath(path, circlePaint);
        }
    
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            viewWidth = w;
            viewHeight = h;
        }
    }

    3、绘制圆一圈:

    其实整个效果就是由两个状态组成,一个是绘制圆路径,一个是由整个圆路径慢慢转变消失,所以这里首先先沿圆的轨迹绘制一圈,比较简单:

    是不是跟咱们上面实现的代码基本雷同对吧,运行一下:

     

    4、让圆消失一圈:

    接下来则反着进行动态处理,也就是让满圆归0,实现起来也非常简单,先来增加两个状态:

    然后在属性动画结束时,主动切换一个当前的绘制状态:

    接下来在绘制这块也需要根据状态改变进行一下处理:

    此时运行效果就如开始看到的了,如果你对于PathMeasure基础打牢之后,对于这样的效果实现还是比较简单的了。 

    总结:

    整体来说这次的操练都不是太难,但是对于PathMeasure的巩固还是挺有帮助的,下一次则来实现如下两个效果进一步对它进行巩固,稍稍要麻烦一些:

    划船这个来自于网上的一个开源项目,用作学习还是很不错的~~

  • 相关阅读:
    Django--模型层进阶
    Django--模板层
    对自己的博客园主题稍作修改
    集群中Session共享解决方案分析
    【检测工具】keepalived安装及配置
    跨域问题简单分析
    Linux设置静态IP后出现的几种问题
    Linux上安装ElasticSearch及遇到的问题
    Linux上安装JDK1.8,tomcat9,以及mysql8的步骤
    归并排序分析
  • 原文地址:https://www.cnblogs.com/webor2006/p/15605936.html
Copyright © 2011-2022 走看看