文章目录

        • 一、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

更多相关文章

  1. Android——多线程(AsyncTask封装)
  2. MPAndroidChart项目实战——MarkerView显示问题解决
  3. Android——使用WebView显示网页
  4. DialogFragment的使用总结
  5. android 常见面试题(三)
  6. 新博andorid 初中级考试评测以及答案-------小林老师出的试卷
  7. Android文件、内存、SDCard管理常用工具类、方法
  8. onConfigurationChanged
  9. Android(安卓)自定义View(二)函数分析

随机推荐

  1. Windows环境下Android(安卓)源码模块下载
  2. Android(安卓)Toolbar
  3. Android(安卓)AppWidget系统框架
  4. Android中ListView中Item的设置
  5. Android(安卓)SDK版本更新
  6. 修改系统Android版本,版本号
  7. 系出名门Android(8) - 控件(View)之TextS
  8. Android也有beacon了
  9. android adb工具
  10. Android微信界面