Android(安卓)web界面丝滑进度条
文章目录
- 一、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"?>
MainActivity 代码如下:
public class MainActivity extends AppCompatActivity { private Button mButton; private WebView webView; private ProgressBar mProgressBar; @SuppressLint("JavascriptInterface") @Override protected 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() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); if (mProgressBar == null) { return; } mProgressBar.setProgress(newProgress); if (newProgress == 100) mProgressBar.setVisibility(View.GONE);else mProgressBar.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: 1015:55:25.238 /smoothprogressdemo E/MainActivity: onProgressChanged: 1015:55:25.941 /smoothprogressdemo E/MainActivity: onProgressChanged: 4015:55:26.196 /smoothprogressdemo E/MainActivity: onProgressChanged: 8015:55:26.511 /smoothprogressdemo E/MainActivity: onProgressChanged: 8215:55:26.711 /smoothprogressdemo E/MainActivity: onProgressChanged: 8315:55:26.759 /smoothprogressdemo E/MainActivity: onProgressChanged: 10015:55:26.760 /smoothprogressdemo E/MainActivity: onProgressChanged: 10015:55:26.763 /smoothprogressdemo E/MainActivity: onProgressChanged: 1015:55:26.876 /smoothprogressdemo E/MainActivity: onProgressChanged: 9015:55:28.515 /smoothprogressdemo E/MainActivity: onProgressChanged: 10015: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; else return 200; } @Override public 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() { @Override public 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 方法。
@Override public 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部分代码:
@Override public 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——多线程(AsyncTask封装)
- MPAndroidChart项目实战——MarkerView显示问题解决
- Android——使用WebView显示网页
- DialogFragment的使用总结
- android 常见面试题(三)
- 新博andorid 初中级考试评测以及答案-------小林老师出的试卷
- Android文件、内存、SDCard管理常用工具类、方法
- onConfigurationChanged
- Android(安卓)自定义View(二)函数分析