关于滑动界面的思考——ScrollView的状态变化

原文:http://blog.csdn.net/CodeTraveller/article/details/50818754
ScrollView的状态变化
前言:
所用代码和及描述仅代表个人观点,欢迎交流。 转载请说明出处:http://blog.csdn.net/CodeTraveller/article/details/50818754
用户场景:
在公司做一个 Android 项目,相信大家都有一个直观的体会,就是一旦某个功能在 iOS 上看上去非常牛X, 负责 Android 开发的程序猿就必须想尽办法把这个相似的功能克隆到 Android 项目中,所以就是如下设计:

这里写图片描述

在Android中实现上图功能,我用一个 ScrollView 包裹了首页的 Layout,然后通过监听 ScrollView 的各种状态来控制下面的那个“返回顶部”的View 的显示或隐藏。
首先我们得知道,ScrollView是没有一个方法能监听它的滑动状态的(ListView有这个方法setOnScrollListener()),和滑动相关的只有一个记录屏幕 Touch 事件的setOnTouchListener()方法。
所以最先想到的是通过手指动作来控制 Layout 的显示,但是这样就遇到下面这些问题:
1, 我们的手指在接触屏幕后,也许会有抖动现象,这样在判断手指上下移动时就会出现错误判断,这会使 Layout 的滑入划出非常的突兀;
2, 我们在 Activity 或 Fragment 中本来就需要处理太多的事,如果在其中还监听屏幕动作的话,会让我们的代(老)码(板)非常混(生)乱(气)。
3, 我仅仅需要一个能够得到其状态的 ScrollView ,不希望让控件的使用者看到太多的判断用户动作的逻辑,并且这样一个可得到其状态的 ScrollView 是在很多界面都需要使用的。
代码分析:
废话说了一大堆,下面直接上代码,先来看看新的 ScrollView 的完整代码:

/**
 * @Created by mingweiliao on 16-3-4.
 */
public class ActionScrollView extends ScrollView {

    public final static int ACTION_SCROLL_TOP = -1;
    public final static int ACTION_SCROLL_BOTTOM = 1;
    public final static int ACTION_SCROLL_UP = 10;
    public final static int ACTION_SCROLL_STOP = 11;
    public final static int ACTION_SCROLL_DOWN = 12;

    private final static String TAG_ACTION = "ACTION";
    private final static String TAG_SCROLL_Y = "SCROLL_Y";
    private final static int HANDLE_WHAT_ACTION = 1;
    private final static int HANDLE_WHAT_TIME = 2;
    private final static int msg = 0;
    private long delayedTime = 0;
    private boolean msgLock;

    private int lastScrollY = 0;
    private ScrollHandler handler = new ScrollHandler();

    private class ScrollHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == HANDLE_WHAT_ACTION) {
                int action = msg.getData().getInt(TAG_ACTION);
                int scrollY = msg.getData().getInt(TAG_SCROLL_Y);
                onScrollActionListener.onScroll(action, scrollY);
                msgLock = true;
                handler.sendEmptyMessageDelayed(HANDLE_WHAT_TIME, delayedTime);
            } else if (msg.what == HANDLE_WHAT_TIME) {
                msgLock = false;
                lastScrollY = getScrollY();
            }
        }
    }

    public ActionScrollView(Context context) {
        super(context);
    }

    public ActionScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private OnScrollActionListener onScrollActionListener;

    public interface OnScrollActionListener {
        void onScroll(int action, int scrollY);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        checkScrollAction();
    }

    private void checkScrollAction() {
        if (onScrollActionListener == null || msgLock)
            return;
        if (handler == null)
            handler = new ScrollHandler();
        int action = -0x1024;
        int scrollY = getScrollY();
        View childView = getChildAt(0);
        if (getScrollY() <= 0) {
            action = ACTION_SCROLL_TOP;
        } else if (childView != null && childView.getHeight() <= scrollY + getHeight()) {
            action = ACTION_SCROLL_BOTTOM;
        } else if (scrollY - lastScrollY < 0) {
            action = ACTION_SCROLL_UP;
        } else if (scrollY - lastScrollY > 0) {
            action = ACTION_SCROLL_DOWN;
        } else if (scrollY - lastScrollY == 0) {
            action = ACTION_SCROLL_STOP;
        }
        if (action != -0x1024) {
            Message msg = new Message();
            msg.what = HANDLE_WHAT_ACTION;
            Bundle bundle = new Bundle();
            bundle.putInt(TAG_ACTION, action);
            bundle.putInt(TAG_SCROLL_Y, scrollY);
            msg.setData(bundle);
            handler.sendMessage(msg);
        }
        Log.e("ActionScrollView", "ScrollY : " + getScrollY() + "lastScrollY : " + lastScrollY);
        lastScrollY = getScrollY();
    }

    public void setActionDelayedTime(long delayedTime) {
        this.delayedTime = delayedTime;
    }

    @Override
    protected void onDetachedFromWindow() {
        try {
            Log.e("ScrollView", " onDetachedFromWindow() ");
            handler.removeMessages(0);
            handler = null;
        } catch (Exception e) {

        }
        super.onDetachedFromWindow();
    }

    public void setOnScrollActionListener(OnScrollActionListener listener) {
        this.onScrollActionListener = listener;
    }
}

