Android scrollToTop实现点击回到顶部(兼容PullTorefreshScrollview)
前言
最近因为项目组需求,特研究了一下“回到顶部”效果,即:页面里有scrollview,内容很多,当滑动到页面下面或者更深时,需要回到顶部,即可点击出现的按钮,省得回滑n久。我没有搜,或许网上有很多这样的例子,此文写的不好的地方,望指点。
效果图如下
实现方法
初一看是不是觉得很简答?没错,当时我也是这样想的页面内容很长,就弄个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中设置下面事件不就完了嘛!!!
mscrollview.setonscrollchangelistener();
很遗憾,报错。alt+enter搞定错误不就完了嘛!很遗憾,运行继续报错,报空指针,神奇吧,找不出来。翻阅资料后发现,scrollview的onscrollchanged方法是受保护的。故按照网上的资料,自定义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中设置setonmyscrolllistener()方法,就可以监控scrollview的状态了。剩下的就是自定义imageview的逻辑了。
可是,自定义totopimageview里面怎么弄呢?它需要有一个高度临界值,与activity传递scrollview的scrolly值比较,来判定totopimageview是否显示。代码简单,搞定。
只是这样有个小问题,onscrollchanged方法是监控滚动状态的,没有说停止。如果在里面判断超过临界值就显示与隐藏imageview,那么会一直设置imageview。这样肯定不是最佳的方法。如果能在滚动停止时再判定是否需要显示与隐藏imageview就好了。此时我还没有想太多,动手简单实现了刚才的分析。
实现后,感觉挺爽。然而在准备加到项目中时发现,我们项目用了pulltorefresh刷新代码库,也简单,把自定义的scrollview替换就可以了。运行,糟糕,没有效果,然后调试,事件没有处罚,可能是pulltorefresh库把事件屏蔽了,咋办?找ontouchlistener监听方法呗。
于是改用了测试:
mscrollview.setontouchlistener()
我去,没有调佣,把pulltorefreshscrollview里面各种监听方法都试遍了,没用。他只提供了顶部和底部的上拉、下拉刷新监听,毛用。
于是查看其源码,发现把事件拦截了。而且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; }
他里面提供了一个方法可以或得到scrollview:
final scrollview scrollview = mscrollview.getrefreshableview();
回想起了以前项目也这么用过,只是当时不明白这句话干啥的,白写。
然后就用上面这种方法得到scrollview,再设置onscrollchanged方法监听滑动。运行报错,同样无效。于是只能换ontouchlistener监听了。
但是这样问题来了,我们只能监听到手势,即何时按下、移动和弹起。当快速滑动手指弹起后,scrollview还在滚动的,什么时候去拿到它的scrolly值呢?犯愁了。
此时不得不用最开始分析的方法,在自定义imageview里面定义线程,扫描当前scrolly和上一次保存的对比,不一样即说明仍在滚动,一样即表明scrollview滚动停止了。
什么时候开启线程呢?在ontouch回调中down、move或者up时调用。
试想下:
如果在down中调用时,用户只在scrollview上点击或短距离滑动,imageview里面要不停地开启线程?浪费资源。
如果在up中调用时,当用户按着屏幕一口气滑过临界值,还不松手呢?还不显示imageview吗?也行,个人觉得不太好。
于是,我选择在move中调用imageview地线程。有人会想,这样会不会启动n多个线程呢?move一直在移动呢。“在iamgeview判断下线程的状态即可,如果已经启动了,就不启动呗”。或许这么写不太好,但我认为是实时的,用户体验好。
看代码:
/** * 获取待监控的view对象 * 实时调起线程,监控是否scroll停止,来判断是否需要显示imageview * @param targetview 需要监控的对象 */ 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(); } }
此处注意,我偷懒了,没有单独设置方法传递需要滚动的scrollview,在此处引进来了。线程加了判断。此处不要传递scrollview的scrolly值进来。比喻当你手指离开屏幕后,之前传递进来的scrolly就已经过时了,scrollview仍在滑动。在消息回调里面实时获取再判断
private class mycallback implements runnable { @override public void run() { /** * 获取实时的卷动值,不要传递scroll值给我 */ endscrollx = targetview.getscrollx(); int scrolly = targetview.getscrolly(); if (endscrolly != scrolly) { endscrolly = scrolly; } else { if (endscrolly >= limitheight) { if (!thisstatevisible) visible(); } else { if (thisstatevisible) gone(); } /** * 已判定,卷动停止,显示或隐藏当前view已完成 * 退出监控scroll线程 */ clearcallbacks(); } } }
由于是用线程来检测scrollview的滚动状态,我用了延时消息。此时又有另外一个潜在bug。在自定义imageview中创建了handler属于主线程,子线程中需要发延时消息。如果延时消息发出后,activity退出了呢?反复这么弄呢?有人会说没谁会这么无聊的。但这毕竟还是潜在的oom。于是我简单的做了线程控制和消息清除的代码,过于简单。感谢评论。
主要思路和逻辑都分析完了,使用起来很简答,你不用关心自定义imageview里面的逻辑(除非你想改进)。
在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(); 无效 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(); }
页面上,在你觉得合适的位置:
<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" />
完事。
总结
以上就是这篇文章的全部内容了,希望本文的内容对各位android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。
下一篇: 手机/移动前端开发需要注意的20个要点