iphone中有很多应用都能够左右滑动,非常cool,关键是实现起来非常简单。android比起来就差远了,网上有不少帖子。 我在这边重新分享下自己的经验吧,将实现细节详细解释下。

FlingGallery这个类摘自网上,有少许修改。
Java代码 收藏代码
  1. packagecom.nuomi.ui;
  2. importjava.util.HashSet;
  3. importjava.util.Set;
  4. importandroid.content.Context;
  5. importandroid.view.GestureDetector;
  6. importandroid.view.KeyEvent;
  7. importandroid.view.MotionEvent;
  8. importandroid.view.View;
  9. importandroid.view.animation.Animation;
  10. importandroid.view.animation.AnimationUtils;
  11. importandroid.view.animation.Interpolator;
  12. importandroid.view.animation.Transformation;
  13. importandroid.widget.Adapter;
  14. importandroid.widget.FrameLayout;
  15. importandroid.widget.LinearLayout;
  16. publicclassFlingGalleryextendsFrameLayout
  17. {
  18. privateSet<OnGalleryChangeListener>listeners;
  19. privatefinalintswipe_min_distance=120;
  20. privatefinalintswipe_max_off_path=250;
  21. privatefinalintswipe_threshold_veloicty=400;
  22. privateintmViewPaddingWidth=0;
  23. privateintmAnimationDuration=250;
  24. privatefloatmSnapBorderRatio=0.5f;
  25. privatebooleanmIsGalleryCircular=true;
  26. privateintmGalleryWidth=0;
  27. privatebooleanmIsTouched=false;
  28. privatebooleanmIsDragging=false;
  29. privatefloatmCurrentOffset=0.0f;
  30. privatelongmScrollTimestamp=0;
  31. privateintmFlingDirection=0;
  32. privateintmCurrentPosition=0;
  33. privateintmCurrentViewNumber=0;
  34. privateContextmContext;
  35. privateAdaptermAdapter;
  36. privateFlingGalleryView[]mViews;
  37. privateFlingGalleryAnimationmAnimation;
  38. privateGestureDetectormGestureDetector;
  39. privateInterpolatormDecelerateInterpolater;
  40. publicFlingGallery(Contextcontext)
  41. {
  42. super(context);
  43. listeners=newHashSet<OnGalleryChangeListener>();
  44. mContext=context;
  45. mAdapter=null;
  46. mViews=newFlingGalleryView[3];
  47. mViews[0]=newFlingGalleryView(0,this);
  48. mViews[1]=newFlingGalleryView(1,this);
  49. mViews[2]=newFlingGalleryView(2,this);
  50. mAnimation=newFlingGalleryAnimation();
  51. mGestureDetector=newGestureDetector(newFlingGestureDetector());
  52. mDecelerateInterpolater=AnimationUtils.loadInterpolator(mContext,android.R.anim.decelerate_interpolator);
  53. }
  54. publicvoidaddGalleryChangeListener(OnGalleryChangeListenerlistener){
  55. listeners.add(listener);
  56. }
  57. publicvoidsetPaddingWidth(intviewPaddingWidth)
  58. {
  59. mViewPaddingWidth=viewPaddingWidth;
  60. }
  61. publicvoidsetAnimationDuration(intanimationDuration)
  62. {
  63. mAnimationDuration=animationDuration;
  64. }
  65. publicvoidsetSnapBorderRatio(floatsnapBorderRatio)
  66. {
  67. mSnapBorderRatio=snapBorderRatio;
  68. }
  69. publicvoidsetIsGalleryCircular(booleanisGalleryCircular)
  70. {
  71. if(mIsGalleryCircular!=isGalleryCircular)
  72. {
  73. mIsGalleryCircular=isGalleryCircular;
  74. if(mCurrentPosition==getFirstPosition())
  75. {
  76. //Weneedtoreloadtheviewimmediatelytothelefttochangeittocircularvieworblank
  77. mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition));
  78. }
  79. if(mCurrentPosition==getLastPosition())
  80. {
  81. //Weneedtoreloadtheviewimmediatelytotherighttochangeittocircularvieworblank
  82. mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition));
  83. }
  84. }
  85. }
  86. publicintgetGalleryCount()
  87. {
  88. return(mAdapter==null)?0:mAdapter.getCount();
  89. }
  90. publicintgetFirstPosition()
  91. {
  92. return0;
  93. }
  94. publicintgetLastPosition()
  95. {
  96. return(getGalleryCount()==0)?0:getGalleryCount()-1;
  97. }
  98. privateintgetPrevPosition(intrelativePosition)
  99. {
  100. intprevPosition=relativePosition-1;
  101. if(prevPosition<getFirstPosition())
  102. {
  103. prevPosition=getFirstPosition()-1;
  104. if(mIsGalleryCircular==true)
  105. {
  106. prevPosition=getLastPosition();
  107. }
  108. }
  109. NotifyGalleryChange();
  110. returnprevPosition;
  111. }
  112. privateintgetNextPosition(intrelativePosition)
  113. {
  114. intnextPosition=relativePosition+1;
  115. if(nextPosition>getLastPosition())
  116. {
  117. nextPosition=getLastPosition()+1;
  118. if(mIsGalleryCircular==true)
  119. {
  120. nextPosition=getFirstPosition();
  121. }
  122. }
  123. NotifyGalleryChange();
  124. returnnextPosition;
  125. }
  126. //
  127. privatevoidNotifyGalleryChange(){
  128. for(OnGalleryChangeListenerlistener:listeners){
  129. listener.onGalleryChange(mCurrentPosition);
  130. }
  131. }
  132. privateintgetPrevViewNumber(intrelativeViewNumber)
  133. {
  134. return(relativeViewNumber==0)?2:relativeViewNumber-1;
  135. }
  136. privateintgetNextViewNumber(intrelativeViewNumber)
  137. {
  138. return(relativeViewNumber==2)?0:relativeViewNumber+1;
  139. }
  140. @Override
  141. protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom)
  142. {
  143. super.onLayout(changed,left,top,right,bottom);
  144. //Calculateourviewwidth
  145. mGalleryWidth=right-left;
  146. if(changed)
  147. {
  148. //Positionviewsatcorrectstartingoffsets
  149. mViews[0].setOffset(0,0,mCurrentViewNumber);
  150. mViews[1].setOffset(0,0,mCurrentViewNumber);
  151. mViews[2].setOffset(0,0,mCurrentViewNumber);
  152. }
  153. }
  154. publicvoidsetAdapter(Adapteradapter)
  155. {
  156. mAdapter=adapter;
  157. mCurrentPosition=0;
  158. mCurrentViewNumber=0;
  159. //Loadtheinitialviewsfromadapter
  160. mViews[0].recycleView(mCurrentPosition);
  161. mViews[1].recycleView(getNextPosition(mCurrentPosition));
  162. mViews[2].recycleView(getPrevPosition(mCurrentPosition));
  163. //Positionviewsatcorrectstartingoffsets
  164. mViews[0].setOffset(0,0,mCurrentViewNumber);
  165. mViews[1].setOffset(0,0,mCurrentViewNumber);
  166. mViews[2].setOffset(0,0,mCurrentViewNumber);
  167. }
  168. privateintgetViewOffset(intviewNumber,intrelativeViewNumber)
  169. {
  170. //Determinewidthincludingconfiguredpaddingwidth
  171. intoffsetWidth=mGalleryWidth+mViewPaddingWidth;
  172. //Positionthepreviousviewonemeasuredwidthtoleft
  173. if(viewNumber==getPrevViewNumber(relativeViewNumber))
  174. {
  175. returnoffsetWidth;
  176. }
  177. //Positionthenextviewonemeasuredwidthtotheright
  178. if(viewNumber==getNextViewNumber(relativeViewNumber))
  179. {
  180. returnoffsetWidth*-1;
  181. }
  182. return0;
  183. }
  184. voidmovePrevious()
  185. {
  186. //Slidetopreviousview
  187. mFlingDirection=1;
  188. processGesture();
  189. }
  190. voidmoveNext()
  191. {
  192. //Slidetonextview
  193. mFlingDirection=-1;
  194. processGesture();
  195. }
  196. @Override
  197. publicbooleanonKeyDown(intkeyCode,KeyEventevent)
  198. {
  199. switch(keyCode)
  200. {
  201. caseKeyEvent.KEYCODE_DPAD_LEFT:
  202. movePrevious();
  203. returntrue;
  204. caseKeyEvent.KEYCODE_DPAD_RIGHT:
  205. moveNext();
  206. returntrue;
  207. caseKeyEvent.KEYCODE_DPAD_CENTER:
  208. caseKeyEvent.KEYCODE_ENTER:
  209. }
  210. returnsuper.onKeyDown(keyCode,event);
  211. }
  212. publicbooleanonGalleryTouchEvent(MotionEventevent)
  213. {
  214. booleanconsumed=mGestureDetector.onTouchEvent(event);
  215. if(event.getAction()==MotionEvent.ACTION_UP)
  216. {
  217. if(mIsTouched||mIsDragging)
  218. {
  219. processScrollSnap();
  220. processGesture();
  221. }
  222. }
  223. returnconsumed;
  224. }
  225. voidprocessGesture()
  226. {
  227. intnewViewNumber=mCurrentViewNumber;
  228. intreloadViewNumber=0;
  229. intreloadPosition=0;
  230. mIsTouched=false;
  231. mIsDragging=false;
  232. if(mFlingDirection>0)
  233. {
  234. if(mCurrentPosition>getFirstPosition()||mIsGalleryCircular==true)
  235. {
  236. //Determinepreviousviewandoutgoingviewtorecycle
  237. newViewNumber=getPrevViewNumber(mCurrentViewNumber);
  238. mCurrentPosition=getPrevPosition(mCurrentPosition);
  239. reloadViewNumber=getNextViewNumber(mCurrentViewNumber);
  240. reloadPosition=getPrevPosition(mCurrentPosition);
  241. }
  242. }
  243. if(mFlingDirection<0)
  244. {
  245. if(mCurrentPosition<getLastPosition()||mIsGalleryCircular==true)
  246. {
  247. //Determinethenextviewandoutgoingviewtorecycle
  248. newViewNumber=getNextViewNumber(mCurrentViewNumber);
  249. mCurrentPosition=getNextPosition(mCurrentPosition);
  250. reloadViewNumber=getPrevViewNumber(mCurrentViewNumber);
  251. reloadPosition=getNextPosition(mCurrentPosition);
  252. }
  253. }
  254. if(newViewNumber!=mCurrentViewNumber)
  255. {
  256. mCurrentViewNumber=newViewNumber;
  257. //Reloadoutgoingviewfromadapterinnewposition
  258. mViews[reloadViewNumber].recycleView(reloadPosition);
  259. }
  260. //Ensureinputfocusonthecurrentview
  261. mViews[mCurrentViewNumber].requestFocus();
  262. //Runtheslideanimationsforviewtransitions
  263. mAnimation.prepareAnimation(mCurrentViewNumber);
  264. this.startAnimation(mAnimation);
  265. //Resetflingstate
  266. mFlingDirection=0;
  267. }
  268. voidprocessScrollSnap()
  269. {
  270. //Snaptonextviewifscrolledpassedsnapposition
  271. floatrollEdgeWidth=mGalleryWidth*mSnapBorderRatio;
  272. introllOffset=mGalleryWidth-(int)rollEdgeWidth;
  273. intcurrentOffset=mViews[mCurrentViewNumber].getCurrentOffset();
  274. if(currentOffset<=rollOffset*-1)
  275. {
  276. //Snaptopreviousview
  277. mFlingDirection=1;
  278. }
  279. if(currentOffset>=rollOffset)
  280. {
  281. //Snaptonextview
  282. mFlingDirection=-1;
  283. }
  284. }
  285. privateclassFlingGalleryView
  286. {
  287. privateintmViewNumber;
  288. privateFrameLayoutmParentLayout;
  289. privateFrameLayoutmInvalidLayout=null;
  290. privateLinearLayoutmInternalLayout=null;
  291. privateViewmExternalView=null;
  292. publicFlingGalleryView(intviewNumber,FrameLayoutparentLayout)
  293. {
  294. mViewNumber=viewNumber;
  295. mParentLayout=parentLayout;
  296. //Invalidlayoutisusedwhenoutsidegallery
  297. mInvalidLayout=newFrameLayout(mContext);
  298. mInvalidLayout.setLayoutParams(newLinearLayout.LayoutParams(
  299. LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
  300. //Internallayoutispermanentforduration
  301. mInternalLayout=newLinearLayout(mContext);
  302. mInternalLayout.setLayoutParams(newLinearLayout.LayoutParams(
  303. LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
  304. mParentLayout.addView(mInternalLayout);
  305. }
  306. publicvoidrecycleView(intnewPosition)
  307. {
  308. if(mExternalView!=null)
  309. {
  310. mInternalLayout.removeView(mExternalView);
  311. }
  312. if(mAdapter!=null)
  313. {
  314. if(newPosition>=getFirstPosition()&&newPosition<=getLastPosition())
  315. {
  316. mExternalView=mAdapter.getView(newPosition,mExternalView,mInternalLayout);
  317. }
  318. else
  319. {
  320. mExternalView=mInvalidLayout;
  321. }
  322. }
  323. if(mExternalView!=null)
  324. {
  325. mInternalLayout.addView(mExternalView,newLinearLayout.LayoutParams(
  326. LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
  327. }
  328. }
  329. publicvoidsetOffset(intxOffset,intyOffset,intrelativeViewNumber)
  330. {
  331. //Scrollthetargetviewrelativetoitsownpositionrelativetocurrentlydisplayedview
  332. mInternalLayout.scrollTo(getViewOffset(mViewNumber,relativeViewNumber)+xOffset,yOffset);
  333. }
  334. publicintgetCurrentOffset()
  335. {
  336. //Returnthecurrentscrollposition
  337. returnmInternalLayout.getScrollX();
  338. }
  339. publicvoidrequestFocus()
  340. {
  341. mInternalLayout.requestFocus();
  342. }
  343. }
  344. privateclassFlingGalleryAnimationextendsAnimation
  345. {
  346. privatebooleanmIsAnimationInProgres;
  347. privateintmRelativeViewNumber;
  348. privateintmInitialOffset;
  349. privateintmTargetOffset;
  350. privateintmTargetDistance;
  351. publicFlingGalleryAnimation()
  352. {
  353. mIsAnimationInProgres=false;
  354. mRelativeViewNumber=0;
  355. mInitialOffset=0;
  356. mTargetOffset=0;
  357. mTargetDistance=0;
  358. }
  359. publicvoidprepareAnimation(intrelativeViewNumber)
  360. {
  361. //Ifweareanimatingrelativetoanewview
  362. if(mRelativeViewNumber!=relativeViewNumber)
  363. {
  364. if(mIsAnimationInProgres==true)
  365. {
  366. //Weonlyhavethreeviewssoifrequestedagaintoanimateinsamedirectionwemustsnap
  367. intnewDirection=(relativeViewNumber==getPrevViewNumber(mRelativeViewNumber))?1:-1;
  368. intanimDirection=(mTargetDistance<0)?1:-1;
  369. //Ifanimationinsamedirection
  370. if(animDirection==newDirection)
  371. {
  372. //Ranoutoftimetoanimatesosnaptothetargetoffset
  373. mViews[0].setOffset(mTargetOffset,0,mRelativeViewNumber);
  374. mViews[1].setOffset(mTargetOffset,0,mRelativeViewNumber);
  375. mViews[2].setOffset(mTargetOffset,0,mRelativeViewNumber);
  376. }
  377. }
  378. //Setrelativeviewnumberforanimation
  379. mRelativeViewNumber=relativeViewNumber;
  380. }
  381. //Note:InthisimplementationthetargetOffsetwillalwaysbezero
  382. //aswearecenteringtheview;butweincludethecalculationsof
  383. //targetOffsetandtargetDistanceforuseinfutureimplementations
  384. mInitialOffset=mViews[mRelativeViewNumber].getCurrentOffset();
  385. mTargetOffset=getViewOffset(mRelativeViewNumber,mRelativeViewNumber);
  386. mTargetDistance=mTargetOffset-mInitialOffset;
  387. //Configurebaseanimationproperties
  388. this.setDuration(mAnimationDuration);
  389. this.setInterpolator(mDecelerateInterpolater);
  390. //Start/continuedanimation
  391. mIsAnimationInProgres=true;
  392. }
  393. @Override
  394. protectedvoidapplyTransformation(floatinterpolatedTime,Transformationtransformation)
  395. {
  396. //EnsureinterpolatedTimedoesnotover-shootthencalculatenewoffset
  397. interpolatedTime=(interpolatedTime>1.0f)?1.0f:interpolatedTime;
  398. intoffset=mInitialOffset+(int)(mTargetDistance*interpolatedTime);
  399. for(intviewNumber=0;viewNumber<3;viewNumber++)
  400. {
  401. //Onlyneedtoanimatethevisibleviewsastheotherviewwillalwaysbeoff-screen
  402. if((mTargetDistance>0&&viewNumber!=getNextViewNumber(mRelativeViewNumber))||
  403. (mTargetDistance<0&&viewNumber!=getPrevViewNumber(mRelativeViewNumber)))
  404. {
  405. mViews[viewNumber].setOffset(offset,0,mRelativeViewNumber);
  406. }
  407. }
  408. }
  409. @Override
  410. publicbooleangetTransformation(longcurrentTime,TransformationoutTransformation)
  411. {
  412. if(super.getTransformation(currentTime,outTransformation)==false)
  413. {
  414. //Performfinaladjustmenttooffsetstocleanupanimation
  415. mViews[0].setOffset(mTargetOffset,0,mRelativeViewNumber);
  416. mViews[1].setOffset(mTargetOffset,0,mRelativeViewNumber);
  417. mViews[2].setOffset(mTargetOffset,0,mRelativeViewNumber);
  418. //Reachedtheanimationtarget
  419. mIsAnimationInProgres=false;
  420. returnfalse;
  421. }
  422. //Cancelifthescreentouched
  423. if(mIsTouched||mIsDragging)
  424. {
  425. //Notethatatthispointwestillconsiderourselvestobeanimating
  426. //becausewehavenotyetreachedthetargetoffset;itsjustthatthe
  427. //userhastemporarilyinterruptedtheanimationwithatouchgesture
  428. returnfalse;
  429. }
  430. returntrue;
  431. }
  432. }
  433. privateclassFlingGestureDetectorextendsGestureDetector.SimpleOnGestureListener
  434. {
  435. @Override
  436. publicbooleanonDown(MotionEvente)
  437. {
  438. //Stopanimation
  439. mIsTouched=true;
  440. //Resetflingstate
  441. mFlingDirection=0;
  442. returntrue;
  443. }
  444. @Override
  445. publicbooleanonScroll(MotionEvente1,MotionEvente2,floatdistanceX,floatdistanceY)
  446. {
  447. if(e2.getAction()==MotionEvent.ACTION_MOVE)
  448. {
  449. if(mIsDragging==false)
  450. {
  451. //Stopanimation
  452. mIsTouched=true;
  453. //Reconfigurescroll
  454. mIsDragging=true;
  455. mFlingDirection=0;
  456. mScrollTimestamp=System.currentTimeMillis();
  457. mCurrentOffset=mViews[mCurrentViewNumber].getCurrentOffset();
  458. }
  459. floatmaxVelocity=mGalleryWidth/(mAnimationDuration/1000.0f);
  460. longtimestampDelta=System.currentTimeMillis()-mScrollTimestamp;
  461. floatmaxScrollDelta=maxVelocity*(timestampDelta/1000.0f);
  462. floatcurrentScrollDelta=e1.getX()-e2.getX();
  463. if(currentScrollDelta<maxScrollDelta*-1)currentScrollDelta=maxScrollDelta*-1;
  464. if(currentScrollDelta>maxScrollDelta)currentScrollDelta=maxScrollDelta;
  465. intscrollOffset=Math.round(mCurrentOffset+currentScrollDelta);
  466. //Wecan'tscrollmorethanthewidthofourownframelayout
  467. if(scrollOffset>=mGalleryWidth)scrollOffset=mGalleryWidth;
  468. if(scrollOffset<=mGalleryWidth*-1)scrollOffset=mGalleryWidth*-1;
  469. mViews[0].setOffset(scrollOffset,0,mCurrentViewNumber);
  470. mViews[1].setOffset(scrollOffset,0,mCurrentViewNumber);
  471. mViews[2].setOffset(scrollOffset,0,mCurrentViewNumber);
  472. }
  473. returnfalse;
  474. }
  475. @Override
  476. publicbooleanonFling(MotionEvente1,MotionEvente2,floatvelocityX,floatvelocityY)
  477. {
  478. if(Math.abs(e1.getY()-e2.getY())<=swipe_max_off_path)
  479. {
  480. if(e2.getX()-e1.getX()>swipe_min_distance&&Math.abs(velocityX)>swipe_threshold_veloicty)
  481. {
  482. movePrevious();
  483. }
  484. if(e1.getX()-e2.getX()>swipe_min_distance&&Math.abs(velocityX)>swipe_threshold_veloicty)
  485. {
  486. moveNext();
  487. }
  488. }
  489. returnfalse;
  490. }
  491. @Override
  492. publicvoidonLongPress(MotionEvente)
  493. {
  494. //Finalisescrolling
  495. mFlingDirection=0;
  496. processGesture();
  497. }
  498. @Override
  499. publicvoidonShowPress(MotionEvente)
  500. {
  501. }
  502. @Override
  503. publicbooleanonSingleTapUp(MotionEvente)
  504. {
  505. //Resetflingstate
  506. mFlingDirection=0;
  507. returnfalse;
  508. }
  509. }
  510. publicGestureDetectorgetMGestureDetector(){
  511. returnmGestureDetector;
  512. }
  513. }


由于我需要在滑动页面时,改动title中的文字,这里采用了观察者模式,加了个OnGalleryChangeListener,有同样需求的同学可以参考下。
Java代码 收藏代码
  1. publicinterfaceOnGalleryChangeListener{
  2. publicvoidonGalleryChange(intcurrentItem);
  3. }

在Activity中,
Java代码 收藏代码
  1. FlingGallerygallery=newFlingGallery(this);
  2. gallery.setAdapter(newArrayAdapter<String>(getApplicationContext(),
  3. android.R.layout.simple_list_item_1,newString[xxxx]){
  4. publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
  5. //返回滑动的deal
  6. returndealViews[position];
  7. }
  8. });
  9. gallery.addGalleryChangeListener(newOnGalleryChangeListener(){
  10. @Override
  11. publicvoidonGalleryChange(intcurrentItem){
  12. //干些想干的事件
  13. }
  14. });

将gallery加到Activity中的最终需要显示的类中。
Adapter中的getView方法中,将需要用来滑动的view添加进来。在GalleryChangeListener中,可以干一些自己想要的滑动后的事情。我在这里改动了标题的文字,进行了我的view中图片的lazyloading。
这里提一下另一个问题。我的这个类,最终嵌入到了tab中,需要在你的tabActivity中dispatchKeyEvent一下,将按键事件分发下去。
最开始,我的滑动的view写得比较通用,所以包含了ScrollView来满足比较长的屏幕,导致手势监听会出一些问题,会出现抖动。当时的解决方案是针对不同屏幕尽量保证一屏能够显示,在res目录下,增加layout-800x480之类的目录,针对每个不同屏幕设计单独的layout,放弃上下滑动,效果也不错。

更多相关文章

  1. Android:如何从堆栈中还原ProGuard混淆后的代码
  2. android recovery 主系统代码分析
  3. ym——Android如何支持多种屏幕
  4. Android 通过蓝牙控制小车源代码+视频
  5. Android的屏幕切换动画—左右滑动切换
  6. Android: 屏蔽屏幕旋转响应

随机推荐

  1. Android中的设计模式--建造者模式
  2. Android 打开系统蓝牙设置
  3. android项目源码异步加载远程图片的小例
  4. android 发送短信,彩信,邮件代码
  5. Android自定义属性,format详解
  6. android整合--屏幕旋转触发事件
  7. Android file.createNewFile方法问题总结
  8. 添加了android:configChanges="orientati
  9. Android拍照得到的照片旋转了90度
  10. Android 无法接收开机广播的问题