自定义控件动画
- 适用场景:当父View和子View属于同一部分,不需要处理子View任何事件
- 优点:控件可复用
- 缺点:动画种类有限,较难实现复杂动画
在res目录新建attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScaleLinearLayout">
<attr name="pressScaleX" format="float" />
<attr name="pressScaleY" format="float" />
<attr name="pressAlpha" format="float" />
<attr name="pressDuration" format="integer" />
<attr name="releaseDuration" format="integer" />
</declare-styleable>
</resources>
如下代码,控件点击后通过缩放动画实现点击效果
public class ScaleLinearLayout extends LinearLayout implements View.OnTouchListener {
private static final String TAG = "ScaleLinearLayout";
private static final float DEFAULT_ORIGINAL_SCALE_X = 1f;
private static final float DEFAULT_ORIGINAL_SCALE_Y = 1f;
private static final float DEFAULT_PRESSED_SCALE_X = 0.95f;
private static final float DEFAULT_PRESSED_SCALE_Y = 0.95f;
private static final float DEFAULT_ORIGINAL_ALPHA = 1f;
private static final float DEFAULT_PRESSED_ALPHA = 0.4f;
private static final int DEFAULT_PRESS_DURATION = 120;
private static final int DEFAULT_RELEASE_DURATION = 120;
private float mPressScaleX;
private float mPressScaleY;
private float mPressAlpha;
private int mPressDuration;
private int mReleaseDuration;
private AnimatorSet currentAnimatorSet;
public ScaleLinearLayout(Context context) {
this(context, null);
}
public ScaleLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScaleLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttr(context, attrs);
initListener();
}
private void initAttr(Context context, AttributeSet attrs) {
try (TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ScaleLinearLayout)) {
mPressScaleX = ta.getFloat(R.styleable.ScaleLinearLayout_pressScaleX, DEFAULT_PRESSED_SCALE_X);
mPressScaleY = ta.getFloat(R.styleable.ScaleLinearLayout_pressScaleY, DEFAULT_PRESSED_SCALE_Y);
mPressAlpha = ta.getFloat(R.styleable.ScaleLinearLayout_pressAlpha, DEFAULT_PRESSED_ALPHA);
mPressDuration = ta.getInteger(R.styleable.ScaleLinearLayout_pressDuration, DEFAULT_PRESS_DURATION);
mReleaseDuration = ta.getInteger(R.styleable.ScaleLinearLayout_releaseDuration, DEFAULT_RELEASE_DURATION);
Log.d(TAG, "initAttr: mPressScaleX = " + mPressScaleX);
Log.d(TAG, "initAttr: mPressScaleY = " + mPressScaleY);
Log.d(TAG, "initAttr: mPressAlpha = " + mPressAlpha);
Log.d(TAG, "initAttr: mPressDuration = " + mPressDuration);
Log.d(TAG, "initAttr: mReleaseDuration = " + mReleaseDuration);
ta.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
private void initListener() {
setOnTouchListener(this);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true; // 父view拦截所有触摸/点击事件
}
private void press() {
Log.d(TAG, "press: ");
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(this, "scaleX", DEFAULT_ORIGINAL_SCALE_X, mPressScaleX);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", DEFAULT_ORIGINAL_SCALE_Y, mPressScaleY);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", DEFAULT_ORIGINAL_ALPHA, mPressAlpha);
scaleXAnimator.setDuration(mPressDuration);
scaleYAnimator.setDuration(mPressDuration);
alphaAnimator.setDuration(mPressDuration);
currentAnimatorSet = new AnimatorSet();
currentAnimatorSet.playTogether(scaleXAnimator, scaleYAnimator, alphaAnimator);
currentAnimatorSet.start();
}
private void release() {
Log.d(TAG, "release: ");
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(this, "scaleX", mPressScaleX, DEFAULT_ORIGINAL_SCALE_X);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", mPressScaleY, DEFAULT_ORIGINAL_SCALE_Y);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", mPressAlpha, DEFAULT_ORIGINAL_ALPHA);
scaleXAnimator.setDuration(mReleaseDuration);
scaleYAnimator.setDuration(mReleaseDuration);
alphaAnimator.setDuration(mReleaseDuration);
currentAnimatorSet = new AnimatorSet();
currentAnimatorSet.playTogether(scaleXAnimator, scaleYAnimator, alphaAnimator);
currentAnimatorSet.start();
}
private void stopCurrentAnimator() {
if (currentAnimatorSet != null && currentAnimatorSet.isRunning()) {
Log.d(TAG, "stopCurrentAnimator: ");
currentAnimatorSet.cancel();
currentAnimatorSet = null;
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
Log.d(TAG, "ScaleLinearLayout: action = " + action);
switch (action) {
case ACTION_DOWN:
stopCurrentAnimator();
press();
break;
case ACTION_UP:
case ACTION_CANCEL:
stopCurrentAnimator();
release();
break;
}
return false;
}
}
使用时可指定动画参数
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.demo0.ScaleLinearLayout
android:id="@+id/btn_container"
android:layout_width="150dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:background="#1A181818"
android:gravity="center"
custom:pressAlpha="0.5"
custom:pressDuration="200"
custom:releaseDuration="200">
<ImageView
android:id="@+id/iv"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="10dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30px"
android:text="按钮"
android:textSize="20sp" />
</com.example.demo0.ScaleLinearLayout>
</RelativeLayout>
一次性动画
- 适用场景:动画只运行一次
- 优点:可以使用复杂动画,并同时对多个View使用
- 缺点:无法控制动画暂停/开始
startAnimation()可以统一执行动画
public class AnimUtil {
public static void startAnimation(List<Animator> objectAnimatorList,
AnimatorListenerAdapter listenerAdapter) {
AnimatorSet animatorSet = new AnimatorSet();
List<Animator> animators = new ArrayList<>(objectAnimatorList);
animatorSet.playTogether(animators);
if (listenerAdapter != null) {
animatorSet.addListener(listenerAdapter);
}
animatorSet.start();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iv1"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:id="@+id/iv2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="10dp"
android:src="@drawable/ic_launcher_background" />
</LinearLayout>
对于上面两个ImageView,点击时消失,然后显示第一个
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView mIv1;
private ImageView mIv2;
private List<ImageView> mHideList;
private List<ImageView> mShowList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIv1 = findViewById(R.id.iv1);
mIv2 = findViewById(R.id.iv2);
mHideList = List.of(mIv1, mIv2);
mShowList = List.of(mIv1);
mIv1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideView();
}
});
}
private void hideView() {
AnimUtil.startAnimation(createHideAnimation(mHideList), new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mHideList.forEach(v -> v.setVisibility(View.GONE));
showView();
}
});
}
private void showView() {
AnimUtil.startAnimation(createShowAnimation(mShowList), new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mShowList.forEach(v -> v.setVisibility(View.VISIBLE));
}
});
}
private List<Animator> createHideAnimation(List<? extends View> views) {
List<Animator> animators = new ArrayList<>();
for (View view : views) {
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
alphaAnim.setDuration(500);
animators.add(alphaAnim);
}
return animators;
}
private List<Animator> createShowAnimation(List<? extends View> views) {
List<Animator> animators = new ArrayList<>();
for (View view : views) {
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
alphaAnim.setDuration(500);
animators.add(alphaAnim);
}
return animators;
}
}
可控制动画
- 适用场景:循环动画,需要控制动画
- 优点:可以使用复杂动画,并同时对多个View使用
- 缺点:若控制不当可能导致内存泄露
如下对多个View循环改变透明度
public class AlphaCycleAnimator {
private static final String TAG = "AlphaCycleAnimator";
private static final float FULL_ALPHA = 1.0f;
private static final float PARTIAL_ALPHA = 0.6f;
private static final long DURATION = 600;
private AnimatorSet animatorSet;
private List<View> targetViews;
public AlphaCycleAnimator(List<View> views) {
this.targetViews = views;
this.animatorSet = new AnimatorSet();
setupAnimation();
}
private void setupAnimation() {
AnimatorSet forwardSet = createAlphaAnimator(FULL_ALPHA, PARTIAL_ALPHA);
AnimatorSet reverseSet = createAlphaAnimator(PARTIAL_ALPHA, FULL_ALPHA);
// 组合动画形成循环
animatorSet.playSequentially(forwardSet, reverseSet);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(android.animation.Animator animation) {
Log.d(TAG, "onAnimationEnd: ");
animatorSet.start(); // 动画结束后重新开始
}
});
}
private AnimatorSet createAlphaAnimator(float fromAlpha, float toAlpha) {
AnimatorSet set = new AnimatorSet();
for (View view : targetViews) {
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", fromAlpha, toAlpha);
animator.setDuration(DURATION);
set.playTogether(animator);
}
return set;
}
public void start() {
Log.d(TAG, "start: ");
if (animatorSet != null && !animatorSet.isRunning()) {
animatorSet.start();
}
}
public void stop() {
Log.d(TAG, "stop: ");
if (animatorSet != null && animatorSet.isRunning()) {
animatorSet.removeAllListeners(); // 需要先停止监听再取消,否则无法停止动画
animatorSet.cancel();
for (View view : targetViews) { // 停止动画后需要恢复透明度
view.setAlpha(FULL_ALPHA);
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iv1"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@mipmap/ic_launcher" />
<ImageView
android:id="@+id/iv2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="10dp"
android:src="@drawable/ic_launcher_background" />
</LinearLayout>
对于如上2个ImageView,点击第一个开始动画,第二个结束动画
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView mIv1;
private ImageView mIv2;
AlphaCycleAnimator mInterruptCycleAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIv1 = findViewById(R.id.iv1);
mIv2 = findViewById(R.id.iv2);
mIv1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mInterruptCycleAnimator == null) {
mInterruptCycleAnimator = new AlphaCycleAnimator(Arrays.asList(mIv1, mIv2));
}
mInterruptCycleAnimator.start();
}
});
mIv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mInterruptCycleAnimator != null) {
mInterruptCycleAnimator.stop();
mInterruptCycleAnimator = null;
}
}
});
}
}
1139






