安卓中动画有帧动画、补间动画以及属性动画。
帧动画:循环重复播放多张图片;
补间动画:对作用对象的某个属性进行操作,比如位移,缩放,透明度,旋转;
属性动画:同补间动画,区别在于,属性动画能够真实改变作用对象的属性位置,补间动画,虽然改变属性,但实际上并没有改变作用对象的属性位置。
今天实战内容:页面加载动画,仿五八同城加载。
实现思路:
1、三个控件
1.1、形状变化控件 ShapeView。绘制圆形,三角形,正方形,同时不断变化这三个形状
1.2、阴影控件 View。根据形状控件的上升或者下落,不断进行阴影控件的缩放
1.3、文字显示控件TextView。显示文字提示
2、形状控件圆形,三角形以及正方形的绘制
3、形状控件,上升,下落以及上升翻转动画
4、阴影控件缩放
5、内存优化,数据加载完成后,需要移除动画以及加载布局,避免内存泄露
实现代码:
/**
指定控件的宽高,保证控件是一个正方形
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//只保证正方形
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(Math.min(width,height),Math.min(width,height));
}
绘制形状控件中三角形、圆形以及正方形
@Override
protected void onDraw(Canvas canvas) {
switch (mCurrentShape){
case SHAPE_CIRCLE:
//画圆形
int center = getWidth()/2;
mPaint.setColor(Color.YELLOW);
canvas.drawCircle(center,center,center,mPaint);
break;
case SHAPE_RECT:
//画正方形
mPaint.setColor(Color.BLUE);
canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
break;
case SHAPE_TRIANGLE:
//画三角形
mPaint.setColor(Color.RED);
if (mPath == null){
//画路径
mPath = new Path();
float triangleBx,triangleBy,triangleCx,triangleCy;
int triangleCenter = getWidth()/2;
Log.e("ShapeView","cos30="+Math.cos(Math.sqrt(3)/2)
+"\nsin30="+Math.sin(0.5));
triangleBx = (float) ((1-Math.sqrt(3)/2)*triangleCenter);
triangleBy = (float) ((1+0.5)*triangleCenter);
triangleCx = (float) ((1+Math.sqrt(3)/2)*triangleCenter);
triangleCy = (float) ((1+0.5)*triangleCenter);
mPath.moveTo(triangleCenter,0);
mPath.lineTo(triangleBx,triangleBy);
mPath.lineTo(triangleCx,triangleCy);
mPath.close();
}
canvas.drawPath(mPath,mPaint);
break;
}
}
形状控件上升动画
/**
* 开始上升动画
*/
private void startUpAnimator() {
if (mIsStopAnimator){
return;
}
Log.e("LoadingView","startUpAnimator2");
//形状控件,上升
//阴影控件,放大
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATION_TIME);
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.playTogether(getAnimator(mShapeView,"translationY",DISTANCE,0),
getAnimator(mShaDomView,"scaleX",0.4f,1f),
getAnimator(mShaDomView,"scaleY",0.4f,1f));
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//上升结束后开启下落动画
startFallAnimator();
}
@Override
public void onAnimationStart(Animator animation) {
// 上升的同时进行形状翻转动画
startRotation();
}
});
animatorSet.start();
}
上升的同时进行形状翻转动画
/**
* 上升时,动画需要进行形状翻转
*/
private void startRotation() {
ObjectAnimator animator = null;
//判断当前形状
switch (mShapeView.getCurrentShape()){
case SHAPE_CIRCLE:
case SHAPE_RECT:
//圆形,正方形,则翻转180度
animator = getAnimator(mShapeView,"rotation",0,180);
break;
case SHAPE_TRIANGLE:
//三角形则翻转120度
animator = getAnimator(mShapeView,"rotation",0,120);
break;
}
animator.setDuration(ANIMATION_TIME);
animator.start();
}
下落动画
/**
* 开始下降动画
*/
private void startFallAnimator() {
if (mIsStopAnimator){
Log.e("LoadingView","startFallAnimator1");
return;
}
Log.e("LoadingView","startFallAnimator2");
//形状控件,下降
//阴影控件,缩小
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATION_TIME);
animatorSet.setInterpolator(new AccelerateInterpolator());
animatorSet.playTogether(getAnimator(mShapeView,"translationY",0,DISTANCE),
getAnimator(mShaDomView,"scaleX",1f,0.4f),
getAnimator(mShaDomView,"scaleY",1f,0.4f));
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//下落结束后改变形状
mShapeView.exchange();
//下落结束后开启上升动画
startUpAnimator();
}
});
animatorSet.start();
}
阴影控件缩放动画
getAnimator(mShaDomView,"scaleX",1f,0.4f);
getAnimator(mShaDomView,"scaleY",1f,0.4f));
/**
* 创造一个属性动画对象
* @param target
* @param propertyName
* @param values
* @return
*/
private ObjectAnimator getAnimator(Object target, String propertyName, float... values){
return ObjectAnimator.ofFloat(target,propertyName,values);
}
优化实现
@Override
public void setVisibility(int visibility) {
//不管是 GONE 还是 INVISIBLE 都设置成 INVISIBLE ,如果是 GONE 的话,需要加载系统测量代码
super.setVisibility(INVISIBLE);
//优化处理
mShapeView.clearAnimation();
mShaDomView.clearAnimation();
//移除加载布局
ViewGroup parent = (ViewGroup) getParent();
if (parent !=null){
parent.removeView(this);
}
removeAllViews();
mIsStopAnimator = true;
}
完整代码实现:
一、LoadingView
/**
* @author yifuyun
* @date 2020/5/15
*/
public class LoadingView extends LinearLayout {
/**
* 阴影形状
*/
private final View mShaDomView;
/**
* 形状控件
*/
private final ShapeView mShapeView;
//动画时间
private final static long ANIMATION_TIME = 800;
//形状控件上升或者下降高度
private int DISTANCE = 0;
//是否停止加载动画
private boolean mIsStopAnimator;
private AnimatorSet mAnimatorSet;
//存储集合动画列表
private List<Animator> mAnimatorList;
//上升时的翻转动画
private ObjectAnimator mRotationAnimator;
public LoadingView(Context context) {
this(context, null);
}
public LoadingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.layout_custom_load_view, this);
mShaDomView = findViewById(R.id.shaDomView);
mShapeView = findViewById(R.id.shapeView);
//开始下降动画
post(() -> {
DISTANCE = mShapeView.getBottom() / 4;
startFallAnimator();
});
}
/**
* 将dp 转换成 px
*
* @param dip
* @return
*/
private int dp2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, dip, getResources().getDisplayMetrics());
}
/**
* 开始下降动画
*/
private void startFallAnimator() {
if (mIsStopAnimator) {
Log.e("LoadingView", "startFallAnimator1");
return;
}
Log.e("LoadingView", "startFallAnimator2");
//形状控件,下降
//阴影控件,缩小
if (mAnimatorSet == null) {
mAnimatorSet = new AnimatorSet();
mAnimatorSet.setDuration(ANIMATION_TIME);
mAnimatorSetListener = new AnimatorSetListener();
mAnimatorSet.addListener(mAnimatorSetListener);
mAnimatorList = new ArrayList<>();
mAnimatorList.add(getAnimator(mShapeView, "translationY", 0, DISTANCE));
mAnimatorList.add(getAnimator(mShaDomView, "scaleX", 1f, 0.4f));
mAnimatorList.add(getAnimator(mShaDomView, "scaleY", 1f, 0.4f));
} else {
setFloatValues(0, 0, DISTANCE);
setFloatValues(1, 1f, 0.4f);
setFloatValues(2, 1f, 0.4f);
}
mAnimatorSet.setInterpolator(new AccelerateInterpolator());
mAnimatorSetListener.setMoveDirection(AnimatorSetListener.DIRECTION_FALL);
mAnimatorSet.playTogether(mAnimatorList);
mAnimatorSet.start();
}
private AnimatorSetListener mAnimatorSetListener;
/**
* 集合动画监听
*/
private class AnimatorSetListener extends AnimatorListenerAdapter {
private int moveDirection;
private final static int DIRECTION_UP = 0;
private final static int DIRECTION_FALL = 1;
public void setMoveDirection(int moveDirection) {
this.moveDirection = moveDirection;
}
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
switch (moveDirection) {
case DIRECTION_UP:
startFallAnimator();
break;
case DIRECTION_FALL:
startUpAnimator();
break;
}
}
@Override
public void onAnimationStart(Animator animation, boolean isReverse) {
//如果是方向上升,需要进行翻转动画
if (moveDirection == DIRECTION_UP) {
//改变形状
mShapeView.exchange();
//启动翻转动画
startRotation();
}
}
}
/**
* 重置value 值
*
* @param index
* @param values
*/
private void setFloatValues(int index, float... values) {
if (mAnimatorList.get(index) instanceof ObjectAnimator) {
ObjectAnimator objectAnimator = (ObjectAnimator) mAnimatorList.get(index);
objectAnimator.setFloatValues(values);
}
}
/**
* 上升时,动画需要进行形状翻转
*/
private void startRotation() {
//判断当前形状
switch (mShapeView.getCurrentShape()) {
case SHAPE_CIRCLE:
case SHAPE_RECT:
//圆形,正方形,则翻转180度
startRotationAnimator(0, 180);
break;
case SHAPE_TRIANGLE:
//三角形则翻转120度
startRotationAnimator(0, 120);
break;
}
}
/**
* 设置旋转动画的value内容
*
* @param values
*/
private void startRotationAnimator(float... values) {
if (mRotationAnimator == null) {
mRotationAnimator = getAnimator(mShapeView, "rotation", values);
mRotationAnimator.setDuration(ANIMATION_TIME);
} else {
mRotationAnimator.setFloatValues(values);
}
mRotationAnimator.start();
}
/**
* 开始上升动画
*/
private void startUpAnimator() {
if (mIsStopAnimator) {
Log.e("LoadingView", "startUpAnimator1");
return;
}
Log.e("LoadingView", "startUpAnimator2");
//形状控件,上升
//阴影控件,放大
setFloatValues(0, DISTANCE, 0);
setFloatValues(1, 0.4f, 1f);
setFloatValues(2, 0.4f, 1f);
mAnimatorSetListener.setMoveDirection(AnimatorSetListener.DIRECTION_UP);
mAnimatorSet.setInterpolator(new DecelerateInterpolator());
mAnimatorSet.start();
}
/**
* 创造一个属性动画对象
*
* @param target
* @param propertyName
* @param values
* @return
*/
private ObjectAnimator getAnimator(Object target, String propertyName, float... values) {
return ObjectAnimator.ofFloat(target, propertyName, values);
}
@Override
public void setVisibility(int visibility) {
//不管是 GONE 还是 INVISIBLE 都设置成 INVISIBLE ,如果是 GONE 的话,需要加载系统测量代码
super.setVisibility(INVISIBLE);
//优化处理
mShapeView.clearAnimation();
mShaDomView.clearAnimation();
//移除加载布局
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
parent.removeView(this);
}
removeAllViews();
mIsStopAnimator = true;
}
/**
* 移除动画监听,避免内存泄露
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
//移除动画监听
if (mAnimatorSet != null) {
mAnimatorSet.removeAllListeners();
mAnimatorSet = null;
mAnimatorSetListener = null;
}
if (mRotationAnimator != null) {
mRotationAnimator = null;
}
}
}
二、layout_custom_load_view布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.yfy.lib_custom_view.view.shape_animation.ShapeView
android:id="@+id/shapeView"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginBottom="90dp"
/>
<View
android:id="@+id/shaDomView"
android:layout_width="25dp"
android:layout_height="4dp"
android:background="@drawable/loading_shape_bg"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="#757575"
android:textSize="14sp"
android:text="玩命加载中..."/>
</LinearLayout>
三、ShapeView
/**
* @author yifuyun
* @date 2020/5/15
*/
public class ShapeView extends View {
private final Paint mPaint;
private Shape mCurrentShape = Shape.SHAPE_CIRCLE;
private Path mPath;
public ShapeView(Context context) {
this(context,null);
}
public ShapeView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ShapeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//只保证正方形
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(Math.min(width,height),Math.min(width,height));
}
@Override
protected void onDraw(Canvas canvas) {
switch (mCurrentShape){
case SHAPE_CIRCLE:
//画圆形
int center = getWidth()/2;
mPaint.setColor(Color.YELLOW);
canvas.drawCircle(center,center,center,mPaint);
break;
case SHAPE_RECT:
//画正方形
mPaint.setColor(Color.BLUE);
canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
break;
case SHAPE_TRIANGLE:
//画三角形
mPaint.setColor(Color.RED);
if (mPath == null){
//画路径
mPath = new Path();
float triangleBx,triangleBy,triangleCx,triangleCy;
int triangleCenter = getWidth()/2;
Log.e("ShapeView","cos30="+Math.cos(Math.sqrt(3)/2)
+"\nsin30="+Math.sin(0.5));
triangleBx = (float) ((1-Math.sqrt(3)/2)*triangleCenter);
triangleBy = (float) ((1+0.5)*triangleCenter);
triangleCx = (float) ((1+Math.sqrt(3)/2)*triangleCenter);
triangleCy = (float) ((1+0.5)*triangleCenter);
mPath.moveTo(triangleCenter,0);
mPath.lineTo(triangleBx,triangleBy);
mPath.lineTo(triangleCx,triangleCy);
mPath.close();
}
canvas.drawPath(mPath,mPaint);
break;
}
}
/**
* 获取当前形状
* @return
*/
public Shape getCurrentShape() {
return mCurrentShape;
}
public enum Shape {
SHAPE_TRIANGLE, SHAPE_RECT, SHAPE_CIRCLE
}
/**
* 改变形状
*/
public void exchange(){
switch (mCurrentShape){
case SHAPE_CIRCLE:
mCurrentShape = Shape.SHAPE_RECT;
break;
case SHAPE_RECT:
mCurrentShape = Shape.SHAPE_TRIANGLE;
break;
case SHAPE_TRIANGLE:
mCurrentShape = Shape.SHAPE_CIRCLE;
break;
}
//刷新显示,不断从新绘制
invalidate();
}
}