48、ViewFlow ---- 滑动广告页

 1 <!-- main.xml -->
 2 <?xml version="1.0" encoding="utf-8"?>
 3 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:app="http://schemas.android.com/apk/res/com.viewflowtest.cjy"
 5     android:layout_width="fill_parent"
 6     android:layout_height="fill_parent"
 7     android:orientation="vertical" >
 8 
 9     <FrameLayout
10         android:id="@+id/framelayout"
11         android:layout_width="fill_parent"
12         android:layout_height="150dip"
13         android:background="#ffffff" >
14 
15         <org.taptwo.android.widget.ViewFlow
16             android:id="@+id/viewflow"
17             android:layout_width="fill_parent"
18             android:layout_height="fill_parent" />
19 
20         <LinearLayout
21             android:layout_width="fill_parent"
22             android:layout_height="wrap_content"
23             android:layout_gravity="bottom"
24             android:background="#88252525"
25             android:gravity="center"
26             android:padding="3dip" >
27 
28             <org.taptwo.android.widget.CircleFlowIndicator
29                 android:id="@+id/viewflowindic"
30                 android:layout_width="wrap_content"
31                 android:layout_height="wrap_content"
32                 android:layout_gravity="center_horizontal|bottom"
33                 android:padding="2dip"
34                 app:activeColor="#ff0000"
35                 app:activeType="fill"
36                 app:circleSeparation="20dip"
37                 app:inactiveColor="#ffffff"
38                 app:inactiveType="fill"
39                 app:radius="4dip" />
40         </LinearLayout>
41     </FrameLayout>
42 
43     <TextView
44         android:layout_width="fill_parent"
45         android:layout_height="wrap_content"
46         android:text="@string/hello" />
47 
48 </LinearLayout>
 1 import org.taptwo.android.widget.CircleFlowIndicator;
 2 import org.taptwo.android.widget.ViewFlow;
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 
 6 public class ViewFlowTestActivity extends Activity {
 7     
 8     private ViewFlow viewFlow;
 9     
10     @Override
11     public void onCreate(Bundle savedInstanceState) {
12         super.onCreate(savedInstanceState);
13         setContentView(R.layout.main);
14         
15         viewFlow = (ViewFlow)findViewById(R.id.viewflow);
16         viewFlow.setAdapter(new ImageAdapter(this));
17         // 实际图片张数, 我的ImageAdapter实际图片张数为3
18         viewFlow.setmSideBuffer(3); 
19         
20         CircleFlowIndicator indic = (CircleFlowIndicator) findViewById(R.id.viewflowindic);
21         viewFlow.setFlowIndicator(indic);
22         viewFlow.setTimeSpan(4500);
23         viewFlow.setSelection(3*1000);    //设置初始位置
24         viewFlow.startAutoFlowTimer();  //启动自动播放
25     }
26 }
package org.taptwo.android.widget;

import java.util.ArrayList;
import java.util.LinkedList;

import com.viewflowtest.cjy.R;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.AbsListView;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.Scroller;

/**
 * A horizontally scrollable {@link ViewGroup} with items populated from an
 * {@link Adapter}. The ViewFlow uses a buffer to store loaded {@link View}s in.
 * The default size of the buffer is 3 elements on both sides of the currently
 * visible {@link View}, making up a total buffer size of 3 * 2 + 1 = 7. The
 * buffer size can be changed using the {@code sidebuffer} xml attribute.
 * 
 */
public class ViewFlow extends AdapterView<Adapter> {

    private static final int SNAP_VELOCITY = 1000;
    private static final int INVALID_SCREEN = -1;
    private final static int TOUCH_STATE_REST = 0;
    private final static int TOUCH_STATE_SCROLLING = 1;

