android中的左右滑动

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

FlingGallery这个类摘自网上,有少许修改。
Java代码 复制代码  收藏代码
  1. package com.nuomi.ui;   
  2.   
  3. import java.util.HashSet;   
  4. import java.util.Set;   
  5.   
  6. import android.content.Context;   
  7. import android.view.GestureDetector;   
  8. import android.view.KeyEvent;   
  9. import android.view.MotionEvent;   
  10. import android.view.View;   
  11. import android.view.animation.Animation;   
  12. import android.view.animation.AnimationUtils;   
  13. import android.view.animation.Interpolator;   
  14. import android.view.animation.Transformation;   
  15. import android.widget.Adapter;   
  16. import android.widget.FrameLayout;   
  17. import android.widget.LinearLayout;   
  18.   
  19.   
  20. public class FlingGallery extends FrameLayout   
  21. {   
  22.        
  23.     private Set<OnGalleryChangeListener> listeners;   
  24.     private final int swipe_min_distance = 120;   
  25.     private final int swipe_max_off_path = 250;   
  26.     private final int swipe_threshold_veloicty = 400;   
  27.   
  28.     private int mViewPaddingWidth = 0;   
  29.     private int mAnimationDuration = 250;   
  30.     private float mSnapBorderRatio = 0.5f;   
  31.     private boolean mIsGalleryCircular = true;   
  32.   
  33.     private int mGalleryWidth = 0;   
  34.     private boolean mIsTouched = false;   
  35.     private boolean mIsDragging = false;   
  36.     private float mCurrentOffset = 0.0f;   
  37.     private long mScrollTimestamp = 0;   
  38.     private int mFlingDirection = 0;   
  39.     private int mCurrentPosition = 0;   
  40.     private int mCurrentViewNumber = 0;   
  41.   
  42.     private Context mContext;   
  43.     private Adapter mAdapter;   
  44.     private FlingGalleryView[] mViews;   
  45.     private FlingGalleryAnimation mAnimation;   
  46.     private GestureDetector mGestureDetector;   
  47.     private Interpolator mDecelerateInterpolater;   
  48.   
  49.     public FlingGallery(Context context)   
  50.     {   
  51.         super(context);   
  52.   
  53.         listeners = new HashSet<OnGalleryChangeListener>();   
  54.            
  55.         mContext = context;   
  56.         mAdapter = null;   
  57.            
  58.         mViews = new FlingGalleryView[3];   
  59.         mViews[0] = new FlingGalleryView(0this);   
  60.         mViews[1] = new FlingGalleryView(1this);   
  61.         mViews[2] = new FlingGalleryView(2this);   
  62.   
  63.         mAnimation = new FlingGalleryAnimation();   
  64.         mGestureDetector = new GestureDetector(new FlingGestureDetector());   
  65.         mDecelerateInterpolater = AnimationUtils.loadInterpolator(mContext, android.R.anim.decelerate_interpolator);   
  66.     }   
  67.   
  68.     public void addGalleryChangeListener(OnGalleryChangeListener listener){   
  69.         listeners.add(listener);   
  70.     }   
  71.        
  72.     public void setPaddingWidth(int viewPaddingWidth)   
  73.     {   
  74.         mViewPaddingWidth = viewPaddingWidth;   
  75.     }   
  76.   
  77.     public void setAnimationDuration(int animationDuration)   
  78.     {   
  79.         mAnimationDuration = animationDuration;   
  80.     }   
  81.        
  82.     public void setSnapBorderRatio(float snapBorderRatio)   
  83.     {   
  84.         mSnapBorderRatio = snapBorderRatio;   
  85.     }   
  86.   
  87.     public void setIsGalleryCircular(boolean isGalleryCircular)    
  88.     {   
  89.         if (mIsGalleryCircular != isGalleryCircular)   
  90.         {   
  91.             mIsGalleryCircular = isGalleryCircular;   
  92.        
  93.             if (mCurrentPosition == getFirstPosition())   
  94.             {   
  95.                 // We need to reload the view immediately to the left to change it to circular view or blank   
  96.                 mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition));              
  97.             }   
  98.        
  99.             if (mCurrentPosition == getLastPosition())   
  100.             {   
  101.                 // We need to reload the view immediately to the right to change it to circular view or blank   
  102.                 mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition));              
  103.             }   
  104.         }   
  105.     }   
  106.   
  107.     public int getGalleryCount()   
  108.     {   
  109.         return (mAdapter == null) ? 0 : mAdapter.getCount();   
  110.     }   
  111.   
  112.     public int getFirstPosition()   
  113.     {   
  114.         return 0;   
  115.     }   
  116.   
  117.     public int getLastPosition()   
  118.     {   
  119.         return (getGalleryCount() == 0) ? 0 : getGalleryCount() - 1;   
  120.     }   
  121.   
  122.     private int getPrevPosition(int relativePosition)   
  123.     {   
  124.         int prevPosition = relativePosition - 1;   
  125.   
  126.         if (prevPosition < getFirstPosition())   
  127.         {   
  128.             prevPosition = getFirstPosition() - 1;   
  129.   
  130.             if (mIsGalleryCircular == true)   
  131.             {   
  132.                 prevPosition = getLastPosition();   
  133.             }   
  134.         }   
  135.         NotifyGalleryChange();   
  136.         return prevPosition;   
  137.     }   
  138.   
  139.     private int getNextPosition(int relativePosition)   
  140.     {   
  141.         int nextPosition = relativePosition + 1;   
  142.   
  143.         if (nextPosition > getLastPosition())   
  144.         {   
  145.             nextPosition = getLastPosition() + 1;   
  146.   
  147.             if (mIsGalleryCircular == true)   
  148.             {   
  149.                 nextPosition = getFirstPosition();   
  150.             }   
  151.         }   
  152.         NotifyGalleryChange();   
  153.         return nextPosition;   
  154.     }   
  155.   
  156.     //   
  157.     private void NotifyGalleryChange() {   
  158.         for (OnGalleryChangeListener listener :listeners) {   
  159.             listener.onGalleryChange(mCurrentPosition);   
  160.         }   
  161.     }   
  162.   
  163.     private int getPrevViewNumber(int relativeViewNumber)   
  164.     {   
  165.         return (relativeViewNumber == 0) ? 2 : relativeViewNumber - 1;   
  166.     }   
  167.   
  168.     private int getNextViewNumber(int relativeViewNumber)   
  169.     {   
  170.         return (relativeViewNumber == 2) ? 0 : relativeViewNumber + 1;   
  171.     }   
  172.        
  173.     @Override  
  174.     protected void onLayout(boolean changed, int left, int top, int right, int bottom)   
  175.     {   
  176.         super.onLayout(changed, left, top, right, bottom);   
  177.   
  178.         // Calculate our view width   
  179.         mGalleryWidth = right - left;   
  180.   
  181.         if (changed)   
  182.         {   
  183.             // Position views at correct starting offsets   
  184.             mViews[0].setOffset(00, mCurrentViewNumber);   
  185.             mViews[1].setOffset(00, mCurrentViewNumber);   
  186.             mViews[2].setOffset(00, mCurrentViewNumber);   
  187.         }   
  188.     }   
  189.   
  190.     public void setAdapter(Adapter adapter)   
  191.     {   
  192.         mAdapter = adapter;   
  193.         mCurrentPosition = 0;   
  194.         mCurrentViewNumber = 0;   
  195.   
  196.         // Load the initial views from adapter   
  197.         mViews[0].recycleView(mCurrentPosition);   
  198.         mViews[1].recycleView(getNextPosition(mCurrentPosition));   
  199.         mViews[2].recycleView(getPrevPosition(mCurrentPosition));   
  200.   
  201.         // Position views at correct starting offsets   
  202.         mViews[0].setOffset(00, mCurrentViewNumber);   
  203.         mViews[1].setOffset(00, mCurrentViewNumber);   
  204.         mViews[2].setOffset(00, mCurrentViewNumber);   
  205.     }   
  206.   
  207.     private int getViewOffset(int viewNumber, int relativeViewNumber)   
  208.     {   
  209.         // Determine width including configured padding width   
  210.         int offsetWidth = mGalleryWidth + mViewPaddingWidth;   
  211.   
  212.         // Position the previous view one measured width to left   
  213.         if (viewNumber == getPrevViewNumber(relativeViewNumber))   
  214.         {   
  215.             return offsetWidth;   
  216.         }   
  217.   
  218.         // Position the next view one measured width to the right   
  219.         if (viewNumber == getNextViewNumber(relativeViewNumber))   
  220.         {   
  221.             return offsetWidth * -1;   
  222.         }   
  223.   
  224.         return 0;   
  225.     }   
  226.   
  227.     void movePrevious()   
  228.     {   
  229.         // Slide to previous view   
  230.         mFlingDirection = 1;   
  231.         processGesture();   
  232.     }   
  233.   
  234.     void moveNext()   
  235.     {   
  236.         // Slide to next view   
  237.         mFlingDirection = -1;   
  238.         processGesture();   
  239.     }   
  240.   
  241.      @Override  
  242.      public boolean onKeyDown(int keyCode, KeyEvent event)   
  243.      {   
  244.         switch (keyCode)   
  245.         {   
  246.         case KeyEvent.KEYCODE_DPAD_LEFT:   
  247.             movePrevious();   
  248.             return true;   
  249.        
  250.         case KeyEvent.KEYCODE_DPAD_RIGHT:   
  251.             moveNext();   
  252.             return true;   
  253.        
  254.         case KeyEvent.KEYCODE_DPAD_CENTER:   
  255.         case KeyEvent.KEYCODE_ENTER:   
  256.         }   
  257.   
  258.         return super.onKeyDown(keyCode, event);   
  259.     }   
  260.   
  261.     public boolean onGalleryTouchEvent(MotionEvent event)   
  262.     {   
  263.         boolean consumed = mGestureDetector.onTouchEvent(event);   
  264.            
  265.         if (event.getAction() == MotionEvent.ACTION_UP)   
  266.         {   
  267.             if (mIsTouched || mIsDragging)   
  268.             {   
  269.                 processScrollSnap();   
  270.                 processGesture();   
  271.             }   
  272.         }   
  273.            
  274.         return consumed;   
  275.     }   
  276.   
  277.     void processGesture()   
  278.     {   
  279.         int newViewNumber = mCurrentViewNumber;   
  280.         int reloadViewNumber = 0;   
  281.         int reloadPosition = 0;   
  282.   
  283.         mIsTouched = false;   
  284.         mIsDragging = false;   
  285.   
  286.         if (mFlingDirection > 0)   
  287.         {   
  288.             if (mCurrentPosition > getFirstPosition() || mIsGalleryCircular == true)   
  289.             {   
  290.                 // Determine previous view and outgoing view to recycle   
  291.                 newViewNumber = getPrevViewNumber(mCurrentViewNumber);   
  292.                 mCurrentPosition = getPrevPosition(mCurrentPosition);   
  293.                 reloadViewNumber = getNextViewNumber(mCurrentViewNumber);    
  294.                 reloadPosition = getPrevPosition(mCurrentPosition);   
  295.             }   
  296.         }   
  297.   
  298.         if (mFlingDirection < 0)   
  299.         {   
  300.             if (mCurrentPosition < getLastPosition() || mIsGalleryCircular == true)   
  301.             {   
  302.                 // Determine the next view and outgoing view to recycle   
  303.                 newViewNumber = getNextViewNumber(mCurrentViewNumber);   
  304.                 mCurrentPosition = getNextPosition(mCurrentPosition);   
  305.                 reloadViewNumber = getPrevViewNumber(mCurrentViewNumber);   
  306.                 reloadPosition = getNextPosition(mCurrentPosition);   
  307.             }   
  308.         }   
  309.   
  310.         if (newViewNumber != mCurrentViewNumber)   
  311.         {   
  312.             mCurrentViewNumber = newViewNumber;    
  313.   
  314.             // Reload outgoing view from adapter in new position   
  315.             mViews[reloadViewNumber].recycleView(reloadPosition);   
  316.         }   
  317.   
  318.         // Ensure input focus on the current view   
  319.         mViews[mCurrentViewNumber].requestFocus();   
  320.   
  321.         // Run the slide animations for view transitions   
  322.         mAnimation.prepareAnimation(mCurrentViewNumber);   
  323.         this.startAnimation(mAnimation);   
  324.   
  325.         // Reset fling state   
  326.         mFlingDirection = 0;   
  327.     }   
  328.   
  329.     void processScrollSnap()   
  330.     {   
  331.         // Snap to next view if scrolled passed snap position   
  332.         float rollEdgeWidth = mGalleryWidth * mSnapBorderRatio;   
  333.         int rollOffset = mGalleryWidth - (int) rollEdgeWidth;   
  334.         int currentOffset = mViews[mCurrentViewNumber].getCurrentOffset();   
  335.   
  336.         if (currentOffset <= rollOffset * -1)   
  337.         {   
  338.             // Snap to previous view   
  339.             mFlingDirection = 1;   
  340.         }   
  341.   
  342.         if (currentOffset >= rollOffset)   
  343.         {   
  344.             // Snap to next view   
  345.             mFlingDirection = -1;   
  346.         }   
  347.     }   
  348.   
  349.     private class FlingGalleryView   
  350.     {   
  351.         private int mViewNumber;   
  352.         private FrameLayout mParentLayout;   
  353.            
  354.         private FrameLayout mInvalidLayout = null;   
  355.         private LinearLayout mInternalLayout = null;   
  356.         private View mExternalView = null;   
  357.   
  358.         public FlingGalleryView(int viewNumber, FrameLayout parentLayout)   
  359.         {   
  360.             mViewNumber = viewNumber;   
  361.             mParentLayout = parentLayout;   
  362.   
  363.             // Invalid layout is used when outside gallery   
  364.             mInvalidLayout = new FrameLayout(mContext);   
  365.             mInvalidLayout.setLayoutParams(new LinearLayout.LayoutParams(    
  366.                     LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));   
  367.   
  368.             // Internal layout is permanent for duration   
  369.             mInternalLayout = new LinearLayout(mContext);   
  370.             mInternalLayout.setLayoutParams(new LinearLayout.LayoutParams(    
  371.                     LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));   
  372.   
  373.             mParentLayout.addView(mInternalLayout);   
  374.         }   
  375.   
  376.         public void recycleView(int newPosition)   
  377.         {   
  378.             if (mExternalView != null)   
  379.             {   
  380.                 mInternalLayout.removeView(mExternalView);   
  381.             }   
  382.   
  383.             if (mAdapter != null)   
  384.             {   
  385.                 if (newPosition >= getFirstPosition() && newPosition <= getLastPosition())   
  386.                 {   
  387.                     mExternalView = mAdapter.getView(newPosition, mExternalView, mInternalLayout);   
  388.                 }   
  389.                 else  
  390.                 {   
  391.                     mExternalView = mInvalidLayout;   
  392.                 }   
  393.             }   
  394.   
  395.             if (mExternalView != null)   
  396.             {   
  397.                 mInternalLayout.addView(mExternalView, new LinearLayout.LayoutParams(    
  398.                     LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));   
  399.             }   
  400.         }   
  401.   
  402.         public void setOffset(int xOffset, int yOffset, int relativeViewNumber)   
  403.         {   
  404.             // Scroll the target view relative to its own position relative to currently displayed view   
  405.             mInternalLayout.scrollTo(getViewOffset(mViewNumber, relativeViewNumber) + xOffset, yOffset);   
  406.         }   
  407.            
  408.         public int getCurrentOffset()   
  409.         {   
  410.             // Return the current scroll position   
  411.             return mInternalLayout.getScrollX();   
  412.         }   
  413.   
  414.         public void requestFocus()   
  415.         {   
  416.             mInternalLayout.requestFocus();   
  417.         }   
  418.     }   
  419.   
  420.     private class FlingGalleryAnimation extends Animation   
  421.     {   
  422.         private boolean mIsAnimationInProgres;   
  423.         private int mRelativeViewNumber;   
  424.         private int mInitialOffset;   
  425.         private int mTargetOffset;   
  426.         private int mTargetDistance;       
  427.     
  428.         public FlingGalleryAnimation()   
  429.         {   
  430.             mIsAnimationInProgres = false;   
  431.             mRelativeViewNumber = 0;   
  432.             mInitialOffset = 0;   
  433.             mTargetOffset = 0;   
  434.             mTargetDistance = 0;   
  435.         }   
  436.     
  437.         public void prepareAnimation(int relativeViewNumber)   
  438.         {   
  439.             // If we are animating relative to a new view   
  440.             if (mRelativeViewNumber != relativeViewNumber)   
  441.             {   
  442.                 if (mIsAnimationInProgres == true)   
  443.                 {   
  444.                     // We only have three views so if requested again to animate in same direction we must snap    
  445.                     int newDirection = (relativeViewNumber == getPrevViewNumber(mRelativeViewNumber)) ? 1 : -1;   
  446.                     int animDirection = (mTargetDistance < 0) ? 1 : -1;    
  447.   
  448.                     // If animation in same direction   
  449.                     if (animDirection == newDirection)   
  450.                     {   
  451.                         // Ran out of time to animate so snap to the target offset   
  452.                         mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);   
  453.                         mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);   
  454.                         mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);    
  455.                     }   
  456.                 }   
  457.        
  458.                 // Set relative view number for animation   
  459.                 mRelativeViewNumber = relativeViewNumber;   
  460.             }   
  461.   
  462.             // Note: In this implementation the targetOffset will always be zero   
  463.             // as we are centering the view; but we include the calculations of   
  464.             // targetOffset and targetDistance for use in future implementations   
  465.   
  466.             mInitialOffset = mViews[mRelativeViewNumber].getCurrentOffset();   
  467.             mTargetOffset = getViewOffset(mRelativeViewNumber, mRelativeViewNumber);   
  468.             mTargetDistance = mTargetOffset - mInitialOffset;   
  469.   
  470.             // Configure base animation properties   
  471.             this.setDuration(mAnimationDuration);   
  472.             this.setInterpolator(mDecelerateInterpolater);   
  473.   
  474.             // Start/continued animation   
  475.             mIsAnimationInProgres = true;   
  476.         }   
  477.   
  478.         @Override  
  479.         protected void applyTransformation(float interpolatedTime, Transformation transformation)   
  480.         {   
  481.             // Ensure interpolatedTime does not over-shoot then calculate new offset   
  482.             interpolatedTime = (interpolatedTime > 1.0f) ? 1.0f : interpolatedTime;   
  483.             int offset = mInitialOffset + (int) (mTargetDistance * interpolatedTime);   
  484.   
  485.             for (int viewNumber = 0; viewNumber < 3; viewNumber++)   
  486.             {   
  487.                 // Only need to animate the visible views as the other view will always be off-screen   
  488.                 if ((mTargetDistance > 0 && viewNumber != getNextViewNumber(mRelativeViewNumber)) ||   
  489.                     (mTargetDistance < 0 && viewNumber != getPrevViewNumber(mRelativeViewNumber)))   
  490.                 {   
  491.                     mViews[viewNumber].setOffset(offset, 0, mRelativeViewNumber);   
  492.                 }   
  493.             }   
  494.         }   
  495.   
  496.         @Override  
  497.         public boolean getTransformation(long currentTime, Transformation outTransformation)   
  498.         {   
  499.             if (super.getTransformation(currentTime, outTransformation) == false)   
  500.             {   
  501.                 // Perform final adjustment to offsets to cleanup animation   
  502.                 mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);   
  503.                 mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);   
  504.                 mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);   
  505.   
  506.                 // Reached the animation target   
  507.                 mIsAnimationInProgres = false;   
  508.   
  509.                 return false;   
  510.             }   
  511.     
  512.             // Cancel if the screen touched   
  513.             if (mIsTouched || mIsDragging)   
  514.             {   
  515.                 // Note that at this point we still consider ourselves to be animating   
  516.                 // because we have not yet reached the target offset; its just that the   
  517.                 // user has temporarily interrupted the animation with a touch gesture   
  518.   
  519.                 return false;   
  520.             }   
  521.   
  522.             return true;   
  523.         }   
  524.     }   
  525.   
  526.     private class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener   
  527.     {   
  528.         @Override  
  529.         public boolean onDown(MotionEvent e)   
  530.         {   
  531.             // Stop animation   
  532.             mIsTouched = true;   
  533.   
  534.             // Reset fling state   
  535.             mFlingDirection = 0;   
  536.             return true;   
  537.         }   
  538.   
  539.         @Override  
  540.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)   
  541.         {   
  542.             if (e2.getAction() == MotionEvent.ACTION_MOVE)   
  543.             {   
  544.                 if (mIsDragging == false)   
  545.                 {   
  546.                     // Stop animation   
  547.                     mIsTouched = true;   
  548.         
  549.                     // Reconfigure scroll   
  550.                     mIsDragging = true;   
  551.                     mFlingDirection = 0;   
  552.                     mScrollTimestamp = System.currentTimeMillis();   
  553.                     mCurrentOffset = mViews[mCurrentViewNumber].getCurrentOffset();   
  554.                 }   
  555.   
  556.                 float maxVelocity = mGalleryWidth / (mAnimationDuration / 1000.0f);   
  557.                 long timestampDelta = System.currentTimeMillis() - mScrollTimestamp;   
  558.                 float maxScrollDelta = maxVelocity * (timestampDelta / 1000.0f);    
  559.                 float currentScrollDelta = e1.getX() - e2.getX();   
  560.   
  561.                 if (currentScrollDelta < maxScrollDelta * -1) currentScrollDelta = maxScrollDelta * -1;   
  562.                 if (currentScrollDelta > maxScrollDelta) currentScrollDelta = maxScrollDelta;   
  563.                 int scrollOffset = Math.round(mCurrentOffset + currentScrollDelta);   
  564.   
  565.                 // We can't scroll more than the width of our own frame layout   
  566.                 if (scrollOffset >= mGalleryWidth) scrollOffset = mGalleryWidth;   
  567.                 if (scrollOffset <= mGalleryWidth * -1) scrollOffset = mGalleryWidth * -1;   
  568.                    
  569.                 mViews[0].setOffset(scrollOffset, 0, mCurrentViewNumber);   
  570.                 mViews[1].setOffset(scrollOffset, 0, mCurrentViewNumber);   
  571.                 mViews[2].setOffset(scrollOffset, 0, mCurrentViewNumber);   
  572.             }   
  573.   
  574.             return false;   
  575.         }   
  576.   
  577.         @Override  
  578.         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)   
  579.         {   
  580.             if (Math.abs(e1.getY() - e2.getY()) <= swipe_max_off_path)   
  581.             {   
  582.                 if (e2.getX() - e1.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)   
  583.                 {   
  584.                     movePrevious();   
  585.                 }   
  586.   
  587.                 if(e1.getX() - e2.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)   
  588.                 {   
  589.                     moveNext();   
  590.                 }   
  591.             }   
  592.   
  593.             return false;   
  594.         }   
  595.   
  596.         @Override  
  597.         public void onLongPress(MotionEvent e)   
  598.         {   
  599.             // Finalise scrolling   
  600.             mFlingDirection = 0;   
  601.             processGesture();   
  602.         }   
  603.   
  604.         @Override  
  605.         public void onShowPress(MotionEvent e)   
  606.         {   
  607.         }   
  608.   
  609.         @Override  
  610.         public boolean onSingleTapUp(MotionEvent e)   
  611.         {   
  612.             // Reset fling state   
  613.             mFlingDirection = 0;   
  614.             return false;   
  615.         }   
  616.            
  617.     }   
  618.   
  619.     public GestureDetector getMGestureDetector() {   
  620.         return mGestureDetector;   
  621.     }   
  622.        
  623. }  
