上一篇我们已经基本实现了下拉刷新的效果,本篇介绍,嵌套ListView,RecyclerView以及ScrollView时的滑动冲突解决方式。
嵌套ListView
我们先看看嵌套了ListView的效果:
在嵌套ListView时,我们的父布局要在什么时候拦截呢?
当ListView滑到顶部时,父布局才能下拉。
当ListView滑到底部时,父布局才能上拉。
那如何判断ListView到达顶部以及底部呢?
第一个可见Item的位置为 0 即 firstVisibleItem=0,并且 firstVisibleitemView.getTop() == 0,ListView的第一个Item的高度为0
if (firstVisibleItem == 0) {
View firstVisibleitemView = listView.getChildAt(0);
if (firstVisibleitemView != null && firstVisibleitemView.getTop() == 0) {
Log.d(TAG, "onScroll: 滑动到顶部 ");
}
同理,我们判断到达底部的代码为
if ((firstVisibleItem + visibleItemCount) == totalItemCount) { //第一个可见Item的位置和总的可见数相加
View lastVisibleItemView = listView.getChildAt(listView.getChildCount() - 1);
if (lastVisibleItemView != null && lastVisibleItemView.getBottom() == listView.getHeight()) {
Log.d(TAG, "onScroll: 滑动到底部 ->"+lastVisibleItemView.getBottom());
}
}
代码
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {
View firstVisibleitemView = listView.getChildAt(0);
if (firstVisibleitemView != null && firstVisibleitemView.getTop() == 0) {
Log.d(TAG, "onScroll: 滑动到顶部 ");
}
} else if ((firstVisibleItem + visibleItemCount) == totalItemCount) { //第一个可见Item的位置和总的可见数相加
View lastVisibleItemView = listView.getChildAt(listView.getChildCount() - 1);
if (lastVisibleItemView != null && lastVisibleItemView.getBottom() == listView.getHeight()) {
Log.d(TAG, "onScroll: 滑动到底部 ->"+lastVisibleItemView.getBottom());
}
}
}
});
上面的代码是我们在Activity中的使用方法。我们把这个思路换到我们的自定义View中,如下,当滑动到ListView的顶部和底部时,我们在ACTION_MOVE进行拦截。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMoveY = y;
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
int deltaY = mLastMoveY - y;
//下拉
if (deltaY < 0) {
//获取最顶部的子View
View child = getChildAt(0);
if (child instanceof AbsListView) {
AbsListView listView = (AbsListView) child;
if (listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() == 0) {
intercept = true;
}
}
} else { //上拉
//获取最底部的View
View child = getChildAt(lastChildIndex);
if (child instanceof AbsListView) {
AbsListView listView = (AbsListView) child;
Log.d(TAG, "onInterceptTouchEvent: " + listView.getCount());
if (listView.getLastVisiblePosition() == listView.getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() == listView.getMeasuredHeight()) {
intercept = true;
}
}
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
return intercept;
}
嵌套RecyclerView
//下拉
if (child instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) child;
if (!recyclerView.canScrollVertically(-1)) {//不能再向上滑
intercept = true;
}
//上拉
if (child instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) child;
if (!recyclerView.canScrollVertically(1)) {//不能再向下滑
intercept = true;
}
利用了View的一个方法。public boolean canScrollVertically (int direction)
这个方法是判断View在竖直方向是否还能 向上,向下 滑动。
根据上面的例子,应该可以看出。 -1 表示 向上, 1 表示向下。
当RecycleView不能再向上滑时,表示已经到达顶部,
当RecycleView不能再向下滑时,表示已经到达底部。
嵌套ScrollView
判断顶部
当我们的ScrollView.getScrollerY < = 0的时候就能判定ScrollView到达了顶部。
if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
if (scrollView.getScrollY() <= 0) {
intercept = true;
}
}
判断底部
当我们的ScrollView.getScrollerY+getHeight > = ScrollView.getChildAt(0).getHeight ,即大于总的ScrollView的长度时,判定ScrollView到达底部。
if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
View scrollChild = scrollView.getChildAt(0);
if (scrollView.getScrollY() + getHeight() >= scrollChild.getHeight()) {
intercept = true;
}
}
全部代码,主要变更的是onInterceptTouchEvent
public class SimpleRefreshLayout extends ViewGroup {
private View mHeader;
private View mFooter;
private TextView pullText;
private onRefreshListener mRefreshListener;
private int mLastMoveY;
private int effectiveScrollY = 100;
private Scroller mLayoutScroller;
private boolean isPullDown = false;
private int mLayoutContentHeight;
private int lastChildIndex;
private String TAG = "SimpleRefreshLayout";
public SimpleRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mHeader = LayoutInflater.from(context).inflate(R.layout.item_header_layout, null);
pullText = mHeader.findViewById(R.id.srl_tv_pull_down);
mFooter = LayoutInflater.from(context).inflate(R.layout.item_footer_layout, null);
mLayoutScroller = new Scroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
lastChildIndex = getChildCount() - 1;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mHeader.setLayoutParams(params);
mFooter.setLayoutParams(params);
addView(mHeader);
addView(mFooter);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量子类
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
//布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mLayoutContentHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child == mHeader) {
child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
} else if (child == mFooter) {
child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), child.getMeasuredHeight() + mLayoutContentHeight);
} else {//内容
child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
mLayoutContentHeight += child.getMeasuredHeight();
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMoveY = y;
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
int deltaY = mLastMoveY - y;
//下拉
if (deltaY < 0) {
//获取最顶部的子View
View child = getChildAt(0);
if (child instanceof AbsListView) {
AbsListView listView = (AbsListView) child;
if (listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() == 0) {
intercept = true;
}
} else if (child instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) child;
if (!recyclerView.canScrollVertically(-1)) {//不能再向上滑
intercept = true;
}
} else if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
if (scrollView.getScrollY() <= 0) {
intercept = true;
}
}
} else { //上拉
//获取最底部的View
View child = getChildAt(lastChildIndex);
if (child instanceof AbsListView) {
AbsListView listView = (AbsListView) child;
Log.d(TAG, "onInterceptTouchEvent: " + listView.getCount());
if (listView.getLastVisiblePosition() == listView.getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() == listView.getMeasuredHeight()) {
intercept = true;
}
} else if (child instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) child;
if (!recyclerView.canScrollVertically(1)) {//不能再向下滑
intercept = true;
}
} else if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
View scrollChild = scrollView.getChildAt(0);
if (scrollView.getScrollY() + getHeight() >= scrollChild.getHeight()) {
intercept = true;
}
}
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMoveY = y;
break;
case MotionEvent.ACTION_MOVE:
int dy = mLastMoveY - y;
if (dy < 0) {//下拉
isPullDown = true;
if (Math.abs(getScrollY()) <= mHeader.getMeasuredHeight() / 2) {
scrollBy(0, dy);
if (Math.abs(getScrollY()) >= effectiveScrollY) {
pullText.setText("松开刷新");
}
}
} else {//上滑
if (Math.abs(getScrollY()) + Math.abs(dy) < mFooter.getMeasuredHeight() / 2) {
scrollBy(0, dy);
isPullDown = false;
}
}
break;
case MotionEvent.ACTION_UP:
if (isPullDown) {
if (Math.abs(getScrollY()) >= effectiveScrollY) {
if (mRefreshListener != null) {
mRefreshListener.onRefresh();
}
mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY() - effectiveScrollY);
invalidate();
} else {
mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY());
invalidate();
}
} else {
if (Math.abs(getScrollY()) >= effectiveScrollY) {
if (mRefreshListener != null) {
mRefreshListener.onBottomRefresh();
}
mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY() + effectiveScrollY);
invalidate();
} else {
mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY());
invalidate();
}
}
break;
}
mLastMoveY = y;
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mLayoutScroller.computeScrollOffset()) {
scrollTo(0, mLayoutScroller.getCurrY());
}
invalidate();
}
public void stopRefresh() {
mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY());
invalidate();
}
public interface onRefreshListener {
void onRefresh();
void onBottomRefresh();
}
public void setRefreshListener(onRefreshListener listener) {
mRefreshListener = listener;
}
}
下一篇我们将对上述代码进行优化,更改上拉和下拉的动态效果。并参考SwipeRefreshLayout对组件进行修改。