您的位置:首页 > 移动开发 > Android开发

【Android】自定义View EdgeEffect效果 ----ScrollView部分源代码剖析

2015-01-15 11:22 344 查看
EdgeEffect提供了一种方式去画可滑动View组件的过度滑动效果。EdgeEffect的接口不多,只有6个接口。下面我们用ScrollView源码来分析一下如何实现过度滑动的晕影效果.

ScrollView实现晕影效果,实际上是通过下面两个EdgeEffect
privateEdgeEffectmEdgeGlowTop;//滑动到顶时,出现的晕影效果 privateEdgeEffectmEdgeGlowBottom;//滑动到底时,出现的晕影效果
从ScrollView的代码中可以看到OverScrollMode会对是否有EdgeEffect有影响,当OverScrollMode为OVER_SCROLL_NEVER的时候,是没有EdgeEffect效果的。
@Override
publicvoidsetOverScrollMode(intmode){
if(mode!=OVER_SCROLL_NEVER){//当mode不为OVER_SCROLL_NEVER的时候,创建EdgeEffect实例。
if(mEdgeGlowTop==null){
Contextcontext=getContext();
mEdgeGlowTop=newEdgeEffect(context);//创建EdgeEffect实例
mEdgeGlowBottom=newEdgeEffect(context);//创建EdgeEffect实例
}
}else{
mEdgeGlowTop=null;
mEdgeGlowBottom=null;
}
super.setOverScrollMode(mode);
}


[/code]

