自定义加载动画(仿58同城)

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>

效果图

loding.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容