代码段解析:

 //滑动条滑动到顶部
    public final static int ACTION_SCROLL_TOP = -1;
    //滑动条滑动到底部
    public final static int ACTION_SCROLL_BOTTOM = 1;
    //滑动条在向上划
    public final static int ACTION_SCROLL_UP = 10;
    //滑动条没有滑动(这个白送的)
    public final static int ACTION_SCROLL_STOP = 11;
    //滑动条在向下划
    public final static int ACTION_SCROLL_DOWN = 12;

上面五个常量记录了ScrollView滑动时的五种状态

 public interface OnScrollActionListener {
        void onScroll(int action, int scrollY);
    }
    public void setOnScrollActionListener(OnScrollActionListener listener) {
        this.onScrollActionListener = listener;
    }

这个借口返回一个是状态值,一个是滑动条的ScrollY的值,通过这个两个值,用户可以知道滑动条具体滑动到哪里以及用户的滑动动作是什么。

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        checkScrollAction();
    }

private void checkScrollAction() {
  //判断用户是否设置监听以及是否对该方法上锁
    if (onScrollActionListener == null || msgLock)
        return;
        //创建handler来对该方法上锁,防止多次执行
    if (handler == null)
        handler = new ScrollHandler();
        //初始化ScrollView动作及当前的Y轴位置
    int action = -0x1024;
    int scrollY = getScrollY();
    //获得子布局高度
    View childView = getChildAt(0);
    if (getScrollY() <= 0) {
      //如果当前位置在顶部
        action = ACTION_SCROLL_TOP;
    } else if (childView != null && childView.getHeight() <= scrollY + getHeight()) {
      //如果位置在底部
        action = ACTION_SCROLL_BOTTOM;
    } else if (scrollY - lastScrollY < 0) {
      //如果ScrollBar在上滑
        action = ACTION_SCROLL_UP;
    } else if (scrollY - lastScrollY > 0) {
      //如果ScrollView在下滑
        action = ACTION_SCROLL_DOWN;
    } else if (scrollY - lastScrollY == 0) {
      //如果ScrollView没有动
        action = ACTION_SCROLL_STOP;
    }
    //如果动作不是初始动作(因为我没有做else处理)
    if (action != -0x1024) {
      //发送Handler消息来通知接口,ScrollBar更新了状态
        Message msg = new Message();
        msg.what = HANDLE_WHAT_ACTION;
        Bundle bundle = new Bundle();
        bundle.putInt(TAG_ACTION, action);
        bundle.putInt(TAG_SCROLL_Y, scrollY);
        msg.setData(bundle);
        handler.sendMessage(msg);
    }
    Log.e("ActionScrollView", "ScrollY : " + getScrollY() + "lastScrollY : " + lastScrollY);
    //每次更新了状态,需要将lastScrollY设置为结束时的ScrollY
    lastScrollY = getScrollY();
}

这个是最关键的方法,onScrollChanged()方法会在ScrollView滑动时调用,并且是滑动过程中实时更新,所以重写了这个方法来实时的返回ScrollView的滑动状态。

其中,判断到达底部的方法是这样的:

childView != null && childView.getHeight() <= scrollY + getHeight()
当ScrollBar滑动到底部时,如果没有弹性滑动,scrollY的值就不会变了,是子布局的高度和ScrollView的高度的差,但是很多手机的ScrollView是可以弹性滑动的,这就导致scrollY + getHeight()的值可能大于子布局的高度,所以,这里需要做“<=”的判断。

private ScrollHandler handler = new ScrollHandler();

   private class ScrollHandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           if (msg.what == HANDLE_WHAT_ACTION) {
               int action = msg.getData().getInt(TAG_ACTION);
               int scrollY = msg.getData().getInt(TAG_SCROLL_Y);
               onScrollActionListener.onScroll(action, scrollY);
               msgLock = true;
               handler.sendEmptyMessageDelayed(HANDLE_WHAT_TIME, delayedTime);
           } else if (msg.what == HANDLE_WHAT_TIME) {
               msgLock = false;
               lastScrollY = getScrollY();
           }
       }
   }

这里使用Handler进行刷新操作,是考虑到可能有些用户不希望实时监听,又不想重写OnTouch()方法,所以,提供一个延迟时间,在单位延迟时间内,接口中的onScroll()方法只会执行一次。
总结:
1,重写onTouchEvent()方法和onScrollChanged()都可以监听用户动作,但是onTouchEvent()不是实时监听,它只会监听用户手指按下到弹起这段时间内的动作,onScrollChanged()则是实时监听ScrollBar的状态,只要其状态改变,就会执行。
2,如果不希望onScroll()中的方法执行多次,可以设置延迟时间,并为onScroll()里面的方法添加同步块。或如例子中,设置synchronized 方法。
3,ScrollBar滑动到底部时,scrollY + ScrollView.getHeight()是大于或者等于子控件的高度的(可滑动距离+ScrollView的高度>=ScrollView中布局的高度)。
项目下载地址: Android-Studio环境:http://download.csdn.net/detail/codetraveller/9454491

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容