android webview 加载进度动画

2023-01-08  本文已影响0人  xq9527

前言:

各位同学大家好,最近在做一些H5 游戏, 涉及到 web的使用 因为加载比较慢 所以就在加载过程做了一个动画, 所以就想分享给大家,这些之前很多网友都做过,我这边就简单分享下。废话不多说正式开始 。

效果图:

screenrecorder-2023-01-30-23-00-22-481[00_00_01--00_00_05].gif

具体实现

package com.example.webviewprogressbar.widget;
import android.animation.Animator;
import android.animation.ObjectAnimator;
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.Paint.Cap;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Property;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.example.webviewprogressbar.R;


/***
 *
 *  创建人:xuqing
 *  创建时间:2023年1月9日14:05:45
 *  类说明:自定义加载进度条
 *
 *
 */

public class EightdRoughtCircleProgress extends View implements View.OnClickListener {

    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();
    private static final Interpolator SWEEP_INTERPOLATOR = new AccelerateDecelerateInterpolator();
    private static final int ANGLE_ANIMATOR_DURATION = 1500;//转速
    private static final int SWEEP_ANIMATOR_DURATION = 1000;
    private static final int MIN_SWEEP_ANGLE = 30;
    private static final int DEFAULT_BORDER_WIDTH = 3;
    private final RectF fBounds = new RectF();
    private ObjectAnimator mObjectAnimatorSweep;
    private ObjectAnimator mObjectAnimatorAngle;
    private ValueAnimator fractionAnimator;
    private boolean mModeAppearing = true;
    private Paint mPaint;
    private float mCurrentGlobalAngleOffset;
    private float mCurrentGlobalAngle;
    private float mCurrentSweepAngle;
    private float mBorderWidth;
    private boolean mRunning;
    private int[] mColors;
    private int mCurrentColorIndex;
    private int mNextColorIndex;
    private static final int STATE_LOADING = 1;
    public static final int STATE_FINISH = 2;
    public static final int STATE_ERROR = 3;
    private int mCurrentState;
    private Path mHook;
    private Paint mHookPaint;
    private Path mArrow;
    private float mRingCenterRadius;
    private static int ARROW_WIDTH = 10 * 2;
    private static int ARROW_HEIGHT = 5 * 2;
    private float mStrokeInset = 2.5f;
    private static final float ARROW_OFFSET_ANGLE = 5;
    private float fraction;
    private Path mError;
    private boolean showArrow;


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

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

    public EightdRoughtCircleProgress(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        float density = context.getResources().getDisplayMetrics().density;
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EightdRoughtCircleProgress, defStyleAttr, 0);
        mBorderWidth = a.getDimension(R.styleable.EightdRoughtCircleProgress_progressBorderWidth,
                DEFAULT_BORDER_WIDTH * density);
        showArrow = a.getBoolean(R.styleable.EightdRoughtCircleProgress_showArrow, false);
        a.recycle();
        ARROW_WIDTH = (int) (mBorderWidth * 2);
        ARROW_HEIGHT = (int) mBorderWidth;
        mColors = new int[4];
        mColors[0] = Color.RED;
        mColors[1] = Color.BLUE;
        mColors[2] = Color.GREEN;
        mColors[3] = Color.GRAY;
        mCurrentColorIndex = 0;
        mNextColorIndex = 1;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Cap.ROUND);
        mPaint.setStrokeWidth(mBorderWidth);
        mPaint.setColor(mColors[mCurrentColorIndex]);
        mHookPaint = new Paint(mPaint);
        Paint mArrowPaint = new Paint(mPaint);
        mHook = new Path();
        mError = new Path();

        setupAnimations();
    }

    private void start() {
        if (isRunning()) {
            return;
        }
        mRunning = true;
        mCurrentState = STATE_LOADING;
        mObjectAnimatorAngle.start();
        mObjectAnimatorSweep.start();
        setOnClickListener(this);
        invalidate();
    }

    public void finish(int type) {
        stop();
        mCurrentState = type;
        if (!fractionAnimator.isRunning()) {
            fractionAnimator.start();
        }
    }

    private void stop() {
        if (!isRunning()) {
            return;
        }
        mRunning = false;
        mObjectAnimatorAngle.cancel();
        mObjectAnimatorSweep.cancel();
        invalidate();
    }

    private boolean isRunning() {
        return mRunning;
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (visibility == VISIBLE) {
            start();
        } else {
            stop();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        start();
        super.onAttachedToWindow();
    }

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

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int min = Math.min(w, h);
        fBounds.left = mBorderWidth * 2f + .5f;
        fBounds.right = min - mBorderWidth * 2f - .5f;
        fBounds.top = mBorderWidth * 2f + .5f;
        fBounds.bottom = min - mBorderWidth * 2f - .5f;
        mRingCenterRadius = Math.min(fBounds.centerX() - fBounds.left, fBounds.centerY() - fBounds.top) - mBorderWidth;

    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        switch (mCurrentState) {
            case STATE_LOADING:
                drawArc(canvas);
                break;
            case STATE_FINISH:
                drawHook(canvas);
                break;
            case STATE_ERROR:
                drawError(canvas);
                break;
        }

    }

    private void drawError(Canvas canvas) {
        mError.reset();
        mError.moveTo(fBounds.centerX() + fBounds.width() * 0.2f * fraction, fBounds.centerY() - fBounds.height() * 0.2f * fraction);
        mError.lineTo(fBounds.centerX() - fBounds.width() * 0.2f * fraction, fBounds.centerY() + fBounds.height() * 0.2f * fraction);
        mError.moveTo(fBounds.centerX() - fBounds.width() * 0.2f * fraction, fBounds.centerY() - fBounds.height() * 0.2f * fraction);
        mError.lineTo(fBounds.centerX() + fBounds.width() * 0.2f * fraction, fBounds.centerY() + fBounds.height() * 0.2f * fraction);
        mHookPaint.setColor(mColors[3]);
        canvas.drawPath(mError, mHookPaint);
        canvas.drawArc(fBounds, 0, 360, false, mHookPaint);
    }

    private void drawHook(Canvas canvas) {
        mHook.reset();
        mHook.moveTo(fBounds.centerX() - fBounds.width() * 0.25f * fraction, fBounds.centerY());
        mHook.lineTo(fBounds.centerX() - fBounds.width() * 0.1f * fraction, fBounds.centerY() + fBounds.height() * 0.18f * fraction);
        mHook.lineTo(fBounds.centerX() + fBounds.width() * 0.25f * fraction, fBounds.centerY() - fBounds.height() * 0.20f * fraction);
        mHookPaint.setColor(mColors[0]);
        canvas.drawPath(mHook, mHookPaint);
        canvas.drawArc(fBounds, 0, 360, false, mHookPaint);

    }

    private void drawArc(Canvas canvas) {
        float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
        float sweepAngle = mCurrentSweepAngle;
        if (mModeAppearing) {
            mPaint.setColor(gradient(mColors[mCurrentColorIndex], mColors[mNextColorIndex],
                    mCurrentSweepAngle / (360 - MIN_SWEEP_ANGLE * 2)));
            sweepAngle += MIN_SWEEP_ANGLE;
        } else {
            startAngle = startAngle + sweepAngle;
            sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
        }
        canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);
        if (showArrow) {
            drawTriangle(canvas, startAngle, sweepAngle);
        }
    }

    public void drawTriangle(Canvas c, float startAngle, float sweepAngle) {
        if (mArrow == null) {
            mArrow = new Path();
            mArrow.setFillType(Path.FillType.EVEN_ODD);
        } else {
            mArrow.reset();
        }

        float x = (float) (mRingCenterRadius + fBounds.centerX());
        float y = (float) (  fBounds.centerY());
        mArrow.moveTo(0, 0);
        float mArrowScale = 1f;
        mArrow.lineTo(ARROW_WIDTH * mArrowScale, 0);
        mArrow.lineTo((ARROW_WIDTH * mArrowScale / 2), (ARROW_HEIGHT
                * mArrowScale));
        mArrow.offset(x, y);
        mArrow.close();
        c.rotate(startAngle + sweepAngle, fBounds.centerX(),
                fBounds.centerY());
        c.drawPoint(x, y, mPaint);
        c.drawPath(mArrow, mPaint);
    }

    private static int gradient(int color1, int color2, float p) {
        int r1 = (color1 & 0xff0000) >> 16;
        int g1 = (color1 & 0xff00) >> 8;
        int b1 = color1 & 0xff;
        int r2 = (color2 & 0xff0000) >> 16;
        int g2 = (color2 & 0xff00) >> 8;
        int b2 = color2 & 0xff;
        int newr = (int) (r2 * p + r1 * (1 - p));
        int newg = (int) (g2 * p + g1 * (1 - p));
        int newb = (int) (b2 * p + b1 * (1 - p));
        return Color.argb(255, newr, newg, newb);
    }

    private void toggleAppearingMode() {
        mModeAppearing = !mModeAppearing;
        if (mModeAppearing) {
            mCurrentColorIndex = ++mCurrentColorIndex % 4;
            mNextColorIndex = ++mNextColorIndex % 4;
            mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
        }
    }
    private Property<EightdRoughtCircleProgress, Float> mAngleProperty = new Property<EightdRoughtCircleProgress, Float>(Float.class, "angle") {
        @Override
        public Float get(EightdRoughtCircleProgress object) {
            return object.getCurrentGlobalAngle();
        }

        @Override
        public void set(EightdRoughtCircleProgress object, Float value) {
            object.setCurrentGlobalAngle(value);
        }
    };

    private Property<EightdRoughtCircleProgress, Float> mSweepProperty = new Property<EightdRoughtCircleProgress, Float>(Float.class, "arc") {
        @Override
        public Float get(EightdRoughtCircleProgress object) {
            return object.getCurrentSweepAngle();
        }

        @Override
        public void set(EightdRoughtCircleProgress object, Float value) {
            object.setCurrentSweepAngle(value);
        }
    };

    private void setupAnimations() {
        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);

        mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2);
        mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
        mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
        mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
        mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                toggleAppearingMode();
            }
        });
        fractionAnimator = ValueAnimator.ofInt(0, 255);
        fractionAnimator.setInterpolator(ANGLE_INTERPOLATOR);
        fractionAnimator.setDuration(100);
        fractionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fraction = animation.getAnimatedFraction();
                mHookPaint.setAlpha((Integer) animation.getAnimatedValue());
                invalidate();
            }
        });
    }

    public void setCurrentGlobalAngle(float currentGlobalAngle) {
        mCurrentGlobalAngle = currentGlobalAngle;
        invalidate();
    }

    public float getCurrentGlobalAngle() {
        return mCurrentGlobalAngle;
    }

    public void setCurrentSweepAngle(float currentSweepAngle) {
        mCurrentSweepAngle = currentSweepAngle;
        invalidate();
    }

    public float getCurrentSweepAngle() {
        return mCurrentSweepAngle;
    }

    @Override
    public void onClick(View v) {
    }
}

