一、 效果图
二、需求
- 请求网络数据时为了对用户进行友好提示,显示一个Loading框
- 多个网络请求可以共用加载框,直到所有请求结束,Loading框消失
- 不影响下个界面的请求Loading显示与消失
三、需求分析(推荐自定义View)
- 可以通过Dialog或者addView添加到DecorView的方式显示加载框
- 可以通过AnimationDrawable或者自定View的重绘实现加载的动态效果
- 推荐使用自定View+addView的方法实现,因为内存占用小,下面是Dialog+Drawable、自定义View+Dialog、自定义View +addView的对比
- 软引用+count计数实现多个网络请求,
- 判断当前Activity区别页面的显示加载框,避免加载框显示混乱
四、主要代码
1 Drawable的方式
load_view_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/load_icon1" android:duration="100"></item>
<item android:drawable="@drawable/load_icon2" android:duration="100"></item>
<item android:drawable="@drawable/load_icon3" android:duration="100"></item>
<item android:drawable="@drawable/load_icon4" android:duration="100"></item>
<item android:drawable="@drawable/load_icon5" android:duration="100"></item>
<item android:drawable="@drawable/load_icon6" android:duration="100"></item>
<item android:drawable="@drawable/load_icon7" android:duration="100"></item>
<item android:drawable="@drawable/load_icon8" android:duration="100"></item>
<item android:drawable="@drawable/load_icon9" android:duration="100"></item>
<item android:drawable="@drawable/load_icon10" android:duration="100"></item>
</animation-list>
LoadDialog
public class LoadDialog extends Dialog implements OnClickListener {
private ImageView iv_load;
private TextView tips_loading_msg, tv_cancel;
private AnimationDrawable animationDrawable;
private String msg = "正在加载...";
private boolean flag = true;
private Context context;
boolean cancelShow = true;
private int referenceTime = 0;
public LoadDialog(Context context) {
super(context);
this.context = context;
}
public Context getBaseContext() {
return context;
}
public LoadDialog(Context context, String message, boolean flag, boolean cancelShow) {
super(context, R.style.load_dialog);
this.setCancelable(flag);
this.setCanceledOnTouchOutside(false);
this.cancelShow = cancelShow;
if (null == message) {
msg = "正在加载...";
} else {
this.msg = message;
}
referenceTime = 0;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = View.inflate(getContext(), R.layout.load_view, null);
this.setContentView(view);
tips_loading_msg = findViewById(R.id.tips_loading_msg);
iv_load = findViewById(R.id.iv_load);
iv_load.setImageResource(R.drawable.load_view_animation);
animationDrawable = (AnimationDrawable) iv_load.getDrawable();
tv_cancel = findViewById(R.id.tv_cancel);
tv_cancel.setClickable(true);
getWindow().getDecorView().setPadding(0, 0, 0, 0);
if (cancelShow) {
tv_cancel.setVisibility(View.VISIBLE);
} else {
tv_cancel.setVisibility(View.GONE);
}
}
@Override
protected void onStart() {
super.onStart();
this.setCancelable(flag);
if (null != msg) {
tips_loading_msg.setText(msg);
}
if (cancelShow) {
tv_cancel.setVisibility(View.VISIBLE);
} else {
tv_cancel.setVisibility(View.GONE);
}
tv_cancel.setOnClickListener(this);
}
public static WeakReference<LoadDialog> dialogReference;
/**
* 只有最新的dialog会显示,后面的会覆盖前面的,自动释放
* runOnUiThread中使用
*/
public static void showDialog(Context context) {
try {
LoadDialog loadDialog = getCorrectDialog(context, false);
loadDialog.referenceTime++;
if (!loadDialog.isShowing()) {
loadDialog.show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static synchronized LoadDialog getCorrectDialog(Context context, boolean cancelShow) {
LoadDialog loadDialog;
if (null == dialogReference) {
loadDialog = new LoadDialog(context, null, cancelShow, true);
dialogReference = new WeakReference<>(loadDialog);
} else {
//是不是当前Activity或者Fragment的Dialog
loadDialog = dialogReference.get();
if (loadDialog == null) {
loadDialog = new LoadDialog(context, null, cancelShow, true);
dialogReference = new WeakReference<>(loadDialog);
} else if (loadDialog.getBaseContext() != context) {
//是其他Activity或Fragment的Dialog则释放,然后重新创建
if (loadDialog.isShowing()) {
loadDialog.dismiss();
}
loadDialog.release();
loadDialog = new LoadDialog(context, null, cancelShow, true);
dialogReference = new WeakReference<>(loadDialog);
}
}
return loadDialog;
}
public static synchronized void showDialog(Context context, boolean cancelShow) {
try {
LoadDialog loadDialog = getCorrectDialog(context, cancelShow);
loadDialog.referenceTime++;
if (!loadDialog.isShowing()) {
loadDialog.show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 隐藏最新的dialog
* runOnUiThread中使用
*/
public static synchronized void dismissDialog() {
try {
if (dialogReference == null || dialogReference.get() == null) {
return;
}
LoadDialog loadDialog = dialogReference.get();
if (loadDialog.referenceTime > 0) {
loadDialog.referenceTime--;
}
if (loadDialog.referenceTime == 0) {
if (loadDialog.isShowing()) {
loadDialog.dismiss();
}
loadDialog.release();
dialogReference.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 清楚当前显示的Dialog
*/
public static synchronized void clear() {
if(dialogReference == null || dialogReference.get() == null){
return;
}
LoadDialog loadDialog = dialogReference.get();
if(loadDialog.isShowing()){
loadDialog.dismiss();
}
loadDialog.referenceTime = 0;
loadDialog.release();
dialogReference.clear();
}
/**
* 清除当前Activity或者Fragment对应Dialog
* @param context
*/
public static synchronized void clear(Context context) {
if(dialogReference == null || dialogReference.get() == null){
return;
}
LoadDialog loadDialog = dialogReference.get();
if (loadDialog.getBaseContext() == context) {
if (loadDialog.isShowing()) {
loadDialog.dismiss();
}
loadDialog.release();
dialogReference.clear();
loadDialog.referenceTime = 0;
}
}
private void release() {
if (animationDrawable == null) {
release();
for (int i = 0; i < animationDrawable.getNumberOfFrames(); i++) {
Drawable frame = animationDrawable.getFrame(i);
if (frame instanceof BitmapDrawable) {
((BitmapDrawable) frame).getBitmap().recycle();
}
frame.setCallback(null);
}
animationDrawable.setCallback(null);
}
}
@Override
public void show() {
if (null == msg) {
msg = "正在加载...";
}
super.show();
animationDrawable.start();
}
@Override
public void dismiss() {
super.dismiss();
referenceTime = 0;
animationDrawable.stop();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_cancel:
dismiss();
break;
}
}
}
2 自定义View的实现(推荐)
LoadingView
public class LoadingView extends View {
private Paint paint = new Paint();
private Paint mBitMapPaint;
private Bitmap mBitMapSRC, mBitMapDST, mBgBitmap;
private int dx;
private ValueAnimator animator;
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);
init();
}
private void init() {
setLayerType(LAYER_TYPE_SOFTWARE, null);
mBitMapPaint = new Paint();
mBitMapPaint.setColor(Color.RED);
mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.load1);
mBitMapDST = BitmapFactory.decodeResource(getResources(), R.drawable.load2);
mBitMapSRC = Bitmap.createBitmap(mBitMapDST.getWidth(), mBitMapDST.getHeight(), Bitmap.Config.ARGB_8888);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w, h;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
w = measureWidth;
h = measureHeight;
mBgBitmap = BitmapUtil.scaleBitmap(mBgBitmap, w, h);
} else if (widthMode == MeasureSpec.EXACTLY) {
w = measureWidth;
h = (measureWidth * mBgBitmap.getHeight()) / mBgBitmap.getWidth();
mBgBitmap = BitmapUtil.scaleBitmap(mBgBitmap, w, h);
} else if (heightMode == MeasureSpec.EXACTLY) {
h = measureHeight;
w = (measureHeight * mBgBitmap.getWidth()) / mBgBitmap.getHeight();
mBgBitmap = BitmapUtil.scaleBitmap(mBgBitmap, w, h);
} else {
w = mBgBitmap.getWidth();
h = mBgBitmap.getHeight();
}
mBitMapDST = BitmapUtil.scaleBitmap(mBitMapDST, w, h);
mBitMapSRC = BitmapUtil.scaleBitmap(mBitMapSRC, w, h);
setMeasuredDimension(w, h);
setBackgroundDrawable(new BitmapDrawable(getResources(), mBgBitmap));
startAnimation();
}
Canvas c;
@Override
protected void onDraw(Canvas canvas) {
// canvas.drawBitmap(mBgBitmap, 0, 0, paint);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
if (c == null) {
c = new Canvas(mBitMapSRC);
}
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// 画不透明的矩形区域
c.drawRect(dx, 0, mBitMapDST.getWidth(), mBitMapDST.getHeight(), mBitMapPaint);
// 画目标图片
canvas.drawBitmap(mBitMapDST, 0, 0, mBitMapPaint);
mBitMapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(mBitMapSRC, 0, 0, mBitMapPaint);
mBitMapPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
public synchronized void startAnimation() {
if (animator == null) {
initAnimation();
}
if (animator.isRunning()) {
return;
}
animator.start();
}
public synchronized void stopAnimation() {
animator.cancel();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
recycleBitmap(mBgBitmap);
recycleBitmap(mBitMapDST);
recycleBitmap(mBitMapSRC);
if(animator != null && animator.isRunning()){
animator.cancel();
}
}
private void recycleBitmap(Bitmap bitmap) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
private void initAnimation() {
animator = ValueAnimator.ofInt(0, mBitMapDST.getWidth());
animator.setDuration(1000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator() {
@Override
public float getInterpolation(float input) {
return 1.2f * input;
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int) animation.getAnimatedValue();
Log.i("LoadingViewDialog","animation dx="+dx );
postInvalidate();
}
});
}
}
SnackLoading
public class SnackLoading {
private static WeakReference<SnackLoading> snackInstance;
private Activity activity;
private View mView;
private LoadingView loadingView;
private int referenceTime;
private SnackLoading(Activity activity) {
this.activity = activity;
mView = View.inflate(activity, R.layout.snack_load_view, null);
((ViewGroup) activity.getWindow().getDecorView()).addView(mView);
loadingView = mView.findViewById(R.id.iv_load);
mView.findViewById(R.id.tv_cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
referenceTime =0;
dismiss();
}
});
referenceTime = 0;
}
public void show() {
if (snackInstance == null || snackInstance.get() == null) {
return;
}
SnackLoading snackLoading = snackInstance.get();
snackLoading.referenceTime++;
if (mView != null && mView.getVisibility() == View.GONE) {
mView.setVisibility(View.VISIBLE);
}
if (loadingView != null)
loadingView.startAnimation();
}
public void dismiss() {
if (snackInstance == null || snackInstance.get() == null) {
return;
}
SnackLoading loadDialog = snackInstance.get();
if (loadDialog.referenceTime > 0) {
loadDialog.referenceTime--;
}
if (loadDialog.referenceTime == 0) {
if (mView != null) {
mView.setVisibility(View.GONE);
}
if (loadingView != null) {
loadingView.stopAnimation();
}
}
}
public void clear() {
if (loadingView != null) {
loadingView.stopAnimation();
}
if (mView != null && mView.getParent() != null) {
((ViewGroup) mView.getParent()).removeView(mView);
}
}
public static synchronized SnackLoading getInstance(Activity activity) {
SnackLoading snackLoading;
if (snackInstance == null || snackInstance.get() == null) {
snackLoading = new SnackLoading(activity);
snackInstance = new WeakReference<>(snackLoading);
} else {
snackLoading = snackInstance.get();
if (snackLoading.activity != activity) {
snackInstance.clear();
snackLoading = new SnackLoading(activity);
snackInstance = new WeakReference<>(snackLoading);
}
}
return snackLoading;
}
}
五 Demo
六 注意
- Bitmap、Drawable、Animator要回收
- 自定义View中onDraw的Canvas要声明成全局,否则内存开销大