Android scrollToTop實(shí)現(xiàn)點(diǎn)擊回到頂部(兼容PullTorefreshScrollview)
前言
最近因?yàn)轫?xiàng)目組需求,特研究了一下“回到頂部”效果,即:頁(yè)面里有scrollview,內(nèi)容很多,當(dāng)滑動(dòng)到頁(yè)面下面或者更深時(shí),需要回到頂部,即可點(diǎn)擊出現(xiàn)的按鈕,省得回滑N久。我沒(méi)有搜,或許網(wǎng)上有很多這樣的例子,此文寫的不好的地方,望指點(diǎn)。
效果圖如下

實(shí)現(xiàn)方法
初一看是不是覺(jué)得很簡(jiǎn)答?沒(méi)錯(cuò),當(dāng)時(shí)我也是這樣想的頁(yè)面內(nèi)容很長(zhǎng),就弄個(gè)scrollview,回到頂部按鈕需要固定在右下角,故大概的布局代碼:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.znke.pulltorefresh_top.MainActivity">
<!--<com.znke.pulltorefresh_top.tool.ToTopScrollView .../>-->
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_top"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="111111111111"
android:textSize="20sp" />
...........
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#AAAAAA" />
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:text="12000000000000"
android:textSize="20sp" />
</LinearLayout>
</ScrollView>
<com.znke.pulltorefresh_top.tool.ToTopImageView
android:id="@+id/imageView_to_top"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/to_top" />
</RelativeLayout>
然后在activity中設(shè)置下面事件不就完了嘛!?。?/p>
mScrollView.setOnScrollChangeListener();
很遺憾,報(bào)錯(cuò)。alt+enter搞定錯(cuò)誤不就完了嘛!很遺憾,運(yùn)行繼續(xù)報(bào)錯(cuò),報(bào)空指針,神奇吧,找不出來(lái)。翻閱資料后發(fā)現(xiàn),scrollview的onScrollChanged方法是受保護(hù)的。故按照網(wǎng)上的資料,自定義ScrollView類,暴露出onScrollChanged方法:
public class ToTopScrollView extends ScrollView {
private OnMyScrollListener onMyScrollListener;
public void setOnMyScrollListener(OnMyScrollListener onMyScrollListener) {
this.onMyScrollListener = onMyScrollListener;
}
...
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(onMyScrollListener != null)
onMyScrollListener.onScrollChanged(l,t,oldl,oldt);
}
public interface OnMyScrollListener{
void onScrollChanged(int x, int y, int oldx, int oldy);
}
}
然后在activity中設(shè)置setOnMyScrollListener()方法,就可以監(jiān)控scrollview的狀態(tài)了。剩下的就是自定義imageview的邏輯了。
可是,自定義ToTopImageView里面怎么弄呢?它需要有一個(gè)高度臨界值,與activity傳遞scrollview的scrollY值比較,來(lái)判定ToTopImageView是否顯示。代碼簡(jiǎn)單,搞定。
只是這樣有個(gè)小問(wèn)題,onScrollChanged方法是監(jiān)控滾動(dòng)狀態(tài)的,沒(méi)有說(shuō)停止。如果在里面判斷超過(guò)臨界值就顯示與隱藏imageview,那么會(huì)一直設(shè)置imageview。這樣肯定不是最佳的方法。如果能在滾動(dòng)停止時(shí)再判定是否需要顯示與隱藏imageview就好了。此時(shí)我還沒(méi)有想太多,動(dòng)手簡(jiǎn)單實(shí)現(xiàn)了剛才的分析。
實(shí)現(xiàn)后,感覺(jué)挺爽。然而在準(zhǔn)備加到項(xiàng)目中時(shí)發(fā)現(xiàn),我們項(xiàng)目用了PullToRefresh刷新代碼庫(kù),也簡(jiǎn)單,把自定義的Scrollview替換就可以了。運(yùn)行,糟糕,沒(méi)有效果,然后調(diào)試,事件沒(méi)有處罰,可能是PullToRefresh庫(kù)把事件屏蔽了,咋辦?找onTouchListener監(jiān)聽方法唄。
于是改用了測(cè)試:
mScrollView.setOnTouchListener()
我去,沒(méi)有調(diào)傭,把pullToRefreshScrollView里面各種監(jiān)聽方法都試遍了,沒(méi)用。他只提供了頂部和底部的上拉、下拉刷新監(jiān)聽,毛用。
于是查看其源碼,發(fā)現(xiàn)把事件攔截了。而且pullToRefreshScrollView根本就不是scrollview,看源碼:
@Override
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
ScrollView scrollView;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
scrollView = new InternalScrollViewSDK9(context, attrs);
} else {
scrollView = new ScrollView(context, attrs);
}
scrollView.setId(R.id.scrollview);
return scrollView;
}
他里面提供了一個(gè)方法可以或得到Scrollview:
final ScrollView scrollView = mScrollView.getRefreshableView();
回想起了以前項(xiàng)目也這么用過(guò),只是當(dāng)時(shí)不明白這句話干啥的,白寫。
然后就用上面這種方法得到scrollview,再設(shè)置onscrollchanged方法監(jiān)聽滑動(dòng)。運(yùn)行報(bào)錯(cuò),同樣無(wú)效。于是只能換onTouchListener監(jiān)聽了。
但是這樣問(wèn)題來(lái)了,我們只能監(jiān)聽到手勢(shì),即何時(shí)按下、移動(dòng)和彈起。當(dāng)快速滑動(dòng)手指彈起后,scrollview還在滾動(dòng)的,什么時(shí)候去拿到它的scrollY值呢?犯愁了。
此時(shí)不得不用最開始分析的方法,在自定義imageview里面定義線程,掃描當(dāng)前scrollY和上一次保存的對(duì)比,不一樣即說(shuō)明仍在滾動(dòng),一樣即表明scrollview滾動(dòng)停止了。
什么時(shí)候開啟線程呢?在onTouch回調(diào)中down、move或者up時(shí)調(diào)用。
試想下:
如果在down中調(diào)用時(shí),用戶只在scrollview上點(diǎn)擊或短距離滑動(dòng),imageview里面要不停地開啟線程?浪費(fèi)資源。
如果在up中調(diào)用時(shí),當(dāng)用戶按著屏幕一口氣滑過(guò)臨界值,還不松手呢?還不顯示imageview嗎?也行,個(gè)人覺(jué)得不太好。
于是,我選擇在move中調(diào)用imageview地線程。有人會(huì)想,這樣會(huì)不會(huì)啟動(dòng)N多個(gè)線程呢?move一直在移動(dòng)呢?!霸趇amgeview判斷下線程的狀態(tài)即可,如果已經(jīng)啟動(dòng)了,就不啟動(dòng)唄”。或許這么寫不太好,但我認(rèn)為是實(shí)時(shí)的,用戶體驗(yàn)好。
看代碼:
/**
* 獲取待監(jiān)控的view對(duì)象
* 實(shí)時(shí)調(diào)起線程,監(jiān)控是否scroll停止,來(lái)判斷是否需要顯示imageView
* @param targetView 需要監(jiān)控的對(duì)象
*/
public void tellMe(View targetView) {
if (targetView == null)
throw new IllegalArgumentException("please set targetView who to scrollTo");
if (this.targetView == null)
this.targetView = targetView;
if (!isStarting) {
new Thread(scanThread).start();
}
}
此處注意,我偷懶了,沒(méi)有單獨(dú)設(shè)置方法傳遞需要滾動(dòng)的scrollview,在此處引進(jìn)來(lái)了。線程加了判斷。此處不要傳遞scrollview的scrollY值進(jìn)來(lái)。比喻當(dāng)你手指離開屏幕后,之前傳遞進(jìn)來(lái)的scrollY就已經(jīng)過(guò)時(shí)了,scrollview仍在滑動(dòng)。在消息回調(diào)里面實(shí)時(shí)獲取再判斷
private class MyCallback implements Runnable {
@Override
public void run() {
/**
* 獲取實(shí)時(shí)的卷動(dòng)值,不要傳遞scroll值給我
*/
endScrollX = targetView.getScrollX();
int scrollY = targetView.getScrollY();
if (endScrollY != scrollY) {
endScrollY = scrollY;
} else {
if (endScrollY >= limitHeight) {
if (!thisStateVisible)
visible();
} else {
if (thisStateVisible)
gone();
}
/**
* 已判定,卷動(dòng)停止,顯示或隱藏當(dāng)前view已完成
* 退出監(jiān)控scroll線程
*/
clearCallBacks();
}
}
}
由于是用線程來(lái)檢測(cè)scrollview的滾動(dòng)狀態(tài),我用了延時(shí)消息。此時(shí)又有另外一個(gè)潛在bug。在自定義imageview中創(chuàng)建了handler屬于主線程,子線程中需要發(fā)延時(shí)消息。如果延時(shí)消息發(fā)出后,activity退出了呢?反復(fù)這么弄呢?有人會(huì)說(shuō)沒(méi)誰(shuí)會(huì)這么無(wú)聊的。但這畢竟還是潛在的OOM。于是我簡(jiǎn)單的做了線程控制和消息清除的代碼,過(guò)于簡(jiǎn)單。感謝評(píng)論。
主要思路和邏輯都分析完了,使用起來(lái)很簡(jiǎn)答,你不用關(guān)心自定義imageview里面的邏輯(除非你想改進(jìn))。
在activity中
private PullToRefreshScrollView mScrollView;
private ToTopImageView imageView_to_top;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pull_to_refresh_scroll_view);
imageView_to_top = (ToTopImageView) findViewById(R.id.imageView_to_top);
imageView_to_top.setLimitHeight(800);
mScrollView = (PullToRefreshScrollView) findViewById(R.id.scrollView);
final ScrollView scrollView = mScrollView.getRefreshableView();
//mScrollView.setOnTouchListener(); 無(wú)效
scrollView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
imageView_to_top.tellMe(scrollView);
break;
}
return false;
}
});
}
@Override
protected void onDestroy() {
imageView_to_top.clearCallBacks();
super.onDestroy();
}
頁(yè)面上,在你覺(jué)得合適的位置:
<com.znke.pulltorefresh_top.tool.ToTopImageView android:id="@+id/imageView_to_top" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginBottom="5dp" android:layout_marginRight="5dp" android:background="@drawable/to_top" />
完事。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開發(fā)者們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- android使用Ultra-PullToRefresh實(shí)現(xiàn)下拉刷新自定義代碼
- Android使用PullToRefresh完成ListView下拉刷新和左滑刪除功能
- Android開源項(xiàng)目PullToRefresh下拉刷新功能詳解
- Android下拉刷新控件PullToRefresh實(shí)例解析
- Android使用PullToRefresh實(shí)現(xiàn)上拉加載和下拉刷新效果的代碼
- Android實(shí)現(xiàn)簡(jiǎn)單的下拉刷新pulltorefresh
- Android程序開發(fā)之使用PullToRefresh實(shí)現(xiàn)下拉刷新和上拉加載
- Android PullToRefreshLayout下拉刷新控件的終結(jié)者
- 分享Android中pullToRefresh的使用心得
- android使用PullToRefresh框架實(shí)現(xiàn)ListView下拉刷新上拉加載更多
相關(guān)文章
Android控件PullRefreshViewGroup實(shí)現(xiàn)下拉刷新和上拉加載
這篇文章主要為大家詳細(xì)介紹了Android控件PullRefreshViewGroup實(shí)現(xiàn)下拉刷新和上拉加載效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android實(shí)現(xiàn)注冊(cè)登錄界面的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)注冊(cè)登錄界面的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
android設(shè)置adb自帶screenrecord錄屏命令
這篇文章主要介紹了android設(shè)置adb自帶screenrecord錄屏命令,需要的朋友可以參考下2018-11-11
Android頂部工具欄和底部工具欄的簡(jiǎn)單實(shí)現(xiàn)代碼
Android頂部工具欄和底部工具欄的簡(jiǎn)單實(shí)現(xiàn)代碼,需要的朋友可以參考一下2013-05-05
java,Android:在eclipse中的快捷鍵(經(jīng)典收藏)
下面的快捷鍵是常用的,本人就本身喜好且常用的收拾一下,現(xiàn)在曬出來(lái)與大家分享,感興趣的朋友可以了解小哦2013-01-01
詳解Android中Runtime解決屏幕旋轉(zhuǎn)問(wèn)題(推薦)
這篇文章主要介紹了Runtime解決屏幕旋轉(zhuǎn)問(wèn)題的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09

