關(guān)于滑動界面的思考——ScrollView的狀態(tài)變化

原文:http://blog.csdn.net/CodeTraveller/article/details/50818754
ScrollView的狀態(tài)變化
前言:
所用代碼和及描述僅代表個人觀點晚岭,歡迎交流吃粒。 轉(zhuǎn)載請說明出處:http://blog.csdn.net/CodeTraveller/article/details/50818754
用戶場景:
在公司做一個 Android 項目,相信大家都有一個直觀的體會纸颜,就是一旦某個功能在 iOS 上看上去非常牛X, 負(fù)責(zé) Android 開發(fā)的程序猿就必須想盡辦法把這個相似的功能克隆到 Android 項目中赴恨,所以就是如下設(shè)計:

這里寫圖片描述

在Android中實現(xiàn)上圖功能晤郑,我用一個 ScrollView 包裹了首頁的 Layout,然后通過監(jiān)聽 ScrollView 的各種狀態(tài)來控制下面的那個“返回頂部”的View 的顯示或隱藏负蚊。
首先我們得知道泥技,ScrollView是沒有一個方法能監(jiān)聽它的滑動狀態(tài)的(ListView有這個方法setOnScrollListener())浆兰,和滑動相關(guān)的只有一個記錄屏幕 Touch 事件的setOnTouchListener()方法。
所以最先想到的是通過手指動作來控制 Layout 的顯示珊豹,但是這樣就遇到下面這些問題:
1簸呈, 我們的手指在接觸屏幕后,也許會有抖動現(xiàn)象店茶,這樣在判斷手指上下移動時就會出現(xiàn)錯誤判斷蜕便,這會使 Layout 的滑入劃出非常的突兀;
2贩幻, 我們在 Activity 或 Fragment 中本來就需要處理太多的事轿腺,如果在其中還監(jiān)聽屏幕動作的話两嘴,會讓我們的代(老)碼(板)非常混(生)亂(氣)族壳。
3溶诞, 我僅僅需要一個能夠得到其狀態(tài)的 ScrollView ,不希望讓控件的使用者看到太多的判斷用戶動作的邏輯决侈,并且這樣一個可得到其狀態(tài)的 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滑動時的五種狀態(tài)

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

這個借口返回一個是狀態(tài)值枉圃,一個是滑動條的ScrollY的值,通過這個兩個值庐冯,用戶可以知道滑動條具體滑動到哪里以及用戶的滑動動作是什么孽亲。

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

private void checkScrollAction() {
  //判斷用戶是否設(shè)置監(jiān)聽以及是否對該方法上鎖
    if (onScrollActionListener == null || msgLock)
        return;
        //創(chuàng)建handler來對該方法上鎖,防止多次執(zhí)行
    if (handler == null)
        handler = new ScrollHandler();
        //初始化ScrollView動作及當(dāng)前的Y軸位置
    int action = -0x1024;
    int scrollY = getScrollY();
    //獲得子布局高度
    View childView = getChildAt(0);
    if (getScrollY() <= 0) {
      //如果當(dāng)前位置在頂部
        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) {
      //發(fā)送Handler消息來通知接口展父,ScrollBar更新了狀態(tài)
        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);
    //每次更新了狀態(tài)返劲,需要將lastScrollY設(shè)置為結(jié)束時的ScrollY
    lastScrollY = getScrollY();
}

這個是最關(guān)鍵的方法,onScrollChanged()方法會在ScrollView滑動時調(diào)用栖茉,并且是滑動過程中實時更新篮绿,所以重寫了這個方法來實時的返回ScrollView的滑動狀態(tài)。

其中吕漂,判斷到達(dá)底部的方法是這樣的:

childView != null && childView.getHeight() <= scrollY + getHeight()
當(dāng)ScrollBar滑動到底部時亲配,如果沒有彈性滑動,scrollY的值就不會變了惶凝,是子布局的高度和ScrollView的高度的差吼虎,但是很多手機(jī)的ScrollView是可以彈性滑動的,這就導(dǎo)致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進(jìn)行刷新操作混滔,是考慮到可能有些用戶不希望實時監(jiān)聽洒疚,又不想重寫OnTouch()方法,所以遍坟,提供一個延遲時間拳亿,在單位延遲時間內(nèi),接口中的onScroll()方法只會執(zhí)行一次愿伴。
總結(jié):
1,重寫onTouchEvent()方法和onScrollChanged()都可以監(jiān)聽用戶動作肺魁,但是onTouchEvent()不是實時監(jiān)聽,它只會監(jiān)聽用戶手指按下到彈起這段時間內(nèi)的動作隔节,onScrollChanged()則是實時監(jiān)聽ScrollBar的狀態(tài)鹅经,只要其狀態(tài)改變寂呛,就會執(zhí)行。
2,如果不希望onScroll()中的方法執(zhí)行多次瘾晃,可以設(shè)置延遲時間贷痪,并為onScroll()里面的方法添加同步塊”奈螅或如例子中劫拢,設(shè)置synchronized 方法。
3,ScrollBar滑動到底部時强胰,scrollY + ScrollView.getHeight()是大于或者等于子控件的高度的(可滑動距離+ScrollView的高度>=ScrollView中布局的高度)舱沧。
項目下載地址: Android-Studio環(huán)境:http://download.csdn.net/detail/codetraveller/9454491

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偶洋,隨后出現(xiàn)的幾起案子熟吏,更是在濱河造成了極大的恐慌,老刑警劉巖玄窝,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牵寺,死亡現(xiàn)場離奇詭異,居然都是意外死亡恩脂,警方通過查閱死者的電腦和手機(jī)帽氓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來东亦,“玉大人杏节,你說我怎么就攤上這事〉湔螅” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵镊逝,是天一觀的道長壮啊。 經(jīng)常有香客問我,道長撑蒜,這世上最難降的妖魔是什么歹啼? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮座菠,結(jié)果婚禮上狸眼,老公的妹妹穿的比我還像新娘。我一直安慰自己浴滴,他們只是感情好拓萌,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著升略,像睡著了一般微王。 火紅的嫁衣襯著肌膚如雪屡限。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天炕倘,我揣著相機(jī)與錄音钧大,去河邊找鬼。 笑死罩旋,一個胖子當(dāng)著我的面吹牛啊央,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涨醋,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劣挫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了东帅?” 一聲冷哼從身側(cè)響起压固,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靠闭,沒想到半個月后帐我,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡愧膀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年拦键,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檩淋。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡芬为,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蟀悦,到底是詐尸還是另有隱情媚朦,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布日戈,位于F島的核電站询张,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浙炼。R本人自食惡果不足惜份氧,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弯屈。 院中可真熱鬧蜗帜,春花似錦、人聲如沸资厉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至店归,卻和暖如春阎抒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背消痛。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工且叁, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秩伞。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓逞带,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纱新。 傳聞我的和親對象是個殘疾皇子展氓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容