Android 自定义炫酷的加载框Loading动画效果 - Go语言中文社区

Android 自定义炫酷的加载框Loading动画效果


     Android 自定义炫酷的加载框Loading动画效果

 

 

一.快速实现第一种:

1.自定义加载框类:

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.TextView;

import com.southsummer.goddessplan.R;


/**
 * Created by sgf
 * 对话框工具类
 */

public class LoadingDialogUtil {
    public static LoadingDialogUtil mInstance;
    public static final int BUTTON_OK = 0;
    public static final int BUTTON_CANCEL = 1;
    private static AlertDialog dlg;
    private static AlertDialog loginDlg;
    private static Animation operatingAnim;
    private static ImageView imageView;
    private static boolean isLoading;

    private LoadingDialogUtil() {
    }

    /**
     * 加载话框
     *
     * @param context
     * @param msg
     */
    public void showLoadingDialog(Context context, String msg) {
        if(null == context || ((Activity)context).isFinishing())return;
        if(isLoading())return;
        dlg = new AlertDialog.Builder(context, R.style.dialogStyle).create();
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading2, null);
        TextView text = view.findViewById(R.id.dialog_loading_text);
        imageView = view.findViewById(R.id.dialog_loading_img);
        text.setText(msg);
        Window window = dlg.getWindow();
        if(null != window){
            window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            window.setWindowAnimations(R.style.dialogWindowAnim);
        }
        dlg.show();
        dlg.setCancelable(false);
        dlg.setCanceledOnTouchOutside(false);
        dlg.setContentView(view);

        operatingAnim = AnimationUtils.loadAnimation(context, R.anim.loading);
        operatingAnim.setInterpolator(new LinearInterpolator());
        openAnim();
        isLoading = true;
    }

    /**
     * 加载话框
     *
     * @param context
     *
     */
    public void showLoadingDialog(Context context) {
        if(isLoading()|| null == context)return;
        dlg = new AlertDialog.Builder(context, R.style.dialogStyle).create();
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading2, null);
        TextView text = view.findViewById(R.id.dialog_loading_text);
        imageView = view.findViewById(R.id.dialog_loading_img);
        text.setVisibility(View.GONE);
        Window window = dlg.getWindow();
        if(null != window){
            window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            window.setWindowAnimations(R.style.dialogWindowAnim);
        }
        dlg.show();
        dlg.setCancelable(false);
        dlg.setCanceledOnTouchOutside(false);
        dlg.setContentView(view);

        operatingAnim = AnimationUtils.loadAnimation(context, R.anim.loading);
        operatingAnim.setInterpolator(new LinearInterpolator());
        openAnim();
        isLoading = true;
    }

    /**
     * 关闭拨打电话返回信息对话框
     */
    public void closeCallMsgDialog() {
        if (dlg != null) {
            dlg.dismiss();
        }
    }

    /**
     * 加载状态
     */
    public boolean isLoading() {
       return isLoading;
    }

    /**
     * 开始旋转
     */
    public void openAnim() {
        if (operatingAnim != null) {
            imageView.startAnimation(operatingAnim);
        }
    }

    /**
     * 停止旋转
     */
    public void closeAnim() {
        if (operatingAnim != null) {
            imageView.clearAnimation();
        }
    }

    /**
     * 关闭请求对话框
     */
    public void closeLoadingDialog() {
        if (dlg != null) {
            closeAnim();
            try {
                dlg.dismiss();
            }catch (Throwable ignored){

            }
        }
        isLoading = false;
    }

    public interface PressCallBack {
        void onPressButton(int buttonIndex);
    }

    /**
     * 单一实例
     *
     * @return
     */
    public static LoadingDialogUtil getInstance() {
        if (mInstance == null) {
            synchronized (LoadingDialogUtil.class) {
                if (mInstance == null) {
                    mInstance = new LoadingDialogUtil();
                    return mInstance;
                }
            }
        }
        return mInstance;
    }
}

2.style

 <!--加载动画属性-->
    <style name="dialogStyle" parent="Theme.AppCompat.Dialog">
        <item name="android:backgroundDimEnabled">false</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

 <style name="dialogWindowAnim" parent="android:Animation" mce_bogus="1">
        <item name="android:windowEnterAnimation">@anim/dialog_enter_anim</item>
        <item name="android:windowExitAnimation">@anim/dialog_exit_anim</item>
    </style>

 

3.布局xml:

<?xml version="1.0" encoding="utf-8"?>
<com.zhy.autolayout.AutoLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_loading_dialog"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="40px">

    <com.zhy.autolayout.AutoLinearLayout
        android:layout_width="@dimen/margin220"
        android:layout_height="@dimen/margin200"
        android:gravity="center"
        android:orientation="vertical">

    <ImageView
        android:id="@+id/dialog_loading_img"
        android:layout_width="90px"
        android:layout_height="90px"
        android:layout_gravity="center_horizontal"
        android:src="@drawable/loading"/>
    <!--loading-->

    <TextView
        android:id="@+id/dialog_loading_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20px"
        android:gravity="center"
        android:text="Loading..."
        android:textColor="@color/white"
        android:textSize="20px"/>
    </com.zhy.autolayout.AutoLinearLayout>
</com.zhy.autolayout.AutoLinearLayout>

 

 

xxhdpi加载图片如上,点击下载即可

4.anim动画xml:

dialog_enter_anim
<?xml version="1.0" encoding="utf-8"?>
<!-- 弹出时动画 -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:duration="600"/>
</set>

 

dialog_exit_anim
<?xml version="1.0" encoding="utf-8"?>
<!-- 退出时动画效果 -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="1"
        android:toAlpha="0"
        android:duration="400"/>
</set>
R.anim.loading
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1500"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="infinite"
        android:repeatMode="restart"
        android:toDegrees="359"/>

5.代码中使用:

LoadingDialogUtil.getInstance().showLoadingDialog(getActivity(), "Loading...");//开启加载动画

LoadingDialogUtil.getInstance().closeLoadingDialog();//关闭动画

 

二.快速实现第二种:

1.自定义类:

import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.github.ybq.android.spinkit.SpinKitView;
import com.github.ybq.android.spinkit.SpriteFactory;
import com.github.ybq.android.spinkit.Style;
import com.github.ybq.android.spinkit.sprite.Sprite;
import com.southsummer.goddessplan.R;

public class LoadingUtils {

    private static Dialog mLoadingDialog;
    private static SpinKitView dialogSpinKit;

    /**
     * 显示加载对话框
     *
     * @param context    上下文
     * @param msg        对话框显示内容
     */
    public static void showDialogForLoading(Context context, String msg) {
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading3, null);
        TextView loadingText = (TextView) view.findViewById(R.id.id_tv_loading_dialog_text);
        dialogSpinKit = (SpinKitView) view.findViewById(R.id.dialog_spin_kit);
        loadingText.setText(msg);

        Style style = Style.values()[8];//设置加载动画效果
        Sprite drawable = SpriteFactory.create(style);
        dialogSpinKit.setIndeterminateDrawable(drawable);

        mLoadingDialog = new Dialog(context, R.style.dialogStyle);
        mLoadingDialog.setCancelable(false);
        mLoadingDialog.setCanceledOnTouchOutside(false);
        mLoadingDialog.setContentView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.MATCH_PARENT));

//        mLoadingDialog.show();

    }
    public static void showLoading(){
        if(mLoadingDialog!=null){
            mLoadingDialog.show();
        }

    }
    public static void closeLoading(){
        if(mLoadingDialog.isShowing()){
            mLoadingDialog.dismiss();
            mLoadingDialog.cancel();
        }
    }
}

2.布局:

<?xml version="1.0" encoding="utf-8"?>
<com.zhy.autolayout.AutoLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@drawable/bg_loading_dialog"
    android:gravity="center"
    android:minHeight="120px"
    android:minWidth="360px"
    android:orientation="vertical"
    android:padding="20px">

    <com.github.ybq.android.spinkit.SpinKitView
        android:id="@+id/dialog_spin_kit"
        style="@style/SpinKitView"
        app:SpinKit_Color="@color/_307AFF"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:padding="20px" />

    <TextView
        android:id="@+id/id_tv_loading_dialog_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10px"
        android:text="Loading…"
        android:textColor="@color/bg_white"
        android:textSize="22px" />

</com.zhy.autolayout.AutoLinearLayout>

 

3.代码中使用:

 LoadingUtils.showDialogForLoading(getActivity(),"Loading...");//启动加载动画
 LoadingUtils.closeLoading();//关闭动画

4.最后的依赖:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.sxxx"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName getDemoVersionName()
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        multiDexEnabled true
        manifestPlaceholders = [
                GETUI_APP_ID    : "xxx",
                GETUI_APP_KEY   : "xxx",
                GETUI_APP_SECRET: "xxx"
        ]
        ndk {
            //设置支持的SO库架构
            abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a', 'armeabi'
        }

        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath = true
            }
        }
    }

    testBuildType TEST_BUILD_TYPE

    signingConfigs {
        release {
//            storeFile file("../../config/xxxx_key.jks")
//            storePassword "your_keystore_password"
//            keyAlias "your_key_alias"
//            keyPassword "your_key_password"
        }
    }

    buildTypes {
        debug {
            buildConfigField "boolean", "LOG_DEBUG", "true"
            versionNameSuffix "-debug"
            jniDebuggable true
        }
        release {
            buildConfigField "boolean", "LOG_DEBUG", "false"
            versionNameSuffix "-release"
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            debuggable true
            jniDebuggable true
            renderscriptDebuggable true
            pseudoLocalesEnabled true
            minifyEnabled false

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        outputFileName = "xxx" + versionName + ".apk"
                    }
                }
            }
        }
    }

    repositories {
        flatDir {
            dirs '3rdlibs'   // aar目录
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs', 'gtlibs', 'bizlibs', 'zegovideofilterlibs']
        }
    }
}


def getDemoVersionName() {
    String versionName = "1.0"
    File version_file = file("../demo_version.txt")
    if (version_file.exists()) {
        versionName = version_file.readLines()[0]
    }
    return versionName
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation fileTree(include: ['*.jar'], dir: 'gtlibs')
    implementation fileTree(include: ['*.jar'], dir: 'bizlibs')
    implementation fileTree(include: ['*.jar'], dir: '3rdlibs')
    implementation fileTree(include: ['*.jar'], dir: 'zegovideofilterlibs')
    implementation 'com.android.support:appcompat-v7:' + rootProject.supportLibVersion
    implementation 'com.android.support:design:' + rootProject.supportLibVersion
    implementation 'com.android.support:cardview-v7:' + rootProject.supportLibVersion
    implementation 'com.jakewharton:butterknife:7.0.1'
    implementation 'com.google.android.gms:play-services-appindexing:8.4.0'
    implementation 'com.tencent.bugly:crashreport:2.2.2'
    implementation 'com.tencent.bugly:nativecrashreport:3.0'
    implementation 'eu.the4thfloor.volley:com.android.volley:2015.05.28'
        implementation 'com.google.guava:guava:18.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    // Testing-only dependencies
    // Force usage of support annotations in the test app, since it is internally used by the runner module.
    androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:' + rootProject.espressoVersion
    androidTestImplementation('com.android.support.test.espresso:espresso-contrib:' + rootProject.espressoVersion) {
        exclude group: 'com.android.support', module: 'support-v4'
        exclude group: 'com.android.support', module: 'appcompat-v7'
        exclude group: 'com.android.support', module: 'design'
        exclude group: 'com.android.support', module: 'recyclerview-v7'
    }
    implementation(name: 'zego-qrcode', ext: 'aar')
    implementation 'com.alibaba:fastjson:1.2.56'
    androidTestImplementation 'com.android.support.test:runner:' + rootProject.runnerVersion
    androidTestImplementation 'com.android.support.test:rules:' + rootProject.rulesVersion
    // Force usage of support annotations in the test app, since it is internally used by the
    // runner module.
    androidTestImplementation 'com.android.support:support-annotations:' + rootProject.supportLibVersion
    // Note that espresso-idling-resource is used in the code under test.
    implementation 'com.android.support.test.espresso:espresso-idling-resource:' + rootProject.espressoVersion
    implementation files('libs/zegoliveroom.jar')
//    implementation 'com.github.bumptech.glide:glide:3.7.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.zhy:autolayout:1.4.5'
    implementation 'com.jcodecraeer:xrecyclerview:1.5.9'
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'
    implementation 'com.squareup.okio:okio:1.11.0'
    implementation 'org.greenrobot:eventbus:3.0.0'
    implementation 'de.hdodenhof:circleimageview:3.0.0'
    implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-21'
    implementation 'com.github.addappcn:android-pickers:1.0.3'
    implementation 'com.airbnb.android:lottie:2.1.0'
    implementation 'com.getui:sdk:2.13.1.0'
    //适配器
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
    //弹幕
//    implementation 'com.github.xujiaji:dmlib2:0.0.5'
//    implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25'
    //选择图片、视频、音频第三方图片选择器,glide 4.5.0将代替glide:3.7.0,需修改写法
    implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.2.3'

    //图片查看库,实现图片浏览功能,支持pinch(捏合)手势或者点击放大缩小
//    implementation 'com.github.chrisbanes.photoview:library:1.2.4'
    //Loading加载动画
    implementation 'com.github.ybq:Android-SpinKit:1.2.0'
    //轮播图
    implementation 'com.youth.banner:banner:1.4.10'

}
 //Loading加载动画
    implementation 'com.github.ybq:Android-SpinKit:1.2.0'
 implementation 'com.zhy:autolayout:1.4.5'

三.快速实现抖音加载框两颗小球转动控件:

1.自定义Loading:

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.LinearInterpolator;

import com.example.m1571.mympandroidchart.R;


/**
 * 
 * 仿抖音v2.5  加载框
 */
public class DYLoadingView extends View {

    //默认值
    private final float RADIUS = dp2px(6);
    private final float GAP = dp2px(0.8f);
    private static final float RTL_SCALE = 0.7f;
    private static final float LTR_SCALR = 1.3f;
    private static final int LEFT_COLOR = 0XFFFF4040;
    private static final int RIGHT_COLOR = 0XFF00EEEE;
    private static final int MIX_COLOR = Color.BLACK;
    private static final int DURATION = 350;
    private static final int PAUSE_DUARTION = 80;
    private static final float SCALE_START_FRACTION = 0.2f;
    private static final float SCALE_END_FRACTION = 0.8f;


    //属性
    private float radius1; //初始时左小球半径
    private float radius2; //初始时右小球半径
    private float gap; //两小球直接的间隔
    private float rtlScale; //小球从右边移动到左边时大小倍数变化(rtl = right to left)
    private float ltrScale;//小球从左边移动到右边时大小倍数变化
    private int color1;//初始左小球颜色
    private int color2;//初始右小球颜色
    private int mixColor;//两小球重叠处的颜色
    private int duration; //小球一次移动时长
    private int pauseDuration;//小球一次移动后停顿时长
    private float scaleStartFraction; //小球一次移动期间,进度在[0,scaleStartFraction]期间根据rtlScale、ltrScale逐渐缩放,取值为[0,0.5]
    private float scaleEndFraction;//小球一次移动期间,进度在[scaleEndFraction,1]期间逐渐恢复初始大小,取值为[0.5,1]

    //绘图
    private Paint paint1, paint2, mixPaint;
    private Path ltrPath, rtlPath, mixPath;
    private float distance; //小球一次移动距离(即两球圆点之间距离)

    //动画
    private ValueAnimator anim;
    private float fraction; //小球一次移动动画的进度百分比
    boolean isAnimCanceled = false;
    boolean isLtr = true;//true = 【初始左球】当前正【从左往右】移动,false = 【初始左球】当前正【从右往左】移动


    public DYLoadingView(Context context) {
        this(context, null);
    }

