创建 "growing ripples" 加载动画 - OutOfMemoryException
Create "growing ripples" loading animation - OutOfMemoryException
我想在 Android 应用程序中显示此动画:
所以我创建了一个 <animation-list>
来这样做。它使用 46 个帧,每个帧由一个 200x200px png
:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/anim_loading_ripple_frame_0" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_1" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_2" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_3" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_4" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_5" android:duration="45" />
...
<item android:drawable="@drawable/anim_loading_ripple_frame_45" android:duration="45" />
</animation-list>
然后我将其设置为 ImageView
的 background
并使用
启动动画
AnimationDrawable loadingAnimation =
(AnimationDrawable) loadingAnimationView.getBackground();
loadingAnimation.start();
除了应用程序有时会因 OutOfMemoryException
而崩溃外,一切都很完美!这种动画对内存的要求似乎很高,因为我看到其他人遇到了同样的问题。
然后我想我可以把每一帧都变成一个 XML shape
可绘制的,所以我尝试了试验,但我不知道如何让圆圈改变大小 - 它们始终占据视图的整个宽度。
我试过这样做
<item>
<shape android:shape="oval">
<padding android:top="30dp" android:right="30dp" android:bottom="30dp" android:left="30dp" />
<size
android:width="100dp"
android:height="100dp" />
<stroke
android:color="#0F0"
android:width="4dp"/>
</shape>
</item>
并且还使用 layer-list
在同一个 XML 文件中定义另一个 shape
但大小不同,认为这会导致圆变小,但他们都最终占据了 100% 的视图。
此外,如果我在 XML 中定义帧而不是一系列 png
图像,我不确定 AnimationDrawable
是否会实际使用更少的内存?也许我仍然会 运行 陷入同样的内存使用问题?
我有什么选择?如果时间不是问题,我会尝试制定一个自定义视图,但似乎仅此动画就需要大量工作。
您可以使用 ScaleAnimation 来改变尺寸。我创建了一个 ImageView 并使用了您在上面定义的形状:
<ImageView
android:id="@+id/circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/circle"/>
这和你想要的略有不同,因为我没有费心去寻找中心点来设置动画。它从左上角开始缩放。 (0,0)
ImageView iv = findViewById(R.id.circle);
float startScale = 0.5f;
float endScale = 3.0f;
Animation scaleAnim = new ScaleAnimation( startScale, endScale,
startScale, endScale,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0);
scaleAnim.setRepeatCount(Animation.INFINITE);
scaleAnim.setRepeatMode(Animation.REPEAT);
scaleAnim.setDuration(2000);
iv.setAnimation(scaleAnim);
scaleAnim.start();
您可以使用 ValueAnimation 类似地为颜色设置动画。
会起作用,但我想我不妨深入研究它并制作一个适当的(我想效率更高的)自定义 View
。特别是在处理多个 "ripples".
时
这是整个视图 class,可以在 init()
中对波纹进行微调,以更改颜色、波纹数量等。
RippleView.java
public class RippleView extends View implements ValueAnimator.AnimatorUpdateListener {
public static final String TAG = "RippleView";
private class Ripple {
AnimatorSet mAnimatorSet;
ValueAnimator mRadiusAnimator;
ValueAnimator mAlphaAnimator;
Paint mPaint;
Ripple(float startRadiusFraction, float stopRadiusFraction, float startAlpha, float stopAlpha, int color, long delay, long duration, float strokeWidth, ValueAnimator.AnimatorUpdateListener updateListener){
mRadiusAnimator = ValueAnimator.ofFloat(startRadiusFraction, stopRadiusFraction);
mRadiusAnimator.setDuration(duration);
mRadiusAnimator.setRepeatCount(ValueAnimator.INFINITE);
mRadiusAnimator.addUpdateListener(updateListener);
mRadiusAnimator.setInterpolator(new DecelerateInterpolator());
mAlphaAnimator = ValueAnimator.ofFloat(startAlpha, stopAlpha);
mAlphaAnimator.setDuration(duration);
mAlphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAlphaAnimator.addUpdateListener(updateListener);
mAlphaAnimator.setInterpolator(new DecelerateInterpolator());
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(mRadiusAnimator, mAlphaAnimator);
mAnimatorSet.setStartDelay(delay);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(color);
mPaint.setAlpha((int)(255*startAlpha));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(strokeWidth);
}
void draw(Canvas canvas, int centerX, int centerY, float radiusMultiplicator){
mPaint.setAlpha( (int)(255*(float)mAlphaAnimator.getAnimatedValue()) );
canvas.drawCircle(centerX, centerY, (float)mRadiusAnimator.getAnimatedValue()*radiusMultiplicator, mPaint);
}
void startAnimation(){
mAnimatorSet.start();
}
void stopAnimation(){
mAnimatorSet.cancel();
}
}
private List<Ripple> mRipples = new ArrayList<>();
public RippleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public RippleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if( isInEditMode() )
return;
/*
Tweak your ripples here!
*/
mRipples = new ArrayList<>();
mRipples.add(new Ripple(0.0f, 1.0f, 1.0f, 0.0f, Color.RED, 0, 2000, 4, this));
mRipples.add(new Ripple(0.0f, 1.0f, 1.0f, 0.0f, Color.WHITE, 500, 2000, 4, this));
}
public void startAnimation() {
setVisibility(View.VISIBLE);
for (Ripple ripple : mRipples) {
ripple.startAnimation();
}
}
public void stopAnimation() {
for (Ripple ripple : mRipples) {
ripple.stopAnimation();
}
setVisibility(View.GONE);
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
int centerX = getWidth()/2;
int centerY = getHeight()/2;
int radiusMultiplicator = getWidth()/2;
for (Ripple ripple : mRipples) {
ripple.draw(canvas, centerX, centerY, radiusMultiplicator);
}
}
}
只需在 RippleView
上调用 .startAnimation()
即可启动动画。
RippleView r = findViewById(R.id.rippleView);
r.startAnimation();
我想在 Android 应用程序中显示此动画:
所以我创建了一个 <animation-list>
来这样做。它使用 46 个帧,每个帧由一个 200x200px png
:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/anim_loading_ripple_frame_0" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_1" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_2" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_3" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_4" android:duration="45" />
<item android:drawable="@drawable/anim_loading_ripple_frame_5" android:duration="45" />
...
<item android:drawable="@drawable/anim_loading_ripple_frame_45" android:duration="45" />
</animation-list>
然后我将其设置为 ImageView
的 background
并使用
AnimationDrawable loadingAnimation =
(AnimationDrawable) loadingAnimationView.getBackground();
loadingAnimation.start();
除了应用程序有时会因 OutOfMemoryException
而崩溃外,一切都很完美!这种动画对内存的要求似乎很高,因为我看到其他人遇到了同样的问题。
然后我想我可以把每一帧都变成一个 XML shape
可绘制的,所以我尝试了试验,但我不知道如何让圆圈改变大小 - 它们始终占据视图的整个宽度。
我试过这样做
<item>
<shape android:shape="oval">
<padding android:top="30dp" android:right="30dp" android:bottom="30dp" android:left="30dp" />
<size
android:width="100dp"
android:height="100dp" />
<stroke
android:color="#0F0"
android:width="4dp"/>
</shape>
</item>
并且还使用 layer-list
在同一个 XML 文件中定义另一个 shape
但大小不同,认为这会导致圆变小,但他们都最终占据了 100% 的视图。
此外,如果我在 XML 中定义帧而不是一系列 png
图像,我不确定 AnimationDrawable
是否会实际使用更少的内存?也许我仍然会 运行 陷入同样的内存使用问题?
我有什么选择?如果时间不是问题,我会尝试制定一个自定义视图,但似乎仅此动画就需要大量工作。
您可以使用 ScaleAnimation 来改变尺寸。我创建了一个 ImageView 并使用了您在上面定义的形状:
<ImageView
android:id="@+id/circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/circle"/>
这和你想要的略有不同,因为我没有费心去寻找中心点来设置动画。它从左上角开始缩放。 (0,0)
ImageView iv = findViewById(R.id.circle);
float startScale = 0.5f;
float endScale = 3.0f;
Animation scaleAnim = new ScaleAnimation( startScale, endScale,
startScale, endScale,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0);
scaleAnim.setRepeatCount(Animation.INFINITE);
scaleAnim.setRepeatMode(Animation.REPEAT);
scaleAnim.setDuration(2000);
iv.setAnimation(scaleAnim);
scaleAnim.start();
您可以使用 ValueAnimation 类似地为颜色设置动画。
View
。特别是在处理多个 "ripples".
这是整个视图 class,可以在 init()
中对波纹进行微调,以更改颜色、波纹数量等。
RippleView.java
public class RippleView extends View implements ValueAnimator.AnimatorUpdateListener {
public static final String TAG = "RippleView";
private class Ripple {
AnimatorSet mAnimatorSet;
ValueAnimator mRadiusAnimator;
ValueAnimator mAlphaAnimator;
Paint mPaint;
Ripple(float startRadiusFraction, float stopRadiusFraction, float startAlpha, float stopAlpha, int color, long delay, long duration, float strokeWidth, ValueAnimator.AnimatorUpdateListener updateListener){
mRadiusAnimator = ValueAnimator.ofFloat(startRadiusFraction, stopRadiusFraction);
mRadiusAnimator.setDuration(duration);
mRadiusAnimator.setRepeatCount(ValueAnimator.INFINITE);
mRadiusAnimator.addUpdateListener(updateListener);
mRadiusAnimator.setInterpolator(new DecelerateInterpolator());
mAlphaAnimator = ValueAnimator.ofFloat(startAlpha, stopAlpha);
mAlphaAnimator.setDuration(duration);
mAlphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAlphaAnimator.addUpdateListener(updateListener);
mAlphaAnimator.setInterpolator(new DecelerateInterpolator());
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(mRadiusAnimator, mAlphaAnimator);
mAnimatorSet.setStartDelay(delay);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(color);
mPaint.setAlpha((int)(255*startAlpha));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(strokeWidth);
}
void draw(Canvas canvas, int centerX, int centerY, float radiusMultiplicator){
mPaint.setAlpha( (int)(255*(float)mAlphaAnimator.getAnimatedValue()) );
canvas.drawCircle(centerX, centerY, (float)mRadiusAnimator.getAnimatedValue()*radiusMultiplicator, mPaint);
}
void startAnimation(){
mAnimatorSet.start();
}
void stopAnimation(){
mAnimatorSet.cancel();
}
}
private List<Ripple> mRipples = new ArrayList<>();
public RippleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public RippleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if( isInEditMode() )
return;
/*
Tweak your ripples here!
*/
mRipples = new ArrayList<>();
mRipples.add(new Ripple(0.0f, 1.0f, 1.0f, 0.0f, Color.RED, 0, 2000, 4, this));
mRipples.add(new Ripple(0.0f, 1.0f, 1.0f, 0.0f, Color.WHITE, 500, 2000, 4, this));
}
public void startAnimation() {
setVisibility(View.VISIBLE);
for (Ripple ripple : mRipples) {
ripple.startAnimation();
}
}
public void stopAnimation() {
for (Ripple ripple : mRipples) {
ripple.stopAnimation();
}
setVisibility(View.GONE);
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
int centerX = getWidth()/2;
int centerY = getHeight()/2;
int radiusMultiplicator = getWidth()/2;
for (Ripple ripple : mRipples) {
ripple.draw(canvas, centerX, centerY, radiusMultiplicator);
}
}
}
只需在 RippleView
上调用 .startAnimation()
即可启动动画。
RippleView r = findViewById(R.id.rippleView);
r.startAnimation();