软件编程
位置:首页>> 软件编程>> Android编程>> Android scrollToTop实现点击回到顶部(兼容PullTorefreshScrollview)

Android scrollToTop实现点击回到顶部(兼容PullTorefreshScrollview)

作者:daisy  发布时间:2021-07-29 09:06:37 

标签:android,回到顶部,PullTorefreshScrollview

前言

最近因为项目组需求,特研究了一下“回到顶部”效果,即:页面里有scrollview,内容很多,当滑动到页面下面或者更深时,需要回到顶部,即可点击出现的按钮,省得回滑N久。我没有搜,或许网上有很多这样的例子,此文写的不好的地方,望指点。

效果图如下

Android scrollToTop实现点击回到顶部(兼容PullTorefreshScrollview)

实现方法

初一看是不是觉得很简答?没错,当时我也是这样想的页面内容很长,就弄个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" />

完事。

总结

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com