1.demo简介
本demo实现的是加载中间过渡动画,主要涉及自定义控件以及属性动画的使用,本代码没有引用任何资源,一切效果都是通过自定义实现的,所以可以直接搬到自己的本地使用。
2.逻辑分析
1.自定义三个图形,并绘制,设置更新数据接口
2.将上一步自定义的图形引入到自定义布局中,并且根据逻辑设置三个动画:图形变化,图形旋转,底部阴影变化
3.将自定义好的布局添加进自己需要的布局中使用
3.代码实现
MainActivity.class(没做任何处理)
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
ShapSwitch.class(自定义图形类)
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class ShapSwitch extends View {
private Path path;
private Paint mpaint;
private Shap mCurrtenShap = Shap.CIRCLE;
enum Shap {
CIRCLE, SQUARE, TRIGON;
}
public ShapSwitch(Context context) {
this(context, null);
}
public ShapSwitch(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ShapSwitch(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ShapSwitch(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@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) {
super.onDraw(canvas);
switchShap(canvas);
}
private void switchShap(Canvas canvas) {
switch (mCurrtenShap) {
case CIRCLE:
mpaint = new Paint();
mpaint.setAntiAlias(true);
mpaint.setColor(Color.YELLOW);
canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, mpaint);
mCurrtenShap = Shap.SQUARE;
break;
case SQUARE:
mpaint = new Paint();
mpaint.setAntiAlias(true);
mpaint.setColor(Color.RED);
Rect rect = new Rect(0, 0, getWidth(), getHeight());
canvas.drawRect(rect, mpaint);
mCurrtenShap = Shap.TRIGON;
break;
case TRIGON:
mpaint.setAntiAlias(true);
mpaint.setColor(Color.BLUE);
mCurrtenShap = Shap.CIRCLE;
if (path == null) {
path = new Path();
}
path.moveTo(getWidth() / 2, 0);
path.lineTo(0, (float) (getWidth() / 2 * Math.sqrt(3)));
path.lineTo(getWidth(), (float) (getWidth() / 2 * Math.sqrt(3)));
path.close();
canvas.drawPath(path, mpaint);
break;
}
}
public Shap getmCurrtenShap() {
return mCurrtenShap;
}
public void exchange() {
switch (mCurrtenShap) {
case CIRCLE:
mCurrtenShap = Shap.SQUARE;
break;
case SQUARE:
mCurrtenShap = Shap.TRIGON;
break;
case TRIGON:
mCurrtenShap = Shap.CIRCLE;
break;
}
invalidate();
}
}
LodingShapLayout.class(自定义布局类,实现属性动画)
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
/**
* 1.将布局加载进来
* 2.绘制三个动画
* 3.将下落和阴影缩放进行绑定
* 4.设置监听,当落地时进行形状变化以及开始旋转
* 5.设置差值器
*/
public class LodingShapLayout extends LinearLayout {
private ShapSwitch shapLoding;
private View shadowLoding;
private boolean isBottom = true;
private final long DURING_TIME = 500;
ObjectAnimator shapAnimator;
ObjectAnimator shadowAnimator;
ObjectAnimator rotationshapAnimator;
AnimatorSet animatorSet;
public LodingShapLayout(Context context) {
this(context, null);
}
public LodingShapLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LodingShapLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public LodingShapLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initShap();
}
/**
* @return void
* @description 初始化数据
* @time 2021/8/20 10:37
*/
private void initShap() {
inflate(getContext(), R.layout.loding_shap, this);
shadowLoding = findViewById(R.id.shadowLoding);
shapLoding = findViewById(R.id.shapLoding);
animatorSet = new AnimatorSet();
startAnimation();
}
private void startAnimation() {
fallAnimation();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (isBottom){
shapLoding.exchange();
rotateAnimation();
isBottom = false;
upAnimation();
}else {
isBottom = true;
fallAnimation();
}
}
});
}
/**
* @description 下落动画
* @time 2021/8/20 11:20
*/
private void fallAnimation(){
shapAnimator = ObjectAnimator.ofFloat(shapLoding, "translationY", 0,dipToPx(40));
shadowAnimator = ObjectAnimator.ofFloat(shadowLoding, "scaleX", 0.5f, 1f);
animatorSet.playTogether(shadowAnimator, shapAnimator);
animatorSet.setDuration(DURING_TIME);
animatorSet.setInterpolator(new AccelerateInterpolator());
animatorSet.start();
}
/**
* @description 上升动画
* @time 2021/8/20 11:20
*/
private void upAnimation(){
shapAnimator = ObjectAnimator.ofFloat(shapLoding, "translationY", dipToPx(40),0);
shadowAnimator = ObjectAnimator.ofFloat(shadowLoding, "scaleX", 1f, 0.5f);
animatorSet.playTogether(shadowAnimator, shapAnimator);
animatorSet.setDuration(DURING_TIME);
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.start();
}
/**
* @description 图形旋转
* @time 2021/8/20 12:17
*/
private void rotateAnimation(){
switch (shapLoding.getmCurrtenShap()){
case CIRCLE:
break;
case SQUARE:
rotationshapAnimator = ObjectAnimator.ofFloat(shapLoding,"rotation",0,180);
rotationshapAnimator.setDuration(DURING_TIME*2);
rotationshapAnimator.start();
break;
case TRIGON:
rotationshapAnimator = ObjectAnimator.ofFloat(shapLoding,"rotation",0,240);
rotationshapAnimator.setDuration(DURING_TIME*2);
rotationshapAnimator.start();
break;
}
}
private int dipToPx(int dip){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
}
}
loding_shap.xml(动画加载xml文件)
<?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="vertical"
android:gravity="center"
tools:ignore="MissingDefaultResource">
<com.incall.apps.shapwhitch.ShapSwitch
android:id="@+id/shapLoding"
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
/>
<View
android:id="@+id/shadowLoding"
android:layout_width="25dp"
android:layout_height="1dp"
android:background="@drawable/shadow"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="玩命加载中..."
android:textSize="15dp"/>
</LinearLayout>
MainActivity.xml(引入动画布局文件)
<?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:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<com.incall.apps.shapwhitch.LodingShapLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>