    private LinkedList<View> mLoadedViews;
    private int mCurrentBufferIndex;
    private int mCurrentAdapterIndex;
    private int mSideBuffer = 2;
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;
    private int mTouchState = TOUCH_STATE_REST;
    private float mLastMotionX;
    private int mTouchSlop;
    private int mMaximumVelocity;
    private int mCurrentScreen;
    private int mNextScreen = INVALID_SCREEN;
    private boolean mFirstLayout = true;
    private ViewSwitchListener mViewSwitchListener;
    private Adapter mAdapter;
    private int mLastScrollDirection;
    private AdapterDataSetObserver mDataSetObserver;
    private FlowIndicator mIndicator;
    private int mLastOrientation = -1;
    private long timeSpan = 3000;
    private Handler handler;
    private OnGlobalLayoutListener orientationChangeListener = new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            getViewTreeObserver().removeGlobalOnLayoutListener(
                    orientationChangeListener);
            setSelection(mCurrentAdapterIndex);
        }
    };

    /**
     * Receives call backs when a new {@link View} has been scrolled to.
     */
    public static interface ViewSwitchListener {

        /**
         * This method is called when a new View has been scrolled to.
         * 
         * @param view
         *            the {@link View} currently in focus.
         * @param position
         *            The position in the adapter of the {@link View} currently in focus.
         */
        void onSwitched(View view, int position);

    }

    public ViewFlow(Context context) {
        super(context);
        mSideBuffer = 3;
        init();
    }

    public ViewFlow(Context context, int sideBuffer) {
        super(context);
        mSideBuffer = sideBuffer;
        init();
    }

    public ViewFlow(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
                R.styleable.ViewFlow);
        mSideBuffer = styledAttrs.getInt(R.styleable.ViewFlow_sidebuffer, 3);
        init();
    }

    private void init() {
        mLoadedViews = new LinkedList<View>();
        mScroller = new Scroller(getContext());
        final ViewConfiguration configuration = ViewConfiguration
                .get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

    public void startAutoFlowTimer(){
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                
                snapToScreen((mCurrentScreen+1)%getChildCount());
                Message message = handler.obtainMessage(0);
                sendMessageDelayed(message, timeSpan);
            }
        };

        Message message = handler.obtainMessage(0);
        handler.sendMessageDelayed(message, timeSpan);
    }
    public void stopAutoFlowTimer(){
        if(handler!=null)
            handler.removeMessages(0);
        handler = null;
    }
    
    public void onConfigurationChanged(Configuration newConfig) {
        if (newConfig.orientation != mLastOrientation) {
            mLastOrientation = newConfig.orientation;
            getViewTreeObserver().addOnGlobalLayoutListener(orientationChangeListener);
        }
    }

    public int getViewsCount() {
        return mSideBuffer;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY && !isInEditMode()) {
            throw new IllegalStateException(
                    "ViewFlow can only be used in EXACTLY mode.");
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY && !isInEditMode()) {
            throw new IllegalStateException(
                    "ViewFlow can only be used in EXACTLY mode.");
        }

        // The children are given the same width and height as the workspace
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
        }

        if (mFirstLayout) {
            mScroller.startScroll(0, 0, mCurrentScreen * width, 0, 0);
            mFirstLayout = false;
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                final int childWidth = child.getMeasuredWidth();
                child.layout(childLeft, 0, childLeft + childWidth,
                        child.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0)
            return false;

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        final float x = ev.getX();

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            /*
             * If being flinged and user touches, stop the fling. isFinished
             * will be false if being flinged.
             */
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }

            // Remember where the motion event started
            mLastMotionX = x;

            mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
                    : TOUCH_STATE_SCROLLING;
            if(handler!=null)
                handler.removeMessages(0);
            break;

        case MotionEvent.ACTION_MOVE:
            final int xDiff = (int) Math.abs(x - mLastMotionX);

            boolean xMoved = xDiff > mTouchSlop;

            if (xMoved) {
                // Scroll if the user moved far enough along the X axis
                mTouchState = TOUCH_STATE_SCROLLING;
            }

            if (mTouchState == TOUCH_STATE_SCROLLING) {
                // Scroll to follow the motion event
                final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;

                final int scrollX = getScrollX();
                if (deltaX < 0) {
                    if (scrollX > 0) {
                        scrollBy(Math.max(-scrollX, deltaX), 0);
                    }
                } else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(
                            getChildCount() - 1).getRight()
                            - scrollX - getWidth();
                    if (availableToScroll > 0) {
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }
                return true;
            }
            break;

        case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                    // Fling hard enough to move left
                    snapToScreen(mCurrentScreen - 1);
                } else if (velocityX < -SNAP_VELOCITY
                        && mCurrentScreen < getChildCount() - 1) {
                    // Fling hard enough to move right
                    snapToScreen(mCurrentScreen + 1);
                } else {
                    snapToDestination();
                }

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
            }

            mTouchState = TOUCH_STATE_REST;
            if(handler!=null){
                Message message = handler.obtainMessage(0);
                handler.sendMessageDelayed(message, timeSpan);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            mTouchState = TOUCH_STATE_REST;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0)
            return false;

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        final float x = ev.getX();

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            /*
             * If being flinged and user touches, stop the fling. isFinished
             * will be false if being flinged.
             */
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }

            // Remember where the motion event started
            mLastMotionX = x;

            mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
                    : TOUCH_STATE_SCROLLING;
            if(handler!=null)
                handler.removeMessages(0);
            break;

        case MotionEvent.ACTION_MOVE:
            final int xDiff = (int) Math.abs(x - mLastMotionX);

            boolean xMoved = xDiff > mTouchSlop;

            if (xMoved) {
                // Scroll if the user moved far enough along the X axis
                mTouchState = TOUCH_STATE_SCROLLING;
            }

            if (mTouchState == TOUCH_STATE_SCROLLING) {
                // Scroll to follow the motion event
                final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;

                final int scrollX = getScrollX();
                if (deltaX < 0) {
                    if (scrollX > 0) {
                        scrollBy(Math.max(-scrollX, deltaX), 0);
                    }
                } else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(
                            getChildCount() - 1).getRight()
                            - scrollX - getWidth();
                    if (availableToScroll > 0) {
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }
                return true;
            }
            break;

        case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                    // Fling hard enough to move left
                    snapToScreen(mCurrentScreen - 1);
                } else if (velocityX < -SNAP_VELOCITY
                        && mCurrentScreen < getChildCount() - 1) {
                    // Fling hard enough to move right
                    snapToScreen(mCurrentScreen + 1);
                } 
