使用SpannableString实现一个load小动画

依然是github开源项目:WaitingDots

这个项目代码不多,实现的非常easy。可是非常有意思由于动画的基本元素不是画出来的,而是使用了spannableString来实现。

  • DotsTextView.java
  • JumpingSpan.java
  • MainActivity.java

    DotstextView是动画的实现主体。

    JumpingSpan是基本元素,是动画中的插件

    MainActivity中仅仅要在布局中引入DotsTextView就可以。

    下面是切割线,show code:

package pl.tajchert.sample;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.text.style.ReplacementSpan;
/* ReplacementSpan真是一个挺奇妙的东西。在官方api上介绍甚少
 *几个主要功能函数也是do nothing.
 * 本例中自己定义的translationX,translationY没有发挥作用。假设给两个变量赋值
 * 那么第一个JumpingSpan距离前面元素距离增大,这里这样使用是为了让每一个"."为一个单独的单元进行独立操作
 */
public class JumpingSpan extends ReplacementSpan {

    private float translationX = 0;
    private float translationY = 0;

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fontMetricsInt) {
        return (int) paint.measureText(text, start, end);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        canvas.drawText(text, start, end, x + translationX, y + translationY, paint);
    }

    public void setTranslationX(float translationX) {
        this.translationX = translationX;
    }

    public void setTranslationY(float translationY) {
        this.translationY = translationY;
    }
}
package pl.tajchert.sample;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.Spanned;
import android.util.AttributeSet;
import android.widget.TextView;

import pl.tajchert.waitingdots.R;

public class DotsTextView extends TextView {

    private JumpingSpan dotOne;
    private JumpingSpan dotTwo;
    private JumpingSpan dotThree;

    private int showSpeed = 700;

    private int jumpHeight;
    private boolean autoPlay;
    private boolean isPlaying;
    private boolean isHide;
    private int period;
    private long startTime;

    private boolean lockDotOne;
    private boolean lockDotTwo;
    private boolean lockDotThree;

    private Handler handler;
    private AnimatorSet mAnimatorSet = new AnimatorSet();
    private float textWidth;

    public DotsTextView(Context context) {
        super(context);
        init(context, null);
    }

