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);
}
}
}
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!