显示动画dialog 逻辑

package com.example.webviewprogressbar.widget;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.example.webviewprogressbar.R;
import java.util.ArrayList;
import com.example.webviewprogressbar.R;
import java.util.ArrayList;



/***
 *
 * 创建人:xuqing
 * 创建时间 :2023年1月9日14:33:39
 * 类说明:自定义控件
 */

public class EightdRoughtLoadView {
    private static final ArrayList<Dialog> LOADERS = new ArrayList<>();
    private static final ArrayList<EightdRoughtCircleProgress> VIEWS = new ArrayList<>();

    private  static   Dialog dialog=null;


    public static void showLoading(Context context, String msg) {
        View avLoadingIndicatorView = AppUtils.inflate(context, R.layout.view_loadview);
        TextView tvShow = avLoadingIndicatorView.findViewById(R.id.tvShow);
        EightdRoughtCircleProgress circleProgress = avLoadingIndicatorView.findViewById(R.id.progress_bar);
        tvShow.setText("加载"+msg+"%");
        if(dialog!=null){
            dialog.setContentView(avLoadingIndicatorView);
            dialog.setCanceledOnTouchOutside(false);
            LOADERS.add(dialog);
            VIEWS.add(circleProgress);
            if(!((Activity)context).isFinishing()){
                if(!dialog.isShowing()) {
                    dialog.show();
                }
            }
        }else{
            Toast.makeText(context,"动画dialog初始化失败",Toast.LENGTH_LONG).show();
        }

    }

    public  static  void  initDialog(Context context){
         dialog = new Dialog(context, R.style.dialog);


    }
public static void stopLoading(int type , DismissListener dismissListener){
    for (int i = 0; i < LOADERS.size(); i++) {
        Dialog dialog = LOADERS.get(i);
        EightdRoughtCircleProgress circleProgress = VIEWS.get(i);
        if (type <= 0) {
            dismiss(dialog);
        } else if (type == EightdRoughtCircleProgress.STATE_ERROR) {
            setCircleProgressState(circleProgress, dialog, EightdRoughtCircleProgress.STATE_ERROR, dismissListener);
        } else {
            setCircleProgressState(circleProgress, dialog, EightdRoughtCircleProgress.STATE_FINISH, dismissListener);
        }
    }
}
    public static void stopLoading(int type) {
       stopLoading(type,null);
    }