package com.nuomi.ui;

import java.util.HashSet;
import java.util.Set;

import android.content.Context;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.Adapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;


public class FlingGallery extends FrameLayout
{
	
	private Set<OnGalleryChangeListener> listeners;
	private final int swipe_min_distance = 120;
    private final int swipe_max_off_path = 250;
    private final int swipe_threshold_veloicty = 400;

	private int mViewPaddingWidth = 0;
    private int mAnimationDuration = 250;
    private float mSnapBorderRatio = 0.5f;
    private boolean mIsGalleryCircular = true;

    private int mGalleryWidth = 0;
    private boolean mIsTouched = false;
    private boolean mIsDragging = false;
    private float mCurrentOffset = 0.0f;
    private long mScrollTimestamp = 0;
    private int mFlingDirection = 0;
    private int mCurrentPosition = 0;
    private int mCurrentViewNumber = 0;

    private Context mContext;
    private Adapter mAdapter;
    private FlingGalleryView[] mViews;
    private FlingGalleryAnimation mAnimation;
    private GestureDetector mGestureDetector;
    private Interpolator mDecelerateInterpolater;

    public FlingGallery(Context context)
	{
		super(context);

		listeners = new HashSet<OnGalleryChangeListener>();
		
		mContext = context;
		mAdapter = null;
		
        mViews = new FlingGalleryView[3];
        mViews[0] = new FlingGalleryView(0, this);
        mViews[1] = new FlingGalleryView(1, this);
        mViews[2] = new FlingGalleryView(2, this);

		mAnimation = new FlingGalleryAnimation();
		mGestureDetector = new GestureDetector(new FlingGestureDetector());
		mDecelerateInterpolater = AnimationUtils.loadInterpolator(mContext, android.R.anim.decelerate_interpolator);
	}

