一、淘宝商品详情页效果
我们的效果
二、实现思路
使用两个scrollView,两个scrollView 竖直排列,通过自定义viewGroup来控制两个scrollView的竖直排列,以及滑动事件的处理。如下图
三、具体实现
1、继承viewGroup自定义布局View 重写onMeasure()和onLayout方法,在onLayout方法中完成对两个子ScrollView的竖直排列布局,代码如下:
布局文件:
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
<RelativeLayout xmlns:android=
"[http://schemas.android.com/apk/res/android](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fschemas.android.com%2Fapk%2Fres%2Fandroid)"
xmlns:tools=
"[http://schemas.android.com/tools](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fschemas.android.com%2Ftools)"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
tools:context=
"com.baoyunlong.view.pulluptoloadmore.MainActivity"
>
<com.baoyunlong.view.pulluptoloadmore.PullUpToLoadMore
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:orientation=
"vertical"
>
<com.baoyunlong.view.pulluptoloadmore.MyScrollView
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:fillViewport=
"true"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:orientation=
"vertical"
>
<ImageView
android:scaleType=
"fitXY"
android:src=
"@drawable/a1"
android:layout_width=
"match_parent"
android:layout_height=
"180dp"
/>
<TextView
android:text=
"这里是标题"
android:textSize=
"18dp"
android:layout_marginRight=
"10dp"
android:layout_marginLeft=
"10dp"
android:layout_marginTop=
"10dp"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
/>
<TextView
android:layout_marginTop=
"10dp"
android:text=
"子标题"
android:layout_marginLeft=
"10dp"
android:layout_marginRight=
"10dp"
android:textSize=
"18dp"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
/>
..............
<LinearLayout
android:layout_height=
"0dp"
android:layout_weight=
"1"
android:gravity=
"bottom"
android:layout_width=
"match_parent"
>
<TextView
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:height=
"50dp"
android:background=
"#b11"
android:gravity=
"center"
android:text=
"继续拖动查看图文详情"
android:textColor=
"#000"
/>
</LinearLayout>
</LinearLayout>
</com.baoyunlong.view.pulluptoloadmore.MyScrollView>
<com.baoyunlong.view.pulluptoloadmore.MyScrollView
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:fillViewport=
"true"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:gravity=
"center"
android:orientation=
"vertical"
>
<ImageView
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:src=
"@drawable/a1"
/>
<ImageView
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:src=
"@drawable/a3"
/>
.........
</LinearLayout>
</com.baoyunlong.view.pulluptoloadmore.MyScrollView> </com.baoyunlong.view.pulluptoloadmore.PullUpToLoadMore>
</RelativeLayout>
代码:
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
public
class
PullUpToLoadMore
extends
ViewGroup {
public
PullUpToLoadMore(Context context) {
super
(context);
}
public
PullUpToLoadMore(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
PullUpToLoadMore(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
}
[@Override](https://my.oschina.net/u/1162528)
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
super
.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
[@Override](https://my.oschina.net/u/1162528)
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
int
childCount = getChildCount();
int
childTop = t;
for
(
int
i =
0
; i < childCount; i++) {
View child = getChildAt(i);
child.layout(l, childTop, r, childTop + child.getMeasuredHeight());
childTop += child.getMeasuredHeight();
}
}
}
2、处理滑动事件
规则如下 :
(1)、当处于第一屏时 第一个ScrollView已经滑动到底部并且滑动方向是往上滑动,这个时候滑动事件应该交给父view处理也就是拦截事件让onInterceptTouchEvent返回true.然后父view通过scrollBy()方法滚动,显示出第二个scrollView。
(2)、当处于第二屏时 第二个ScrollView已经滑动到顶部并且滑动方向是往下滑动,这个时候滑动事件交给父view处理,根据滑动事件显示出第一个ScrollView。
(3)、当手指离开屏幕时,根据滑动速度来决定是回弹到第一个ScrollView还是第二个ScrollView,通过VelocityTracker来获取滑动速度。
3、一些细节的处理
(1)、如果仔细看观察淘宝的实现效果你会发现,当你滑动到刚刚看到 “继续拖动,查看图文详情”的时候,手指抬起,然后再按下重新向上拖动你会发现,第二页并不会划出来,而是停留在了“继续拖动,查看图文详情”的底部,京东的效果也是一样。这样用户体验不太好,我们来优化一下。其实通过查看ScrollView的源码可以看出来,这是因为ScrollView类的onTouchEvent方法的默认实现,调用了parent.requestDisallowInterceptTouchEvent(true)方法 阻止了我们拦截事件,导致我们父view的onInterceptTouchEvent方法无法执行,也就拦截不到事件,拦截不到事件我们的onTouchEvent就无法执行,onTouchEvent无法执行,我们写在onTouchEvent里面的滚动逻辑就执行不到了,导致了上面我们看到的划不动的效果。解决方法就是,我们需要重写dispatchTouchEvent()方法,防止子view干扰我们,这样我们滑动的时候就可以一气呵成了。代码如下:
1
2
3
4
5
6
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
//防止子View禁止父view拦截事件
this
.requestDisallowInterceptTouchEvent(
false
);
return
super
.dispatchTouchEvent(ev);
}
(2)、监听ScrollView滑动事件的问题
ScrollView没有提供滚动事件的监听方法,也就没法判断是否滚动到了顶部,或者底部,这里我们继承ScrollView 自己实现滚动事件监听。
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
/**
* Created by baoyunlong on 16/6/8.
*/
public
class
MyScrollView
extends
ScrollView {
private
static
String TAG=MyScrollView.
class
.getName();
public
void
setScrollListener(ScrollListener scrollListener) {
this
.mScrollListener = scrollListener;
}
private
ScrollListener mScrollListener;
public
MyScrollView(Context context) {
super
(context);
}
public
MyScrollView(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
MyScrollView(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
}
@Override
public
boolean
onTouchEvent(MotionEvent ev) {
switch
(ev.getAction()){
case
MotionEvent.ACTION_MOVE:
if
(mScrollListener!=
null
){
int
contentHeight=getChildAt(
0
).getHeight();
int
scrollHeight=getHeight();
int
scrollY=getScrollY();
mScrollListener.onScroll(scrollY);
if
(scrollY+scrollHeight>=contentHeight||contentHeight<=scrollHeight){
mScrollListener.onScrollToBottom();
}
else
{
mScrollListener.notBottom();
}
if
(scrollY==
0
){
mScrollListener.onScrollToTop();
}
}
break
;
}
boolean
result=
super
.onTouchEvent(ev);
requestDisallowInterceptTouchEvent(
false
);
return
result;
}
public
interface
ScrollListener{
void
onScrollToBottom();
void
onScrollToTop();
void
onScroll(
int
scrollY);
void
notBottom();
}
4、完整代码如下
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
/**
* Created by baoyunlong on 16/6/8.
*/
public
class
PullUpToLoadMore
extends
ViewGroup {
public
static
String TAG = PullUpToLoadMore.
class
.getName();
MyScrollView topScrollView, bottomScrollView;
VelocityTracker velocityTracker = VelocityTracker.obtain();
Scroller scroller =
new
Scroller(getContext());
int
currPosition =
0
;
int
position1Y;
int
lastY;
public
int
scaledTouchSlop;
//最小滑动距离
int
speed =
200
;
boolean
isIntercept;
public
boolean
bottomScrollVIewIsInTop =
false
;
public
boolean
topScrollViewIsBottom =
false
;
public
PullUpToLoadMore(Context context) {
super
(context);
init();
}
public
PullUpToLoadMore(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
public
PullUpToLoadMore(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
init();
}
private
void
init() {
post(
new
Runnable() {
@Override
public
void
run() {
topScrollView = (MyScrollView) getChildAt(
0
);
bottomScrollView = (MyScrollView) getChildAt(
1
);
topScrollView.setScrollListener(
new
MyScrollView.ScrollListener() {
@Override
public
void
onScrollToBottom() {
topScrollViewIsBottom =
true
;
}
@Override
public
void
onScrollToTop() {
}
@Override
public
void
onScroll(
int
scrollY) {
}
@Override
public
void
notBottom() {
topScrollViewIsBottom =
false
;
}
});
bottomScrollView.setScrollListener(
new
MyScrollView.ScrollListener() {
@Override
public
void
onScrollToBottom() {
}
@Override
public
void
onScrollToTop() {
}
@Override
public
void
onScroll(
int
scrollY) {
if
(scrollY ==
0
) {
bottomScrollVIewIsInTop =
true
;
}
else
{
bottomScrollVIewIsInTop =
false
;
}
}
@Override
public
void
notBottom() {
}
});
position1Y = topScrollView.getBottom();
scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
});
}
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
//防止子View禁止父view拦截事件
this
.requestDisallowInterceptTouchEvent(
false
);
return
super
.dispatchTouchEvent(ev);
}
@Override
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
int
y = (
int
) ev.getY();
switch
(ev.getAction()) {
case
MotionEvent.ACTION_DOWN:
lastY = y;
break
;
case
MotionEvent.ACTION_MOVE:
//判断是否已经滚动到了底部
if
(topScrollViewIsBottom) {
int
dy = lastY - y;
//判断是否是向上滑动和是否在第一屏
if
(dy >
0
&& currPosition ==
0
) {
if
(dy >= scaledTouchSlop) {
isIntercept =
true
;
//拦截事件
lastY=y;
}
}
}
if
(bottomScrollVIewIsInTop) {
int
dy = lastY - y;
//判断是否是向下滑动和是否在第二屏
if
(dy <
0
&& currPosition ==
1
) {
if
(Math.abs(dy) >= scaledTouchSlop) {
isIntercept =
true
;
}
}
}
break
;
}
return
isIntercept;
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
int
y = (
int
) event.getY();
velocityTracker.addMovement(event);
switch
(event.getAction()) {
case
MotionEvent.ACTION_MOVE:
int
dy = lastY - y;
if
(getScrollY() + dy <
0
) {
dy = getScrollY() + dy + Math.abs(getScrollY() + dy);
}
if
(getScrollY() + dy + getHeight() > bottomScrollView.getBottom()) {
dy = dy - (getScrollY() + dy - (bottomScrollView.getBottom() - getHeight()));
}
scrollBy(
0
, dy);
break
;
case
MotionEvent.ACTION_UP:
isIntercept =
false
;
velocityTracker.computeCurrentVelocity(
1000
);
float
yVelocity = velocityTracker.getYVelocity();
if
(currPosition ==
0
) {
if
(yVelocity <
0
&& yVelocity < -speed) {
smoothScroll(position1Y);
currPosition =
1
;
}
else
{
smoothScroll(
0
);
}
}
else
{
if
(yVelocity >
0
&& yVelocity > speed) {
smoothScroll(
0
);
currPosition =
0
;
}
else
{
smoothScroll(position1Y);
}
}
break
;
}
lastY = y;
return
true
;
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
super
.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected
void
onLayout(
boolean
changed,
int
l,
int
t,
int
r,
int
b) {
int
childCount = getChildCount();
int
childTop = t;
for
(
int
i =
0
; i < childCount; i++) {
View child = getChildAt(i);
child.layout(l, childTop, r, childTop + child.getMeasuredHeight());
childTop += child.getMeasuredHeight();
}
}
//通过Scroller实现弹性滑动
private
void
smoothScroll(
int
tartY) {
int
dy = tartY - getScrollY();
scroller.startScroll(getScrollX(), getScrollY(),
0
, dy);
invalidate();
}
@Override
public
void
computeScroll() {
if
(scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
}
源码:
以上所述是小编给大家介绍的Android 仿淘宝、京东商品详情页向上拖动查看图文详情控件DEMO详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
您可能感兴趣的文章: