Android 自定义View实现抽屉效果
Android自定义View实现抽屉效果
说明
- 这个自定义View,没有处理好多点触摸问题
- View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子View产生滚动效果menuView.layout(menuLeft,0,menuLeft+menuWidth,menuHeight);
- 相应的,由于没有使用scrollBy方法,就没有产生getScrollX值,所以不能通过Scroller的startScroll方法来完成手指离开后的平滑滚动效果,而是使用了Animation动画的applyTransformation方法来完成插值,从而实现动画效果
主要算法是:动画当前值=起始值+(目标值-起始值)*interpolatedTime
其中interpolatedTime是一个0.0f~1.0f的数字,系统自己插值计算好了(默认是线性变化的),当然你可以自己写插值器
/** *由于上面不能使用scrollBy,那么这里就不能使用Scroller这个类来完成平滑移动了,还好我们有动画 */ classMyAnimationextendsAnimation{ privateintviewCurrentLfet; privateintviewStartLfet; privateintviewTargetLfet; privateintviewWidth; privateViewview; privateintcha; publicMyAnimation(Viewview,intviewStartLfet,intviewTargetLfet,intviewWidth){ this.view=view; this.viewStartLfet=viewStartLfet; this.viewTargetLfet=viewTargetLfet; this.viewWidth=viewWidth; cha=viewTargetLfet-viewStartLfet; setDuration(Math.abs(cha)); } @Override protectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){ super.applyTransformation(interpolatedTime,t); viewCurrentLfet=(int)(viewStartLfet+cha*interpolatedTime); view.layout(viewCurrentLfet,0,viewCurrentLfet+viewWidth,menuHeight); } }
完整代码
packagecom.sunshine.choutidemo; importandroid.content.Context; importandroid.util.AttributeSet; importandroid.util.Log; importandroid.view.MotionEvent; importandroid.view.VelocityTracker; importandroid.view.View; importandroid.view.ViewConfiguration; importandroid.view.ViewGroup; importandroid.view.animation.Animation; importandroid.view.animation.AnimationSet; importandroid.view.animation.Transformation; /** *Createdbyaon2016/8/15. */ publicclassChouTiViewextendsViewGroup{ privateViewmainView; privateViewmenuView; privateintmenuWidth; privateintdownX; privateintlastX; privateintmoveX; privateintdeltaX; privateintmenuLeft; privateintmainLeft; privateintmenuHeight; privateintmainWidth; privateintmainHeight; privateintmenuLeftBorder; privateintmainLeftBorder; privateintmenuRightBorder; privateintmainRightBorder; privateintmMaxVelocity; privateVelocityTrackermVelocityTracker; privateintmPointerId; privatefloatvelocityX; privatefloatvelocityY; publicChouTiView(Contextcontext){ super(context); init(); } publicChouTiView(Contextcontext,AttributeSetattrs){ super(context,attrs); init(); } privatevoidinit(){ //0.获得此次最大速率 mMaxVelocity=ViewConfiguration.get(getContext()).getMaximumFlingVelocity(); } @Override protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){ super.onMeasure(widthMeasureSpec,heightMeasureSpec); mainView.measure(widthMeasureSpec,heightMeasureSpec); menuView.measure(widthMeasureSpec,heightMeasureSpec); //获得子View的正确宽度(只能获取具体的数字值),但是不能这样获取高度,因为这里match—parent为-1 menuWidth=menuView.getLayoutParams().width; menuLeft=(int)(-menuWidth*0.5); menuLeftBorder=(int)(-menuWidth*0.5); menuRightBorder=0; mainLeft=0; mainLeftBorder=0; mainRightBorder=menuWidth; } @Override protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){ menuHeight=b; mainWidth=r; mainHeight=b; mainView.layout(l,t,r,b); menuView.layout(menuLeft,t,menuLeft+menuWidth,b); } @Override protectedvoidonFinishInflate(){ super.onFinishInflate(); mainView=getChildAt(1); menuView=getChildAt(0); } @Override publicbooleanonTouchEvent(MotionEventevent){ finalintaction=event.getActionMasked(); acquireVelocityTracker(event);//1.向VelocityTracker添加MotionEvent finalVelocityTrackerverTracker=mVelocityTracker; switch(action){ caseMotionEvent.ACTION_DOWN: //2.求第一个触点的id,此时可能有多个触点,但至少一个 //获取索引为0的手指id mPointerId=event.getPointerId(0); downX=(int)event.getX(); lastX=downX; break; caseMotionEvent.ACTION_MOVE: //获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0 //的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指 //屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹, //因此此处不能使用event.getActionIndex()来获得索引 finalintpointerIndex=event.findPointerIndex(mPointerId); moveX=(int)event.getX(pointerIndex); deltaX=moveX-lastX; //把触摸移动引起的增量,体现在menu和main的左侧left上 menuLeft=(int)(menuLeft+deltaX*0.43);//让菜单移动的慢一点 mainLeft=mainLeft+deltaX; //让菜单根据手指增量移动,考虑两侧边界问题(通过不停地layout实现移动效果) //为何不适用scrollBy,因为scrollBy移动的是外层的大View,现在需求是分别移动这个大view内的两个小View //scrollBy的话,会让菜单和主页面同时移动,不会产生错位效果, //你会想,那让小view自己scrollBy,这样也是不行的, //因为让小view,例如menu调用scrollBy的话,会让menu自己的边框在动, //看上去,是menu内部的文字在移动,但是menu并没有在外层的大View里移动 //说的很拗口,但是真的不能用scrollBy if(menuLeft>=menuRightBorder){ menuLeft=menuRightBorder; }elseif(menuLeft<=menuLeftBorder){ menuLeft=menuLeftBorder; } menuView.layout(menuLeft,0,menuLeft+menuWidth,menuHeight); //让主页面根据手指增量移动,考虑两侧边界问题 if(mainLeft>=mainRightBorder){ mainLeft=mainRightBorder; }elseif(mainLeft<=mainLeftBorder){ mainLeft=mainLeftBorder; } mainView.layout(mainLeft,0,mainLeft+mainWidth,mainHeight); lastX=moveX; break; caseMotionEvent.ACTION_UP: //3.求伪瞬时速度 verTracker.computeCurrentVelocity(1000,mMaxVelocity); velocityX=verTracker.getXVelocity(mPointerId); Log.e("qwe",velocityX+"/"+mMaxVelocity); if(velocityX>1000){ smoothToMenu(); }elseif(velocityX<-2000){ smoothToMain(); }else{ //判断松手的位置,如果大于1/2.5的菜单宽度就打开菜单,否则打开主页面 if(mainLeft>menuWidth/2.5){ Log.e("qqq","显示菜单"); smoothToMenu(); }else{ Log.e("qqq","显示主页面"); smoothToMain(); } } //4.ACTION_UP释放VelocityTracker,交给其他控件使用 releaseVelocityTracker(); break; caseMotionEvent.ACTION_CANCEL: //4.ACTION_UP释放VelocityTracker,交给其他控件使用 releaseVelocityTracker(); caseMotionEvent.ACTION_POINTER_UP: //获取离开屏幕的手指的索引 intpointerIndexLeave=event.getActionIndex(); intpointerIdLeave=event.getPointerId(pointerIndexLeave); if(mPointerId==pointerIdLeave){ //离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker intreIndex=pointerIndexLeave==0?1:0; mPointerId=event.getPointerId(reIndex); //调整触摸位置,防止出现跳动 downX=(int)event.getX(reIndex); //y=event.getY(reIndex); releaseVelocityTracker(); } releaseVelocityTracker(); break; } returntrue; } privatevoidsmoothToMain(){ MyAnimationmenuAnimation=newMyAnimation(menuView,menuLeft,menuLeftBorder,menuWidth); MyAnimationmainAnimation=newMyAnimation(mainView,mainLeft,mainLeftBorder,mainWidth); AnimationSetanimationSet=newAnimationSet(true); animationSet.addAnimation(menuAnimation); animationSet.addAnimation(mainAnimation); startAnimation(animationSet); //一定记得更新menu和main的左侧状态,这影响到了,再次手指触摸时候的动画,否则突变 menuLeft=menuLeftBorder; mainLeft=mainLeftBorder; } privatevoidsmoothToMenu(){ MyAnimationmenuAnimation=newMyAnimation(menuView,menuLeft,menuRightBorder,menuWidth); MyAnimationmainAnimation=newMyAnimation(mainView,mainLeft,mainRightBorder,mainWidth); AnimationSetanimationSet=newAnimationSet(true); animationSet.addAnimation(menuAnimation); animationSet.addAnimation(mainAnimation); startAnimation(animationSet); //一定记得更新menu和main的左侧状态,这影响到了,再次手指触摸时候的动画,否则突变 menuLeft=menuRightBorder; mainLeft=mainRightBorder; } /** *@paramevent向VelocityTracker添加MotionEvent *@seeandroid.view.VelocityTracker#obtain() *@seeandroid.view.VelocityTracker#addMovement(MotionEvent) */ privatevoidacquireVelocityTracker(finalMotionEventevent){ if(null==mVelocityTracker){ mVelocityTracker=VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** *释放VelocityTracker * *@seeandroid.view.VelocityTracker#clear() *@seeandroid.view.VelocityTracker#recycle() */ privatevoidreleaseVelocityTracker(){ if(null!=mVelocityTracker){ mVelocityTracker.clear(); mVelocityTracker.recycle(); mVelocityTracker=null; } } /** *由于上面不能使用scrollBy,那么这里就不能使用Scroller这个类来完成平滑移动了,还好我们有动画 */ classMyAnimationextendsAnimation{ privateintviewCurrentLfet; privateintviewStartLfet; privateintviewTargetLfet; privateintviewWidth; privateViewview; privateintcha; publicMyAnimation(Viewview,intviewStartLfet,intviewTargetLfet,intviewWidth){ this.view=view; this.viewStartLfet=viewStartLfet; this.viewTargetLfet=viewTargetLfet; this.viewWidth=viewWidth; cha=viewTargetLfet-viewStartLfet; setDuration(Math.abs(cha)); } @Override protectedvoidapplyTransformation(floatinterpolatedTime,Transformationt){ super.applyTransformation(interpolatedTime,t); viewCurrentLfet=(int)(viewStartLfet+cha*interpolatedTime); view.layout(viewCurrentLfet,0,viewCurrentLfet+viewWidth,menuHeight); } } }
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!