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

具体实现
-
自定义加载条
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 和转发
网友评论