文章中标出ERROR的地方就是问题点 解决方案:自定义使用自定义View的裁剪+绘制实现这个效果
MainActivity代码
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Button animatedButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
animatedButton = findViewById(R.id.animatedButton);
final GradientDrawable buttonBackground = new GradientDrawable();
buttonBackground.setColor(0xFFFFFFFF); // 初始颜色为白色
buttonBackground.setCornerRadius(65f); // 初始圆角度数为65
animatedButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startAnimation();
}
});
}
private void startAnimation() {
// Scale animation
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1.0f, 0.5f, 1.0f);
scaleAnimator.setDuration(1000);
scaleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (float) animation.getAnimatedValue();
animatedButton.setScaleX(scale);
animatedButton.setScaleY(scale);
}
});
// Color animation
ValueAnimator colorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), 0xFFFFFFFF, 0xFFFF0000);
colorAnimator.setDuration(1000);
colorAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
int animatedValue = (int) animator.getAnimatedValue();
animatedButton.setBackgroundColor(animatedValue);
}
});
// Corner radius animation
ValueAnimator cornerAnimator = ValueAnimator.ofFloat(65f, 10f);
cornerAnimator.setDuration(1000);
cornerAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// 初始化背景为GradientDrawable,以便于动态改变圆角
GradientDrawable drawable = new GradientDrawable();
cornerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float radius = (float) animation.getAnimatedValue();
drawable.setShape(GradientDrawable.RECTANGLE);
drawable.setCornerRadius(radius);
animatedButton.setBackground(drawable);
}
});
// Start animations simultaneously
scaleAnimator.start();
colorAnimator.start();//ERROR:设置了背景,角度不生效。
cornerAnimator.start();//ERROR:设置了角度,背景不生效
}
}
xml代码 :ERROE:如果这里设置了圆角角度,同时动态代码中设置了背景色,会导致两个绘制都失效!!! android:background="@drawable/button_background"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:id="@+id/animatedButton"
android:layout_width="200dp"
android:layout_height="100dp"
android:text="Animate me!"
android:textColor="#000" />
</RelativeLayout>
drawable代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF"/>
<corners android:radius="60dp"/>
</shape>
解决方案: 自定义使用自定义View的裁剪+绘制实现这个效果
自定义View
AnimatedButtonView
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.Nullable;
public class AnimatedButtonView extends View {
private Paint paint;
private int currentColor;
private float currentRadius;
private float currentScale;
private static final int START_COLOR = 0xFF00FFFF;
private static final int END_COLOR = 0xFFFFFF00;
private static final float START_RADIUS = 85f;
private static final float END_RADIUS = 30f;
private static final long ANIMATION_DURATION = 1000;
private RectF rectF;
private Path path;
public AnimatedButtonView(Context context) {
super(context);
init();
}
public AnimatedButtonView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public AnimatedButtonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
rectF = new RectF();
path = new Path();
currentColor = START_COLOR;
currentRadius = START_RADIUS;
currentScale = 1.0f;
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startAnimation();
}
});
}
private void startAnimation() {
// Color animation
ValueAnimator colorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), START_COLOR, END_COLOR);
colorAnimator.setDuration(ANIMATION_DURATION);
colorAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentColor = (int) animation.getAnimatedValue();
invalidate();
}
});
// Radius animation
ValueAnimator radiusAnimator = ValueAnimator.ofFloat(START_RADIUS, END_RADIUS);
radiusAnimator.setDuration(ANIMATION_DURATION);
radiusAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
radiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentRadius = (float) animation.getAnimatedValue();
invalidate();
}
});
// Scale animation
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1.0f, 0.5f, 1.0f);
scaleAnimator.setDuration(ANIMATION_DURATION);
scaleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentScale = (float) animation.getAnimatedValue();
invalidate();
}
});
// Start all animations
colorAnimator.start();
radiusAnimator.start();
scaleAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Determine the scaled size
float width = getWidth() * currentScale;
float height = getHeight() * currentScale;
// Check for division by zero
int viewWidth = getWidth();
int viewHeight = getHeight();
if (viewWidth == 0 || viewHeight == 0) {
return; // Avoid division by zero
}
// Calculate the dimensions for the rectangle to draw
float left = (viewWidth - width) / 2;
float top = (viewHeight - height) / 2;
float right = left + width;
float bottom = top + height;
// Set the dimensions of the RectF
rectF.set(left, top, right, bottom);
// Ensure paint is initialized
if (paint == null) {
throw new NullPointerException("Paint object is not initialized");
}
paint.setColor(currentColor);
path.reset(); // Reset the path before adding new geometry
path.addRoundRect(rectF, currentRadius, currentRadius, Path.Direction.CW);
canvas.drawPath(path, paint);
}
}
xml代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<com.example.myandroid.AnimatedButtonView
android:layout_width="200dp"
android:layout_height="100dp"/>
</RelativeLayout>
然后再MainActivity中加载出来就行
AnimatedButtonView优化自定义控件
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.Nullable;
/**
* 优化的问题点:
* 减少内存泄漏风险 使用局部变量而非匿名内部类来定义动画更新监听器,并确保在动画结束后释放这些监听器的引用。
* 提高线程安全性 考虑将动画更新逻辑封装成单独的方法,并通过适当的调度机制减少不必要的重绘操作。
* 加强初始化检查 在init()方法中增加对paint对象的初始化检查,并在其他相关方法中添加必要的异常处理逻辑。
* 完善异常处理 在关键方法中增加日志记录或异常抛出,以便于调试和维护。
* 主要优化点:
* 匿名内部类替换为局部类:避免内存泄漏,监听器实现为独立类。
* 线程安全性:triggerRedraw方法使用postInvalidate确保从主线程调用绘制操作。
* 初始化检查:init方法中确保paint正确初始化,并抛出异常。
* 异常处理:使用Log.e记录重要的异常信息,以协助调试。
*/
public class AnimatedButtonView extends View {
private Paint paint;
private int currentColor;
private float currentRadius;
private float currentScale;
private static final int START_COLOR = 0xFF00FFFF;
private static final int END_COLOR = 0xFFFFFF00;
private static final float START_RADIUS = 85f;
private static final float END_RADIUS = 30f;
private static final long ANIMATION_DURATION = 1000;
private final RectF rectF;
private final Path path;
public AnimatedButtonView(Context context) {
this(context, null);
}
public AnimatedButtonView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimatedButtonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
rectF = new RectF();
path = new Path();
try {
init();
} catch (Exception e) {
Log.e("AnimatedButtonView", "Initialization failed: " + e.getMessage());
}
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
if (paint == null) {
throw new IllegalStateException("Failed to initialize paint");
}
currentColor = START_COLOR;
currentRadius = START_RADIUS;
currentScale = 1.0f;
setOnClickListener(v -> startAnimations());
}
private void startAnimations() {
ValueAnimator colorAnimator = createColorAnimator();
ValueAnimator radiusAnimator = createRadiusAnimator();
ValueAnimator scaleAnimator = createScaleAnimator();
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATION_DURATION);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.playTogether(colorAnimator, radiusAnimator, scaleAnimator);
animatorSet.start();
}
private ValueAnimator createColorAnimator() {
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), START_COLOR, END_COLOR);
animator.addUpdateListener(new ColorUpdateListener());
return animator;
}
private class ColorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentColor = (int) animation.getAnimatedValue();
triggerRedraw();
}
}
private ValueAnimator createRadiusAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(START_RADIUS, END_RADIUS);
animator.addUpdateListener(new RadiusUpdateListener());
return animator;
}
private class RadiusUpdateListener implements ValueAnimator.AnimatorUpdateListener {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentRadius = (float) animation.getAnimatedValue();
triggerRedraw();
}
}
private ValueAnimator createScaleAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.5f, 1.0f);
animator.addUpdateListener(new ScaleUpdateListener());
return animator;
}
private class ScaleUpdateListener implements ValueAnimator.AnimatorUpdateListener {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentScale = (float) animation.getAnimatedValue();
triggerRedraw();
}
}
private void triggerRedraw() {
postInvalidate(); // Ensure invalidate is called on the UI thread
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float width = getWidth() * currentScale;
float height = getHeight() * currentScale;
float left = (getWidth() - width) / 2;
float top = (getHeight() - height) / 2;
float right = left + width;
float bottom = top + height;
rectF.set(left, top, right, bottom);
try {
paint.setColor(currentColor);
path.reset();
path.addRoundRect(rectF, currentRadius, currentRadius, Path.Direction.CW);
canvas.drawPath(path, paint);
} catch (Exception e) {
Log.e("AnimatedButtonView", "Drawing failed: " + e.getMessage());
}
}
}