    public DotsTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public DotsTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        handler = new Handler(Looper.getMainLooper());
        //自己定义属性
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaitingDots);
            period = typedArray.getInt(R.styleable.WaitingDots_period, 6000);
            jumpHeight = typedArray.getInt(R.styleable.WaitingDots_jumpHeight, (int) (getTextSize() / 4));
            autoPlay = typedArray.getBoolean(R.styleable.WaitingDots_autoplay, true);
            typedArray.recycle();
        }
        dotOne = new JumpingSpan();
        dotTwo = new JumpingSpan();
        dotThree = new JumpingSpan();
        //将每一个点设置为jumpingSpan类型
        SpannableString spannable = new SpannableString("...");
        spannable.setSpan(dotOne, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        spannable.setSpan(dotTwo, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        spannable.setSpan(dotThree, 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        setText(spannable, BufferType.SPANNABLE);

        textWidth = getPaint().measureText(".", 0, 1);
        //一下两个是把updateListener加到点1上,通过它来进行刷新动作
        ObjectAnimator dotOneJumpAnimator = createDotJumpAnimator(dotOne, 0);
        dotOneJumpAnimator.addUpdateListener(new AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                invalidate();
            }
        });
        //这里通过animationSet来控制三个点的组合动作
        mAnimatorSet.playTogether(dotOneJumpAnimator, createDotJumpAnimator(dotTwo,
                period / 6), createDotJumpAnimator(dotThree, period * 2 / 6));

        isPlaying = autoPlay;
        if(autoPlay) {
            start();
        }
    }

    public void start() {
        isPlaying = true;
        //一旦開始就INFINITE
        setAllAnimationsRepeatCount(ValueAnimator.INFINITE);
        mAnimatorSet.start();
    }
    /*动画的实现核心
    *@param jumpingSpan 传入点。
    * @delay 动画执行延迟,通过这个參数让三个点进行有时差的运动
     */
    private ObjectAnimator createDotJumpAnimator(JumpingSpan jumpingSpan, long delay) {
        ObjectAnimator jumpAnimator = ObjectAnimator.ofFloat(jumpingSpan, "translationY", 0, -jumpHeight);
        /*setEvaluator这个重要。功能是为了通过方程来平滑的实现点运动的“节奏感”,能够试试把这段去掉。
            你会发现点会以默认的速度上下运动,特别生硬。TypeEvaluator中的evaluate能够计算出点的当前位置。
            通过对当前点的计算间接设计了点的轨迹运动,和时间插值TimeInterpolator达到同样的效果。就好比你不知道速度可是你知道每秒所在的位置相当于速度了。

这个计算方法是这种:能够參见这个博文
            http://blog.csdn.net/serapme/article/details/47006049
            ValueAnimator还封装了一个TypeAnimator。依据開始、结束值与TimeIniterpolator计算得到的值计算出属性值。
            ValueAnimator依据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后依据TimeInterpolator计算出还有一个因子,最后TypeAnimator通过这个因子计算出属性值,如上例中10ms时:
            首先计算出时间因子,即经过的时间百分比:t=10ms/40ms=0.25
            经插值计算(inteplator)后的插值因子:大约为0.15。上述样例中用了AccelerateDecelerateInterpolator,计算公式为(input即为时间因子):
            (Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
            最后依据TypeEvaluator计算出在10ms时的属性值:0.15*(40-0)=6pixel。上例中TypeEvaluator为FloatEvaluator,计算方法为 :
            public Float evaluate(float fraction, Number startValue, Number endValue) {
                    float startFloat = startValue.floatValue();
                    return startFloat + fraction * (endValue.floatValue() - startFloat);
             }
         */
        jumpAnimator.setEvaluator(new TypeEvaluator<Number>() {

            @Override
            public Number evaluate(float fraction, Number from, Number to) {
                return Math.max(0, Math.sin(fraction * Math.PI * 2)) * (to.floatValue() - from.floatValue());
            }
        });
        jumpAnimator.setDuration(period);
        jumpAnimator.setStartDelay(delay);
        jumpAnimator.setRepeatCount(ValueAnimator.INFINITE);
        jumpAnimator.setRepeatMode(ValueAnimator.RESTART);
        return jumpAnimator;
    }
    //下面部分非核心功能也没难度就不凝视了~主要是由于懒~
    public void stop() {
        isPlaying = false;
        setAllAnimationsRepeatCount(0);
    }

    private void setAllAnimationsRepeatCount(int repeatCount) {
        for (Animator animator : mAnimatorSet.getChildAnimations()) {
            if (animator instanceof ObjectAnimator) {
                ((ObjectAnimator) animator).setRepeatCount(repeatCount);
            }
        }
    }

    public void hide() {

        createDotHideAnimator(dotThree, 2).start();

        ObjectAnimator dotTwoMoveRightToLeft = createDotHideAnimator(dotTwo, 1);
        dotTwoMoveRightToLeft.addUpdateListener(new AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                invalidate();
            }
        });

        dotTwoMoveRightToLeft.start();
        isHide = true;
    }

    public void show() {
        ObjectAnimator dotThreeMoveRightToLeft = createDotShowAnimator(dotThree, 2);

        dotThreeMoveRightToLeft.start();

        ObjectAnimator dotTwoMoveRightToLeft = createDotShowAnimator(dotTwo, 1);
        dotTwoMoveRightToLeft.addUpdateListener(new AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                invalidate();
            }
        });

        dotTwoMoveRightToLeft.start();
        isHide = false;
    }

    private ObjectAnimator createDotHideAnimator(JumpingSpan span, float widthMultiplier) {
        return createDotHorizontalAnimator(span, 0, -textWidth * widthMultiplier);
    }

    private ObjectAnimator createDotShowAnimator(JumpingSpan span, int widthMultiplier) {
        return createDotHorizontalAnimator(span, -textWidth * widthMultiplier, 0);
    }

    private ObjectAnimator createDotHorizontalAnimator(JumpingSpan span, float from, float to) {
        ObjectAnimator dotThreeMoveRightToLeft = ObjectAnimator.ofFloat(span, "translationX", from, to);
        dotThreeMoveRightToLeft.setDuration(showSpeed);
        return dotThreeMoveRightToLeft;
    }

    public void showAndPlay() {
        show();
        start();
    }

    public void hideAndStop() {
        hide();
        stop();
    }

    public boolean isHide() {
        return isHide;
    }

    public boolean isPlaying() {
        return isPlaying;
    }

    public void setJumpHeight(int jumpHeight) {
        this.jumpHeight = jumpHeight;
    }

    public void setPeriod(int period) {
        this.period = period;
    }
}

毫无疑问在activity中引入布局中就可以。

dotsTextView = (DotsTextView) findViewById(R.id.dots);

一个简单使用的loadingview。

时间: 2024-08-25 04:19:40

使用SpannableString实现一个load小动画的相关文章

一个扇形的动画效果

