文章目录

  • 一、ProgressBar不平滑的原因
  • 二、web平滑ProgressBar实现
  • 三、SmoothProgressBar源码解析

一、ProgressBar不平滑的原因

Android编写Web界面,基本都是WebView + ProgressBar相结合使用。通过在WebChromeClient的onProgressChanged 方法中调用ProgressBar的setProgress方法将当前网页加载进度展示在UI界面上,基础代码如下,demo很简单,不过我还是要罗列出来,方便文章阅读。代码如下:

activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns: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"android:background="#F0F0F0"tools:context=".MainActivity"><WebViewandroid:id="@+id/webView"android:layout_width="match_parent"android:layout_height="match_parent"/><ProgressBarandroid:id="@+id/progressBar"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="4dp"android:indeterminateOnly="false"android:max="100"android:progressDrawable="@drawable/progress_bar_states"/><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="loadUrl"android:text="loadUrl"android:textAllCaps="false"/>
</RelativeLayout>

MainActivity 代码如下:

public class MainActivity extends AppCompatActivity {private Button mButton;private WebView webView;private ProgressBar mProgressBar;@SuppressLint("JavascriptInterface")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mProgressBar = findViewById(R.id.progressBar);mButton = findViewById(R.id.button);webView = findViewById(R.id.webView);webView.setWebChromeClient(webChromeClient);}private WebChromeClient webChromeClient = new WebChromeClient() {@Overridepublic void onProgressChanged(WebView view, int newProgress) {super.onProgressChanged(view, newProgress);if (mProgressBar == null) {return;}mProgressBar.setProgress(newProgress);if (newProgress == 100) mProgressBar.setVisibility(View.GONE);elsemProgressBar.setVisibility(View.VISIBLE);}};public void loadUrl(View view) {mButton.setVisibility(View.GONE);webView.loadUrl("http://baidu.com/");}
}

点击button将Button设置为Gone,并且开始加载url,运行效果如下:


对onProgressChanged中newProgress打印:

15:55:25.115 /smoothprogressdemo E/MainActivity: onProgressChanged: 10
15:55:25.238 /smoothprogressdemo E/MainActivity: onProgressChanged: 10
15:55:25.941 /smoothprogressdemo E/MainActivity: onProgressChanged: 40
15:55:26.196 /smoothprogressdemo E/MainActivity: onProgressChanged: 80
15:55:26.511 /smoothprogressdemo E/MainActivity: onProgressChanged: 82
15:55:26.711 /smoothprogressdemo E/MainActivity: onProgressChanged: 83
15:55:26.759 /smoothprogressdemo E/MainActivity: onProgressChanged: 100
15:55:26.760 /smoothprogressdemo E/MainActivity: onProgressChanged: 100
15:55:26.763 /smoothprogressdemo E/MainActivity: onProgressChanged: 10
15:55:26.876 /smoothprogressdemo E/MainActivity: onProgressChanged: 90
15:55:28.515 /smoothprogressdemo E/MainActivity: onProgressChanged: 100
15:55:28.545 /smoothprogressdemo E/MainActivity: onProgressChanged: 100

从上面日志我们可以分析出如下规律:

  • 一次完整的网页加载进度为 (0,100],且网页加载完成一定会回调100
  • 当前网页加载进度回调值之间的间隔有时候会很大,这也是为什么ProgressBar不能够平滑移动的原因
  • 网页加载进度之间可能会存在数值相同的情况,如上 10 = 10 100=100
  • 网页如果是重定向的话,网络进度条规律(0,100] -> (0,100] … ,这个很好理解重定向相当于跳转多个网页,如果从定向一次则(0,100] -> (0,100] ,如果重定向两次则(0,100] -> (0,100] ->(0,100] ,依次类推,本次加载的http://www.baidu.com 即是重定向了一次,15:55:26.763分可以看到这种情况

知道了webView 加载的网页进度的规律,我们就开始着手解决问题

二、web平滑ProgressBar实现

先上一张实现后的效果图如下,录制Gif效果要比实际手机运行效果差一些,大家可以在自己的项目中查看实际效果

通过第一节我们已经了解了WebView加载网页的返回进度的规律了,既然进度值之间的间距较大,导致了progressBar不能平滑移动,那么我们可以通过动画,设置起始点为上一个进度值,终点为下一个进度值,让ProgressBar平滑的移动过去即可。使用属性动画可以实现,不过我并没有使用属性动画而是使用Scroller实现的,完整代码如下,使用请参考demo,文末下载demo:

