(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - joe
链接:http://android.jobbole.com/85136/
点击 → 了解如何加入专栏作者
啦啦啦,这是山寨UC浏览器的下拉刷新效果的第二篇,第一篇请移步
Android 自定义View UC下拉刷新效果(一)
我们看图说话:
[GIF图超过2MB,微信无法显示]
主要工作
1.下拉刷新的圆形向回首页的圆形的过度以及返回的效果。
2.View的事件分发等等。
3.相关接口回调。
对于第一块,就是这个切换是的效果,其实在
Android drawPath实现QQ拖拽泡泡
我的第一篇文章中就讲了,主要就是使用贝塞尔曲线来实现的。
只是这里我试着使用了四阶的贝塞尔曲线,因为控制点如果就一个的话,看起来有时候会觉得那个弧度拉得特别的尖,一点都不好看,而且我山寨的这个效果也没有UC的那个那么帅气,可能还需要做相关的改进,如果你有好的点子请记得给我留言,一起完善嘛!!
private
void
drawSecPath
(
Canvas
canvas
)
{
path
.
reset
();
path
.
moveTo
((
float
)
(
secondRectf
.
centerX
()
+
Math
.
cos
(
180
/
Math
.
PI *
30
)
*
(
secondRectf
.
centerX
()
-
secondRectf
.
left
)),
(
float
)
(
secondRectf
.
centerY
()
+
Math
.
sin
(
180
/
Math
.
PI *
30
)
*
(
secondRectf
.
centerY
()
-
secondRectf
.
top
)));
path
.
cubicTo
(
secondRectf
.
centerX
()
-
10
*
density
,
secondRectf
.
centerY
()
-
backpaths
,
secondRectf
.
centerX
()
+
10
*
density
,
secondRectf
.
centerY
()
-
backpaths
,
(
float
)
(
secondRectf
.
centerX
()
-
Math
.
cos
(
180
/
Math
.
PI *
30
)
*
(
secondRectf
.
centerX
()
-
secondRectf
.
left
)),
(
float
)
(
secondRectf
.
centerY
()
+
Math
.
sin
(
180
/
Math
.
PI *
30
)
*
(
secondRectf
.
centerY
()
-
secondRectf
.
top
)));
//path.quadTo(secondRectf.centerX(), secondRectf.centerY() - backpaths, (float) (secondRectf.centerX() - Math.cos(180 / Math.PI * 30) * (secondRectf.centerX() - secondRectf.left)), (float) (secondRectf.centerY() + Math.sin(180 / Math.PI * 30) * (secondRectf.centerY() - secondRectf.top)));
canvas
.
drawArc
(
secondRectf
,
0
,
360
,
true
,
secPaint
);
canvas
.
drawPath
(
path
,
secPaint
);
//drawArc(canvas);
}
private
void
drawFirstPath
(
Canvas
canvas
)
{
path
.
reset
();
path
.
moveTo
((
float
)
(
outRectF
.
centerX
()
-
Math
.
cos
(
180
/
Math
.
PI *
30
)
*
(
outRectF
.
centerX
()
-
outRectF
.
left
)),
(
float
)
(
outRectF
.
centerY
()
-
Math
.
sin
(
180
/
Math
.
PI *
30
)
*
(
outRectF
.
centerY
()
-
outRectF
.
top
)));
//path.quadTo(outRectF.centerX(), outRectF.centerY() + paths, (float) (outRectF.centerX() + Math.cos(180 / Math.PI * 30) * (outRectF.centerX() - outRectF.left)), (float) (outRectF.centerY() - Math.sin(180 / Math.PI * 30) * (outRectF.centerY() - outRectF.top)));
path
.
cubicTo
(
outRectF
.
centerX
()
+
10
*
density
,
outRectF
.
centerY
()
+
paths
,
outRectF
.
centerX
()
-
10
*
density
,
outRectF
.
centerY
()
+
paths
,
(
float
)
(
outRectF
.
centerX
()
+
Math
.
cos
(
180
/
Math
.
PI *
30
)
*
(
outRectF
.
centerX
()
-
outRectF
.
left
)),
(
float
)
(
outRectF
.
centerY
()
-
Math
.
sin
(
180
/
Math
.
PI *
30
)
*
(
outRectF
.
centerY
()
-
outRectF
.
top
)));
canvas
.
drawArc
(
outRectF
,
0
,
360
,
true
,
paint
);
canvas
.
drawPath
(
path
,
paint
);
drawArc
(
canvas
);
}
这里两个控制点的偏移量是写死的,而不是根据圆形的size的百分比计算出来的,所以如果你修改了圆形的半径,那么这里可能会出现小小的问题,需要手动完善下!
下拉刷新
其实现在的下拉刷新也是烂大街的,就我现在理解的下拉刷新其实有两种模式了,一种是之前的写好一个头布局在那个HeaderLayout中,然后margin将其隐藏掉,然后在下拉的时候拦截相关事件,决定是否应该让Header显示出来。拦截的条件就是子View(ListView ScrollView RecycleView等等是否在顶部了而且手势是向下拉(dy
今天我们不说这种下拉,而是介绍Google在Android5.0(希望我没有记错 )提供的嵌套滑动的新机制
向下兼容的问题
从API21(就是5.0开始),ViewParent的接口里面多了onStartNestedScroll()、onStopNestedScroll()等等的方法!当然,对应的ViewGroup中也有了这些方法,目测是空实现,因为它实现了这个接口嘛。那么问题来了,如果你要向下兼容肿么办呢?!
这里有supportV4包来提供向下兼容,不会写不懂这玩意儿不着急,想想Android新的控件(RecycleView SwipeRefreshLayout NestedScrollView)这些都是支持嵌套滑动滴。。
相关接口方法
NestedScrollingParent和NestedScrollingChild这两个接口就是用来实现相关的向下兼容的方法滴。。
This interface should be implemented by ViewGroup subclasses that wish to support scrolling operations delegated by a nested child view.
Classes implementing this interface should create a final instance of a NestedScrollingParentHelper as a field and delegate any View or ViewGroup methods to the NestedScrollingParentHelper methods of the same signature.
Views invoking nested scrolling functionality should always do so from the relevant ViewCompat, ViewGroupCompat or ViewParentCompat compatibility shim static methods. This ensures interoperability with nested scrolling views on Android 5.0 Lollipop and newer.
这个是NestedScrollingParent自己的一番解释,可以明确知道,在5.0或者更新的,什么ViewCompat等就提供了相关支持了(这个就是前面我说的那个嘛!),然后兼容的话,就要用这个,而且还要使用一个叫NestedScrollingParentHelper的辅助类来统一处理一些东西。
NestedScrollingParent相关的方法.png
NestedScrollingChild的相关方法.png
然后是不是感觉要哔了狗了,这么多方法要实现?!其实我也是醉醉的,然后打算抄抄别人的就好了!
private
final
NestedScrollingParentHelper
mNestedScrollingParentHelper
;
private
final
NestedScrollingChildHelper
mNestedScrollingChildHelper
;
//初始化两个helper
mNestedScrollingParentHelper
=
new
NestedScrollingParentHelper
(
this
);
mNestedScrollingChildHelper
=
new
NestedScrollingChildHelper
(
this
);
setNestedScrollingEnabled
(
true
);
然后各种实现的方法中:
@
Override
public
boolean
onStartNestedScroll
(
View
child
,
View
target
,
int
nestedScrollAxes
)
{
return
isEnabled
()
&&
canChildScrollUp
()
&& !
mReturningToStart
&& !
mRefreshing
&&
(
nestedScrollAxes
&
ViewCompat
.
SCROLL_AXIS_VERTICAL
)
!=
0
;
}
@
Override
public
void
onNestedScrollAccepted
(
View
child
,
View
target
,
int
axes
)
{
// Reset the counter of how much leftover scroll needs to be consumed.
mNestedScrollingParentHelper
.
onNestedScrollAccepted
(
child
,
target
,
axes
);
// Dispatch up to the nested parent
startNestedScroll
(
axes
&
ViewCompat
.
SCROLL_AXIS_VERTICAL
);
mTotalUnconsumed
=
0
;
mNestedScrollInProgress
=
true
;
}
@
Override
public
boolean
hasNestedScrollingParent
()
{
return
mNestedScrollingChildHelper
.
hasNestedScrollingParent
();
}
@
Override
public
boolean
dispatchNestedScroll
(
int
dxConsumed
,
int
dyConsumed
,
int
dxUnconsumed
,
int
dyUnconsumed
,
int
[]
offsetInWindow
)
{
return
mNestedScrollingChildHelper
.
dispatchNestedScroll
(
dxConsumed
,
dyConsumed
,
dxUnconsumed
,
dyUnconsumed
,
offsetInWindow
);
}
@
Override
public
boolean
dispatchNestedPreScroll
(
int
dx
,
int
dy
,
int
[]
consumed
,
int
[]
offsetInWindow
)
{
return
mNestedScrollingChildHelper
.
dispatchNestedPreScroll
(
dx
,
dy
,
consumed
,
offsetInWindow
);
}
@
Override
public
boolean
onNestedPreFling
(
View
target
,
float
velocityX
,
float
velocityY
)
{
return
dispatchNestedPreFling
(
velocityX
,
velocityY
);
}
@
Override
public
boolean
onNestedFling
(
View
target
,
float
velocityX
,
float
velocityY
,
boolean
consumed
)
{
return
dispatchNestedFling
(
velocityX
,
velocityY
,
consumed
);
}
.......
新的嵌套滑动的分发机制:
子
View
parent
startNestedScroll
--->
onStartNestedScroll
、
onNestedScrollAccepted
dispatchNestedPreScroll
--->
onNestedPreScroll
dispatchNestedScroll
--->
onNestedScroll
stopNestedScroll
--->
onStopNestedScroll
所以说并不是很复杂,其实就是在以前的事件分发的基础上给父View提供了一个消费事件的机会,以前的话,谁接受了DOWN事件,那么之后所有的事件都会交给它处理,直到它不处理的时候才会又依次返回给父View,或者直到新的DOWN事件开始分发。
嵌套滑动的意思就是在子View处理相关事件的时候,可以根据情况反馈给父View,然后根据父View处理的结果再进行下一步的处理!
RecycleView实现了NestedScrollingChild,在TouchEvet()中有以下逻辑:
switch
(
action
)
{
case
MotionEvent
.
ACTION_DOWN
:
{
.....
startNestedScroll
(
nestedScrollAxis
);
}
break
;
case
MotionEvent
.
ACTION_MOVE
:
{
if
(
dispatchNestedPreScroll
(
dx
,
dy
,
mScrollConsumed
,
mScrollOffset
))
{
dx
-=
mScrollConsumed
[
0
];
dy
-=
mScrollConsumed
[
1
];
vtev
.
offsetLocation
(
mScrollOffset
[
0
],
mScrollOffset
[
1
]);
// Updated the nested offsets
mNestedOffsets
[
0
]
+=
mScrollOffset
[
0
];
mNestedOffsets
[
1
]
+=
mScrollOffset
[
1
];
}
.....
}
break
;
case
MotionEvent
.
ACTION_UP
:
{
resetTouch
();
}
break
;
......
}
private
void
resetTouch
()
{
if
(
mVelocityTracker
!=
null
)
{
mVelocityTracker
.
clear
();
}
stopNestedScroll
();
releaseGlows
();
}
根据上面的代码可以看出onNestedPreScroll(),这个就是在子View还没有滑动之前会先走的,如果父View有相关消费,那么子View会计算出父View消费的偏移量,继续消费剩余的偏移量。而在子View的消费的过程中,它会计算出过程中并没有消费的偏移量。
if
(
y
!=
0
)
{
consumedY
=
mLayout
.
scrollVerticallyBy
(
y
,
mRecycler
,
mState
);
unconsumedY
=
y
-
consumedY
;
}
然后回调dispatchNestedScroll,父View就可以在onNestedScroll()中进行处理了!
最后在UP或者CANCLE事件中,子View会stopNestedScroll(),然后父View就走到了onStopNestedScroll()。整个嵌套滑动到此结束!
具体实现
1.下拉的时候展现头布局
这里其实就是走onNestedScroll(),因为这个时候子View已经在顶部了,向下拉的dy偏移量它肯定消费不了,所以在onNestedScroll()中unconsumedY就是父View需要消费的。
2.下拉的过程中又开始向上滑动
这里就需要注意了,这个时候,父View和子View都可以响应和消费对应的事件的,因为他们现在都是可以向上滑动的,但是这里必须要父View优先消费事件,所以这里就要在onNestedPreScroll()中做相关的处理。
@
Override
public
void
onNestedPreScroll
(
View
target
,
int
dx
,
int
dy