android中的左右滑动
16lz
2021-01-23
iphone中有很多应用都能够左右滑动,非常cool,关键是实现起来非常简单。android比起来就差远了,网上有不少帖子。 我在这边重新分享下自己的经验吧,将实现细节详细解释下。
FlingGallery这个类摘自网上,有少许修改。
Java代码
由于我需要在滑动页面时,改动title中的文字,这里采用了观察者模式,加了个OnGalleryChangeListener,有同样需求的同学可以参考下。
Java代码
在Activity中,
Java代码
将gallery加到Activity中的最终需要显示的类中。
Adapter中的getView方法中,将需要用来滑动的view添加进来。在GalleryChangeListener中,可以干一些自己想要的滑动后的事情。我在这里改动了标题的文字,进行了我的view中图片的lazyloading。
这里提一下另一个问题。我的这个类,最终嵌入到了tab中,需要在你的tabActivity中dispatchKeyEvent一下,将按键事件分发下去。
最开始,我的滑动的view写得比较通用,所以包含了ScrollView来满足比较长的屏幕,导致手势监听会出一些问题,会出现抖动。当时的解决方案是针对不同屏幕尽量保证一屏能够显示,在res目录下,增加layout-800x480之类的目录,针对每个不同屏幕设计单独的layout,放弃上下滑动,效果也不错。
FlingGallery这个类摘自网上,有少许修改。
Java代码
- packagecom.nuomi.ui;
- importjava.util.HashSet;
- importjava.util.Set;
- importandroid.content.Context;
- importandroid.view.GestureDetector;
- importandroid.view.KeyEvent;
- importandroid.view.MotionEvent;
- importandroid.view.View;
- importandroid.view.animation.Animation;
- importandroid.view.animation.AnimationUtils;
- importandroid.view.animation.Interpolator;
- importandroid.view.animation.Transformation;
- importandroid.widget.Adapter;
- importandroid.widget.FrameLayout;
- importandroid.widget.LinearLayout;
- publicclassFlingGalleryextendsFrameLayout
- {
- privateSet<OnGalleryChangeListener>listeners;
- privatefinalintswipe_min_distance=120;
- privatefinalintswipe_max_off_path=250;
- privatefinalintswipe_threshold_veloicty=400;
- privateintmViewPaddingWidth=0;
- privateintmAnimationDuration=250;
- privatefloatmSnapBorderRatio=0.5f;
- privatebooleanmIsGalleryCircular=true;
- privateintmGalleryWidth=0;
- privatebooleanmIsTouched=false;
- privatebooleanmIsDragging=false;
- privatefloatmCurrentOffset=0.0f;
- privatelongmScrollTimestamp=0;
- privateintmFlingDirection=0;
- privateintmCurrentPosition=0;
- privateintmCurrentViewNumber=0;
- privateContextmContext;
- privateAdaptermAdapter;
- privateFlingGalleryView[]mViews;
- privateFlingGalleryAnimationmAnimation;
- privateGestureDetectormGestureDetector;
- privateInterpolatormDecelerateInterpolater;
- publicFlingGallery(Contextcontext)
- {
- super(context);
- listeners=newHashSet<OnGalleryChangeListener>();
- mContext=context;
- mAdapter=null;
- mViews=newFlingGalleryView[3];
- mViews[0]=newFlingGalleryView(0,this);
- mViews[1]=newFlingGalleryView(1,this);
- mViews[2]=newFlingGalleryView(2,this);
- mAnimation=newFlingGalleryAnimation();
- mGestureDetector=newGestureDetector(newFlingGestureDetector());
- mDecelerateInterpolater=AnimationUtils.loadInterpolator(mContext,android.R.anim.decelerate_interpolator);
- }
- publicvoidaddGalleryChangeListener(OnGalleryChangeListenerlistener){
- listeners.add(listener);
- }
- publicvoidsetPaddingWidth(intviewPaddingWidth)
- {
- mViewPaddingWidth=viewPaddingWidth;
- }
- publicvoidsetAnimationDuration(intanimationDuration)
- {
- mAnimationDuration=animationDuration;
- }
- publicvoidsetSnapBorderRatio(floatsnapBorderRatio)
- {
- mSnapBorderRatio=snapBorderRatio;
- }
- publicvoidsetIsGalleryCircular(booleanisGalleryCircular)
- {
- if(mIsGalleryCircular!=isGalleryCircular)
- {
- mIsGalleryCircular=isGalleryCircular;
- if(mCurrentPosition==getFirstPosition())
- {
- //Weneedtoreloadtheviewimmediatelytothelefttochangeittocircularvieworblank
- mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition));
- }
- if(mCurrentPosition==getLastPosition())
- {
- //Weneedtoreloadtheviewimmediatelytotherighttochangeittocircularvieworblank
- mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition));
- }
- }
- }
- publicintgetGalleryCount()
- {
- return(mAdapter==null)?0:mAdapter.getCount();
- }
- publicintgetFirstPosition()
- {
- return0;
- }
- publicintgetLastPosition()
- {
- return(getGalleryCount()==0)?0:getGalleryCount()-1;
- }
- privateintgetPrevPosition(intrelativePosition)
- {
- intprevPosition=relativePosition-1;
- if(prevPosition<getFirstPosition())
- {
- prevPosition=getFirstPosition()-1;
- if(mIsGalleryCircular==true)
- {
- prevPosition=getLastPosition();
- }
- }
- NotifyGalleryChange();
- returnprevPosition;
- }
- privateintgetNextPosition(intrelativePosition)
- {
- intnextPosition=relativePosition+1;
- if(nextPosition>getLastPosition())
- {
- nextPosition=getLastPosition()+1;
- if(mIsGalleryCircular==true)
- {
- nextPosition=getFirstPosition();
- }
- }
- NotifyGalleryChange();
- returnnextPosition;
- }
- //
- privatevoidNotifyGalleryChange(){
- for(OnGalleryChangeListenerlistener:listeners){
- listener.onGalleryChange(mCurrentPosition);
- }
- }
- privateintgetPrevViewNumber(intrelativeViewNumber)
- {
- return(relativeViewNumber==0)?2:relativeViewNumber-1;
- }
- privateintgetNextViewNumber(intrelativeViewNumber)
- {
- return(relativeViewNumber==2)?0:relativeViewNumber+1;
- }
- @Override
- protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom)
- {
- super.onLayout(changed,left,top,right,bottom);
- //Calculateourviewwidth
- mGalleryWidth=right-left;
- if(changed)
- {
- //Positionviewsatcorrectstartingoffsets
- mViews[0].setOffset(0,0,mCurrentViewNumber);
- mViews[1].setOffset(0,0,mCurrentViewNumber);
- mViews[2].setOffset(0,0,mCurrentViewNumber);
- }
- }
- publicvoidsetAdapter(Adapteradapter)
- {
- mAdapter=adapter;
- mCurrentPosition=0;
- mCurrentViewNumber=0;
- //Loadtheinitialviewsfromadapter
- mViews[0].recycleView(mCurrentPosition);
- mViews[1].recycleView(getNextPosition(mCurrentPosition));
- mViews[2].recycleView(getPrevPosition(mCurrentPosition));
- //Positionviewsatcorrectstartingoffsets
- mViews[0].setOffset(0,0,mCurrentViewNumber);
- mViews[1].setOffset(0,0,mCurrentViewNumber);
- mViews[2].setOffset(0,0,mCurrentViewNumber);
- }
- privateintgetViewOffset(intviewNumber,intrelativeViewNumber)
- {
- //Determinewidthincludingconfiguredpaddingwidth
- intoffsetWidth=mGalleryWidth+mViewPaddingWidth;
- //Positionthepreviousviewonemeasuredwidthtoleft
- if(viewNumber==getPrevViewNumber(relativeViewNumber))
- {
- returnoffsetWidth;
- }
- //Positionthenextviewonemeasuredwidthtotheright
- if(viewNumber==getNextViewNumber(relativeViewNumber))
- {
- returnoffsetWidth*-1;
- }
- return0;
- }
- voidmovePrevious()
- {
- //Slidetopreviousview
- mFlingDirection=1;
- processGesture();
- }
- voidmoveNext()
- {
- //Slidetonextview
- mFlingDirection=-1;
- processGesture();
- }
- @Override
- publicbooleanonKeyDown(intkeyCode,KeyEventevent)
- {
- switch(keyCode)
- {
- caseKeyEvent.KEYCODE_DPAD_LEFT:
- movePrevious();
- returntrue;
- caseKeyEvent.KEYCODE_DPAD_RIGHT:
- moveNext();
- returntrue;
- caseKeyEvent.KEYCODE_DPAD_CENTER:
- caseKeyEvent.KEYCODE_ENTER:
- }
- returnsuper.onKeyDown(keyCode,event);
- }
- publicbooleanonGalleryTouchEvent(MotionEventevent)
- {
- booleanconsumed=mGestureDetector.onTouchEvent(event);
- if(event.getAction()==MotionEvent.ACTION_UP)
- {
- if(mIsTouched||mIsDragging)
- {
- processScrollSnap();
- processGesture();
- }
- }
- returnconsumed;
- }
- voidprocessGesture()
- {
- intnewViewNumber=mCurrentViewNumber;
- intreloadViewNumber=0;
- intreloadPosition=0;
- mIsTouched=false;
- mIsDragging=false;
- if(mFlingDirection>0)
- {
- if(mCurrentPosition>getFirstPosition()||mIsGalleryCircular==true)
- {
- //Determinepreviousviewandoutgoingviewtorecycle
- newViewNumber=getPrevViewNumber(mCurrentViewNumber);
- mCurrentPosition=getPrevPosition(mCurrentPosition);
- reloadViewNumber=getNextViewNumber(mCurrentViewNumber);
- reloadPosition=getPrevPosition(mCurrentPosition);
- }
- }
- if(mFlingDirection<0)
- {
- if(mCurrentPosition<getLastPosition()||mIsGalleryCircular==true)
- {
- //Determinethenextviewandoutgoingviewtorecycle
- newViewNumber=getNextViewNumber(mCurrentViewNumber);
- mCurrentPosition=getNextPosition(mCurrentPosition);
- reloadViewNumber=getPrevViewNumber(mCurrentViewNumber);
- reloadPosition=getNextPosition(mCurrentPosition);
- }
- }
- if(newViewNumber!=mCurrentViewNumber)
- {
- mCurrentViewNumber=newViewNumber;
- //Reloadoutgoingviewfromadapterinnewposition
- mViews[reloadViewNumber].recycleView(reloadPosition);
- }
- //Ensureinputfocusonthecurrentview
- mViews[mCurrentViewNumber].requestFocus();
- //Runtheslideanimationsforviewtransitions
- mAnimation.prepareAnimation(mCurrentViewNumber);
- this.startAnimation(mAnimation);
- //Resetflingstate
- mFlingDirection=0;
- }
- voidprocessScrollSnap()
- {
- //Snaptonextviewifscrolledpassedsnapposition
- floatrollEdgeWidth=mGalleryWidth*mSnapBorderRatio;
- introllOffset=mGalleryWidth-(int)rollEdgeWidth;
- intcurrentOffset=mViews[mCurrentViewNumber].getCurrentOffset();
- if(currentOffset<=rollOffset*-1)
- {
- //Snaptopreviousview
- mFlingDirection=1;
- }
- if(currentOffset>=rollOffset)
- {
- //Snaptonextview
- mFlingDirection=-1;
- }
- }
- privateclassFlingGalleryView
- {
- privateintmViewNumber;
- privateFrameLayoutmParentLayout;
- privateFrameLayoutmInvalidLayout=null;
- privateLinearLayoutmInternalLayout=null;
- privateViewmExternalView=null;
- publicFlingGalleryView(intviewNumber,FrameLayoutparentLayout)
- {
- mViewNumber=viewNumber;
- mParentLayout=parentLayout;
- //Invalidlayoutisusedwhenoutsidegallery
- mInvalidLayout=newFrameLayout(mContext);
- mInvalidLayout.setLayoutParams(newLinearLayout.LayoutParams(
- LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
- //Internallayoutispermanentforduration
- mInternalLayout=newLinearLayout(mContext);
- mInternalLayout.setLayoutParams(newLinearLayout.LayoutParams(
- LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
- mParentLayout.addView(mInternalLayout);
- }
- publicvoidrecycleView(intnewPosition)
- {
- if(mExternalView!=null)
- {
- mInternalLayout.removeView(mExternalView);
- }
- if(mAdapter!=null)
- {
- if(newPosition>=getFirstPosition()&&newPosition<=getLastPosition())
- {
- mExternalView=mAdapter.getView(newPosition,mExternalView,mInternalLayout);
- }
- else
- {
- mExternalView=mInvalidLayout;
- }
- }
- if(mExternalView!=null)
- {
- mInternalLayout.addView(mExternalView,newLinearLayout.LayoutParams(
- LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
- }
- }
- publicvoidsetOffset(intxOffset,intyOffset,intrelativeViewNumber)
- {
- //Scrollthetargetviewrelativetoitsownpositionrelativetocurrentlydisplayedview
- mInternalLayout.scrollTo(getViewOffset(mViewNumber,relativeViewNumber)+xOffset,yOffset);
- }
- publicintgetCurrentOffset()
- {
- //Returnthecurrentscrollposition
- returnmInternalLayout.getScrollX();
- }
- publicvoidrequestFocus()
- {
- mInternalLayout.requestFocus();
- }
- }
- privateclassFlingGalleryAnimationextendsAnimation
- {
- privatebooleanmIsAnimationInProgres;
- privateintmRelativeViewNumber;
- privateintmInitialOffset;
- privateintmTargetOffset;
- privateintmTargetDistance;
- publicFlingGalleryAnimation()
- {
- mIsAnimationInProgres=false;
- mRelativeViewNumber=0;
- mInitialOffset=0;
- mTargetOffset=0;
- mTargetDistance=0;
- }
- publicvoidprepareAnimation(intrelativeViewNumber)
- {
- //Ifweareanimatingrelativetoanewview
- if(mRelativeViewNumber!=relativeViewNumber)
- {
- if(mIsAnimationInProgres==true)
- {
- //Weonlyhavethreeviewssoifrequestedagaintoanimateinsamedirectionwemustsnap
- intnewDirection=(relativeViewNumber==getPrevViewNumber(mRelativeViewNumber))?1:-1;
- intanimDirection=(mTargetDistance<0)?1:-1;
- //Ifanimationinsamedirection
- if(animDirection==newDirection)
- {
- //Ranoutoftimetoanimatesosnaptothetargetoffset
- mViews[0].setOffset(mTargetOffset,0,mRelativeViewNumber);
- mViews[1].setOffset(mTargetOffset,0,mRelativeViewNumber);
- mViews[2].setOffset(mTargetOffset,0,mRelativeViewNumber);
- }
- }
- //Setrelativeviewnumberforanimation
- mRelativeViewNumber=relativeViewNumber;
- }
- //Note:InthisimplementationthetargetOffsetwillalwaysbezero
- //aswearecenteringtheview;butweincludethecalculationsof
- //targetOffsetandtargetDistanceforuseinfutureimplementations
- mInitialOffset=mViews[mRelativeViewNumber].getCurrentOffset();
- mTargetOffset=getViewOffset(mRelativeViewNumber,mRelativeViewNumber);
- mTargetDistance=mTargetOffset-mInitialOffset;
- //Configurebaseanimationproperties
- this.setDuration(mAnimationDuration);
- this.setInterpolator(mDecelerateInterpolater);
- //Start/continuedanimation
- mIsAnimationInProgres=true;
- }
- @Override
- protectedvoidapplyTransformation(floatinterpolatedTime,Transformationtransformation)
- {
- //EnsureinterpolatedTimedoesnotover-shootthencalculatenewoffset
- interpolatedTime=(interpolatedTime>1.0f)?1.0f:interpolatedTime;
- intoffset=mInitialOffset+(int)(mTargetDistance*interpolatedTime);
- for(intviewNumber=0;viewNumber<3;viewNumber++)
- {
- //Onlyneedtoanimatethevisibleviewsastheotherviewwillalwaysbeoff-screen
- if((mTargetDistance>0&&viewNumber!=getNextViewNumber(mRelativeViewNumber))||
- (mTargetDistance<0&&viewNumber!=getPrevViewNumber(mRelativeViewNumber)))
- {
- mViews[viewNumber].setOffset(offset,0,mRelativeViewNumber);
- }
- }
- }
- @Override
- publicbooleangetTransformation(longcurrentTime,TransformationoutTransformation)
- {
- if(super.getTransformation(currentTime,outTransformation)==false)
- {
- //Performfinaladjustmenttooffsetstocleanupanimation
- mViews[0].setOffset(mTargetOffset,0,mRelativeViewNumber);
- mViews[1].setOffset(mTargetOffset,0,mRelativeViewNumber);
- mViews[2].setOffset(mTargetOffset,0,mRelativeViewNumber);
- //Reachedtheanimationtarget
- mIsAnimationInProgres=false;
- returnfalse;
- }
- //Cancelifthescreentouched
- if(mIsTouched||mIsDragging)
- {
- //Notethatatthispointwestillconsiderourselvestobeanimating
- //becausewehavenotyetreachedthetargetoffset;itsjustthatthe
- //userhastemporarilyinterruptedtheanimationwithatouchgesture
- returnfalse;
- }
- returntrue;
- }
- }
- privateclassFlingGestureDetectorextendsGestureDetector.SimpleOnGestureListener
- {
- @Override
- publicbooleanonDown(MotionEvente)
- {
- //Stopanimation
- mIsTouched=true;
- //Resetflingstate
- mFlingDirection=0;
- returntrue;
- }
- @Override
- publicbooleanonScroll(MotionEvente1,MotionEvente2,floatdistanceX,floatdistanceY)
- {
- if(e2.getAction()==MotionEvent.ACTION_MOVE)
- {
- if(mIsDragging==false)
- {
- //Stopanimation
- mIsTouched=true;
- //Reconfigurescroll
- mIsDragging=true;
- mFlingDirection=0;
- mScrollTimestamp=System.currentTimeMillis();
- mCurrentOffset=mViews[mCurrentViewNumber].getCurrentOffset();
- }
- floatmaxVelocity=mGalleryWidth/(mAnimationDuration/1000.0f);
- longtimestampDelta=System.currentTimeMillis()-mScrollTimestamp;
- floatmaxScrollDelta=maxVelocity*(timestampDelta/1000.0f);
- floatcurrentScrollDelta=e1.getX()-e2.getX();
- if(currentScrollDelta<maxScrollDelta*-1)currentScrollDelta=maxScrollDelta*-1;
- if(currentScrollDelta>maxScrollDelta)currentScrollDelta=maxScrollDelta;
- intscrollOffset=Math.round(mCurrentOffset+currentScrollDelta);
- //Wecan'tscrollmorethanthewidthofourownframelayout
- if(scrollOffset>=mGalleryWidth)scrollOffset=mGalleryWidth;
- if(scrollOffset<=mGalleryWidth*-1)scrollOffset=mGalleryWidth*-1;
- mViews[0].setOffset(scrollOffset,0,mCurrentViewNumber);
- mViews[1].setOffset(scrollOffset,0,mCurrentViewNumber);
- mViews[2].setOffset(scrollOffset,0,mCurrentViewNumber);
- }
- returnfalse;
- }
- @Override
- publicbooleanonFling(MotionEvente1,MotionEvente2,floatvelocityX,floatvelocityY)
- {
- if(Math.abs(e1.getY()-e2.getY())<=swipe_max_off_path)
- {
- if(e2.getX()-e1.getX()>swipe_min_distance&&Math.abs(velocityX)>swipe_threshold_veloicty)
- {
- movePrevious();
- }
- if(e1.getX()-e2.getX()>swipe_min_distance&&Math.abs(velocityX)>swipe_threshold_veloicty)
- {
- moveNext();
- }
- }
- returnfalse;
- }
- @Override
- publicvoidonLongPress(MotionEvente)
- {
- //Finalisescrolling
- mFlingDirection=0;
- processGesture();
- }
- @Override
- publicvoidonShowPress(MotionEvente)
- {
- }
- @Override
- publicbooleanonSingleTapUp(MotionEvente)
- {
- //Resetflingstate
- mFlingDirection=0;
- returnfalse;
- }
- }
- publicGestureDetectorgetMGestureDetector(){
- returnmGestureDetector;
- }
- }
由于我需要在滑动页面时,改动title中的文字,这里采用了观察者模式,加了个OnGalleryChangeListener,有同样需求的同学可以参考下。
Java代码
- publicinterfaceOnGalleryChangeListener{
- publicvoidonGalleryChange(intcurrentItem);
- }
在Activity中,
Java代码
- FlingGallerygallery=newFlingGallery(this);
- gallery.setAdapter(newArrayAdapter<String>(getApplicationContext(),
- android.R.layout.simple_list_item_1,newString[xxxx]){
- publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
- //返回滑动的deal
- returndealViews[position];
- }
- });
- gallery.addGalleryChangeListener(newOnGalleryChangeListener(){
- @Override
- publicvoidonGalleryChange(intcurrentItem){
- //干些想干的事件
- }
- });
将gallery加到Activity中的最终需要显示的类中。
Adapter中的getView方法中,将需要用来滑动的view添加进来。在GalleryChangeListener中,可以干一些自己想要的滑动后的事情。我在这里改动了标题的文字,进行了我的view中图片的lazyloading。
这里提一下另一个问题。我的这个类,最终嵌入到了tab中,需要在你的tabActivity中dispatchKeyEvent一下,将按键事件分发下去。
最开始,我的滑动的view写得比较通用,所以包含了ScrollView来满足比较长的屏幕,导致手势监听会出一些问题,会出现抖动。当时的解决方案是针对不同屏幕尽量保证一屏能够显示,在res目录下,增加layout-800x480之类的目录,针对每个不同屏幕设计单独的layout,放弃上下滑动,效果也不错。
更多相关文章
- Android:如何从堆栈中还原ProGuard混淆后的代码
- android recovery 主系统代码分析
- ym——Android如何支持多种屏幕
- Android 通过蓝牙控制小车源代码+视频
- Android的屏幕切换动画—左右滑动切换
- Android: 屏蔽屏幕旋转响应