//                else if (velocityX < -SNAP_VELOCITY
//                            && mCurrentScreen == getChildCount() - 1) {
//                        snapToScreen(0);
//                } 
//                else if (velocityX > SNAP_VELOCITY
//                            && mCurrentScreen == 0) {
//                        snapToScreen(getChildCount() - 1);
//                }
                else {
                    snapToDestination();
                }

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
            }

            mTouchState = TOUCH_STATE_REST;

            if(handler!=null){
                Message message = handler.obtainMessage(0);
                handler.sendMessageDelayed(message, timeSpan);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            snapToDestination();
            mTouchState = TOUCH_STATE_REST;
        }
        return true;
    }

    @Override
    protected void onScrollChanged(int h, int v, int oldh, int oldv) {
        super.onScrollChanged(h, v, oldh, oldv);
        if (mIndicator != null) {
            /*
             * The actual horizontal scroll origin does typically not match the
             * perceived one. Therefore, we need to calculate the perceived
             * horizontal scroll origin here, since we use a view buffer.
             */
            int hPerceived = h + (mCurrentAdapterIndex - mCurrentBufferIndex)
                    * getWidth();
            mIndicator.onScrolled(hPerceived, v, oldh, oldv);
        }
    }

    private void snapToDestination() {
        final int screenWidth = getWidth();
        final int whichScreen = (getScrollX() + (screenWidth / 2))
                / screenWidth;

        snapToScreen(whichScreen);
    }

    private void snapToScreen(int whichScreen) {
        mLastScrollDirection = whichScreen - mCurrentScreen;
        if (!mScroller.isFinished())
            return;

        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));

        mNextScreen = whichScreen;

        final int newX = whichScreen * getWidth();
        final int delta = newX - getScrollX();
        mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        } else if (mNextScreen != INVALID_SCREEN) {
            mCurrentScreen = Math.max(0,
                    Math.min(mNextScreen, getChildCount() - 1));
            mNextScreen = INVALID_SCREEN;
            postViewSwitched(mLastScrollDirection);
        }
    }

    /**
     * Scroll to the {@link View} in the view buffer specified by the index.
     * 
     * @param indexInBuffer
     *            Index of the view in the view buffer.
     */
    private void setVisibleView(int indexInBuffer, boolean uiThread) {
        mCurrentScreen = Math.max(0,
                Math.min(indexInBuffer, getChildCount() - 1));
        int dx = (mCurrentScreen * getWidth()) - mScroller.getCurrX();
        mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), dx,
                0, 0);
        if(dx == 0)
            onScrollChanged(mScroller.getCurrX() + dx, mScroller.getCurrY(), mScroller.getCurrX() + dx, mScroller.getCurrY());
        if (uiThread)
            invalidate();
        else
            postInvalidate();
    }

    /**
     * Set the listener that will receive notifications every time the {code
     * ViewFlow} scrolls.
     * 
     * @param l
     *            the scroll listener
     */
    public void setOnViewSwitchListener(ViewSwitchListener l) {
        mViewSwitchListener = l;
    }

    @Override
    public Adapter getAdapter() {
        return mAdapter;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        setAdapter(adapter, 0);
    }
    
    public void setAdapter(Adapter adapter, int initialPosition) {
        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        mAdapter = adapter;

        if (mAdapter != null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

        }
        if (mAdapter == null || mAdapter.getCount() == 0)
            return;
        
        setSelection(initialPosition);        
    }
    
    @Override
    public View getSelectedView() {
        return (mCurrentBufferIndex < mLoadedViews.size() ? mLoadedViews
                .get(mCurrentBufferIndex) : null);
    }

    @Override
    public int getSelectedItemPosition() {
        return mCurrentAdapterIndex;
    }

    /**
     * Set the FlowIndicator
     * 
     * @param flowIndicator
     */
    public void setFlowIndicator(FlowIndicator flowIndicator) {
        mIndicator = flowIndicator;
        mIndicator.setViewFlow(this);
    }

    @Override
    public void setSelection(int position) {
        mNextScreen = INVALID_SCREEN;
        mScroller.forceFinished(true);
        if (mAdapter == null)
            return;
        
        position = Math.max(position, 0);
        position =  Math.min(position, mAdapter.getCount()-1);

        ArrayList<View> recycleViews = new ArrayList<View>();
        View recycleView;
        while (!mLoadedViews.isEmpty()) {
            recycleViews.add(recycleView = mLoadedViews.remove());
            detachViewFromParent(recycleView);
        }

        View currentView = makeAndAddView(position, true,
                (recycleViews.isEmpty() ? null : recycleViews.remove(0)));
        mLoadedViews.addLast(currentView);
        
        for(int offset = 1; mSideBuffer - offset >= 0; offset++) {
            int leftIndex = position - offset;
            int rightIndex = position + offset;
            if(leftIndex >= 0)
                mLoadedViews.addFirst(makeAndAddView(leftIndex, false,
                        (recycleViews.isEmpty() ? null : recycleViews.remove(0))));
            if(rightIndex < mAdapter.getCount())
                mLoadedViews.addLast(makeAndAddView(rightIndex, true,
                        (recycleViews.isEmpty() ? null : recycleViews.remove(0))));
        }

        mCurrentBufferIndex = mLoadedViews.indexOf(currentView);
        mCurrentAdapterIndex = position;

        for (View view : recycleViews) {
            removeDetachedView(view, false);
        }
        requestLayout();
        setVisibleView(mCurrentBufferIndex, false);
        if (mIndicator != null) {
            mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex),
                    mCurrentAdapterIndex);
        }
        if (mViewSwitchListener != null) {
            mViewSwitchListener
                    .onSwitched(mLoadedViews.get(mCurrentBufferIndex),
                            mCurrentAdapterIndex);
        }
    }

    private void resetFocus() {
        mLoadedViews.clear();
        removeAllViewsInLayout();

        for (int i = Math.max(0, mCurrentAdapterIndex - mSideBuffer); i < Math
                .min(mAdapter.getCount(), mCurrentAdapterIndex + mSideBuffer
                        + 1); i++) {
            mLoadedViews.addLast(makeAndAddView(i, true, null));
            if (i == mCurrentAdapterIndex)
                mCurrentBufferIndex = mLoadedViews.size() - 1;
        }
        requestLayout();
    }

    private void postViewSwitched(int direction) {
        if (direction == 0)
            return;

        if (direction > 0) { // to the right
            mCurrentAdapterIndex++;
            mCurrentBufferIndex++;
            
//            if(direction > 1) {
//                mCurrentAdapterIndex += mAdapter.getCount() - 2;
//                mCurrentBufferIndex += mAdapter.getCount() - 2;
//            }

            View recycleView = null;

            // Remove view outside buffer range
            if (mCurrentAdapterIndex > mSideBuffer) {
                recycleView = mLoadedViews.removeFirst();
                detachViewFromParent(recycleView);
                // removeView(recycleView);
                mCurrentBufferIndex--;
            }

            // Add new view to buffer
            int newBufferIndex = mCurrentAdapterIndex + mSideBuffer;
            if (newBufferIndex < mAdapter.getCount())
                mLoadedViews.addLast(makeAndAddView(newBufferIndex, true,
                        recycleView));

        } else { // to the left
            mCurrentAdapterIndex--;
            mCurrentBufferIndex--;
            
//            if(direction < -1) {
//                mCurrentAdapterIndex -= mAdapter.getCount() - 2;
//                mCurrentBufferIndex -= mAdapter.getCount() - 2;
//            }
            
            View recycleView = null;

            // Remove view outside buffer range
            if (mAdapter.getCount() - 1 - mCurrentAdapterIndex > mSideBuffer) {
                recycleView = mLoadedViews.removeLast();
                detachViewFromParent(recycleView);
            }

            // Add new view to buffer
            int newBufferIndex = mCurrentAdapterIndex - mSideBuffer;
            if (newBufferIndex > -1) {
                mLoadedViews.addFirst(makeAndAddView(newBufferIndex, false,
                        recycleView));
                mCurrentBufferIndex++;
            }

        }

        requestLayout();
        setVisibleView(mCurrentBufferIndex, true);
        if (mIndicator != null) {
            mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex),
                    mCurrentAdapterIndex);
        }
        if (mViewSwitchListener != null) {
            mViewSwitchListener
                    .onSwitched(mLoadedViews.get(mCurrentBufferIndex),
                            mCurrentAdapterIndex);
        }
    }

    private View setupChild(View child, boolean addToEnd, boolean recycle) {
        ViewGroup.LayoutParams p = (ViewGroup.LayoutParams) child
                .getLayoutParams();
        if (p == null) {
            p = new AbsListView.LayoutParams(
                    ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
        }
        if (recycle)
            attachViewToParent(child, (addToEnd ? -1 : 0), p);
        else
            addViewInLayout(child, (addToEnd ? -1 : 0), p, true);
        return child;
    }

    private View makeAndAddView(int position, boolean addToEnd, View convertView) {
        View view = mAdapter.getView(position, convertView, this);
        return setupChild(view, addToEnd, convertView != null);
    }

    class AdapterDataSetObserver extends DataSetObserver {

        @Override
        public void onChanged() {
            View v = getChildAt(mCurrentBufferIndex);
            if (v != null) {
                for (int index = 0; index < mAdapter.getCount(); index++) {
                    if (v.equals(mAdapter.getItem(index))) {
                        mCurrentAdapterIndex = index;
                        break;
                    }
                }
            }
            resetFocus();
        }

        @Override
        public void onInvalidated() {
            // Not yet implemented!
        }

    }

    public void setTimeSpan(long timeSpan) {
        this.timeSpan = timeSpan;
    }

    public void setmSideBuffer(int mSideBuffer) {
        this.mSideBuffer = mSideBuffer;
    }
}
package org.taptwo.android.widget;