public class SmoothProgressBar extends ProgressBar {private final String TAG = SmoothProgressBar.class.getSimpleName();private AtomicInteger mAtomicInteger = new AtomicInteger();private Scroller mScroller;public SmoothProgressBar(Context context) {super(context);init(context);}public SmoothProgressBar(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public SmoothProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {mScroller = new Scroller(context, new LinearInterpolator());}public void smoothScrollTo(int progress) {int current = mAtomicInteger.get();if (current == progress)return;int previous = mAtomicInteger.getAndSet(progress);int target = mAtomicInteger.get();if (previous == 0 && target > 0) {setVisibility(View.VISIBLE);mScroller.startScroll(0, 0, target, 0, dynamicGetAnimationDuration(target));invalidate();} else if (mScroller.isFinished() && target > previous) {int delta = target - previous;setVisibility(VISIBLE);mScroller.startScroll(previous, 0, delta, 0, dynamicGetAnimationDuration(delta));invalidate();}}/*** 根据要移动的间距动态的确定时间** @param delta* @return*/private int dynamicGetAnimationDuration(int delta) {if (delta <= 5)return 80;if (delta <= 20)return 150;elsereturn 200;}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {int progress = Math.min(mScroller.getCurrX(), mScroller.getFinalX());setProgress(progress);if (progress == mScroller.getFinalX()) {mScroller.abortAnimation();}postInvalidate();} else {int target = mAtomicInteger.get();if (target > getProgress()) {int delta = target - getProgress();mScroller.startScroll(getProgress(), 0, delta, 0, dynamicGetAnimationDuration(delta));postInvalidate();} else if (target >= getMax()) {mScroller = new Scroller(getContext(), new LinearInterpolator());mAtomicInteger.set(0);setVisibility(GONE);} else if (target < getProgress()) {//target移动的位置,小于当前progress值,说明已经开始加载另外一个网页了setVisibility(VISIBLE);mScroller = new Scroller(getContext(), new LinearInterpolator());mScroller.startScroll(0, 0, target, 0, dynamicGetAnimationDuration(target));}}}
}

我自定义了View,通过继承ProgressBar并不改变本身任何方法,添加了smoothScrollTo方法,该方法实现了ProgressBar的平滑实现。使用方法:

    private WebChromeClient webChromeClient = new WebChromeClient() {@Overridepublic void onProgressChanged(WebView view, int newProgress) {super.onProgressChanged(view, newProgress);if (mProgressBar == null) {return;}if(newProgress > 0){//此处切记不要在对ProgressBar 设置 setVisibility,SmoothProgressBar中已经做了相应处理mProgressBar.smoothScrollTo(newProgress);}}};

XML中的声明就不写了,只需要将ProgressBar替换为SmoothProgressBar,另外切记一定要添加 android:max=“100” 运行之后的效果图如上。完整demo文末下载

代码实现起来很简洁,短短100行代码就实现了功能,接下来解释下部分代码

三、SmoothProgressBar源码解析

首先对于Scroller和AtomicInteger 这两个类不知道怎么用的小伙伴,可以直接百度一下,使用起来也很简单,在这我就不重复了。

之前我一直在想ProgressBar使用动画,在平移动画过程中可能多个progress值已经回调过来了,要不要使用队列或者列表去维护这些progress值呢?仔细想了想实际上没有必要去维护所有回调过来的progress值,在上一个动画执行完毕,我只需要拿当前最新回调过来的progress值,重新执行动画到这个最新的progress值即可,至于中间的过程值完全没有必要在意。这也是我为什么选择使用AtomicInteger 的原因。

smoothScrollTo

 public void smoothScrollTo(int progress) {int current = mAtomicInteger.get();if (current == progress)return;int previous = mAtomicInteger.getAndSet(progress);int target = mAtomicInteger.get();if (previous == 0 && target > 0) {setVisibility(View.VISIBLE);mScroller.startScroll(0, 0, target, 0, dynamicGetAnimationDuration(target));invalidate();} else if (mScroller.isFinished() && target > previous) {int delta = target - previous;setVisibility(VISIBLE);mScroller.startScroll(previous, 0, delta, 0, dynamicGetAnimationDuration(delta));invalidate();}}
  • 第一节我们知道由于web界面回调过来的值,可能上一个progress和下一个progress值有可能是相等的,但是我们完全没必要再重新set一遍progress值,所以如果current == progress 则直接返回。
  • 当加载一个url首次有进度值回调进来的时候,则满足previous == 0 && target > 0 那么这个时候就调用Scroller的 startScroll方法并调用invalidate从新刷新当前View,dynamicGetAnimationDuration方法动态返回动画需要执行的时间,毕竟 0-10 和 10-90 之间动画的运行时间不应该相同,这样做一定程度上提升了SmoothProgressBar的效率。
  • 当mScroller.isFinished() && target > previous 为true时暂时先这下面会将到。

由于调用了invalidate,则View会重新执行onDraw(Canvas canvas)方法,而在onDraw方法又会调用复写的computeScroll 方法。

 @Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {int progress = Math.min(mScroller.getCurrX(), mScroller.getFinalX());setProgress(progress);if (progress == mScroller.getFinalX()) {mScroller.abortAnimation();}postInvalidate();} else{...}}

在computeScroll方法中不断的setProgress值,当progress值为finlX时,终止动画,并调用postInvalidate() ,View 会再次执行到computeScroll,这个时候mScroller.computeScrollOffset方法返回false,则执行else部分代码:

   @Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {...} else {int target = mAtomicInteger.get();if (target > getProgress()) {int delta = target - getProgress();mScroller.startScroll(getProgress(), 0, delta, 0, dynamicGetAnimationDuration(delta));postInvalidate();} else if (target >= getMax()) {mScroller = new Scroller(getContext(), new LinearInterpolator());mAtomicInteger.set(0);setVisibility(GONE);} else if (target < getProgress()) {setVisibility(VISIBLE);mScroller = new Scroller(getContext(), new LinearInterpolator());mScroller.startScroll(0, 0, target, 0, dynamicGetAnimationDuration(target));}}}
  • 从AtomicInteger中拿当前最新的值target如果值大于getProgress那么使用Scroller重新调用startScroll,传递起始progress值为getProgress 再次开启动画
  • 如果target 大于等于ProgressBar的getMax(从前面在xml中声明中对max的声明我们知道getMax为100)则表示当前网页已经加载完毕,则将一些变量恢复初始值并将View设置为Gone
  • 如果target < getProgress则表示现在已经开始加载另外一个网页了,使用Scoller重新调用startScroll 传递起始progress值为0,从新开始动画progress值会从0 -> target
  • 那么如果target == getProgress值,表示当前并没有新的progress值回调过来,则不进行任何出来,一旦有新的progress值回调过来,则执行SmoothScrollTo方法中mScroller.isFinished() && target > previous 部分代码,从新开启动画。

好了,上面源码部分就到这里,点击下载完整demo

Android web界面丝滑进度条相关推荐

  1. Android学习笔记之progressBar(进度条)

    一.说明 <1>在某项延续性工作的进展过程中为了不让用户觉得程序死掉了,需要有个活动的进度条,表示此过程正在进行中. <2>在某些操作的进度中的可视指示器,为用户呈现操作的进度 ...

  2. android 自定义进度条 水量,Android自定义带水滴的进度条样式(带渐变色效果)...

    一.直接看效果 二.直接上代码 1.自定义控件部分 package com.susan.project.myapplication; import android.app.Activity; impo ...

  3. android自定义view 模仿win10进度条

    android自定义view 模仿win10进度条 本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. PS:有朋友反映动画无法播放,那是因为PathMeasure ...

  4. Android开发之绘制自定义进度条 | 渐变进度条 | 斜角进度条的方法

    老套路先上图 先看上面的斜角进度条的实现方法: package cn.yhsh.appwidget;import android.content.Context; import android.gra ...

  5. android 流程指示,Android实现带有指示器的进度条

    背景 当我们看到UI给我们设计的效果的时候,我们习惯性的思路就是看看google有没有为我们提供相应的控件或者是能否在网上找到一些合适的轮子拿过来直接用.但是,有时候很不巧的是没有这样的轮子供我们直接 ...

  6. android自定义view圆环,Android自定义View实现圆环进度条

    本文实例为大家分享了android自定义view实现圆环进度条的具体代码,供大家参考,具体内容如下 效果展示 动画效果 view实现 1.底层圆环是灰色背景 2.上层圆环是红色背景 3.使用动画画一条 ...

  7. Android淘宝好评星级进度条RatingBar原来可以这么玩

    Android淘宝好评星级进度条RatingBar原来可以这么玩 系统自带的比较老土 布局文件:activity_main.xml <LinearLayout xmlns:android=&qu ...

  8. Android:<17>进度条和动画

    上一期我们讲解了一下进度条对话框,通过代码实现的,还记得吗? progressDialog = new ProgressDialog(this); progressDialog.setTitle(&q ...

  9. android webview设置加载进度条

    1.自定义属性文件--attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources& ...

最新文章

  1. 图之典—可视化图表的词典
  2. Win7环境下搭建GO开发平台——SublimeText 2
  3. 2021年春季学期-信号与系统-第十次作业参考答案-第五小题
  4. java instanceof 区别_Java 中 instanceof 和 isInstance 的区别
  5. FileSystem close Exception
  6. ISA Best Practices Analyzer Tool
  7. leetcode 242. 有效的字母异位词 思考分析
  8. __ATTRIBUTE__ 你知多少?
  9. [JS] 001_JavaScript基础增强
  10. linux中nm、ldd、readelf命令
  11. 【解决方案 二十五】如何对Excel表数据进行彻底转置
  12. 虚拟化实战——存储(二)
  13. HearthBuddy 调试肯瑞托法师寒冰屏障的配合
  14. 详解Unicode与UTF-8、UTF-16、UTF-32.
  15. 特斯拉充电电流设置多大_特斯拉充电时间
  16. SAP中GR IR PGI的含义
  17. 如何使用U-Net-train进行语义分段,并在Keras中测试您的自定义数据
  18. Linux下获取WIFI状态信息(c语言)
  19. H5画布绘制透明圆环
  20. 浮点数加减丢失精度原因剖析

热门文章

  1. android 7 文件恢复,手机文件恢复APP
  2. 网上点餐的背景和意义是什么
  3. elementUI select popper弹出方向
  4. android2010有什么手机,2010年7款最佳Android手机
  5. 2018新型手机号正则表达式
  6. c++ 计算长方形面积 类对象传参
  7. 流量回放:保证业务技术升级的神器
  8. java 读取文件 效率_Java 逐行读取文本文件的几种方式以及效率对比
  9. 极智AI | 谈谈昇腾 CANN AIPP
  10. wandb.log()的异常