http://www.oschina.net/code/snippet_16_2541
(另)http://www.eoeandroid.com/thread-48286-1-1.html(带附件)
[代码] 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 |
} |