Android 跳动的TextView JumpingBeans

作者: BRYANB | 来源:发表于2017-02-17 15:15 被阅读589次

    版权声明:本文为博主原创文章,未经博主允许不得转载。

    在做项目的时候,经常碰到在加载数据时需要等待,这个时候就需要一个加载框。在项目开发中,碰到了这个这样的需求:只对界面中局部添加加载进度条,其他地方功能不受其影响。然后琢磨着就把JumpingBeans给用上了。本篇只讲JumpingBeans的使用。

    第一步:前期准备工作

    1、第一种方式: compile 'net.frakbot:jumpingbeans:1.3.0'
    2、第二种方式:使用JumpingBeans,JumpingBeansSpan这个两个类就可以了。下面是代码:

    A.JumpingBeans.java:

       /*
        * Copyright 2014 Frakbot (Sebastiano Poggi and Francesco Pontillo)
        *
        *    Licensed under the Apache License, Version 2.0 (the "License");
        *    you may not use this file except in compliance with the License.
        *    You may obtain a copy of the License at
        *
        *        http://www.apache.org/licenses/LICENSE-2.0
        *
        *    Unless required by applicable law or agreed to in writing, software
        *    distributed under the License is distributed on an "AS IS" BASIS,
        *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        *    See the License for the specific language governing permissions and
        *    limitations under the License.
        */
    
      package com.jieshun.jscarlife.widgets;
      import android.support.annotation.NonNull;
      import android.text.SpannableStringBuilder;
      import android.text.Spanned;
      import android.text.TextUtils;
      import android.widget.TextView;
      import java.lang.ref.WeakReference;
    
    
      public final class JumpingBeans {
    
    /**
     * The default fraction of the whole animation time spent actually animating.
     * The rest of the range will be spent in "resting" state.
     * This the "duty cycle" of the jumping animation.
     */
    public static final float DEFAULT_ANIMATION_DUTY_CYCLE = 0.65f;
    
    /**
     * The default duration of a whole jumping animation loop, in milliseconds.
     */
    public static final int DEFAULT_LOOP_DURATION = 1300;   // ms
    
    public static final String ELLIPSIS_GLYPH = "…";
    public static final String THREE_DOTS_ELLIPSIS = "...";
    public static final int THREE_DOTS_ELLIPSIS_LENGTH = 3;
    
    private final JumpingBeansSpan[] jumpingBeans;
    private final WeakReference<TextView> textView;
    
    private JumpingBeans(@NonNull JumpingBeansSpan[] beans, @NonNull TextView textView) {
        this.jumpingBeans = beans;
        this.textView = new WeakReference<>(textView);
    }
    
    /**
     * Create an instance of the {@link net.frakbot.jumpingbeans.JumpingBeans.Builder}
     * applied to the provided {@code TextView}.
     *
     * @param textView The TextView to apply the JumpingBeans to
     * @return the {@link net.frakbot.jumpingbeans.JumpingBeans.Builder}
     */
    public static Builder with(@NonNull TextView textView) {
        return new Builder(textView);
    }
    
    /**
     * Stops the jumping animation and frees up the animations.
     */
    public void stopJumping() {
        for (JumpingBeansSpan bean : jumpingBeans) {
            if (bean != null) {
                bean.teardown();
            }
        }
    
        cleanupSpansFrom(textView.get());
    }
    
    private static void cleanupSpansFrom(TextView tv) {
        if (tv != null) {
            CharSequence text = tv.getText();
            if (text instanceof Spanned) {
                CharSequence cleanText = removeJumpingBeansSpansFrom((Spanned) text);
                tv.setText(cleanText);
            }
        }
    }
    
            private static CharSequence removeJumpingBeansSpansFrom(Spanned text) {
             SpannableStringBuilder sbb = new SpannableStringBuilder(text.toString());
               Object[] spans = text.getSpans(0, text.length(), Object.class);
             for (Object span : spans) {
             if (!(span instanceof JumpingBeansSpan)) {
                sbb.setSpan(span, text.getSpanStart(span),
                        text.getSpanEnd(span), text.getSpanFlags(span));
                   }
              }
                    return sbb;
             }
    
            private static CharSequence appendThreeDotsEllipsisTo(TextView textView) {
               CharSequence text = getTextSafe(textView);
               if (text.length() > 0 && endsWithEllipsisGlyph(text)) {
                    text = text.subSequence(0, text.length() - 1);
              }
    
             if (!endsWithThreeEllipsisDots(text)) {
                  text = new SpannableStringBuilder(text).append(THREE_DOTS_ELLIPSIS);  // Preserve spans in original text
              }
             return text;
             }
    
           private static CharSequence getTextSafe(TextView textView) {
            return !TextUtils.isEmpty(textView.getText()) ? textView.getText() : "";
            }
    
            private static boolean endsWithEllipsisGlyph(CharSequence text) {
             return TextUtils.equals(text.subSequence(text.length() - 1, text.length()), ELLIPSIS_GLYPH);
            }
    
           private static boolean endsWithThreeEllipsisDots(@NonNull CharSequence text) {
             if (text.length() < THREE_DOTS_ELLIPSIS_LENGTH) {
            // TODO we should try to normalize "invalid" ellipsis (e.g., ".." or "....")
            return false;
           }
              return TextUtils.equals(text.subSequence(text.length() - THREE_DOTS_ELLIPSIS_LENGTH, text.length()), THREE_DOTS_ELLIPSIS);
           }
            private static CharSequence ensureTextCanJump(int startPos, int endPos, CharSequence text) {
             if (text == null) {
                  throw new NullPointerException("The textView text must not be null");
            }
            if (endPos < startPos) {
                 throw new IllegalArgumentException("The start position must be smaller than the end position");
            }
            if (startPos < 0) {
            throw new IndexOutOfBoundsException("The start position must be non-negative");
            }
             if (endPos > text.length()) {
                  throw new IndexOutOfBoundsException("The end position must be smaller than the text        length");
            }
           return text;
           }
           public static class Builder {
           private int startPos, endPos;
           private float animRange = DEFAULT_ANIMATION_DUTY_CYCLE;
           private int loopDuration = DEFAULT_LOOP_DURATION;
           private int waveCharDelay = -1;
           private CharSequence text;
           private TextView textView;
           private boolean wave;
    
              /*package*/ Builder(TextView textView) {
            this.textView = textView;
           }
    
               public Builder appendJumpingDots() {
                CharSequence text = appendThreeDotsEllipsisTo(textView);
                  this.text = text;
                  this.wave = true;
                  this.startPos = text.length() - THREE_DOTS_ELLIPSIS_LENGTH;
                  this.endPos = text.length();
                  return this;
               }
    
               public Builder makeTextJump(int startPos, int endPos) {
                  CharSequence text = textView.getText();
                  ensureTextCanJump(startPos, endPos, text);
    
                  this.text = text;
                  this.wave = true;
                  this.startPos = startPos;
                  this.endPos = endPos;
    
                  return this;
               }
    
                public Builder setAnimatedDutyCycle(float animatedRange) {
                   if (animatedRange <= 0f || animatedRange > 1f) {
                        throw new IllegalArgumentException("The animated range must be in the (0, 1] range");
                    }
                   this.animRange = animatedRange;
                   return this;
               }
             public Builder setLoopDuration(int loopDuration) {
                  if (loopDuration < 1) {
                      throw new IllegalArgumentException("The loop duration must be bigger than zero");
                  }
                  this.loopDuration = loopDuration;
                  return this;
               }
               public Builder setWavePerCharDelay(int waveCharOffset) {
                   if (waveCharOffset < 0) {
                      throw new IllegalArgumentException("The wave char offset must be non-negative");
                  }
                  this.waveCharDelay = waveCharOffset;
                  return this;
               }         
               public Builder setIsWave(boolean wave) {
                this.wave = wave;
                 return this;
               }
                public JumpingBeans build() {
                   SpannableStringBuilder sbb = new SpannableStringBuilder(text);
                  JumpingBeansSpan[] spans;
                   if (wave) {
                   spans = getJumpingBeansSpans(sbb);
                  } else {
                      spans = buildSingleSpan(sbb);
                   }
                   textView.setText(sbb);
                   return new JumpingBeans(spans, textView);
                }
    
               private JumpingBeansSpan[] getJumpingBeansSpans(SpannableStringBuilder sbb) {
                   JumpingBeansSpan[] spans;
                    if (waveCharDelay == -1) {
                waveCharDelay = loopDuration / (3 * (endPos - startPos));
                   }
                   spans = new JumpingBeansSpan[endPos - startPos];
                   for (int pos = startPos; pos < endPos; pos++) {
                JumpingBeansSpan jumpingBean =
                        new JumpingBeansSpan(textView, loopDuration, pos - startPos, waveCharDelay, animRange);
                sbb.setSpan(jumpingBean, pos, pos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                spans[pos - startPos] = jumpingBean;
                   }
                   return spans;
               }
    
                 private JumpingBeansSpan[] buildSingleSpan(SpannableStringBuilder sbb) {
                JumpingBeansSpan[] spans;
                spans = new JumpingBeansSpan[]{new JumpingBeansSpan(textView, loopDuration, 0, 0,  animRange)};
                sbb.setSpan(spans[0], startPos, endPos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 return spans;
                   }
               }
                   }
    

    B.JumpingBeansSpan.java

     /*
     * Copyright 2014 Frakbot (Sebastiano Poggi and Francesco Pontillo)
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *        http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    
    package com.jieshun.jscarlife.widgets;
    
    import android.animation.TimeInterpolator;
    import android.animation.ValueAnimator;
    import android.annotation.SuppressLint;
    import android.os.Build;
    import android.support.annotation.NonNull;
    import android.text.TextPaint;
    import android.text.style.SuperscriptSpan;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    import java.lang.ref.WeakReference;
    
    /*package*/
    @SuppressLint("ParcelCreator")
    final class JumpingBeansSpan extends SuperscriptSpan implements       ValueAnimator.AnimatorUpdateListener {
    
    private final WeakReference<TextView> textView;
    private final int delay;
    private final int loopDuration;
    private final float animatedRange;
    private int shift;
    private ValueAnimator jumpAnimator;
    
    
    
    public JumpingBeansSpan(@NonNull TextView textView, int loopDuration, int position, int waveCharOffset,
                            float animatedRange) {
        this.textView = new WeakReference<>(textView);
        this.delay = waveCharOffset * position;
        this.loopDuration = loopDuration;
        this.animatedRange = animatedRange;
    }
    
    @Override
    public void updateMeasureState(@NonNull TextPaint tp) {
        initIfNecessary(tp.ascent());
        tp.baselineShift = shift;
    }
    
    @Override
    public void updateDrawState(@NonNull TextPaint tp) {
        initIfNecessary(tp.ascent());
        tp.baselineShift = shift;
    }
    
    private void initIfNecessary(float ascent) {
        if (jumpAnimator != null) {
            return;
        }
    
        this.shift = 0;
        int maxShift = (int) ascent / 2;
        jumpAnimator = ValueAnimator.ofInt(0, maxShift);
        jumpAnimator
                .setDuration(loopDuration)
                .setStartDelay(delay);
        jumpAnimator.setInterpolator(new JumpInterpolator(animatedRange));
        jumpAnimator.setRepeatCount(ValueAnimator.INFINITE);
        jumpAnimator.setRepeatMode(ValueAnimator.RESTART);
        jumpAnimator.addUpdateListener(this);
        jumpAnimator.start();
    }
    
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        // No need for synchronization as this always run on main thread anyway
        TextView v = textView.get();
        if (v != null) {
            updateAnimationFor(animation, v);
        } else {
            cleanupAndComplainAboutUserBeingAFool();
        }
    }
    
    private void updateAnimationFor(@NonNull ValueAnimator animation, @NonNull TextView v) {
        if (isAttachedToHierarchy(v)) {
            shift = (int) animation.getAnimatedValue();
            v.invalidate();
        }
    }
    
    private static boolean isAttachedToHierarchy(View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return v.isAttachedToWindow();
        }
        return v.getParent() != null;   // Best-effort fallback
    }
    
    private void cleanupAndComplainAboutUserBeingAFool() {
        // The textview has been destroyed and teardown() hasn't been called
        teardown();
        Log.w("JumpingBeans", "!!! Remember to call JumpingBeans.stopJumping() when appropriate !!!");
    }
    
    /*package*/ void teardown() {
        if (jumpAnimator != null) {
            jumpAnimator.cancel();
            jumpAnimator.removeAllListeners();
        }
        if (textView.get() != null) {
            textView.clear();
        }
    }
    
    /**
     * A tweaked {@link android.view.animation.AccelerateDecelerateInterpolator}
     * that covers the full range in a fraction of its input range, and holds on
     * the final value on the rest of the input range. By default, this fraction
     * is 65% of the full range.
     *
     * @see net.frakbot.jumpingbeans.JumpingBeans#DEFAULT_ANIMATION_DUTY_CYCLE
     */
    private static class JumpInterpolator implements TimeInterpolator {
    
        private final float animRange;
    
        public JumpInterpolator(float animatedRange) {
            animRange = Math.abs(animatedRange);
        }
    
        @Override
        public float getInterpolation(float input) {
            // We want to map the [0, PI] sine range onto [0, animRange]
            double radians = (input / animRange) * Math.PI;
            double interpolatedValue = Math.max(0f, Math.sin(radians));
            return (float) interpolatedValue;
        }
    
    }
    
    }
    

    第二步:具体使用

    1、使用TextView布局,findViewById就可以。
    2、声明一个JumpingBeans对象 :private JumpingBeans jumpingTvQry;
    3、进行设置
    jumpingTvQry = JumpingBeans.with(tvQrying)
    .makeTextJump(0, tvQrying.getText().toString().length())
    .setIsWave(true)
    .setLoopDuration(1000)
    .build();
    with://把这个TextView绑定到JumpingBeans 中
    makeTextJump://具体动作的哪几个字如传入(0,3)就是第1-第4个字有动画效果
    setIsWave://是否允许有动画
    setLoopDuration://效果持续的时间,毫秒为单位
    build://判断一系列参数,构建效果并执行(重新对TextView的字符串内容进行拼接)

    第三步:关闭开启的动画

    @Override
    protected void onDestroy()
    {
    jumpingTvQry.stopJumping();
    jumpingTvQryDot.stopJumping();
    super.onDestroy();
    }

    至此,整个JumpingBeans的使用就告一段落了。希望对你们有帮助。
    补个效果图:


    Paste_Image.png

    相关文章

      网友评论

        本文标题:Android 跳动的TextView JumpingBeans

        本文链接:https://www.haomeiwen.com/subject/ujxlwttx.html