[代码]FlingGalleryActivity importandroid.app.Activity; importandroid.os.Bundle;
importandroid.content.Context; importandroid.graphics.Color; importandroid.util.Log; importandroid.view.Gravity; importandroid.view.MotionEvent; importandroid.view.View; importandroid.view.ViewGroup; importandroid.view.View.OnClickListener; importandroid.widget.ArrayAdapter; importandroid.widget.Button; importandroid.widget.CheckBox; importandroid.widget.EditText; importandroid.widget.LinearLayout; importandroid.widget.TableLayout; importandroid.widget.TextView;
publicclass FlingGalleryActivity extends Activity { privatefinal int color_red = Color.argb(100, 200, 0, 0); privatefinal int color_green = Color.argb(100, 0, 200, 0); privatefinal int color_blue = Color.argb(100, 0, 0, 200); privatefinal int color_yellow = Color.argb(100, 200, 200, 0); privatefinal int color_purple = Color.argb(100, 200, 0, 200);
privatefinal String[] mLabelArray = {"View1", "View2","View3", "View4", "View5"}; privatefinal int[] mColorArray = {color_red, color_green, color_blue,color_yellow, color_purple};
privateFlingGallery mGallery; privateCheckBox mCheckBox;
//Note: The following handler is critical to correct function of //the FlingGallery class. This enables the FlingGallery class to //detect when the motion event has ended by finger being lifted
@Override publicboolean onTouchEvent(MotionEvent event) { returnmGallery.onGalleryTouchEvent(event); }
publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
mGallery= new FlingGallery(this); mGallery.setPaddingWidth(5); mGallery.setAdapter(newArrayAdapter<String>(getApplicationContext(),android.R.layout.simple_list_item_1, mLabelArray) { @Override publicView getView(int position, View convertView, ViewGroup parent) { Log.d("111","count="+position); // if (convertView != null && convertView instanceofGalleryViewItem) // { // GalleryViewItem galleryView = (GalleryViewItem)convertView; // // galleryView.mEdit1.setText(""); // galleryView.mText1.setText(mLabelArray[position]); // galleryView.mText1.setBackgroundColor(mColorArray[position]); // galleryView.mText2.setText(mLabelArray[position]); // galleryView.mText2.setBackgroundColor(mColorArray[position]); // // Log.d("111", "count="+position); // // return galleryView; // // } returnnew GalleryViewItem(getApplicationContext(), position); } });
LinearLayoutlayout = new LinearLayout(getApplicationContext()); layout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParamslayoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
layoutParams.setMargins(10,10, 10, 10); layoutParams.weight= 1.0f;
layout.addView(mGallery,layoutParams); mCheckBox= new CheckBox(getApplicationContext()); mCheckBox.setText("Galleryis Circular"); mCheckBox.setText("Galleryis Circular"); mCheckBox.setPadding(50,10, 0, 10); mCheckBox.setTextSize(30); mCheckBox.setChecked(true); mCheckBox.setOnClickListener(newOnClickListener() { @Override publicvoid onClick(View view) { mGallery.setIsGalleryCircular(mCheckBox.isChecked()); } });
layout.addView(mCheckBox,new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); setContentView(layout); }
privateclass GalleryViewItem extends TableLayout { privateEditText mEdit1; privateTextView mText1; privateTextView mText2; privateButton mButton1; privateButton mButton2;
publicGalleryViewItem(Context context, int position) { super(context);
this.setOrientation(LinearLayout.VERTICAL);
this.setLayoutParams(newLinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); mEdit1= new EditText(context);
this.addView(mEdit1,new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
mText1= new TextView(context); mText1.setText(mLabelArray[position]); mText1.setTextSize(30); mText1.setGravity(Gravity.LEFT); mText1.setBackgroundColor(mColorArray[position]);
this.addView(mText1,new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
mButton1= new Button(context); mButton1.setText("<<"); mButton1.setGravity(Gravity.LEFT); mButton1.setOnClickListener(newOnClickListener() { @Override publicvoid onClick(View view) { mGallery.movePrevious(); } });
this.addView(mButton1,new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
mButton2= new Button(context); mButton2.setText(">>"); mButton2.setGravity(Gravity.RIGHT); mButton2.setOnClickListener(newOnClickListener() { @Override publicvoid onClick(View view) { mGallery.moveNext(); } });
this.addView(mButton2,new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
mText2= new TextView(context); mText2.setText(mLabelArray[position]); mText2.setTextSize(30); mText2.setGravity(Gravity.RIGHT); mText2.setBackgroundColor(mColorArray[position]);
this.addView(mText2,new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,1)); } } }
[代码]FlingGallery
importandroid.content.Context; importandroid.view.GestureDetector; importandroid.view.KeyEvent; importandroid.view.MotionEvent; importandroid.view.View; importandroid.view.animation.Animation; importandroid.view.animation.AnimationUtils; importandroid.view.animation.Interpolator; importandroid.view.animation.Transformation; importandroid.widget.Adapter; importandroid.widget.FrameLayout; importandroid.widget.LinearLayout;
//TODO:
//1. In order to improve performance Cache screen bitmap and use foranimation //2. Establish superfluous memory allocations and delay or replace withreused objects // Probably need to make sure we are not allocating objects (strings,etc.) in loops
publicclass FlingGallery extends FrameLayout { //Constants privatefinal int swipe_min_distance = 120; privatefinal int swipe_max_off_path = 250; privatefinal int swipe_threshold_veloicty = 400;
//Properties
privateint mViewPaddingWidth = 0; privateint mAnimationDuration = 250; privatefloat mSnapBorderRatio = 0.5f; privateboolean mIsGalleryCircular = true;
//Members
privateint mGalleryWidth = 0; privateboolean mIsTouched = false; privateboolean mIsDragging = false; privatefloat mCurrentOffset = 0.0f; privatelong mScrollTimestamp = 0; privateint mFlingDirection = 0; privateint mCurrentPosition = 0; privateint mCurrentViewNumber = 0;
privateContext mContext; privateAdapter mAdapter; privateFlingGalleryView[] mViews; privateFlingGalleryAnimation mAnimation; privateGestureDetector mGestureDetector; privateInterpolator mDecelerateInterpolater;
publicFlingGallery(Context context) { super(context);
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); }
publicvoid setPaddingWidth(int viewPaddingWidth) { mViewPaddingWidth= viewPaddingWidth; }
publicvoid setAnimationDuration(int animationDuration) { mAnimationDuration= animationDuration; } publicvoid setSnapBorderRatio(float snapBorderRatio) { mSnapBorderRatio= snapBorderRatio; }
publicvoid setIsGalleryCircular(boolean isGalleryCircular) { if(mIsGalleryCircular != isGalleryCircular) { mIsGalleryCircular= isGalleryCircular; if(mCurrentPosition == getFirstPosition()) { //We need to reload the view immediately to the left to change it tocircular 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 tocircular view or blank mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition)); } } }
publicint getGalleryCount() { return(mAdapter == null) ? 0 : mAdapter.getCount(); }
publicint getFirstPosition() { return0; }
publicint getLastPosition() { return(getGalleryCount() == 0) ? 0 : getGalleryCount() - 1; }
privateint getPrevPosition(int relativePosition) { intprevPosition = relativePosition - 1;
if(prevPosition < getFirstPosition()) { prevPosition= getFirstPosition() - 1;
if(mIsGalleryCircular == true) { prevPosition= getLastPosition(); } }
returnprevPosition; }
privateint getNextPosition(int relativePosition) { intnextPosition = relativePosition + 1;
if(nextPosition > getLastPosition()) { nextPosition= getLastPosition() + 1;
if(mIsGalleryCircular == true) { nextPosition= getFirstPosition(); } }
returnnextPosition; }
privateint getPrevViewNumber(int relativeViewNumber) { return(relativeViewNumber == 0) ? 2 : relativeViewNumber - 1; }
privateint getNextViewNumber(int relativeViewNumber) { return(relativeViewNumber == 2) ? 0 : relativeViewNumber + 1; } @Override protectedvoid onLayout(boolean changed, int left, int top, int right, intbottom) { super.onLayout(changed,left, top, right, bottom);
//Calculate our view width mGalleryWidth= right - left;
if(changed == true) { // Position views at correct starting offsets mViews[0].setOffset(0, 0, mCurrentViewNumber); mViews[1].setOffset(0, 0, mCurrentViewNumber); mViews[2].setOffset(0, 0, mCurrentViewNumber); } }
publicvoid 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); }
privateint getViewOffset(int viewNumber, int relativeViewNumber) { //Determine width including configured padding width intoffsetWidth = mGalleryWidth + mViewPaddingWidth;
//Position the previous view one measured width to left if(viewNumber == getPrevViewNumber(relativeViewNumber)) { returnoffsetWidth; }
//Position the next view one measured width to the right if(viewNumber == getNextViewNumber(relativeViewNumber)) { returnoffsetWidth * -1; }
return0; }
voidmovePrevious() { //Slide to previous view mFlingDirection= 1; processGesture(); }
voidmoveNext() { //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); }
publicboolean onGalleryTouchEvent(MotionEvent event) { booleanconsumed = mGestureDetector.onTouchEvent(event); if(event.getAction() == MotionEvent.ACTION_UP) { if(mIsTouched || mIsDragging) { processScrollSnap(); processGesture(); } } returnconsumed; }
voidprocessGesture() { intnewViewNumber = mCurrentViewNumber; intreloadViewNumber = 0; intreloadPosition = 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; }
voidprocessScrollSnap() { //Snap to next view if scrolled passed snap position floatrollEdgeWidth = mGalleryWidth * mSnapBorderRatio; introllOffset = mGalleryWidth - (int) rollEdgeWidth; intcurrentOffset = mViews[mCurrentViewNumber].getCurrentOffset();
if(currentOffset <= rollOffset * -1) { //Snap to previous view mFlingDirection= 1; }
if(currentOffset >= rollOffset) { //Snap to next view mFlingDirection= -1; } }
privateclass FlingGalleryView { privateint mViewNumber; privateFrameLayout mParentLayout; privateFrameLayout mInvalidLayout = null; privateLinearLayout mInternalLayout = null; privateView mExternalView = null;
publicFlingGalleryView(int viewNumber, FrameLayout parentLayout) { mViewNumber= viewNumber; mParentLayout= parentLayout;
//Invalid layout is used when outside gallery mInvalidLayout= new FrameLayout(mContext); mInvalidLayout.setLayoutParams(newLinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
//Internal layout is permanent for duration mInternalLayout= new LinearLayout(mContext); mInternalLayout.setLayoutParams(newLinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mParentLayout.addView(mInternalLayout); }
publicvoid 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.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } }
publicvoid setOffset(int xOffset, int yOffset, int relativeViewNumber) { //Scroll the target view relative to its own position relative tocurrently displayed view mInternalLayout.scrollTo(getViewOffset(mViewNumber,relativeViewNumber) + xOffset, yOffset); } publicint getCurrentOffset() { //Return the current scroll position returnmInternalLayout.getScrollX(); }
publicvoid requestFocus() { mInternalLayout.requestFocus(); } }
privateclass FlingGalleryAnimation extends Animation { privateboolean mIsAnimationInProgres; privateint mRelativeViewNumber; privateint mInitialOffset; privateint mTargetOffset; privateint mTargetDistance;
publicFlingGalleryAnimation() { mIsAnimationInProgres= false; mRelativeViewNumber= 0; mInitialOffset= 0; mTargetOffset= 0; mTargetDistance= 0; }
publicvoid 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 samedirection we must snap intnewDirection = (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 protectedvoid applyTransformation(float interpolatedTime, Transformationtransformation) { //Ensure interpolatedTime does not over-shoot then calculate new offset interpolatedTime= (interpolatedTime > 1.0f) ? 1.0f : interpolatedTime; intoffset = mInitialOffset + (int) (mTargetDistance * interpolatedTime);
for(int viewNumber = 0; viewNumber < 3; viewNumber++) { //Only need to animate the visible views as the other view will alwaysbe off-screen if((mTargetDistance > 0 && viewNumber !=getNextViewNumber(mRelativeViewNumber)) || (mTargetDistance< 0 && viewNumber !=getPrevViewNumber(mRelativeViewNumber))) { mViews[viewNumber].setOffset(offset,0, mRelativeViewNumber); } } }
@Override publicboolean getTransformation(long currentTime, TransformationoutTransformation) { 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;
returnfalse; }
//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
returnfalse; }
returntrue; } }
privateclass FlingGestureDetector extendsGestureDetector.SimpleOnGestureListener { @Override publicboolean onDown(MotionEvent e) { //Stop animation mIsTouched= true;
//Reset fling state mFlingDirection= 0; returntrue; }
@Override publicboolean 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); longtimestampDelta = System.currentTimeMillis() - mScrollTimestamp; floatmaxScrollDelta = maxVelocity * (timestampDelta / 1000.0f); floatcurrentScrollDelta = 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); }
returnfalse; }
@Override publicboolean 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(); } }
returnfalse; }
@Override publicvoid onLongPress(MotionEvent e) { //Finalise scrolling mFlingDirection= 0; processGesture(); }
@Override publicvoid onShowPress(MotionEvent e) { }
@Override publicboolean onSingleTapUp(MotionEvent e) { //Reset fling state mFlingDirection= 0; returnfalse; } } }
|