用cashapelayer和core animation实现的一个扇形的动画效果. 直接贴代码 可以让cashapelayer跟着动画里面的一个path动态的绘图 -(void)addarcanimation { CAShapeLayer *linelayer=[CAShapeLayer layer]; linelayer.strokeColor=[[UIColor colorWithRed:0.400 green:1.000 blue:1.000 alpha:1.000] CGColor];

每日计划-html+css作品:一个加载动画

动画挺简单,关键是渐隐渐现的次序及时间 那个缩放可以去掉的,懒得调了... <style> #show { height : 140px; width : 150px; margin:auto; margin-top:45%; transform: scale(2,2) } #load {font-family : Arial; text-align : center; padding-top:20px; } .t{ height:30px; width:30px; float:left; b

一个Hibernate小程序

基本步骤 在前一篇博文Hibernate环境搭建中为大家详细的介绍如何搭建一个学习新类库的学习环境.今天,为大家带来一个Hibernate小例子,让大家能够快速上手. 步骤如下: 1.配置hibernate.cfg.xml 2.编写映射文件User.hbm.xml 3.编写一个持久化类User.java 4.编写辅助类HibernateUtil.java 5.加载并存储对象UserManager.java 配置Hibernate.cfg.xml a.在新建的XHibernate项目的src目录下

分享一个Unity3D小作品,欢迎索取源码!

在一年多前知道了Unity这款游戏引擎.在得知她极大地简化游戏开发的难度并可以使用我最熟悉的C#开发后,便毅然决然地开始学习Unity3D.说来惭愧,期间,由于个人原因,学习断断续续,直到现在才有一个勉强拿的出手的小作品.这款小游戏是一款类似超级马里奥的冒险游戏,玩法简单明了不费脑. 游戏截图 菜单界面 查看最高分 设置游戏难度,主要是设置主角受攻击时的伤害 可以通过跳跃攻击小怪兽 匕首攻击 滑行越过障碍物 乘坐来回移动的平台去往目的地 你赢了!就这样! 操作键设置 在该项目中自定义了几个操作键

【Android】 给我一个Path,还你一个酷炫动画

本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家公布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/53040506 本文出自:[张旭童的CSDN](http://blog.csdn.net/zxt0601) 代码传送门:喜欢的话.随手点个star.多谢 https://github.com/mcxtzhang/PathAnimView 一 概述 原本仅仅是想模仿一下我魂牵梦萦的StoreHouse效果.没想到意

从入门到上线一个天气小程序

作者:wuwhs segmentfault.com/a/1190000017388333 前言 学习了一段时间小程序,大致过了两遍开发文档,抽空做个自己的天气预报小程序,全当是练手,在这记录下.小程序开发的安装.注册和接入等流程就不罗列了,在小程序接入指南已经写得很清楚了,以下只对开发过程常用到得一些概念进行简单梳理,类比 Vue 加强记忆,最后选取个人项目天气小程序中要注意的几点来说明. 欢迎扫码体验: 源码请戳这里,欢迎start~ 初始化项目目录结构 安装好开发者工具,填好申请到的 App

两周撸一个微信小程序

利益相关 无 说明 该小程序代码已开源,点击可查看源码,可随意 star.也可以先扫描下方的小程序码直接体验. 写在前面 前段时间写了一个简单的小程序 QuietWeather,源码在这里,具体实现相关可查看这篇文章:两天撸一个天气应用微信小程序.但是这个 小程序 和 QuietWeather 完全不是一个数量级的.so,该文章梳理内容会有那么一点儿多,想跳过的可以直接拉到最下面... 这里先上效果图,感兴趣的也可以 查看源码 .实际体验可扫描??上面的小程序码. 效果图 PC 开发者工具录制,

Linux下安装MyEclipse和Tomcat服务器详解,以及我安装过程中所出现的问题以及解决办法,并实现一个web小程序

1.首先,先要去MyEclipse和Tomcat的官网去下载Linux版的压缩文件,而MyEclipse的中文官网是需要登录并有可能要付钱,大家可以去网上下载,还有就是Tomcat的linux版,这个直接上官网就可以下载了,下载后我还是通通把它们放在我E盘下的as目录底下,如果大家还没配置好jdk,即Java环境的话,可看我这篇文章:http://blog.csdn.net/u012561176/article/details/45119047 这里我就直接进行操作了! 我E盘下的as文件夹

突发奇想想学习做一个HTML5小游戏

前言: 最近一期文化馆轮到我分享了,分享了两个,一个是关于童年教科书的回忆,一个是关于免费电子书的.最后我觉得应该会不敌web,只能说是自己在这中间回忆了一下那个只是会学习的年代,那个充满梦想的年代.有人说如果一个人开始回忆童年的时候,那么他开始变老了,不知道是不是这样一个原因,我突然想起了很多以前的老朋友,开始想起了一些童年时期的玩伴.也就想做这样一款简单的游戏,也只是单纯的想回忆一下童年. 计划: 游戏其实很简单,我们把它叫着裤裆棋,又叫什么狗卵坨还是什么的,有些记忆模糊了,反正大致是这样子