一步步自定义下拉刷新上拉加载——自定义刷新组件进阶

上一篇我们已经基本实现了下拉刷新的效果,本篇介绍,嵌套ListView,RecyclerView以及ScrollView时的滑动冲突解决方式。

嵌套ListView

我们先看看嵌套了ListView的效果:

在嵌套ListView时,我们的父布局要在什么时候拦截呢?
当ListView滑到顶部时,父布局才能下拉。
当ListView滑到底部时,父布局才能上拉。
那如何判断ListView到达顶部以及底部呢?

第一个可见Item的位置为 0 即 firstVisibleItem=0,并且 firstVisibleitemView.getTop() == 0,ListView的第一个Item的高度为0

1
2
3
4
5
if (firstVisibleItem == 0) {
View firstVisibleitemView = listView.getChildAt(0);
if (firstVisibleitemView != null && firstVisibleitemView.getTop() == 0) {
Log.d(TAG, "onScroll: 滑动到顶部 ");
}

同理,我们判断到达底部的代码为

1
2
3
4
5
6
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());
}
}

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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进行拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
//下拉
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到达了顶部。

1
2
3
4
5
6
if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
if (scrollView.getScrollY() <= 0) {
intercept = true;
}
}

判断底部

当我们的ScrollView.getScrollerY+getHeight > = ScrollView.getChildAt(0).getHeight ,即大于总的ScrollView的长度时,判定ScrollView到达底部。

1
2
3
4
5
6
7
if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
View scrollChild = scrollView.getChildAt(0);
if (scrollView.getScrollY() + getHeight() >= scrollChild.getHeight()) {
intercept = true;
}
}

全部代码,主要变更的是onInterceptTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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对组件进行修改。

0%