【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效果的。
[/code]
每次画的时候,每次画的时候都会调用draw。
但仅仅于此还是不行的,怎么判断滑动到边上呢?
从上面可以看出,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;
}
画了个简单的图示,方便理解。
[/code]
上面基本已经实现了EdgeEffect效果,以及缓慢拖动的时候,晕影的渐变效果。为了使晕影效果更加平滑,Android在computeScroll中做了一些处理。
到此就已经结束了。
总结一下:
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
ScrollView实现晕影效果,实际上是通过下面两个EdgeEffect
从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的偏移量
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
相关文章推荐
- Android 自定义View EdgeEffect效果 ----ScrollView部分源代码剖析
- Android自定义View--ScrollView实现回弹效果
- Android自定义View实现HTML图文环绕效果
- Android自定义View实现HTML图文环绕效果
- android 自定义ImageView实现图片手势滑动,多点触摸放大缩小效果
- Android开发之ViewPager滑动页面效果实现(源代码分享)
- android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)
- Android自定义ScrollView实现反弹效果
- Android自定义View实现转盘旋转的效果
- 利用Android自定义View实现转盘旋转的效果
- Android开发之LisitView的图文并排效果实现(源代码分享)
- Android自定义View实现HTML图文环绕效果
- android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)
- android 自定义ImageView实现图片手势滑动,多点触摸放大缩小效果
- Android自定义ScrollView实现反弹效果
- Android进阶篇-自定义ScrollView反弹效果
- Android自定义View实现HTML图文环绕效果
- android 自定义ImageView实现图片手势滑动,多点触摸放大缩小效果
- android 自定义ImageView实现图片手势滑动,多点触摸放大缩小效果