    public DYLoadingView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DYLoadingView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DYLoadingView);
        radius1 = ta.getDimension(R.styleable.DYLoadingView_radius1, RADIUS);
        radius2 = ta.getDimension(R.styleable.DYLoadingView_radius2, RADIUS);
        gap = ta.getDimension(R.styleable.DYLoadingView_gap, GAP);
        rtlScale = ta.getFloat(R.styleable.DYLoadingView_rtlScale, RTL_SCALE);
        ltrScale = ta.getFloat(R.styleable.DYLoadingView_ltrScale, LTR_SCALR);
        color1 = ta.getColor(R.styleable.DYLoadingView_color1, LEFT_COLOR);
        color2 = ta.getColor(R.styleable.DYLoadingView_color2, RIGHT_COLOR);
        mixColor = ta.getColor(R.styleable.DYLoadingView_mixColor, MIX_COLOR);
        duration = ta.getInt(R.styleable.DYLoadingView_duration, DURATION);
        pauseDuration = ta.getInt(R.styleable.DYLoadingView_pauseDuration, PAUSE_DUARTION);
        scaleStartFraction = ta.getFloat(R.styleable.DYLoadingView_scaleStartFraction, SCALE_START_FRACTION);
        scaleEndFraction = ta.getFloat(R.styleable.DYLoadingView_scaleEndFraction, SCALE_END_FRACTION);
        ta.recycle();

        checkAttr();
        distance = gap + radius1 + radius2;

        initDraw();

        initAnim();

    }


    /**
     * 属性合法性检查校正
     */
    private void checkAttr() {
        radius1 = radius1 > 0 ? radius1 : RADIUS;
        radius2 = radius2 > 0 ? radius2 : RADIUS;
        gap = gap >= 0 ? gap : GAP;
        rtlScale = rtlScale >= 0 ? rtlScale : RTL_SCALE;
        ltrScale = ltrScale >= 0 ? ltrScale : LTR_SCALR;
        duration = duration > 0 ? duration : DURATION;
        pauseDuration = pauseDuration >= 0 ? pauseDuration : PAUSE_DUARTION;
        if (scaleStartFraction < 0 || scaleStartFraction > 0.5f) {
            scaleStartFraction = SCALE_START_FRACTION;
        }
        if (scaleEndFraction < 0.5 || scaleEndFraction > 1) {
            scaleEndFraction = SCALE_END_FRACTION;
        }
    }

    /**
     * 初始化绘图数据
     */
    private void initDraw() {
        paint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
        mixPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        paint1.setColor(color1);
        paint2.setColor(color2);
        mixPaint.setColor(mixColor);

        ltrPath = new Path();
        rtlPath = new Path();
        mixPath = new Path();
    }

    private void initAnim() {
        fraction = 0.0f;

        stop();

        anim = ValueAnimator.ofFloat(0.0f, 1.0f);
        anim.setDuration(duration);
        if (pauseDuration > 0) {
            anim.setStartDelay(pauseDuration);
            anim.setInterpolator(new AccelerateDecelerateInterpolator());
        } else {
            anim.setRepeatCount(ValueAnimator.INFINITE);
            anim.setRepeatMode(ValueAnimator.RESTART);
            anim.setInterpolator(new LinearInterpolator());
        }
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fraction = animation.getAnimatedFraction();
                invalidate();
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(Animator animation) {
                isLtr = !isLtr;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                isLtr = !isLtr;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                isAnimCanceled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!isAnimCanceled) {
                    anim.start();
                }
            }
        });

    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);

        //WRAP_CONTENT时控件大小为最大可能的大小,保证显示的下
        float maxScale = Math.max(rtlScale, ltrScale);
        maxScale = Math.max(maxScale, 1);

        if (wMode != MeasureSpec.EXACTLY) {
            wSize = (int) (gap + (2 * radius1 + 2 * radius2) * maxScale + dp2px(1));  //宽度= 间隙 + 2球直径*最大比例 + 1dp
        }
        if (hMode != MeasureSpec.EXACTLY) {

            hSize = (int) (2 * Math.max(radius1, radius2) * maxScale + dp2px(1)); // 高度= 1球直径*最大比例 + 1dp
        }
        setMeasuredDimension(wSize, hSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float centerY = getMeasuredHeight() / 2.0f;

        float ltrInitRadius, rtlInitRadius;
        Paint ltrPaint, rtlPaint;

        //确定当前【从左往右】移动的是哪颗小球
        if (isLtr) {
            ltrInitRadius = radius1;
            rtlInitRadius = radius2;
            ltrPaint = paint1;
            rtlPaint = paint2;
        } else {
            ltrInitRadius = radius2;
            rtlInitRadius = radius1;
            ltrPaint = paint2;
            rtlPaint = paint1;
        }


        float ltrX = getMeasuredWidth() / 2.0f - distance / 2.0f;
        ltrX = ltrX + (distance * fraction);//当前从左往右的球的X坐标

        float rtlX = getMeasuredWidth() / 2.0f + distance / 2.0f;
        rtlX = rtlX - (distance * fraction);//当前从右往左的球的X坐标

        //计算小球移动过程中的大小变化
        float ltrBallRadius, rtlBallRadius;
        if (fraction <= scaleStartFraction) { //动画进度[0,scaleStartFraction]时,球大小由1倍逐渐缩放至ltrScale/rtlScale倍
            float scaleFraction = 1.0f / scaleStartFraction * fraction; //百分比转换 [0,scaleStartFraction]] -> [0,1]
            ltrBallRadius = ltrInitRadius * (1 + (ltrScale - 1) * scaleFraction);
            rtlBallRadius = rtlInitRadius * (1 + (rtlScale - 1) * scaleFraction);
        } else if (fraction >= scaleEndFraction) { //动画进度[scaleEndFraction,1],球大小由ltrScale/rtlScale倍逐渐恢复至1倍
            float scaleFraction = (fraction - 1) / (scaleEndFraction - 1); //百分比转换,[scaleEndFraction,1] -> [1,0]
            ltrBallRadius = ltrInitRadius * (1 + (ltrScale - 1) * scaleFraction);
            rtlBallRadius = rtlInitRadius * (1 + (rtlScale - 1) * scaleFraction);
        } else { //动画进度[scaleStartFraction,scaleEndFraction],球保持缩放后的大小
            ltrBallRadius = ltrInitRadius * ltrScale;
            rtlBallRadius = rtlInitRadius * rtlScale;
        }

        ltrPath.reset();
        ltrPath.addCircle(ltrX, centerY, ltrBallRadius, Path.Direction.CW);
        rtlPath.reset();
        rtlPath.addCircle(rtlX, centerY, rtlBallRadius, Path.Direction.CW);
        mixPath.op(ltrPath, rtlPath, Path.Op.INTERSECT);

        canvas.drawPath(ltrPath, ltrPaint);
        canvas.drawPath(rtlPath, rtlPaint);
        canvas.drawPath(mixPath, mixPaint);
    }


    @Override
    protected void onDetachedFromWindow() {
        stop();
        super.onDetachedFromWindow();
    }


    private float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }


    //公开方法

    /**
     * 停止动画
     */
    public void stop() {
        if (anim != null) {
            anim.cancel();
            anim = null;
        }
    }

    /**
     * 开始动画
     */
    public void start() {
        if (anim == null) {
            initAnim();
        }
        if (anim.isRunning()) {
            anim.cancel();
        }

        post(new Runnable() {
            @Override
            public void run() {
                isAnimCanceled = false;
                isLtr = false;
                anim.start();
            }
        });
    }

    /**
     * 设置小球半径和两小球间隔
     */
    public void setRadius(float radius1, float radius2, float gap) {
        stop();
        this.radius1 = radius1;
        this.radius2 = radius2;
        this.gap = gap;
        checkAttr();
        distance = gap + radius1 + radius2;
        requestLayout(); //可能涉及宽高变化
    }


    /**
     * 设置小球颜色和重叠处颜色
     */
    public void setColors(int color1, int color2, int mixColor) {
        this.color1 = color1;
        this.color2 = color2;
        this.mixColor = color2;
        checkAttr();
        paint1.setColor(color1);
        paint2.setColor(color2);
        mixPaint.setColor(mixColor);
        invalidate();
    }

    /**
     * 设置动画时长
     *
     * @param duration      {@link #duration}
     * @param pauseDuration {@link #pauseDuration}
     */
    public void setDuration(int duration, int pauseDuration) {
        this.duration = duration;
        this.pauseDuration = pauseDuration;
        checkAttr();
        initAnim();
    }

    /**
     * 设置移动过程中缩放倍数
     *
     * @param ltrScale {@link #ltrScale}
     * @param rtlScale {@link #rtlScale}
     */
    public void setScales(float ltrScale, float rtlScale) {
        stop();
        this.ltrScale = ltrScale;
        this.rtlScale = rtlScale;
        checkAttr();
        requestLayout(); //可能涉及宽高变化
    }

    /**
     * 设置缩放开始、结束的范围
     *
     * @param scaleStartFraction {@link #scaleStartFraction}
     * @param scaleEndFraction   {@link #scaleEndFraction}
     */
    public void setStartEndFraction(float scaleStartFraction, float scaleEndFraction) {
        this.scaleStartFraction = scaleStartFraction;
        this.scaleEndFraction = scaleEndFraction;
        checkAttr();
        invalidate();
    }


    public float getRadius1() {
        return radius1;
    }

    public float getRadius2() {
        return radius2;
    }

    public float getGap() {
        return gap;
    }

    public float getRtlScale() {
        return rtlScale;
    }

    public float getLtrScale() {
        return ltrScale;
    }

    public int getColor1() {
        return color1;
    }

    public int getColor2() {
        return color2;
    }

    public int getMixColor() {
        return mixColor;
    }

    public int getDuration() {
        return duration;
    }

    public int getPauseDuration() {
        return pauseDuration;
    }

    public float getScaleStartFraction() {
        return scaleStartFraction;
    }

    public float getScaleEndFraction() {
        return scaleEndFraction;
    }

}

