[代码] FlingGalleryActivity
001 | import android.app.Activity; |
002 | import android.os.Bundle; |
003 | |
004 | import android.content.Context; |
005 | import android.graphics.Color; |
006 | import android.util.Log; |
007 | import android.view.Gravity; |
008 | import android.view.MotionEvent; |
009 | import android.view.View; |
010 | import android.view.ViewGroup; |
011 | import android.view.View.OnClickListener; |
012 | import android.widget.ArrayAdapter; |
013 | import android.widget.Button; |
014 | import android.widget.CheckBox; |
015 | import android.widget.EditText; |
016 | import android.widget.LinearLayout; |
017 | import android.widget.TableLayout; |
018 | import android.widget.TextView; |
019 | |
020 | public class FlingGalleryActivity extends Activity |
021 | { |
022 | private final int color_red = Color.argb( 100 , 200 , 0 , 0 ); |
023 | private final int color_green = Color.argb( 100 , 0 , 200 , 0 ); |
024 | private final int color_blue = Color.argb( 100 , 0 , 0 , 200 ); |
025 | private final int color_yellow = Color.argb( 100 , 200 , 200 , 0 ); |
026 | private final int color_purple = Color.argb( 100 , 200 , 0 , 200 ); |
027 | |
028 | private final String[] mLabelArray = { "View1" , "View2" , "View3" , "View4" , "View5" }; |
029 | private final int [] mColorArray = {color_red, color_green, color_blue, color_yellow, color_purple}; |
030 | |
031 | private FlingGallery mGallery; |
032 | private CheckBox mCheckBox; |
033 | |
034 | // Note: The following handler is critical to correct function of |
035 | // the FlingGallery class. This enables the FlingGallery class to |
036 | // detect when the motion event has ended by finger being lifted |
037 | |
038 | @Override |
039 | public boolean onTouchEvent(MotionEvent event) |
040 | { |
041 | return mGallery.onGalleryTouchEvent(event); |
042 | } |
043 | |
044 | public void onCreate(Bundle savedInstanceState) |
045 | { |
046 | super .onCreate(savedInstanceState); |
047 | |
048 | mGallery = new FlingGallery( this ); |
049 | mGallery.setPaddingWidth( 5 ); |
050 | mGallery.setAdapter( new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, mLabelArray) |
051 | { |
052 | @Override |
053 | public View getView( int position, View convertView, ViewGroup parent) |
054 | { |
055 | Log.d( "111" , "count=" +position); |
056 | // if (convertView != null && convertView instanceof GalleryViewItem) |
057 | // { |
058 | // GalleryViewItem galleryView = (GalleryViewItem) convertView; |
059 | // |
060 | // galleryView.mEdit1.setText(""); |
061 | // galleryView.mText1.setText(mLabelArray[position]); |
062 | // galleryView.mText1.setBackgroundColor(mColorArray[position]); |
063 | // galleryView.mText2.setText(mLabelArray[position]); |
064 | // galleryView.mText2.setBackgroundColor(mColorArray[position]); |
065 | // |
066 | // Log.d("111", "count="+position); |
067 | // |
068 | // return galleryView; |
069 | // |
070 | // } |
071 | |
072 | return new GalleryViewItem(getApplicationContext(), position); |
073 | } |
074 | }); |
075 | |
076 | LinearLayout layout = new LinearLayout(getApplicationContext()); |
077 | layout.setOrientation(LinearLayout.VERTICAL); |
078 | |
079 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( |
080 | LinearLayout.LayoutParams.MATCH_PARENT, |
081 | LinearLayout.LayoutParams.MATCH_PARENT); |
082 | |
083 | layoutParams.setMargins( 10 , 10 , 10 , 10 ); |
084 | layoutParams.weight = 1 .0f; |
085 | |
086 | layout.addView(mGallery, layoutParams); |
087 | |
088 | mCheckBox = new CheckBox(getApplicationContext()); |
089 | mCheckBox.setText( "Gallery is Circular" ); |
090 | mCheckBox.setText( "Gallery is Circular" ); |
091 | mCheckBox.setPadding( 50 , 10 , 0 , 10 ); |
092 | mCheckBox.setTextSize( 30 ); |
093 | mCheckBox.setChecked( true ); |
094 | mCheckBox.setOnClickListener( new OnClickListener() |
095 | { |
096 | @Override |
097 | public void onClick(View view) |
098 | { |
099 | mGallery.setIsGalleryCircular(mCheckBox.isChecked()); |
100 | } |
101 | }); |
102 | |
103 | layout.addView(mCheckBox, new LinearLayout.LayoutParams( |
104 | LinearLayout.LayoutParams.MATCH_PARENT, |
105 | LinearLayout.LayoutParams.WRAP_CONTENT)); |
106 | |
107 | setContentView(layout); |
108 | } |
109 | |
110 | private class GalleryViewItem extends TableLayout |
111 | { |
112 | private EditText mEdit1; |
113 | private TextView mText1; |
114 | private TextView mText2; |
115 | private Button mButton1; |
116 | private Button mButton2; |
117 | |
118 | public GalleryViewItem(Context context, int position) |
119 | { |
120 | super (context); |
121 | |
122 | this .setOrientation(LinearLayout.VERTICAL); |
123 | |
124 | this .setLayoutParams( new LinearLayout.LayoutParams( |
125 | LinearLayout.LayoutParams.MATCH_PARENT, |
126 | LinearLayout.LayoutParams.MATCH_PARENT)); |
127 | |
128 | mEdit1 = new EditText(context); |
129 | |
130 | this .addView(mEdit1, new LinearLayout.LayoutParams( |
131 | LinearLayout.LayoutParams.MATCH_PARENT, |
132 | LinearLayout.LayoutParams.WRAP_CONTENT)); |
133 | |
134 | mText1 = new TextView(context); |
135 | mText1.setText(mLabelArray[position]); |
136 | mText1.setTextSize( 30 ); |
137 | mText1.setGravity(Gravity.LEFT); |
138 | mText1.setBackgroundColor(mColorArray[position]); |
139 | |
140 | this .addView(mText1, new LinearLayout.LayoutParams( |
141 | LinearLayout.LayoutParams.MATCH_PARENT, |
142 | LinearLayout.LayoutParams.WRAP_CONTENT)); |
143 | |
144 | mButton1 = new Button(context); |
145 | mButton1.setText( "<<" ); |
146 | mButton1.setGravity(Gravity.LEFT); |
147 | mButton1.setOnClickListener( new OnClickListener() |
148 | { |
149 | @Override |
150 | public void onClick(View view) |
151 | { |
152 | mGallery.movePrevious(); |
153 | } |
154 | }); |
155 | |
156 | this .addView(mButton1, new LinearLayout.LayoutParams( |
157 | LinearLayout.LayoutParams.MATCH_PARENT, |
158 | LinearLayout.LayoutParams.WRAP_CONTENT)); |
159 | |
160 | mButton2 = new Button(context); |
161 | mButton2.setText( ">>" ); |
162 | mButton2.setGravity(Gravity.RIGHT); |
163 | mButton2.setOnClickListener( new OnClickListener() |
164 | { |
165 | @Override |
166 | public void onClick(View view) |
167 | { |
168 | mGallery.moveNext(); |
169 | } |
170 | }); |
171 | |
172 | this .addView(mButton2, new LinearLayout.LayoutParams( |
173 | LinearLayout.LayoutParams.MATCH_PARENT, |
174 | LinearLayout.LayoutParams.WRAP_CONTENT)); |
175 | |
176 | mText2 = new TextView(context); |
177 | mText2.setText(mLabelArray[position]); |
178 | mText2.setTextSize( 30 ); |
179 | mText2.setGravity(Gravity.RIGHT); |
180 | mText2.setBackgroundColor(mColorArray[position]); |
181 | |
182 | this .addView(mText2, new LinearLayout.LayoutParams( |
183 | LinearLayout.LayoutParams.MATCH_PARENT, |
184 | LinearLayout.LayoutParams.MATCH_PARENT, 1 )); |
185 | } |
186 | } |
187 | } |
[代码] FlingGallery
001 | import android.content.Context; |
002 | import android.view.GestureDetector; |
003 | import android.view.KeyEvent; |
004 | import android.view.MotionEvent; |
005 | import android.view.View; |
006 | import android.view.animation.Animation; |
007 | import android.view.animation.AnimationUtils; |
008 | import android.view.animation.Interpolator; |
009 | import android.view.animation.Transformation; |
010 | import android.widget.Adapter; |
011 | import android.widget.FrameLayout; |
012 | import android.widget.LinearLayout; |
013 | |
014 | // TODO: |
015 | |
016 | // 1. In order to improve performance Cache screen bitmap and use for animation |
017 | // 2. Establish superfluous memory allocations and delay or replace with reused objects |
018 | // Probably need to make sure we are not allocating objects (strings, etc.) in loops |
019 | |
020 | public class FlingGallery extends FrameLayout |
021 | { |
022 | // Constants |
023 | |
024 | private final int swipe_min_distance = 120 ; |
025 | private final int swipe_max_off_path = 250 ; |
026 | private final int swipe_threshold_veloicty = 400 ; |
027 | |
028 | // Properties |
029 | |
030 | private int mViewPaddingWidth = 0 ; |
031 | private int mAnimationDuration = 250 ; |
032 | private float mSnapBorderRatio = 0 .5f; |
033 | private boolean mIsGalleryCircular = true ; |
034 | |
035 | // Members |
036 | |
037 | private int mGalleryWidth = 0 ; |
038 | private boolean mIsTouched = false ; |
039 | private boolean mIsDragging = false ; |
040 | private float mCurrentOffset = 0 .0f; |
041 | private long mScrollTimestamp = 0 ; |
042 | private int mFlingDirection = 0 ; |
043 | private int mCurrentPosition = 0 ; |
044 | private int mCurrentViewNumber = 0 ; |
045 | |
046 | private Context mContext; |
047 | private Adapter mAdapter; |
048 | private FlingGalleryView[] mViews; |
049 | private FlingGalleryAnimation mAnimation; |
050 | private GestureDetector mGestureDetector; |
051 | private Interpolator mDecelerateInterpolater; |
052 | |
053 | public FlingGallery(Context context) |
054 | { |
055 | super (context); |
056 | |
057 | mContext = context; |
058 | mAdapter = null ; |
059 | |
060 | mViews = new FlingGalleryView[ 3 ]; |
061 | mViews[ 0 ] = new FlingGalleryView( 0 , this ); |
062 | mViews[ 1 ] = new FlingGalleryView( 1 , this ); |
063 | mViews[ 2 ] = new FlingGalleryView( 2 , this ); |
064 | |
065 | mAnimation = new FlingGalleryAnimation(); |
066 | mGestureDetector = new GestureDetector( new FlingGestureDetector()); |
067 | mDecelerateInterpolater = AnimationUtils.loadInterpolator(mContext, android.R.anim.decelerate_interpolator); |
068 | } |
069 | |
070 | public void setPaddingWidth( int viewPaddingWidth) |
071 | { |
072 | mViewPaddingWidth = viewPaddingWidth; |
073 | } |
074 | |
075 | public void setAnimationDuration( int animationDuration) |
076 | { |
077 | mAnimationDuration = animationDuration; |
078 | } |
079 | |
080 | public void setSnapBorderRatio( float snapBorderRatio) |
081 | { |
082 | mSnapBorderRatio = snapBorderRatio; |
083 | } |
084 | |
085 | public void setIsGalleryCircular( boolean isGalleryCircular) |
086 | { |
087 | if (mIsGalleryCircular != isGalleryCircular) |
088 | { |
089 | mIsGalleryCircular = isGalleryCircular; |
090 | |
091 | if (mCurrentPosition == getFirstPosition()) |
092 | { |
093 | // We need to reload the view immediately to the left to change it to circular view or blank |
094 | mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition)); |
095 | } |
096 | |
097 | if (mCurrentPosition == getLastPosition()) |
098 | { |
099 | // We need to reload the view immediately to the right to change it to circular view or blank |
100 | mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition)); |
101 | } |
102 | } |
103 | } |
104 | |
105 | public int getGalleryCount() |
106 | { |
107 | return (mAdapter == null ) ? 0 : mAdapter.getCount(); |
108 | } |
109 | |
110 | public int getFirstPosition() |
111 | { |
112 | return 0 ; |
113 | } |
114 | |
115 | public int getLastPosition() |
116 | { |
117 | return (getGalleryCount() == 0 ) ? 0 : getGalleryCount() - 1 ; |
118 | } |
119 | |
120 | private int getPrevPosition( int relativePosition) |
121 | { |
122 | int prevPosition = relativePosition - 1 ; |
123 | |
124 | if (prevPosition < getFirstPosition()) |
125 | { |
126 | prevPosition = getFirstPosition() - 1 ; |
127 | |
128 | if (mIsGalleryCircular == true ) |
129 | { |
130 | prevPosition = getLastPosition(); |
131 | } |
132 | } |
133 | |
134 | return prevPosition; |
135 | } |
136 | |
137 | private int getNextPosition( int relativePosition) |
138 | { |
139 | int nextPosition = relativePosition + 1 ; |
140 | |
141 | if (nextPosition > getLastPosition()) |
142 | { |
143 | nextPosition = getLastPosition() + 1 ; |
144 | |
145 | if (mIsGalleryCircular == true ) |
146 | { |
147 | nextPosition = getFirstPosition(); |
148 | } |
149 | } |
150 | |
151 | return nextPosition; |
152 | } |
153 | |
154 | private int getPrevViewNumber( int relativeViewNumber) |
155 | { |
156 | return (relativeViewNumber == 0 ) ? 2 : relativeViewNumber - 1 ; |
157 | } |
158 | |
159 | private int getNextViewNumber( int relativeViewNumber) |
160 | { |
161 | return (relativeViewNumber == 2 ) ? 0 : relativeViewNumber + 1 ; |
162 | } |
163 | |
164 | @Override |
165 | protected void onLayout( boolean changed, int left, int top, int right, int bottom) |
166 | { |
167 | super .onLayout(changed, left, top, right, bottom); |
168 | |
169 | // Calculate our view width |
170 | mGalleryWidth = right - left; |
171 | |
172 | if (changed == true ) |
173 | { |
174 | // Position views at correct starting offsets |
175 | mViews[ 0 ].setOffset( 0 , 0 , mCurrentViewNumber); |
176 | mViews[ 1 ].setOffset( 0 , 0 , mCurrentViewNumber); |
177 | mViews[ 2 ].setOffset( 0 , 0 , mCurrentViewNumber); |
178 | } |
179 | } |
180 | |
181 | public void setAdapter(Adapter adapter) |
182 | { |
183 | mAdapter = adapter; |
184 | mCurrentPosition = 0 ; |
185 | mCurrentViewNumber = 0 ; |
186 | |
187 | // Load the initial views from adapter |
188 | mViews[ 0 ].recycleView(mCurrentPosition); |
189 | mViews[ 1 ].recycleView(getNextPosition(mCurrentPosition)); |
190 | mViews[ 2 ].recycleView(getPrevPosition(mCurrentPosition)); |
191 | |
192 | // Position views at correct starting offsets |
193 | mViews[ 0 ].setOffset( 0 , 0 , mCurrentViewNumber); |
194 | mViews[ 1 ].setOffset( 0 , 0 , mCurrentViewNumber); |
195 | mViews[ 2 ].setOffset( 0 , 0 , mCurrentViewNumber); |
196 | } |
197 | |
198 | private int getViewOffset( int viewNumber, int relativeViewNumber) |
199 | { |
200 | // Determine width including configured padding width |
201 | int offsetWidth = mGalleryWidth + mViewPaddingWidth; |
202 | |
203 | // Position the previous view one measured width to left |
204 | if (viewNumber == getPrevViewNumber(relativeViewNumber)) |
205 | { |
206 | return offsetWidth; |
207 | } |
208 | |
209 | // Position the next view one measured width to the right |
210 | if (viewNumber == getNextViewNumber(relativeViewNumber)) |
211 | { |
212 | return offsetWidth * - 1 ; |
213 | } |
214 | |
215 | return 0 ; |
216 | } |
217 | |
218 | void movePrevious() |
219 | { |
220 | // Slide to previous view |
221 | mFlingDirection = 1 ; |
222 | processGesture(); |
223 | } |
224 | |
225 | void moveNext() |
226 | { |
227 | // Slide to next view |
228 | mFlingDirection = - 1 ; |
229 | processGesture(); |
230 | } |
231 | |
232 | @Override |
233 | public boolean onKeyDown( int keyCode, KeyEvent event) |
234 | { |
235 | switch (keyCode) |
236 | { |
237 | case KeyEvent.KEYCODE_DPAD_LEFT: |
238 | movePrevious(); |
239 | return true ; |
240 | |
241 | case KeyEvent.KEYCODE_DPAD_RIGHT: |
242 | moveNext(); |
243 | return true ; |
244 | |
245 | case KeyEvent.KEYCODE_DPAD_CENTER: |
246 | case KeyEvent.KEYCODE_ENTER: |
247 | } |
248 | |
249 | return super .onKeyDown(keyCode, event); |
250 | } |
251 | |
252 | public boolean onGalleryTouchEvent(MotionEvent event) |
253 | { |
254 | boolean consumed = mGestureDetector.onTouchEvent(event); |
255 | |
256 | if (event.getAction() == MotionEvent.ACTION_UP) |
257 | { |
258 | if (mIsTouched || mIsDragging) |
259 | { |
260 | processScrollSnap(); |
261 | processGesture(); |
262 | } |
263 | } |
264 | |
265 | return consumed; |
266 | } |
267 | |
268 | void processGesture() |
269 | { |
270 | int newViewNumber = mCurrentViewNumber; |
271 | int reloadViewNumber = 0 ; |
272 | int reloadPosition = 0 ; |
273 | |
274 | mIsTouched = false ; |
275 | mIsDragging = false ; |
276 | |
277 | if (mFlingDirection > 0 ) |
278 | { |
279 | if (mCurrentPosition > getFirstPosition() || mIsGalleryCircular == true ) |
280 | { |
281 | // Determine previous view and outgoing view to recycle |
282 | newViewNumber = getPrevViewNumber(mCurrentViewNumber); |
283 | mCurrentPosition = getPrevPosition(mCurrentPosition); |
284 | reloadViewNumber = getNextViewNumber(mCurrentViewNumber); |
285 | reloadPosition = getPrevPosition(mCurrentPosition); |
286 | } |
287 | } |
288 | |
289 | if (mFlingDirection < 0 ) |
290 | { |
291 | if (mCurrentPosition < getLastPosition() || mIsGalleryCircular == true ) |
292 | { |
293 | // Determine the next view and outgoing view to recycle |
294 | newViewNumber = getNextViewNumber(mCurrentViewNumber); |
295 | mCurrentPosition = getNextPosition(mCurrentPosition); |
296 | reloadViewNumber = getPrevViewNumber(mCurrentViewNumber); |
297 | reloadPosition = getNextPosition(mCurrentPosition); |
298 | } |
299 | } |
300 | |
301 | if (newViewNumber != mCurrentViewNumber) |
302 | { |
303 | mCurrentViewNumber = newViewNumber; |
304 | |
305 | // Reload outgoing view from adapter in new position |
306 | mViews[reloadViewNumber].recycleView(reloadPosition); |
307 | } |
308 | |
309 | // Ensure input focus on the current view |
310 | mViews[mCurrentViewNumber].requestFocus(); |
311 | |
312 | // Run the slide animations for view transitions |
313 | mAnimation.prepareAnimation(mCurrentViewNumber); |
314 | this .startAnimation(mAnimation); |
315 | |
316 | // Reset fling state |
317 | mFlingDirection = 0 ; |
318 | } |
319 | |
320 | void processScrollSnap() |
321 | { |
322 | // Snap to next view if scrolled passed snap position |
323 | float rollEdgeWidth = mGalleryWidth * mSnapBorderRatio; |
324 | int rollOffset = mGalleryWidth - ( int ) rollEdgeWidth; |
325 | int currentOffset = mViews[mCurrentViewNumber].getCurrentOffset(); |
326 | |
327 | if (currentOffset <= rollOffset * - 1 ) |
328 | { |
329 | // Snap to previous view |
330 | mFlingDirection = 1 ; |
331 | } |
332 | |
333 | if (currentOffset >= rollOffset) |
334 | { |
335 | // Snap to next view |
336 | mFlingDirection = - 1 ; |
337 | } |
338 | } |
339 | |
340 | private class FlingGalleryView |
341 | { |
342 | private int mViewNumber; |
343 | private FrameLayout mParentLayout; |
344 | |
345 | private FrameLayout mInvalidLayout = null ; |
346 | private LinearLayout mInternalLayout = null ; |
347 | private View mExternalView = null ; |
348 | |
349 | public FlingGalleryView( int viewNumber, FrameLayout parentLayout) |
350 | { |
351 | mViewNumber = viewNumber; |
352 | mParentLayout = parentLayout; |
353 | |
354 | // Invalid layout is used when outside gallery |
355 | mInvalidLayout = new FrameLayout(mContext); |
356 | mInvalidLayout.setLayoutParams( new LinearLayout.LayoutParams( |
357 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); |
358 | |
359 | // Internal layout is permanent for duration |
360 | mInternalLayout = new LinearLayout(mContext); |
361 | mInternalLayout.setLayoutParams( new LinearLayout.LayoutParams( |
362 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); |
363 | |
364 | mParentLayout.addView(mInternalLayout); |
365 | } |
366 | |
367 | public void recycleView( int newPosition) |
368 | { |
369 | if (mExternalView != null ) |
370 | { |
371 | mInternalLayout.removeView(mExternalView); |
372 | } |
373 | |
374 | if (mAdapter != null ) |
375 | { |
376 | if (newPosition >= getFirstPosition() && newPosition <= getLastPosition()) |
377 | { |
378 | mExternalView = mAdapter.getView(newPosition, mExternalView, mInternalLayout); |
379 | } |
380 | else |
381 | { |
382 | mExternalView = mInvalidLayout; |
383 | } |
384 | } |
385 | |
386 | if (mExternalView != null ) |
387 | { |
388 | mInternalLayout.addView(mExternalView, new LinearLayout.LayoutParams( |
389 | LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); |
390 | } |
391 | } |
392 | |
393 | public void setOffset( int xOffset, int yOffset, int relativeViewNumber) |
394 | { |
395 | // Scroll the target view relative to its own position relative to currently displayed view |
396 | mInternalLayout.scrollTo(getViewOffset(mViewNumber, relativeViewNumber) + xOffset, yOffset); |
397 | } |
398 | |
399 | public int getCurrentOffset() |
400 | { |
401 | // Return the current scroll position |
402 | return mInternalLayout.getScrollX(); |
403 | } |
404 | |
405 | public void requestFocus() |
406 | { |
407 | mInternalLayout.requestFocus(); |
408 | } |
409 | } |
410 | |
411 | private class FlingGalleryAnimation extends Animation |
412 | { |
413 | private boolean mIsAnimationInProgres; |
414 | private int mRelativeViewNumber; |
415 | private int mInitialOffset; |
416 | private int mTargetOffset; |
417 | private int mTargetDistance; |
418 | |
419 | public FlingGalleryAnimation() |
420 | { |
421 | mIsAnimationInProgres = false ; |
422 | mRelativeViewNumber = 0 ; |
423 | mInitialOffset = 0 ; |
424 | mTargetOffset = 0 ; |
425 | mTargetDistance = 0 ; |
426 | } |
427 | |
428 | public void prepareAnimation( int relativeViewNumber) |
429 | { |
430 | // If we are animating relative to a new view |
431 | if (mRelativeViewNumber != relativeViewNumber) |
432 | { |
433 | if (mIsAnimationInProgres == true ) |
434 | { |
435 | // We only have three views so if requested again to animate in same direction we must snap |
436 | int newDirection = (relativeViewNumber == getPrevViewNumber(mRelativeViewNumber)) ? 1 : - 1 ; |
437 | int animDirection = (mTargetDistance < 0 ) ? 1 : - 1 ; |
438 | |
439 | // If animation in same direction |
440 | if (animDirection == newDirection) |
441 | { |
442 | // Ran out of time to animate so snap to the target offset |
443 | mViews[ 0 ].setOffset(mTargetOffset, 0 , mRelativeViewNumber); |
444 | mViews[ 1 ].setOffset(mTargetOffset, 0 , mRelativeViewNumber); |
445 | mViews[ 2 ].setOffset(mTargetOffset, 0 , mRelativeViewNumber); |
446 | } |
447 | } |
448 | |
449 | // Set relative view number for animation |
450 | mRelativeViewNumber = relativeViewNumber; |
451 | } |
452 | |
453 | // Note: In this implementation the targetOffset will always be zero |
454 | // as we are centering the view; but we include the calculations of |
455 | // targetOffset and targetDistance for use in future implementations |
456 | |
457 | mInitialOffset = mViews[mRelativeViewNumber].getCurrentOffset(); |
458 | mTargetOffset = getViewOffset(mRelativeViewNumber, mRelativeViewNumber); |
459 | mTargetDistance = mTargetOffset - mInitialOffset; |
460 | |
461 | // Configure base animation properties |
462 | this .setDuration(mAnimationDuration); |
463 | this .setInterpolator(mDecelerateInterpolater); |
464 | |
465 | // Start/continued animation |
466 | mIsAnimationInProgres = true ; |
467 | } |
468 | |
469 | @Override |
470 | protected void applyTransformation( float interpolatedTime, Transformation transformation) |
471 | { |
472 | // Ensure interpolatedTime does not over-shoot then calculate new offset |
473 | interpolatedTime = (interpolatedTime > 1 .0f) ? 1 .0f : interpolatedTime; |
474 | int offset = mInitialOffset + ( int ) (mTargetDistance * interpolatedTime); |
475 | |
476 | for ( int viewNumber = 0 ; viewNumber < 3 ; viewNumber++) |
477 | { |
478 | // Only need to animate the visible views as the other view will always be off-screen |
479 | if ((mTargetDistance > 0 && viewNumber != getNextViewNumber(mRelativeViewNumber)) || |
480 | (mTargetDistance < 0 && viewNumber != getPrevViewNumber(mRelativeViewNumber))) |
481 | { |
482 | mViews[viewNumber].setOffset(offset, 0 , mRelativeViewNumber); |
483 | } |
484 | } |
485 | } |
486 | |
487 | @Override |
488 | public boolean getTransformation( long currentTime, Transformation outTransformation) |
489 | { |
490 | if ( super .getTransformation(currentTime, outTransformation) == false ) |
491 | { |
492 | // Perform final adjustment to offsets to cleanup animation |
493 | mViews[ 0 ].setOffset(mTargetOffset, 0 , mRelativeViewNumber); |
494 | mViews[ 1 ].setOffset(mTargetOffset, 0 , mRelativeViewNumber); |
495 | mViews[ 2 ].setOffset(mTargetOffset, 0 , mRelativeViewNumber); |
496 | |
497 | // Reached the animation target |
498 | mIsAnimationInProgres = false ; |
499 | |
500 | return false ; |
501 | } |
502 | |
503 | // Cancel if the screen touched |
504 | if (mIsTouched || mIsDragging) |
505 | { |
506 | // Note that at this point we still consider ourselves to be animating |
507 | // because we have not yet reached the target offset; its just that the |
508 | // user has temporarily interrupted the animation with a touch gesture |
509 | |
510 | return false ; |
511 | } |
512 | |
513 | return true ; |
514 | } |
515 | } |
516 | |
517 | private class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener |
518 | { |
519 | @Override |
520 | public boolean onDown(MotionEvent e) |
521 | { |
522 | // Stop animation |
523 | mIsTouched = true ; |
524 | |
525 | // Reset fling state |
526 | mFlingDirection = 0 ; |
527 | return true ; |
528 | } |
529 | |
530 | @Override |
531 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) |
532 | { |
533 | if (e2.getAction() == MotionEvent.ACTION_MOVE) |
534 | { |
535 | if (mIsDragging == false ) |
536 | { |
537 | // Stop animation |
538 | mIsTouched = true ; |
539 | |
540 | // Reconfigure scroll |
541 | mIsDragging = true ; |
542 | mFlingDirection = 0 ; |
543 | mScrollTimestamp = System.currentTimeMillis(); |
544 | mCurrentOffset = mViews[mCurrentViewNumber].getCurrentOffset(); |
545 | } |
546 | |
547 | float maxVelocity = mGalleryWidth / (mAnimationDuration / 1000 .0f); |
548 | long timestampDelta = System.currentTimeMillis() - mScrollTimestamp; |
549 | float maxScrollDelta = maxVelocity * (timestampDelta / 1000 .0f); |
550 | float currentScrollDelta = e1.getX() - e2.getX(); |
551 | |
552 | if (currentScrollDelta < maxScrollDelta * - 1 ) currentScrollDelta = maxScrollDelta * - 1 ; |
553 | if (currentScrollDelta > maxScrollDelta) currentScrollDelta = maxScrollDelta; |
554 | int scrollOffset = Math.round(mCurrentOffset + currentScrollDelta); |
555 | |
556 | // We can't scroll more than the width of our own frame layout |
557 | if (scrollOffset >= mGalleryWidth) scrollOffset = mGalleryWidth; |
558 | if (scrollOffset <= mGalleryWidth * - 1 ) scrollOffset = mGalleryWidth * - 1 ; |
559 | |
560 | mViews[ 0 ].setOffset(scrollOffset, 0 , mCurrentViewNumber); |
561 | mViews[ 1 ].setOffset(scrollOffset, 0 , mCurrentViewNumber); |
562 | mViews[ 2 ].setOffset(scrollOffset, 0 , mCurrentViewNumber); |
563 | } |
564 | |
565 | return false ; |
566 | } |
567 | |
568 | @Override |
569 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) |
570 | { |
571 | if (Math.abs(e1.getY() - e2.getY()) <= swipe_max_off_path) |
572 | { |
573 | if (e2.getX() - e1.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty) |
574 | { |
575 | movePrevious(); |
576 | } |
577 | |
578 | if (e1.getX() - e2.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty) |
579 | { |
580 | moveNext(); |
581 | } |
582 | } |
583 | |
584 | return false ; |
585 | } |
586 | |
587 | @Override |
588 | public void onLongPress(MotionEvent e) |
589 | { |
590 | // Finalise scrolling |
591 | mFlingDirection = 0 ; |
592 | processGesture(); |
593 | } |
594 | |
595 | @Override |
596 | public void onShowPress(MotionEvent e) |
597 | { |
598 | } |
599 | |
600 | @Override |
601 | public boolean onSingleTapUp(MotionEvent e) |
602 | { |
603 | // Reset fling state |
604 | mFlingDirection = 0 ; |
605 | return false ; |
606 | } |
607 | } |
608 | } |