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 |
} |