版权声明:本文为博主原创文章,未经博主允许不得转载。
在做项目的时候,经常碰到在加载数据时需要等待,这个时候就需要一个加载框。在项目开发中,碰到了这个这样的需求:只对界面中局部添加加载进度条,其他地方功能不受其影响。然后琢磨着就把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
网友评论