android实现uc和墨迹天气那样的左右拖动效果

本文出自:http://www.oschina.net/code/snippet_16_2541

import android.app.Activity;

import android.os.Bundle;

import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TextView;

public class FlingGalleryActivity extends Activity
{
    private final int color_red = Color.argb(100, 200, 0, 0);
    private final int color_green = Color.argb(100, 0, 200, 0);
    private final int color_blue = Color.argb(100, 0, 0, 200);
    private final int color_yellow = Color.argb(100, 200, 200, 0);
    private final int color_purple = Color.argb(100, 200, 0, 200);

    private final String[] mLabelArray = {"View1", "View2", "View3", "View4", "View5"};
    private final int[] mColorArray = {color_red, color_green, color_blue, color_yellow, color_purple};

    private FlingGallery mGallery;
    private CheckBox 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
    public boolean onTouchEvent(MotionEvent event)
    {
        return mGallery.onGalleryTouchEvent(event);
    }

    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        mGallery = new FlingGallery(this);
        mGallery.setPaddingWidth(5);
        mGallery.setAdapter(new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, mLabelArray)
        {
            @Override
            public View getView(int position, View convertView, ViewGroup parent)
            {
                Log.d("111", "count="+position);
//                if (convertView != null && convertView instanceof GalleryViewItem)
//                {
//                    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;
//                    
//                }
                
                return new GalleryViewItem(getApplicationContext(), position);
            }
        });

        LinearLayout layout = new LinearLayout(getApplicationContext());
        layout.setOrientation(LinearLayout.VERTICAL);

        LinearLayout.LayoutParams layoutParams = 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("Gallery is Circular");
        mCheckBox.setText("Gallery is Circular");
        mCheckBox.setPadding(50, 10, 0, 10);
        mCheckBox.setTextSize(30);
        mCheckBox.setChecked(true);
        mCheckBox.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                mGallery.setIsGalleryCircular(mCheckBox.isChecked());
            }
        });

        layout.addView(mCheckBox, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT));
        
        setContentView(layout);
    }    

    private class GalleryViewItem extends TableLayout
    {
        private EditText mEdit1;
        private TextView mText1;
        private TextView mText2;
        private Button mButton1;
        private Button mButton2;

        public GalleryViewItem(Context context, int position)
        {
            super(context);

            this.setOrientation(LinearLayout.VERTICAL);

            this.setLayoutParams(new LinearLayout.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(new OnClickListener()
            {
                @Override
                public void 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(new OnClickListener()
            {
                @Override
                public void 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));
        }
    }
}


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

// TODO:

// 1. In order to improve performance Cache screen bitmap and use for animation
// 2. Establish superfluous memory allocations and delay or replace with reused objects
//      Probably need to make sure we are not allocating objects (strings, etc.) in loops

public class FlingGallery extends FrameLayout
{
    // Constants
    
    private final int swipe_min_distance = 120;
    private final int swipe_max_off_path = 250;
    private final int swipe_threshold_veloicty = 400;

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

    // Members

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

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

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

        mContext = context;
        mAdapter = null;
        
        mViews = new FlingGalleryView[3];
        mViews[0] = new FlingGalleryView(0, this);
        mViews[1] = new FlingGalleryView(1, this);
        mViews[2] = new FlingGalleryView(2, this);

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

    public void setPaddingWidth(int viewPaddingWidth)
    {
        mViewPaddingWidth = viewPaddingWidth;
    }

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

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

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

    public int getFirstPosition()
    {
        return 0;
    }

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

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

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

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

        return prevPosition;
    }

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

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

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

        return nextPosition;
    }

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

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

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

        if (changed == 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);
        }
    }

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

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

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

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

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

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

        return 0;
    }

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

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

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

        return super.onKeyDown(keyCode, event);
    }

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

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

        mIsTouched = false;
        mIsDragging = false;

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

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

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

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

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

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

        // Reset fling state
        mFlingDirection = 0;
    }

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

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

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

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

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

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

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

            mParentLayout.addView(mInternalLayout);
        }

        public void recycleView(int newPosition)
        {
            if (mExternalView != null)
            {
                mInternalLayout.removeView(mExternalView);
            }

            if (mAdapter != null)
            {
                if (newPosition >= getFirstPosition() && newPosition <= getLastPosition())
                {
                    mExternalView = mAdapter.getView(newPosition, mExternalView, mInternalLayout);
                }
                else
                {
                    mExternalView = mInvalidLayout;
                }
            }

            if (mExternalView != null)
            {
                mInternalLayout.addView(mExternalView, new LinearLayout.LayoutParams(
                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            }
        }

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

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

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

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

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

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

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

            // Start/continued animation
            mIsAnimationInProgres = true;
        }

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

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

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

                // Reached the animation target
                mIsAnimationInProgres = false;

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

                return false;
            }

            return true;
        }
    }

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

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

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

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

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

                // We can't scroll more than the width of our own frame layout
                if (scrollOffset >= mGalleryWidth) scrollOffset = mGalleryWidth;
                if (scrollOffset <= mGalleryWidth * -1) scrollOffset = mGalleryWidth * -1;
                
                mViews[0].setOffset(scrollOffset, 0, mCurrentViewNumber);
                mViews[1].setOffset(scrollOffset, 0, mCurrentViewNumber);
                mViews[2].setOffset(scrollOffset, 0, mCurrentViewNumber);
            }

            return false;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
        {
            if (Math.abs(e1.getY() - e2.getY()) <= swipe_max_off_path)
            {
                if (e2.getX() - e1.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)
                {
                    movePrevious();
                }

                if(e1.getX() - e2.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)
                {
                    moveNext();
                }
            }

            return false;
        }

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

        @Override
        public void onShowPress(MotionEvent e)
        {
        }

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


你可能感兴趣的:(android,animation,float,scroll,transformation,distance)