仿淘宝上面的ViewFlow的循环动画切换的效果图

时间:2021-07-06 17:58:33

                         ViewFlow是一个Android UI部件提供水平滚的viewGroup从一个适配器填充的项,

在布局中的用法如下:


<org.taptwo.android.widget.ViewFlow
android:id="@+id/viewflow"
app:sidebuffer="5"
/>

在Acitivity中的用法如下:

ViewFlow viewFlow = (ViewFlow) findViewById(R.id.viewflow);
viewFlow.setAdapter(myAdapter);


设置不同位置的初始位置(0为默认的值)
viewFlow.setAdapter(myAdapter, 8);

设置屏幕上改变的监听事件


viewFlow.setOnViewSwitchListener(new ViewSwitchListener() {
public void onSwitched(View v, int position) {
// Your code here
}
});


设置初始化视图的监听事件

viewFlow.setOnViewLazyInitializeListener(new ViewLazyInitializeListener() {
public void onViewLazyInitialize(View view, int position) {
// Your code here e.g.
((MyAdapter)((AbsListView)view).getAdapter()).initializeData();
}
});

Circle Flow Indicator

的一般布局如下:
<org.taptwo.android.widget.CircleFlowIndicator
android:padding="10dip" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:id="@+id/viewflowindic"
android:background="#00000000"/>


接着,你需要您的 ViewFlow 连接的 FlowIndicator

CircleFlowIndicator indic = (CircleFlowIndicator) findViewById(R.id.viewflowindic);viewFlow.setFlowIndicator(indic);
然后呢,看一个小Demo

首先是布局:

viewflowdemo.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.nyist.activity"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<FrameLayout
android:id="@+id/framelayout"
android:layout_width="fill_parent"
android:layout_height="150dip"
android:background="#ffffff" >

<com.nyist.viewflowtest.ViewFlow
android:id="@+id/viewflow"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</com.nyist.viewflowtest.ViewFlow>

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#88252525"
android:gravity="center"
android:padding="3dip" >

<com.nyist.viewflowtest.CircleFlowIndicator
android:id="@+id/viewflowindic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:padding="2dip"
app:activeColor="#ff0000"
app:activeType="fill"
app:circleSeparation="20dip"
app:inactiveColor="#ffffff"
app:inactiveType="fill"
app:radius="4dip" />
</LinearLayout>
</FrameLayout>

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />

</LinearLayout>


image_item.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

<ImageView
android:id="@+id/imgView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:contentDescription="@string/app_name"
android:scaleType="fitXY"
android:src="@drawable/t1" >

</ImageView>
</LinearLayout>


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>

然后是实现的活动,和一些类

ViewFlow

package com.nyist.viewflowtest;



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

import com.nyist.activity.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;


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

/**
* 当一个新的接收调用支持{ @link视图
*/
public static interface ViewSwitchListener {

/**
*
*
* @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;
}
}

ImageAdapter

package com.nyist.viewflowtest;


import com.nyist.activity.R;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.Toast;

public class ImageAdapter extends BaseAdapter {

private Context mContext;
private LayoutInflater mInflater;
private static final int[] ids = {R.drawable.t1, R.drawable.t2, R.drawable.t3 };

public ImageAdapter(Context context) {
mContext = context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public int getCount() {
//返回很大的值使得getView中的position不断增大来实现循环
return Integer.MAX_VALUE;
}

@Override
public Object getItem(int position) {
return position;
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.image_item, null);
}
((ImageView) convertView.findViewById(R.id.imgView)).setImageResource(ids[position%ids.length]);
convertView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

//在这里可以设置跳转界面
//Intent intent = new Intent(mContext,DetailActivity.class);
//Bundle bundle = new Bundle();
//bundle.putInt("image_id", ids[position%ids.length]);
//intent.putExtras(bundle);
//mContext.startActivity(intent);
}
});
return convertView;
}

}

FlowIndicator

package com.nyist.viewflowtest;

import com.nyist.viewflowtest.ViewFlow.ViewSwitchListener;
//定义一个接口,FlowIndicator负责显示一个视觉指示器的总数量和当前视图可见视图。
public interface FlowIndicator extends ViewSwitchListener {

/*
* 设置当前ViewFlow。这个方法被调用的ViewFlow当FlowIndicator附属于它。
*/
public void setViewFlow(ViewFlow view);

/**
*
*滚动位置已经被改变了。一个FlowIndicator可能实现这个方法,以反映当前的位置
*/
public void onScrolled(int h, int v, int oldh, int oldv);
}

CircleFlowIndicator


package com.nyist.viewflowtest;

import com.nyist.activity.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;

public CircleFlowIndicator(Context context) {
super(context);
initColors(0xFFFFFFFF, 0xFFFFFFFF, STYLE_FILL, STYLE_STROKE);
}

public CircleFlowIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
// 检索风格
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CircleFlowIndicator);

// 变得不活跃的圆式,默认为“填补”
int activeType = a.getInt(R.styleable.CircleFlowIndicator_activeType,
STYLE_FILL);

int activeDefaultColor = 0xFFFFFFFF;

// 得到一个自定义颜色
int activeColor = a
.getColor(R.styleable.CircleFlowIndicator_activeColor,
activeDefaultColor);

// 变得不活跃的圆式
int inactiveType = a.getInt(
R.styleable.CircleFlowIndicator_inactiveType, STYLE_STROKE);

int inactiveDefaultColor = 0x44FFFFFF;
int inactiveColor = a.getColor(
R.styleable.CircleFlowIndicator_inactiveColor,
inactiveDefaultColor);

//检索半径
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);
// 检索淡出时间
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) {
// 选择涂料类型给定类型attr
switch (inactiveType) {
case STYLE_FILL:
mPaintInactive.setStyle(Style.FILL);
break;
default:
mPaintInactive.setStyle(Style.STROKE);
}
mPaintInactive.setColor(inactiveColor);

//
switch (activeType) {
case STYLE_STROKE:
mPaintActive.setStyle(Style.STROKE);
break;
default:
mPaintActive.setStyle(Style.FILL);
}
mPaintActive.setColor(activeColor);
}

protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int count = 3;
if (viewFlow != null) {
count = viewFlow.getViewsCount();
}

// 第一圈数量应该抵消,使得整个事情集中
float centeringOffset = 0;

int leftPadding = getPaddingLeft();

// 画圆
for (int iLoop = 0; iLoop < count; iLoop++) {
canvas.drawCircle(leftPadding + radius + (iLoop * circleSeparation)
+ centeringOffset, getPaddingTop() + radius, radius,
mPaintInactive);
}
float cx = 0;
if (flowWidth != 0) {
// 画的实心圆根据当前的滚动的值
cx = (currentScroll * circleSeparation) / flowWidth;
}
// 流宽度已经更新
canvas.drawCircle(leftPadding + radius + cx + centeringOffset,
getPaddingTop() + radius, radius + activeRadius, mPaintActive);
}

@Override
public void onSwitched(View view, int position) {
}

@Override
public void setViewFlow(ViewFlow view) {
resetTimer();
viewFlow = view;
flowWidth = viewFlow.getWidth();
invalidate();
}

@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();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}

private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

// 设置尺寸
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
// 计算宽度根据视图计数
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);
//
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}

/**
*
* 确定这一视图的高度
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
}
// 确定这一视图的高度
else {
result = (int) (2 * radius + getPaddingTop() + getPaddingBottom() + 1);
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}

public void setFillColor(int color) {
mPaintActive.setColor(color);
invalidate();
}

public void setStrokeColor(int color) {
mPaintInactive.setColor(color);
invalidate();
}

/**
* 重置为0的淡出计时器
*/
private void resetTimer() {
//只有设置定时器,如果我们有一个超时至少1毫秒
if (fadeOutTime > 0) {
// 检查是否我们需要创建一个新的计时器
if (timer == null || timer._run == false) {
timer = new FadeTimer();
timer.execute();
} else {
timer.resetTimer();
}
}
}

private class FadeTimer extends AsyncTask<Void, Void, Void> {
private int timer = 0;
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);
timer++;

if (timer == fadeOutTime) {
_run = false;
}
} catch (InterruptedException e) {
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) {
}
}


ViewFlowDemoActivity


package com.nyist.activity;

import com.nyist.viewflowtest.CircleFlowIndicator;
import com.nyist.viewflowtest.ImageAdapter;
import com.nyist.viewflowtest.ViewFlow;

import android.app.Activity;
import android.os.Bundle;

public class ViewFlowDemoActivity extends Activity {
private ViewFlow viewFlow;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewflowdemo);
circleimage();
}

void circleimage() {

viewFlow = (ViewFlow) findViewById(R.id.viewflow);
viewFlow.setAdapter(new ImageAdapter(this));
viewFlow.setmSideBuffer(3); // 实际图片张数, 我的ImageAdapter实际图片张数为3
CircleFlowIndicator indic = (CircleFlowIndicator) findViewById(R.id.viewflowindic);
viewFlow.setFlowIndicator(indic);
viewFlow.setTimeSpan(4500);
viewFlow.setSelection(3 * 1000); // 设置初始位置
viewFlow.startAutoFlowTimer(); // 启动自动播放

}
}

最后,效果如图:

仿淘宝上面的ViewFlow的循环动画切换的效果图仿淘宝上面的ViewFlow的循环动画切换的效果图仿淘宝上面的ViewFlow的循环动画切换的效果图