    private static void setCircleProgressState(EightdRoughtCircleProgress circleProgress, final Dialog dialog, int type, final DismissListener dismissListener) {
        if (circleProgress != null) {
            circleProgress.finish(type);
            circleProgress.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (dismissListener!=null){
                        dismiss(dialog);
                        dismissListener.dismiss();
                    }else {
                        dismiss(dialog);
                    }
                }
            }, 600);
        } else {
            dismiss(dialog);
        }
    }

    private static void dismiss(Dialog dialog) {
        if (dialog != null && dialog.isShowing()) {
            dialog.cancel();
        }
    }

    public static void stopLoading() {
        stopLoading(-1);
    }
    public interface DismissListener{
        void dismiss();
    }
}

具体调用

mainactivity 布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >
    <WebView
        android:id="@+id/webview1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

mainactivity 逻辑代码
关键代码

     EightdRoughtLoadView.initDialog(mContext);
        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                if (newProgress == 100) {
                    EightdRoughtLoadView.stopLoading();
                } else {
                    EightdRoughtLoadView.showLoading(mContext, newProgress + "");
                    if (newProgress == 100) {
                        EightdRoughtLoadView.stopLoading();
                    }
                }

            }
        });

我们调用 setWebChromeClient 方法通过 实现 WebChromeClient 接口里面的 onProgressChanged 方法 这里面的 newProgress就是我们webview 在加载网页时候返回的进度比例 我们只需要把这个值传到我们的动画启动的方法里面即可 。

完整mainactivity 代码

package com.example.webviewprogressbar;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.example.webviewprogressbar.widget.EightdRoughtLoadView;


/***
 *
 * 创建人 :xuqing
 * 创建时间:2023年1月9日13:51:10
 * 类说明:主业activity
 *
 */
public class MainActivity extends Activity {

    private String testUrl = "https://www.taobao.com";
    private Context mContext = MainActivity.this;
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        webView = (WebView) findViewById(R.id.webview1);
        WebSettings settings = webView.getSettings();
        settings.setDomStorageEnabled(true);
        settings.setJavaScriptEnabled(true);
        settings.setBlockNetworkImage(true);
        settings.setBlockNetworkImage(false);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        settings.setSupportZoom(true);//是否可以缩放,默认true
        settings.setBuiltInZoomControls(false);//是否显示缩放按钮,默认false
        settings.setUseWideViewPort(true);//设置此属性,可任意比例缩放。大视图模式
        settings.setLoadWithOverviewMode(true);//和setUseWideViewPort(true)一起解决网页自适应问题
        settings.setAppCacheEnabled(true);//是否使用缓存
        settings.setDomStorageEnabled(true);//DOM Storage
        settings.setAllowFileAccessFromFileURLs(true);
        settings.setBuiltInZoomControls(true);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        settings.setDefaultTextEncodingName("utf-8");
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        settings.setJavaScriptEnabled(true);//设置webview支持javascript脚本
        webView.requestFocus();
        // android 5.0以上默认不支持混合调用http与https,需要设置WebSettings来兼容一下
        if (Build.VERSION.SDK_INT >= 21) {
            webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
        }

        webView.setWebViewClient(new WebViewClient() {
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                //  重写此方法表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边
                //view.loadUrl(url);
                return super.shouldOverrideUrlLoading(view, url);
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                view.loadUrl("javascript:window.local_obj.showSource('<head>'+" +
                        "document.getElementsByTagName('html')[0].innerHTML+'</head>');");
                super.onPageFinished(view, url);
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
            }
        });
        EightdRoughtLoadView.initDialog(mContext);
        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                if (newProgress == 100) {
                    EightdRoughtLoadView.stopLoading();
                } else {
                    EightdRoughtLoadView.showLoading(mContext, newProgress + "");
                    if (newProgress == 100) {
                        EightdRoughtLoadView.stopLoading();
                    }
                }

            }
        });
        webView.loadUrl(testUrl);
    }
}

最后总结:

文章主要是对webview 加载过程做一个过度动画 从感官层面 上面给用户一个反馈。 动画是使用了dialog 配合自定义view实现。 当然动画也可以用在其他网络加载中,这边只是配合使用下 主要我们需要监听 WebChromeClient 接口里面回调方法 onProgressChanged 来实现我们加载进度效果。 最后希望我都文章能帮助各位同学工作和学习 。如果觉得文章还不错希望能给我一个star 和转发

代码:

码云 https://gitee.com/qiuyu123/webviewprogress-bar

上一篇 下一篇

猜你喜欢

热点阅读