    public void addGalleryChangeListener(OnGalleryChangeListener listener){
    	listeners.add(listener);
    }
    
	public void setPaddingWidth(int viewPaddingWidth)
	{
		mViewPaddingWidth = viewPaddingWidth;
	}

	public void setAnimationDuration(int animationDuration)
	{
		mAnimationDuration = animationDuration;
	}
	
	public void setSnapBorderRatio(float snapBorderRatio)
	{
		mSnapBorderRatio = snapBorderRatio;
	}

	public void setIsGalleryCircular(boolean isGalleryCircular) 
	{
		if (mIsGalleryCircular != isGalleryCircular)
		{
			mIsGalleryCircular = isGalleryCircular;
	
			if (mCurrentPosition == getFirstPosition())
			{
				// We need to reload the view immediately to the left to change it to circular view or blank
		    	mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition));			
			}
	
			if (mCurrentPosition == getLastPosition())
			{
				// We need to reload the view immediately to the right to change it to circular view or blank
		    	mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition));			
			}
		}
	}

	public int getGalleryCount()
	{
		return (mAdapter == null) ? 0 : mAdapter.getCount();
	}

	public int getFirstPosition()
	{
		return 0;
	}

	public int getLastPosition()
	{
		return (getGalleryCount() == 0) ? 0 : getGalleryCount() - 1;
	}

	private int getPrevPosition(int relativePosition)
	{
		int prevPosition = relativePosition - 1;

		if (prevPosition < getFirstPosition())
		{
			prevPosition = getFirstPosition() - 1;

			if (mIsGalleryCircular == true)
			{
				prevPosition = getLastPosition();
			}
		}
		NotifyGalleryChange();
		return prevPosition;
	}

	private int getNextPosition(int relativePosition)
	{
		int nextPosition = relativePosition + 1;

		if (nextPosition > getLastPosition())
		{
			nextPosition = getLastPosition() + 1;

			if (mIsGalleryCircular == true)
			{
				nextPosition = getFirstPosition();
			}
		}
		NotifyGalleryChange();
		return nextPosition;
	}

	//
	private void NotifyGalleryChange() {
		for (OnGalleryChangeListener listener :listeners) {
			listener.onGalleryChange(mCurrentPosition);
		}
	}

	private int getPrevViewNumber(int relativeViewNumber)
	{
		return (relativeViewNumber == 0) ? 2 : relativeViewNumber - 1;
	}

	private int getNextViewNumber(int relativeViewNumber)
	{
		return (relativeViewNumber == 2) ? 0 : relativeViewNumber + 1;
	}
	
	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom)
	{
		super.onLayout(changed, left, top, right, bottom);

		// Calculate our view width
		mGalleryWidth = right - left;

		if (changed)
		{
	    	// Position views at correct starting offsets
	    	mViews[0].setOffset(0, 0, mCurrentViewNumber);
	    	mViews[1].setOffset(0, 0, mCurrentViewNumber);
	    	mViews[2].setOffset(0, 0, mCurrentViewNumber);
	    }
	}

	public void setAdapter(Adapter adapter)
    {
    	mAdapter = adapter;
    	mCurrentPosition = 0;
        mCurrentViewNumber = 0;

        // Load the initial views from adapter
        mViews[0].recycleView(mCurrentPosition);
    	mViews[1].recycleView(getNextPosition(mCurrentPosition));
    	mViews[2].recycleView(getPrevPosition(mCurrentPosition));

    	// Position views at correct starting offsets
    	mViews[0].setOffset(0, 0, mCurrentViewNumber);
    	mViews[1].setOffset(0, 0, mCurrentViewNumber);
    	mViews[2].setOffset(0, 0, mCurrentViewNumber);
    }

	private int getViewOffset(int viewNumber, int relativeViewNumber)
	{
		// Determine width including configured padding width
		int offsetWidth = mGalleryWidth + mViewPaddingWidth;

		// Position the previous view one measured width to left
		if (viewNumber == getPrevViewNumber(relativeViewNumber))
		{
			return offsetWidth;
		}

		// Position the next view one measured width to the right
		if (viewNumber == getNextViewNumber(relativeViewNumber))
		{
			return offsetWidth * -1;
		}

		return 0;
	}

	void movePrevious()
	{
		// Slide to previous view
		mFlingDirection = 1;
		processGesture();
	}

	void moveNext()
	{
		// Slide to next view
		mFlingDirection = -1;
		processGesture();
	}

	 @Override
	 public boolean onKeyDown(int keyCode, KeyEvent event)
	 {
	    switch (keyCode)
	    {
	    case KeyEvent.KEYCODE_DPAD_LEFT:
	        movePrevious();
	        return true;
	
	    case KeyEvent.KEYCODE_DPAD_RIGHT:
	        moveNext();
	        return true;
	
	    case KeyEvent.KEYCODE_DPAD_CENTER:
	    case KeyEvent.KEYCODE_ENTER:
	    }

	    return super.onKeyDown(keyCode, event);
	}

	public boolean onGalleryTouchEvent(MotionEvent event)
	{
		boolean consumed = mGestureDetector.onTouchEvent(event);
		
		if (event.getAction() == MotionEvent.ACTION_UP)
		{
			if (mIsTouched || mIsDragging)
			{
				processScrollSnap();
				processGesture();
			}
		}
		
        return consumed;
    }

	void processGesture()
	{
		int newViewNumber = mCurrentViewNumber;
		int reloadViewNumber = 0;
		int reloadPosition = 0;

		mIsTouched = false;
		mIsDragging = false;

		if (mFlingDirection > 0)
		{
			if (mCurrentPosition > getFirstPosition() || mIsGalleryCircular == true)
			{
				// Determine previous view and outgoing view to recycle
				newViewNumber = getPrevViewNumber(mCurrentViewNumber);
				mCurrentPosition = getPrevPosition(mCurrentPosition);
				reloadViewNumber = getNextViewNumber(mCurrentViewNumber); 
				reloadPosition = getPrevPosition(mCurrentPosition);
			}
		}

		if (mFlingDirection < 0)
		{
			if (mCurrentPosition < getLastPosition() || mIsGalleryCircular == true)
			{
				// Determine the next view and outgoing view to recycle
				newViewNumber = getNextViewNumber(mCurrentViewNumber);
				mCurrentPosition = getNextPosition(mCurrentPosition);
				reloadViewNumber = getPrevViewNumber(mCurrentViewNumber);
				reloadPosition = getNextPosition(mCurrentPosition);
			}
		}

		if (newViewNumber != mCurrentViewNumber)
		{
			mCurrentViewNumber = newViewNumber; 

			// Reload outgoing view from adapter in new position
			mViews[reloadViewNumber].recycleView(reloadPosition);
		}

		// Ensure input focus on the current view
		mViews[mCurrentViewNumber].requestFocus();

		// Run the slide animations for view transitions
		mAnimation.prepareAnimation(mCurrentViewNumber);
		this.startAnimation(mAnimation);

		// Reset fling state
		mFlingDirection = 0;
	}

	void processScrollSnap()
	{
		// Snap to next view if scrolled passed snap position
		float rollEdgeWidth = mGalleryWidth * mSnapBorderRatio;
		int rollOffset = mGalleryWidth - (int) rollEdgeWidth;
		int currentOffset = mViews[mCurrentViewNumber].getCurrentOffset();

		if (currentOffset <= rollOffset * -1)
		{
			// Snap to previous view
			mFlingDirection = 1;
		}

		if (currentOffset >= rollOffset)
		{
			// Snap to next view
			mFlingDirection = -1;
		}
	}

	private class FlingGalleryView
	{
		private int mViewNumber;
		private FrameLayout mParentLayout;
		
		private FrameLayout mInvalidLayout = null;
		private LinearLayout mInternalLayout = null;
		private View mExternalView = null;

		public FlingGalleryView(int viewNumber, FrameLayout parentLayout)
		{
			mViewNumber = viewNumber;
			mParentLayout = parentLayout;

			// Invalid layout is used when outside gallery
			mInvalidLayout = new FrameLayout(mContext);
			mInvalidLayout.setLayoutParams(new LinearLayout.LayoutParams( 
	                LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

			// Internal layout is permanent for duration
			mInternalLayout = new LinearLayout(mContext);
			mInternalLayout.setLayoutParams(new LinearLayout.LayoutParams( 
	                LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

			mParentLayout.addView(mInternalLayout);
		}

		public void recycleView(int newPosition)
		{
			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, new LinearLayout.LayoutParams( 
	                LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
			}
		}

		public void setOffset(int xOffset, int yOffset, int relativeViewNumber)
		{
			// Scroll the target view relative to its own position relative to currently displayed view
			mInternalLayout.scrollTo(getViewOffset(mViewNumber, relativeViewNumber) + xOffset, yOffset);
		}
		
		public int getCurrentOffset()
		{
			// Return the current scroll position
			return mInternalLayout.getScrollX();
		}

		public void requestFocus()
		{
			mInternalLayout.requestFocus();
		}
	}

    private class FlingGalleryAnimation extends Animation
    {
    	private boolean mIsAnimationInProgres;
    	private int mRelativeViewNumber;
    	private int mInitialOffset;
    	private int mTargetOffset;
    	private int mTargetDistance;   	
 
    	public FlingGalleryAnimation()
    	{
    		mIsAnimationInProgres = false;
    		mRelativeViewNumber = 0;
        	mInitialOffset = 0;
        	mTargetOffset = 0;
        	mTargetDistance = 0;
    	}
 
    	public void prepareAnimation(int relativeViewNumber)
    	{
    		// If we are animating relative to a new view
    		if (mRelativeViewNumber != relativeViewNumber)
    		{
				if (mIsAnimationInProgres == true)
				{
					// We only have three views so if requested again to animate in same direction we must snap 
					int newDirection = (relativeViewNumber == getPrevViewNumber(mRelativeViewNumber)) ? 1 : -1;
	    			int animDirection = (mTargetDistance < 0) ? 1 : -1; 

	    			// If animation in same direction
	    			if (animDirection == newDirection)
	    			{
		        		// Ran out of time to animate so snap to the target offset
		        		mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);
						mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);
						mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);	
	    			}
				}
	
				// Set relative view number for animation
	    		mRelativeViewNumber = relativeViewNumber;
    		}

			// Note: In this implementation the targetOffset will always be zero
    		// as we are centering the view; but we include the calculations of
			// targetOffset and targetDistance for use in future implementations

			mInitialOffset = mViews[mRelativeViewNumber].getCurrentOffset();
			mTargetOffset = getViewOffset(mRelativeViewNumber, mRelativeViewNumber);
			mTargetDistance = mTargetOffset - mInitialOffset;

			// Configure base animation properties
			this.setDuration(mAnimationDuration);
			this.setInterpolator(mDecelerateInterpolater);

			// Start/continued animation
			mIsAnimationInProgres = true;
		}

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation transformation)
        {
        	// Ensure interpolatedTime does not over-shoot then calculate new offset
        	interpolatedTime = (interpolatedTime > 1.0f) ? 1.0f : interpolatedTime;
			int offset = mInitialOffset + (int) (mTargetDistance * interpolatedTime);

			for (int viewNumber = 0; viewNumber < 3; viewNumber++)
			{
				// Only need to animate the visible views as the other view will always be off-screen
				if ((mTargetDistance > 0 && viewNumber != getNextViewNumber(mRelativeViewNumber)) ||
					(mTargetDistance < 0 && viewNumber != getPrevViewNumber(mRelativeViewNumber)))
				{
					mViews[viewNumber].setOffset(offset, 0, mRelativeViewNumber);
				}
			}
        }

        @Override
        public boolean getTransformation(long currentTime, Transformation outTransformation)
        {
        	if (super.getTransformation(currentTime, outTransformation) == false)
        	{
        		// Perform final adjustment to offsets to cleanup animation
        		mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);
				mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);
				mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);

				// Reached the animation target
				mIsAnimationInProgres = false;

				return false;
        	}
 
        	// Cancel if the screen touched
        	if (mIsTouched || mIsDragging)
        	{
        		// Note that at this point we still consider ourselves to be animating
        		// because we have not yet reached the target offset; its just that the
        		// user has temporarily interrupted the animation with a touch gesture

        		return false;
        	}

        	return true;
        }
    }

	private class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener
    {
    	@Override
    	public boolean onDown(MotionEvent e)
    	{
    		// Stop animation
    		mIsTouched = true;

    		// Reset fling state
    		mFlingDirection = 0;
            return true;
    	}

    	@Override
    	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
    	{
    		if (e2.getAction() == MotionEvent.ACTION_MOVE)
        	{
	    		if (mIsDragging == false)
	    		{
	        		// Stop animation
	    			mIsTouched = true;
	 
	    			// Reconfigure scroll
	    			mIsDragging = true;
	    			mFlingDirection = 0;
	    			mScrollTimestamp = System.currentTimeMillis();
	    			mCurrentOffset = mViews[mCurrentViewNumber].getCurrentOffset();
	    		}

        	    float maxVelocity = mGalleryWidth / (mAnimationDuration / 1000.0f);
        		long timestampDelta = System.currentTimeMillis() - mScrollTimestamp;
        		float maxScrollDelta = maxVelocity * (timestampDelta / 1000.0f); 
        		float currentScrollDelta = e1.getX() - e2.getX();

        		if (currentScrollDelta < maxScrollDelta * -1) currentScrollDelta = maxScrollDelta * -1;
        		if (currentScrollDelta > maxScrollDelta) currentScrollDelta = maxScrollDelta;
	        	int scrollOffset = Math.round(mCurrentOffset + currentScrollDelta);

        		// We can't scroll more than the width of our own frame layout
        		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);
        	}

            return false;
    	}

    	@Override
    	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
    	{
            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();
                }
            }

            return false;
    	}

    	@Override
    	public void onLongPress(MotionEvent e)
    	{
    		// Finalise scrolling
    		mFlingDirection = 0;
            processGesture();
    	}

    	@Override
    	public void onShowPress(MotionEvent e)
    	{
    	}

    	@Override
    	public boolean onSingleTapUp(MotionEvent e)
    	{
    		// Reset fling state
    		mFlingDirection = 0;
            return false;
    	}
    	
    }

	public GestureDetector getMGestureDetector() {
		return mGestureDetector;
	}
	
}


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