每次画的时候,每次画的时候都会调用draw。
@Override
publicvoiddraw(Canvascanvas){
super.draw(canvas);//画ScrollView
if(mEdgeGlowTop!=null){
finalintscrollY=mScrollY;
if(!mEdgeGlowTop.isFinished()){//画滑到顶的晕影效果
finalintrestoreCount=canvas.save();
finalintwidth=getWidth()-mPaddingLeft-mPaddingRight;

canvas.translate(mPaddingLeft,Math.min(0,scrollY));
mEdgeGlowTop.setSize(width,getHeight());
if(mEdgeGlowTop.draw(canvas)){
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
if(!mEdgeGlowBottom.isFinished()){//画滑动到底的晕影效果
finalintrestoreCount=canvas.save();
finalintwidth=getWidth()-mPaddingLeft-mPaddingRight;
finalintheight=getHeight();

canvas.translate(-width+mPaddingLeft,
Math.max(getScrollRange(),scrollY)+height);
canvas.rotate(180,width,0);
mEdgeGlowBottom.setSize(width,height);
if(mEdgeGlowBottom.draw(canvas)){
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
}
}



但仅仅于此还是不行的,怎么判断滑动到边上呢?

@Override
publicbooleanonTouchEvent(MotionEventev){
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);

finalintaction=ev.getAction();

switch(action&MotionEvent.ACTION_MASK){
caseMotionEvent.ACTION_DOWN:{
if(getChildCount()==0){
returnfalse;
}
if((mIsBeingDragged=!mScroller.isFinished())){
finalViewParentparent=getParent();
if(parent!=null){
parent.requestDisallowInterceptTouchEvent(true);
}
}

/*
*Ifbeingflingedandusertouches,stopthefling.isFinished
*willbefalseifbeingflinged.
*/
if(!mScroller.isFinished()){
mScroller.abortAnimation();
if(mFlingStrictSpan!=null){
mFlingStrictSpan.finish();
mFlingStrictSpan=null;
}
}

//Rememberwherethemotioneventstarted
mLastMotionY=(int)ev.getY();
mActivePointerId=ev.getPointerId(0);
break;
}
caseMotionEvent.ACTION_MOVE:
finalintactivePointerIndex=ev.findPointerIndex(mActivePointerId);
if(activePointerIndex==-1){
Log.e(TAG,"InvalidpointerId="+mActivePointerId+"inonTouchEvent");
break;
}

finalinty=(int)ev.getY(activePointerIndex);
intdeltaY=mLastMotionY-y;//shif(!mIsBeingDragged&&Math.abs(deltaY)>mTouchSlop){
finalViewParentparent=getParent();
if(parent!=null){
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged=true;
if(deltaY>0){
deltaY-=mTouchSlop;
}else{
deltaY+=mTouchSlop;
}
}
if(mIsBeingDragged){
//Scrolltofollowthemotionevent
mLastMotionY=y;

finalintoldX=mScrollX;
finalintoldY=mScrollY;
finalintrange=getScrollRange();//ScrollView的高度
finalintoverscrollMode=getOverScrollMode();
finalbooleancanOverscroll=overscrollMode==OVER_SCROLL_ALWAYS||
(overscrollMode==OVER_SCROLL_IF_CONTENT_SCROLLS&&range>0);

if(overScrollBy(0,deltaY,0,mScrollY,
0,range,0,mOverscrollDistance,true)){
//Breakourvelocityifwehitascrollbarrier.
mVelocityTracker.clear();
}
onScrollChanged(mScrollX,mScrollY,oldX,oldY);

if(canOverscroll){
finalintpulledToY=oldY+deltaY;
if(pulledToY<0){
mEdgeGlowTop.onPull((float)deltaY/getHeight());//ScrollView滑动到顶if(!mEdgeGlowBottom.isFinished()){
mEdgeGlowBottom.onRelease();
}
}elseif(pulledToY>range){//ScrollView滑动到底
mEdgeGlowBottom.onPull((float)deltaY/getHeight());
if(!mEdgeGlowTop.isFinished()){
mEdgeGlowTop.onRelease();
}
}
if(mEdgeGlowTop!=null
&&(!mEdgeGlowTop.isFinished()||!mEdgeGlowBottom.isFinished())){
postInvalidateOnAnimation();//使ViewRoot重新去画
}
}
}
break;
caseMotionEvent.ACTION_UP:
if(mIsBeingDragged){
finalVelocityTrackervelocityTracker=mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
intinitialVelocity=(int)velocityTracker.getYVelocity(mActivePointerId);

if(getChildCount()>0){
if((Math.abs(initialVelocity)>mMinimumVelocity)){
fling(-initialVelocity);
}else{
if(mScroller.springBack(mScrollX,mScrollY,0,0,0,
getScrollRange())){
postInvalidateOnAnimation();
}
}
}

mActivePointerId=INVALID_POINTER;
endDrag();
}
break;//重置EdgeEffect
caseMotionEvent.ACTION_CANCEL:
if(mIsBeingDragged&&getChildCount()>0){
if(mScroller.springBack(mScrollX,mScrollY,0,0,0,getScrollRange())){
postInvalidateOnAnimation();
}
mActivePointerId=INVALID_POINTER;
endDrag();//重置EdgeEffect
}
break;
caseMotionEvent.ACTION_POINTER_DOWN:{
finalintindex=ev.getActionIndex();
mLastMotionY=(int)ev.getY(index);
mActivePointerId=ev.getPointerId(index);
break;
}
caseMotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionY=(int)ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
returntrue;
}




从上面可以看出,ScrollView计算滑动到顶部,实际上是计算之前的mScrollY+移动距离是否大于0.

计算滑动到底部,实际上是计算之前的mScrollY+移动距离是否大于ScrollView的滑动到底时Child的偏移量
privateintgetScrollRange(){
intscrollRange=0;
if(getChildCount()>0){
Viewchild=getChildAt(0);//ScrollView仅有一个Child
//计算滑动到底时候Child的偏移量
scrollRange=Math.max(0,
child.getHeight()-(getHeight()-mPaddingBottom-mPaddingTop));
}
returnscrollRange;
}



画了个简单的图示,方便理解。
privatevoidendDrag(){
mIsBeingDragged=false;

recycleVelocityTracker();

if(mEdgeGlowTop!=null){
mEdgeGlowTop.onRelease();//重置滑到顶的EdgeEffect
mEdgeGlowBottom.onRelease();//重置滑到底的EdgeEffect
}

if(mScrollStrictSpan!=null){
mScrollStrictSpan.finish();
mScrollStrictSpan=null;
}
}


[/code]

上面基本已经实现了EdgeEffect效果,以及缓慢拖动的时候,晕影的渐变效果。为了使晕影效果更加平滑,Android在computeScroll中做了一些处理。

@Override
publicvoidcomputeScroll(){
if(mScroller.computeScrollOffset()){
//ThisiscalledatdrawingtimebyViewGroup.Wedon'twantto
//re-showthescrollbarsatthispoint,whichscrollTowilldo,
//sowereplicatemostofscrollTohere.
//
//It'salittleoddtocallonScrollChangedfrominsidethedrawing.
//
//Itis,exceptwhenyourememberthatcomputeScroll()isusedto
//animatescrolling.SounlesswewanttodefertheonScrollChanged()
//untiltheendoftheanimatedscrolling,wedon'treallyhavea
//choicehere.
//
//Iagree.Thealternative,whichIthinkwouldbeworse,istopost
//somethingandtellthesubclasseslater.Thisisbadbecausethere
//willbeawindowwheremScrollX/Yisdifferentfromwhattheapp
//thinksitis.
//
intoldX=mScrollX;
intoldY=mScrollY;
intx=mScroller.getCurrX();
inty=mScroller.getCurrY();

if(oldX!=x||oldY!=y){
finalintrange=getScrollRange();
finalintoverscrollMode=getOverScrollMode();
finalbooleancanOverscroll=overscrollMode==OVER_SCROLL_ALWAYS||
(overscrollMode==OVER_SCROLL_IF_CONTENT_SCROLLS&&range>0);

overScrollBy(x-oldX,y-oldY,oldX,oldY,0,range,
0,mOverflingDistance,false);
onScrollChanged(mScrollX,mScrollY,oldX,oldY);

if(canOverscroll){
if(y<0&&oldY>=0){
mEdgeGlowTop.onAbsorb((int)mScroller.getCurrVelocity());//画的时候的吸收效果
}elseif(y>range&&oldY<=range){
mEdgeGlowBottom.onAbsorb((int)mScroller.getCurrVelocity());
}
}
}

if(!awakenScrollBars()){
//Keepondrawinguntiltheanimationhasfinished.
postInvalidateOnAnimation();
}
}else{
if(mFlingStrictSpan!=null){
mFlingStrictSpan.finish();
mFlingStrictSpan=null;
}
}
}


到此就已经结束了。

总结一下:

1.对于每一个需要画OverScroll晕影效果的边,都需要定义自己的EdgeEffect

2.在接收到ACTION_MOVE的event时,判断是否已经滑动到边上,如果是就调用EdgeEffect的onPull方法。

如果调用了onPull,调用invalidate()或者postInvalidateOnAnimation()去触发重新去画。

3.在收到ACTION_MOVE或者ACITION_CANCEL的时候,调用EdgeEffect的onRelease方法重置。

在调用onRelease方法后,调用invalidate()或者postInvalidateOnAnimation()去触发重新去画。

4.重写draw方法,在super.draw(canvas)之后调用EdgeEffect的draw方法。如果EdgeEffect没有finish.做旋转和平移的变换,然后调用EdgeEffect的setSize和draw方法。如果EdgeEffect的draw方法返回ture,调用invalidate()或者postInvalidateOnAnimation()去触发重新去画。

5.在失去WindowFocus的时候,调用EdgeEffect的finish方法(AbsListView.java)。对于EdgeEffect的onAbsorb方法一般是在computeScroll中调用的。但具体还不是特别清楚,有知道的告诉我一下。

作者补充:另外说明一下,前几天遇到一个问题,在有的机子上不会调用到draw方法,需要在构造函数中加setWillNotDraw(false),就可以了。

转自http://m.oschina.net/blog/202820
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: