14_加载数据Loading框

DailyWorkSummary

一、 效果图

在这里插入图片描述

二、需求

  1. 请求网络数据时为了对用户进行友好提示,显示一个Loading框
  2. 多个网络请求可以共用加载框,直到所有请求结束,Loading框消失
  3. 不影响下个界面的请求Loading显示与消失

三、需求分析(推荐自定义View)

  1. 可以通过Dialog或者addView添加到DecorView的方式显示加载框
  2. 可以通过AnimationDrawable或者自定View的重绘实现加载的动态效果
  3. 推荐使用自定View+addView的方法实现,因为内存占用小,下面是Dialog+Drawable、自定义View+Dialog、自定义View +addView的对比
    在这里插入图片描述
  4. 软引用+count计数实现多个网络请求,
  5. 判断当前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

LoadingDialogActivity

六 注意

  • Bitmap、Drawable、Animator要回收
  • 自定义View中onDraw的Canvas要声明成全局,否则内存开销大

七 LoadingView进一步优化

  • 绘制Loading效果,可以直接用Bitmap,不需要saveLayer
  • ValueAnimator泄漏:通过添加Activity的监听器,及时cancel掉Animator

修改后的LoadingView(注意Fragment不用v4的,兼容)

/**
 * Created by Ray on 2019-12-18.
 */
public class LoadingView extends View {

    private final String TAG = "LoadingView";

    private Paint mBitMapPaint;
    private Bitmap mBitMapDST;
    private Rect rect;
    private int dx;
    private ValueAnimator animator;

    private LifeListener mLifeListener = new LifeListener() {

        @Override
        public void onCreate(Bundle bundle) {

        }

        @Override
        public void onStart() {
            Log.d(TAG, "onStart() ");
        }

        @Override
        public void onResume() {
            Log.d(TAG, "onResume() ");
        }

        @Override
        public void onPause() {
            Log.d(TAG, "onPause() ");
        }

        @Override
        public void onStop() {
            Log.d(TAG, "onStop() ");
        }

        @Override
        public void onDestroy() {
            if (animator != null ) {
                animator.cancel();
                animator = null;
            }
            Log.d(TAG, "onDestroy() ");
        }
    };

    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();
        setBackgroundResource(R.drawable.load1);
        mBitMapDST = BitmapFactory.decodeResource(getResources(), R.drawable.load2);
        rect = new Rect();
        rect.top = 0;
    }

    @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;
        } else if (widthMode == MeasureSpec.EXACTLY) {
            w = measureWidth;
            h = (measureWidth * mBitMapDST.getHeight()) / mBitMapDST.getWidth();
        } else if (heightMode == MeasureSpec.EXACTLY) {
            h = measureHeight;
            w = (measureHeight * mBitMapDST.getWidth()) / mBitMapDST.getHeight();
        } else {
            w = mBitMapDST.getWidth();
            h = mBitMapDST.getHeight();
        }

        mBitMapDST = scaleBitmap(mBitMapDST, w, h);
        setMeasuredDimension(w, h);
        startAnimation();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        rect.bottom = getHeight();
        rect.right = getWidth();
        rect.left = dx;
        canvas.drawBitmap(mBitMapDST, rect, rect, mBitMapPaint);
    }


    public synchronized void startAnimation() {
        if (animator == null) {
            initAnimation();
        }
        if (animator.isRunning()) {
            return;
        }
        post(() -> animator.start());

    }

    public static Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {
        if (origin == null) {
            return null;
        }
        int height = origin.getHeight();
        int width = origin.getWidth();
        if (newWidth == width && newHeight == height) {
            return origin;
        }
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);// 使用后乘
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        if (!origin.isRecycled()) {
            origin.recycle();
        }
        return newBM;
    }


    public synchronized void stopAnimation() {
        if (animator != null && animator.isRunning()) {
            animator.cancel();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Activity activity = getActivity();
        if (activity != null) {
            addLifeListener(activity);
        }
    }

    //获取宿主Activity
    private Activity getActivity() {
        View parent = this;
        Activity activity = null;
        do {
            final Context context = parent.getContext();
            //   Log.d(TAG, "view: " + parent + ", context: " + context);
            if (context != null && context instanceof Activity) {
                activity = (Activity) context;
                break;
            }
        } while ((parent = (View) parent.getParent()) != null);
        return activity;
    }

    private void addLifeListener(Activity activity) {
        LifeListenerFragment fragment = getLifeListenerFragment(activity);
        fragment.addLifeListener(mLifeListener);
    }

    private LifeListenerFragment getLifeListenerFragment(Activity activity) {
        FragmentManager manager = activity.getFragmentManager();
        return getLifeListenerFragment(manager);
    }

    //添加空白fragment
    private LifeListenerFragment getLifeListenerFragment(FragmentManager manager) {
        LifeListenerFragment fragment = (LifeListenerFragment) manager.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new LifeListenerFragment();
            manager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss();
        }

        return fragment;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        recycleBitmap(mBitMapDST);
        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(animation -> {
            if (Math.abs((int) animation.getAnimatedValue() - dx) > 5) {
                dx = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
    }

    //生命周期回调接口
    public interface LifeListener {

        void onCreate(Bundle bundle);

        void onStart();

        void onResume();

        void onPause();

        void onStop();

        void onDestroy();
    }

    //空白Fragment
    public static class LifeListenerFragment extends Fragment {

        private LifeListener mLifeListener;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }

        public void addLifeListener(LifeListener listener) {
            mLifeListener = listener;
        }

        public void removeLifeListener() {
            mLifeListener = null;
        }


        @Override
        public void onStart() {
            super.onStart();
            if (mLifeListener != null) {
                mLifeListener.onStart();
            }
        }

        @Override
        public void onStop() {
            super.onStop();
            if (mLifeListener != null) {
                mLifeListener.onStop();
            }
        }

        @Override
        public void onResume() {
            super.onResume();

            if (mLifeListener != null) {
                mLifeListener.onResume();
            }
        }

        @Override
        public void onDestroy() {
            super.onDestroy();

            if (mLifeListener != null) {
                mLifeListener.onDestroy();
            }
            removeLifeListener();
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值