首先要说说MyScrollView是什么东西,其实这个控件大家用过的话都会了解,Android原生自带的ScrollView中如果包裹了RecyclerView或者ListView等控件的话,那么运行效果不尽如人意,主要体现在RecyclerView或ListView等数据不能完全显示出来,这是因为系统内部对这些并没有处理,所以这个时候MyScrollView基于ScrollView进行了改造,使得我们的数据能够完全显示并且没有滑动冲突等问题,这类代码网上有很多,我这里只简单的提供一个供大家参考:
public class MyScrollView extends ScrollView {
private List<OnScrollListener> onScrollListeners;
//滑动开始、结束状态回调
private List<OnScrollStateListener> mOnScrollStateListeners;
/** 当前滑动到y坐标 */
private int mCurrentY;
/** 滑动到底部监听器 */
private OnScrollToBottomListener mOnScrollToBottom;
/** 滑动到底部监听器 */
public interface OnScrollToBottomListener {
void onScrollBottom();
}
/**
* 主要是用在用户手指离开MyScrollView,MyScrollView还在继续滑动,我们用来保存Y的距离,然后做比较
*/
private int lastScrollY;
private long DELAY_TIME = 100; //延迟判断滑动是否结束
private long lastScrollTime = -1; //上次滑动时间
/**
* 用于用户手指离开MyScrollView的时候获取MyScrollView滚动的Y距离,然后回调给onScroll方法中
*/
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
int scrollY = MyScrollView.this.getScrollY();
// 此时的距离和记录下的距离不相等,在隔5毫秒给handler发送消息
if (lastScrollY != scrollY) {
lastScrollY = scrollY;
handler.sendMessageDelayed(handler.obtainMessage(), 5);
}
if (onScrollListeners != null) {
for (OnScrollListener listner : onScrollListeners) {
listner.onScroll(scrollY);
}
}
}
;
};
//通过延时时间判断onScroll函数是否执行
private Runnable scrollTask = new Runnable() {
@Override public void run() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastScrollTime > DELAY_TIME) {
lastScrollTime = -1;
onScrollStop();
} else {
//滑动中
handler.postDelayed(this, DELAY_TIME);
}
}
};
//开始滑动
private void onScrollStart() {
if (mOnScrollStateListeners == null) {
return;
}
for (OnScrollStateListener listener:mOnScrollStateListeners) {
listener.onScrollStart();
}
}
//停止滑动
private void onScrollStop() {
if (mOnScrollStateListeners == null) {
return;
}
for (OnScrollStateListener listener:mOnScrollStateListeners) {
listener.onScrollStop();
}
}
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 设置滑动状态监听
*/
public void setOnScrollStateListener(OnScrollStateListener listener) {
if (mOnScrollStateListeners == null) {
mOnScrollStateListeners = new ArrayList<>();
}
if (listener != null) {
mOnScrollStateListeners.add(listener);
}
}
public void removeScrollStateListener(OnScrollStateListener listener) {
if (listener != null && mOnScrollStateListeners != null) {
mOnScrollStateListeners.remove(listener);
}
}
/**
* 设置滚动接口
*/
public void setOnScrollListener(OnScrollListener onScrollListener) {
if (onScrollListeners == null) {
onScrollListeners = new ArrayList<>();
}
onScrollListeners.add(onScrollListener);
}
public void removeAllScrollListener() {
if (onScrollListeners != null) {
onScrollListeners.clear();
}
}
public void setOnScrollToBottomListener(OnScrollToBottomListener listener) {
mOnScrollToBottom = listener;
}
private float xDistance, yDistance, lastX, lastY;
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (xDistance > yDistance) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
/**
* 重写onTouchEvent, 当用户的手在MyScrollView上面的时候,
* 直接将MyScrollView滑动的Y方向距离回调给onScroll方法中,当用户抬起手的时候,
* MyScrollView可能还在滑动,所以当用户抬起手我们隔5毫秒给handler发送消息,在handler处理
* MyScrollView滑动的距离
*/
@Override public boolean onTouchEvent(MotionEvent ev) {
if (onScrollListeners != null) {
for (OnScrollListener listner : onScrollListeners) {
listner.onScroll(lastScrollY = this.getScrollY());
listner.onTouchEvent(ev);
}
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
handler.sendMessageDelayed(handler.obtainMessage(), 20);
break;
}
return super.onTouchEvent(ev);
}
public interface OnScrollStateListener {
void onScrollStart(); //开始滑动
void onScrollStop(); //停止滑动
}
/**
* 滚动的回调接口
*/
public interface OnScrollListener {
/**
* 回调方法, 返回MyScrollView滑动的Y方向距离
*/
public void onScroll(int scrollY);
/**
* 回调方法, 返回MyScrollView滑动到Y方向上的位置
*/
public void onScrollPosition(int position);
public void onTouchEvent(MotionEvent ev);
}
@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if (onScrollListeners != null) {
for (OnScrollListener listner : onScrollListeners) {
int scrollY = getScrollY();
listner.onScrollPosition(scrollY);
}
}
if (lastScrollTime == -1) {
onScrollStart();
postDelayed(scrollTask, DELAY_TIME);
}
lastScrollTime = System.currentTimeMillis(); //更新scrollView的滑动时间
super.onScrollChanged(l, t, oldl, oldt);
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
//下滑
if (scrollY > mCurrentY && scrollY > 0 && clampedY) {
if (null != mOnScrollToBottom) {
mOnScrollToBottom.onScrollBottom();
}
}
mCurrentY = scrollY;
}
}
但是如果使用起来的话还是会发现另一个问题,那就是当其中的RecyclerView的数据进行刷新或者发生变化时,这个时候会发现页面内容会滑动到RecyclerView的顶部,而它上面的内容会滑出屏幕,这个原因主要是因为RecyclerView获取到了焦点,从而进行了一个滑动的操作,要想解决这个问题的方法也很简单,那就是在xml文件中MyScrollView下的第一个子节点添加2个属性,举个例子:
<com.view.MyScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:focusable="true"
android:focusableInTouchMode="true"
android:paddingBottom="24dp">
......
其中
android:focusable=“true”
android:focusableInTouchMode=“true”
这2个属性表示LinearLayout会获取焦点,这个时候RecyclerView就不会拿到焦点了,从而在数据发生变化时不会自动的滑动到列表的顶部。
良好的记录习惯,方便他人,也方便自己~