【转载请注明出处】
作者:DrkCore (http://blog.csdn.net/DrkCore)
原文链接:(http://blog.csdn.net/drkcore/article/details/50999932)

为什么需要给ScrollView添加滑块滚动条

说到滚动控件广大开发者朋友们想到的无非就是ListView和ScrollView两大控件,对于前者而言添加滑块无非是XML里面一句话的事情,但是对于后者而言就没那么容易了,至少笔者至今并没有找到简单的解决方案。

也罢,没有轮子的话就自己造一个。

思路的话比较简单粗暴:使用SeekBar作为滑块绑定到ScrollView上。

垂直的SeekBar

要给ScrollView添加滑块的话SeekBar必须是垂直的,这个问题要解决很容易,当然是直接上我们程序员最大的交友网站GitHub搜一下咯~,这是搜索页的传送门。

当然,如果你想要给HorizontalScrollView添加滑块的话原生的SeekBar就够了。

ScrollView的监听和滚动范围

大部分的控件都提供监听者模式的回调,然而悲剧的是ScrollView本身没有暴露监听滚动的方法。如何魔改ScrollView使之暴露接口请点此度娘传送门,本篇博文不再赘述。

这里为了简单一些笔者直接使用了Google官方提供的NestScrollView。我们先分析一下其回调接口:

public interface OnScrollChangeListener {        void onScrollChange(NestedScrollView v, int scrollX, int scrollY,int oldScrollX, int oldScrollY);}

其中scrollY是当前的滚动位置,和可滚动范围的关系图解如下:

理解了这一层关系之后要做的就很简单了,我们将可滚动范围和scrollY映射到SeekBar上即可。

滚动绑定

明白了逻辑之后我们只需要将ScrollView的滚动映射到SeekBar,再将SeekBar的用户拖动映射回ScrollView就行了。以下是笔者封装了的一个辅助类,注释比较详尽:

import android.support.v4.widget.NestedScrollView;import android.view.View;import android.widget.SeekBar;/** * 用于绑定ScrollView和SeekBar的辅助类 * * @author DrkCore * @since 2016年3月28日18:10:17 */public class ScrollBindHelper implements SeekBar.OnSeekBarChangeListener, NestedScrollView.OnScrollChangeListener {    private final SeekBar seekBar;    private final NestedScrollView scrollView;    private final View scrollContent;    //使用静态方法绑定并返回对象    public static ScrollBindHelper bind (SeekBar seekBar, NestedScrollView scrollView) {        ScrollBindHelper helper = new ScrollBindHelper(seekBar, scrollView);        seekBar.setOnSeekBarChangeListener(helper);        scrollView.setOnScrollChangeListener(helper);        return helper;    }    private ScrollBindHelper (SeekBar seekBar, NestedScrollView scrollView) {        this.seekBar = seekBar;        this.scrollView = scrollView;        this.scrollContent = scrollView.getChildAt(0);    }    /*继承*/    //用户是否正在拖动SeekBar的标志    private boolean isUserSeeking;    //获取滚动范围    private int getScrollRange () {        return scrollContent.getHeight() - scrollView.getHeight();    }    @Override    public void onScrollChange (NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {        //用户拖动SeekBar时不触发ScrollView的回调        if (isUserSeeking) {return;}        //计算当前滑动位置相对于整个范围的百分比,并映射到SeekBar上        int range = getScrollRange();        seekBar.setProgress(range != 0 ? scrollY * 100 / range : 0);    }    @Override    public void onProgressChanged (SeekBar seekBar, int progress, boolean fromUser) {        //当不是用户操作,也就是ScrollView的滚动隐射过来时不执行操作        if (!fromUser) { return;}        //将拖动的百分比换算成Y值,并映射到SrollView上。        scrollView.scrollTo(0, progress * getScrollRange() / 100);    }    @Override    public void onStartTrackingTouch (SeekBar seekBar) {        //标记用户正在拖动SeekBar        isUserSeeking = true;    }    @Override    public void onStopTrackingTouch (SeekBar seekBar) {        //标记用户已经不再操作SeekBar        isUserSeeking = false;    }}

可见性和动画

我们知道滚动视图的滑块并不是一直存在的,它有着如下的行为:

1. 默认不可见2. 内容视图高度太小,比如小于三个屏幕高度时不出现滑块3. 出现之后界面滚动或者滑块被用户触控时滑块不会消失4. 停止操作若干毫秒后消失

可见性的切换我们只要切换滑块控件的Visible属性即可而滚动和触控都在我们的回调之中,最后停止操作若干秒后消失可以直接使用一个handler搞定。我们创建一个只响应最后一次操作的Handler基类

//只响应最后一次操作的基类public abstract class LastMsgHandler extends Handler {    //标记是第几次count    private int count = 0;    /** * 增加Count数。 */    public synchronized final void increaseCount() {        count++;    }    //直接发送消息    public final void sendMsg() {        sendMsgDelayed(0);    }    //增加count数后发送延时消息    //如果延时小于或者等于0则直接发送。    public final void sendMsgDelayed(long delay) {        increaseCount();        if (delay <= 0) {            sendEmptyMessage(0);        } else {            sendEmptyMessageDelayed(0, delay);        }    }    //清空所有count和消息    public synchronized final void clearAll() {        count = 0;        removeCallbacksAndMessages(null);    }    @Override    public synchronized final void handleMessage(Message msg) {        super.handleMessage(msg);        count--;        //确保count数不会异常        if (count < 0) {            throw new IllegalStateException("count数异常");        }        //当count为0时说明是最后一次请求        if (count == 0) {            handleLastMessage(msg);        }    }    /*回调*/    //响应最后一次请求    protected abstract void handleLastMessage(Message msg);}

之后我们继承该类实现最后一次响应请求时切换滑块可见性的方法:

private static class VisibleHandler extends LastMsgHandler {         public static final long DEFAULT_TIME_OUT = 1000L;             private ScrollBindHelper helper;        public VisibleHandler(ScrollBindHelper helper) {            this.helper = helper;        }        public void reset() {            sendMsgDelayed(DEFAULT_TIME_OUT);        }        @Override        protected void handleLastMessage(Message msg) {            helper.hideScroll();        }    }

之后只需要将计时器安插进原有的回调即可。最后给出完整版的ScrollHelper。

public class ScrollBindHelper implements SeekBar.OnSeekBarChangeListener, NestedScrollView.OnScrollChangeListener {    private final SeekBar seekBar;    private final NestedScrollView scrollView;    private final View scrollContent;    /** * 使用静态方法来绑定逻辑,代码可读性更高。 */    public static ScrollBindHelper bind(SeekBar seekBar, NestedScrollView scrollView) {        ScrollBindHelper helper = new ScrollBindHelper(seekBar, scrollView);        seekBar.setOnSeekBarChangeListener(helper);        scrollView.setOnScrollChangeListener(helper);        return helper;    }    private ScrollBindHelper(SeekBar seekBar, NestedScrollView scrollView) {        this.seekBar = seekBar;        this.scrollView = scrollView;        this.scrollContent = scrollView.getChildAt(0);    }    /*继承*/    private boolean isUserSeeking;    private int getContentRange() {        return scrollContent.getHeight();    }    private int getScrollRange() {        return scrollContent.getHeight() - scrollView.getHeight();    }    @Override    public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {        //用户触控时不触发        if (isUserSeeking) {            return;        } else if (getContentRange() < ViewUtil.getScreenHeightPx() * 3) {//宽度小于三个屏幕不做处理            return;        }        int range = getScrollRange();        seekBar.setProgress(range != 0 ? scrollY * 100 / range : 0);    }    @Override    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {        showScroll();        if (!isUserSeeking) {            handler.reset();        }        //不是用户操作的时候不触发        if (!fromUser) {            return;        }        scrollView.scrollTo(0, progress * getScrollRange() / 100);    }    @Override    public void onStartTrackingTouch(SeekBar seekBar) {        isUserSeeking = true;        handler.clearAll();    }    @Override    public void onStopTrackingTouch(SeekBar seekBar) {        isUserSeeking = false;        handler.reset();    }    /*动画*/    public static final long DEFAULT_TIME_OUT = 1000L;    private static class VisibleHandler extends WeakRefLastMsgHandler<ScrollBindHelper> {        public VisibleHandler(ScrollBindHelper ref) {            super(ref);        }        public void reset() {            sendMsgDelayed(DEFAULT_TIME_OUT);        }        @Override        protected void onLastMessageLively(@NonNull ScrollBindHelper ref, Message msg) {            ref.hideScroll();        }    }    private VisibleHandler handler = new VisibleHandler(this);    private void hideScroll() {        ViewUtil.hideWithAnim(seekBar, 0);    }    private void showScroll() {        ViewUtil.showWithAnim(seekBar, 0);    }}

最终效果图如下:

以下是源码地址,部分代码和博客中的可能有所出入:
demo下载地址:http://download.csdn.net/detail/drkcore/9505456

更多相关文章

  1. Android(安卓)JNI操作Bitmap实现黑白图片
  2. Android《第五章:ContentProvider》
  3. Android之TextView实现文字过长时省略部分或者滚动显示
  4. Android(安卓)Room牛刀小试
  5. 【Android(安卓)Developers Training】 16. 暂停和恢复一个Activ
  6. Android--Task(stack)的使用
  7. 安卓数据库连接解决办法 ,避免 sqlite3 database is locked
  8. Android(安卓)View的滚动scroll 、android.widget.Scroller和 属
  9. Android(安卓)Scroller的理解

随机推荐

  1. Android sensor 学习--sensor介绍
  2. Android的权限 permission
  3. 在android获取root权限的方法^_^。
  4. Error:(19, 0) Gradle DSL method not fo
  5. Android SDK Manager解决更新时的问题 :Fa
  6. FFmpeg Android编译运行出现 Abort messa
  7. Android-->build.gradle-->buildTypes
  8. JNI在Android中的简单使用
  9. 2011.07.08——— android n获得壁纸
  10. Android 软键盘那点事