2.attrs.xml:


    <declare-styleable name="DYLoadingView">
        <attr name="radius1" format="dimension"/>
        <attr name="radius2" format="dimension"/>
        <attr name="rtlScale" format="float"/>
        <attr name="ltrScale" format="float"/>
        <attr name="color1" format="color"/>
        <attr name="color2" format="color"/>
        <attr name="mixColor" format="color"/>
        <attr name="duration" format="integer"/>
        <attr name="pauseDuration" format="integer"/>
        <attr name="gap" format="dimension"/>
        <attr name="scaleStartFraction" format="float"/>
        <attr name="scaleEndFraction" format="float"/>
    </declare-styleable>

 3.布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="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=".DouyinLoadActivity">

    <com.xxt.utils.DYLoadingView
        android:id="@+id/dy3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000000"
        app:color1="#FF00EEEE"
        app:duration="350"
        app:pauseDuration="10"
        app:color2="#FFFF4040"/>

    <Button
        android:id="@+id/bt_start"
        android:text="开始"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/bt_stop"
        android:text="停止"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

4. 主函数调用:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.m1571.mympandroidchart.utils.DYLoadingView;

//https://github.com/CCY0122/douyinloadingview
public class DouyinLoadActivity extends AppCompatActivity {

    private DYLoadingView dy3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_douyin_load);
        dy3 = findViewById(R.id.dy3);
        Button bt_start = findViewById(R.id.bt_start);
        Button bt_stop = findViewById(R.id.bt_stop);
        bt_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                dy3.setColors(R.color.colorAccent,R.color.colorPrimary,R.color.white); //设置属性(可选)
                dy3.start(); //开始动画
            }
        });
        bt_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dy3.stop();
            }
        });

    }
}

四.参考案例:

https://github.com/CCY0122/douyinloadingview

https://github.com/81813780/AVLoadingIndicatorView

https://github.com/ybq/Android-SpinKit

https://github.com/dinuscxj/LoadingDrawable

https://github.com/jlmd/AnimatedCircleLoadingView

 

 

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/shenggaofei/article/details/106483673
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-12-11 12:45:17
  • 阅读 ( 1637 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