先来看看
AdapterView<Adapter>的API,对它有个初步的认识
public class IntroduceViewFlow 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);
}
/**
* 构造函数
* @param context
*/
public IntroduceViewFlow(Context context) {
super(context);
mSideBuffer = 3;
init();
}
public IntroduceViewFlow(Context context, int sideBuffer) {
super(context);
mSideBuffer = sideBuffer;
init();
}
public IntroduceViewFlow(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
R.styleable.IntroduceViewFlow);
mSideBuffer = styledAttrs.getInt(R.styleable.IntroduceViewFlow_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(
"IntroduceViewFlow can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY && !isInEditMode()) {
throw new IllegalStateException(
"IntroduceViewFlow 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);
}
public void snapToNext() {
snapToScreen(mCurrentScreen + 1);
}
public void snapToPre() {
snapToScreen(mCurrentScreen - 1);
}
private void snapToScreen(int whichScreen) {
mLastScrollDirection = whichScreen - mCurrentScreen;
// if (!mScroller.isFinished())
// return;
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
final int newX = whichScreen * getWidth();
final int delta = newX - getScrollX();
if (Math.abs(delta) >0) {
mNextScreen = whichScreen;
mScroller.startScroll(getScrollX(), 0, delta, 0, 300);
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
* IntroduceViewFlow} 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;
}
}
xml文件布局
<com.himedia.mg.launcher.hi_introduce.views.IntroduceViewFlow
android:id="@+id/viewflow"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</com.himedia.mg.launcher.hi_introduce.views.IntroduceViewFlow>
IntroduceViewFlow的使用
private IntroduceViewFlow viewFlow;
private LayoutInflater mInflater;
private View controlview1, controlview2, controlview3;
private ArrayList<View> views = new ArrayList<View>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.introduce_hicontrol);
viewFlow = (IntroduceViewFlow) findViewById(R.id.viewflow);
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
controlview1 = mInflater.inflate(R.layout.hicontrol1, null);
controlview2 = mInflater.inflate(R.layout.hicontrol2, null);
controlview3 = mInflater.inflate(R.layout.hicontrol3, null);
views.add(controlview1);
views.add(controlview2);
views.add(controlview3);
ViewAdapter adapter2 = new ViewAdapter(views);
viewFlow.setAdapter(adapter2);
viewFlow.setmSideBuffer(3);
}
适配器
public class ViewAdapter extends BaseAdapter {
private ArrayList<View> mWidgeViews;
public ViewAdapter(ArrayList<View> widgetViews) {
mWidgeViews = widgetViews;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mWidgeViews.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return mWidgeViews.get(position % mWidgeViews.size());
}
}