import com.viewflowtest.cjy.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation.AnimationListener;


public class CircleFlowIndicator extends View implements FlowIndicator,
        AnimationListener {
    private static final int STYLE_STROKE = 0;
    private static final int STYLE_FILL = 1;

    private float radius = 4;
    private float circleSeparation = 2*radius+radius;
    private float activeRadius = 0.5f;
    private int fadeOutTime = 0;
    private final Paint mPaintInactive = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint mPaintActive = new Paint(Paint.ANTI_ALIAS_FLAG);
    private ViewFlow viewFlow;
    private int currentScroll = 0;
    private int flowWidth = 0;
    private FadeTimer timer;
    public AnimationListener animationListener = this;
    private Animation animation;
    private boolean mCentered = false;

    /**
     * Default constructor
     * 
     * @param context
     */
    public CircleFlowIndicator(Context context) {
        super(context);
        initColors(0xFFFFFFFF, 0xFFFFFFFF, STYLE_FILL, STYLE_STROKE);
    }

    /**
     * The contructor used with an inflater
     * 
     * @param context
     * @param attrs
     */
    public CircleFlowIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        // Retrieve styles attributs
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.CircleFlowIndicator);

        // Gets the inactive circle type, defaulting to "fill"
        int activeType = a.getInt(R.styleable.CircleFlowIndicator_activeType,
                STYLE_FILL);
        
        int activeDefaultColor = 0xFFFFFFFF;
        
        // Get a custom inactive color if there is one
        int activeColor = a
                .getColor(R.styleable.CircleFlowIndicator_activeColor,
                        activeDefaultColor);

        // Gets the inactive circle type, defaulting to "stroke"
        int inactiveType = a.getInt(
                R.styleable.CircleFlowIndicator_inactiveType, STYLE_STROKE);

        int inactiveDefaultColor = 0x44FFFFFF;
        // Get a custom inactive color if there is one
        int inactiveColor = a.getColor(
                R.styleable.CircleFlowIndicator_inactiveColor,
                inactiveDefaultColor);

        // Retrieve the radius
        radius = a.getDimension(R.styleable.CircleFlowIndicator_radius, 4.0f);
        
        circleSeparation = a.getDimension(R.styleable.CircleFlowIndicator_circleSeparation, 2*radius+radius);
        activeRadius = a.getDimension(R.styleable.CircleFlowIndicator_activeRadius, 0.5f);
        // Retrieve the fade out time
        fadeOutTime = a.getInt(R.styleable.CircleFlowIndicator_fadeOut, 0);
        
        mCentered = a.getBoolean(R.styleable.CircleFlowIndicator_centered, false);
        
        initColors(activeColor, inactiveColor, activeType, inactiveType);
    }

    private void initColors(int activeColor, int inactiveColor, int activeType,
            int inactiveType) {
        // Select the paint type given the type attr
        switch (inactiveType) {
        case STYLE_FILL:
            mPaintInactive.setStyle(Style.FILL);
            break;
        default:
            mPaintInactive.setStyle(Style.STROKE);
        }
        mPaintInactive.setColor(inactiveColor);

        // Select the paint type given the type attr
        switch (activeType) {
        case STYLE_STROKE:
            mPaintActive.setStyle(Style.STROKE);
            break;
        default:
            mPaintActive.setStyle(Style.FILL);
        }
        mPaintActive.setColor(activeColor);
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int count = 3;
        if (viewFlow != null) {
            count = viewFlow.getViewsCount();
        }
        
        //this is the amount the first circle should be offset to make the entire thing centered
        float centeringOffset = 0;
        
        int leftPadding = getPaddingLeft();
        
        // Draw stroked circles
        for (int iLoop = 0; iLoop < count; iLoop++) {
            canvas.drawCircle(leftPadding + radius
                    + (iLoop * circleSeparation) + centeringOffset,
                    getPaddingTop() + radius, radius, mPaintInactive);
        }
        float cx = 0;
        if (flowWidth != 0) {
            // Draw the filled circle according to the current scroll
            cx = (currentScroll * circleSeparation) / flowWidth;
        }
        // The flow width has been upadated yet. Draw the default position
        canvas.drawCircle(leftPadding + radius + cx+centeringOffset, getPaddingTop()
                + radius, radius + activeRadius, mPaintActive);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.taptwo.android.widget.ViewFlow.ViewSwitchListener#onSwitched(android
     * .view.View, int)
     */
    @Override
    public void onSwitched(View view, int position) {
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.taptwo.android.widget.FlowIndicator#setViewFlow(org.taptwo.android
     * .widget.ViewFlow)
     */
    @Override
    public void setViewFlow(ViewFlow view) {
        resetTimer();
        viewFlow = view;
        flowWidth = viewFlow.getWidth();
        invalidate();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.taptwo.android.widget.FlowIndicator#onScrolled(int, int, int,
     * int)
     */
    @Override
    public void onScrolled(int h, int v, int oldh, int oldv) {
        setVisibility(View.VISIBLE);
        resetTimer();
        flowWidth = viewFlow.getWidth();
        if(viewFlow.getViewsCount()*flowWidth!=0){
            currentScroll = h%(viewFlow.getViewsCount()*flowWidth);
        }else {
            currentScroll = h;
        }
        invalidate();
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.view.View#onMeasure(int, int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

    /**
     * Determines the width of this view
     * 
     * @param measureSpec
     *            A measureSpec packed into an int
     * @return The width of the view, honoring constraints from measureSpec
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        // We were told how big to be
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }
        // Calculate the width according the views count
        else {
            int count = 3;
            if (viewFlow != null) {
                count = viewFlow.getViewsCount();
            }
            float temp = circleSeparation - 2*radius;
            result = (int) (getPaddingLeft() + getPaddingRight()
                    + (count * 2 * radius) + (count - 1) * temp + 1);
            // Respect AT_MOST value if that was what is called for by
            // measureSpec
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * Determines the height of this view
     * 
     * @param measureSpec
     *            A measureSpec packed into an int
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        // We were told how big to be
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }
        // Measure the height
        else {
            result = (int) (2 * radius + getPaddingTop() + getPaddingBottom() + 1);
            // Respect AT_MOST value if that was what is called for by
            // measureSpec
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * Sets the fill color
     * 
     * @param color
     *            ARGB value for the text
     */
    public void setFillColor(int color) {
        mPaintActive.setColor(color);
        invalidate();
    }

    /**
     * Sets the stroke color
     * 
     * @param color
     *            ARGB value for the text
     */
    public void setStrokeColor(int color) {
        mPaintInactive.setColor(color);
        invalidate();
    }

    /**
     * Resets the fade out timer to 0. Creating a new one if needed
     */
    private void resetTimer() {
        // Only set the timer if we have a timeout of at least 1 millisecond
        if (fadeOutTime > 0) {
            // Check if we need to create a new timer
            if (timer == null || timer._run == false) {
                // Create and start a new timer
                timer = new FadeTimer();
                timer.execute();
            } else {
                // Reset the current tiemr to 0
                timer.resetTimer();
            }
        }
    }

    /**
     * Counts from 0 to the fade out time and animates the view away when
     * reached
     */
    private class FadeTimer extends AsyncTask<Void, Void, Void> {
        // The current count
        private int timer = 0;
        // If we are inside the timing loop
        private boolean _run = true;

        public void resetTimer() {
            timer = 0;
        }

        @Override
        protected Void doInBackground(Void... arg0) {
            while (_run) {
                try {
                    // Wait for a millisecond
                    Thread.sleep(1);
                    // Increment the timer
                    timer++;

                    // Check if we've reached the fade out time
                    if (timer == fadeOutTime) {
                        // Stop running
                        _run = false;
                    }
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            animation = AnimationUtils.loadAnimation(getContext(),
                    android.R.anim.fade_out);
            animation.setAnimationListener(animationListener);
            startAnimation(animation);
        }
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        setVisibility(View.GONE);
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    }

    @Override
    public void onAnimationStart(Animation animation) {
    }
}
View Code
package org.taptwo.android.widget;

import org.taptwo.android.widget.ViewFlow.ViewSwitchListener;

/**
 * An interface which defines the contract between a ViewFlow and a
 * FlowIndicator.<br/>
 * A FlowIndicator is responsible to show an visual indicator on the total views
 * number and the current visible view.<br/>
 * 
 */
public interface FlowIndicator extends ViewSwitchListener {

    /**
     * Set the current ViewFlow. This method is called by the ViewFlow when the
     * FlowIndicator is attached to it.
     * 
     * @param view
     */
    public void setViewFlow(ViewFlow view);

    /**
     * The scroll position has been changed. A FlowIndicator may implement this
     * method to reflect the current position
     * 
     * @param h
     * @param v
     * @param oldh
     * @param oldv
     */
    public void onScrolled(int h, int v, int oldh, int oldv);
}

values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <declare-styleable name="ViewFlow">
        <attr name="sidebuffer" format="integer" />
    </declare-styleable>
    <declare-styleable name="CircleFlowIndicator">
        <attr name="activeColor" format="color" />
        <attr name="inactiveColor" format="color" />
        <attr name="radius" format="dimension" />
        <attr name="centered" format="boolean" />
        <attr name="fadeOut" format="integer" />
        <attr name="inactiveType">
            <flag name="stroke" value="0" />
            <flag name="fill" value="1" />
        </attr>
        <attr name="activeType">
            <flag name="stroke" value="0" />
            <flag name="fill" value="1" />
        </attr>
        <attr name="circleSeparation" format="dimension" />
        <attr name="activeRadius" format="dimension" />
    </declare-styleable>     
</resources>
原文地址:https://www.cnblogs.com/androidsj/p/4710989.html