在Activity中,
Java代码 复制代码  收藏代码
  1. FlingGallery gallery = new FlingGallery(this);   
  2.   
  3.         gallery.setAdapter(new ArrayAdapter<String>(getApplicationContext(),   
  4.                 android.R.layout.simple_list_item_1, new String[xxxx]) {   
  5.             public View getView(int position, View convertView, ViewGroup parent) {   
  6.                                // 返回滑动的deal   
  7.                 return dealViews[position];   
  8.             }   
  9.         });   
  10.         gallery.addGalleryChangeListener(new OnGalleryChangeListener() {   
  11.   
  12.             @Override  
  13.             public void onGalleryChange(int currentItem) {   
  14.                 // 干些想干的事件   
  15.   
  16.             }   
  17.   
  18.         });  
FlingGallery gallery = new FlingGallery(this);

		gallery.setAdapter(new ArrayAdapter<String>(getApplicationContext(),
				android.R.layout.simple_list_item_1, new String[xxxx]) {
			public View getView(int position, View convertView, ViewGroup parent) {
                               // 返回滑动的deal
				return dealViews[position];
			}
		});
		gallery.addGalleryChangeListener(new OnGalleryChangeListener() {

			@Override
			public void onGalleryChange(int currentItem) {
				// 干些想干的事件

			}

		});

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值