社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
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
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!