自定义的广告轮播 --(三)(无限循环)

本文介绍了一种自定义ViewPager的实现方式,并结合一个自定义的指示器CircleFlowIndicator,实现了带圆点指示器的水平滑动效果。通过定义ViewFlow类作为水平滑动容器,CircleFlowIndicator类作为指示器,文章详细讲解了组件的自定义过程,包括属性配置、绘制逻辑、事件处理等关键步骤。

先做一些准备的工作

  • 自定义的圆点
package widget;


/**
 * 一个接口定义ViewFlow和之间的合同FlowIndicator
 * FlowIndicator负责节目的视觉指示器的总观点
 * 数量和当前视图可见。
 */

public interface FlowIndicator extends ViewFlow.ViewSwitchListener {

    /**
     * 设置当前ViewFlow。由ViewFlow当调用此方法
     * FlowIndicator相连。
     */
    public void setViewFlow(ViewFlow view);

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


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 com.example.administrator.viewpagercarousel.R;

/**
 * 一个FlowIndicator画圆圈(一个为每个视图)。
 * activeColor:定义用于画活动圆的颜色(默认为白色)
 * inactiveColor:定义用于画不活跃的圆圈的颜色(默认为0 x44ffffff)
 * inactiveType:定义如何画不活跃的圈子,中风或填补中风(默认)
 * activeType:定义如何画活动圈,中风或填补填补(默认)
 * 渐隐:定义时间(ms),直到指示器将淡出(默认为0 =从未淡出)
 * 半径:定义圆半径(默认为4.0)
 */
public class CircleFlowIndicator extends View implements FlowIndicator,
        Animation.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 Animation.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);
        // Retrieve styles attributs  检索风格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  选择油漆类型类型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   选择油漆类型类型attr
        switch (activeType) {
            case STYLE_STROKE:
                mPaintActive.setStyle(Style.STROKE);
                break;
            default:
                mPaintActive.setStyle(Style.FILL);
        }
        mPaintActive.setColor(activeColor);
    }


    @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  流宽度已经upadated呢。把默认的位置
        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));
    }

    /**
     * 决定这个视图的宽度
     *
     * @param measureSpec measureSpec挤进一个int
     * @return视图的宽度,纪念从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);
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * 的高度决定了这一观点
     *
     * @param measureSpec measureSpec挤进一个int
     * @return视图的高度,从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) {
    }
}
  • 自定义的水平滚动(相当于Viewpager)

做准备工作:

再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>
package widget;

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;

import com.example.administrator.viewpagercarousel.R;

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

/**
 * 相当于Viewpager
 * 水平滚动{ @link ViewGroup }与物品从一个填充
 * { @link适配器}。ViewFlow使用缓冲区来存储加载{ @link视图}年代。
 * 缓冲区的默认大小是3目前两岸的元素
 * } { @link视图可见,占总缓冲区大小3 * 2 + 1 = 7。的
 * 缓冲区大小可以改变使用{ @code sidebuffer } xml属性。
 */


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

    /**
     * 重新计算宽、高
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @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;
            }
        }
    }

    /**
     * 事件分发
     * @param ev
     * @return
     */
    @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;
    }

    /**
     * 触摸的监听
     * @param ev
     * @return
     */
    @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 (!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;
    }

    /**
     * 滚动改变
     * @param h
     * @param v
     * @param oldh
     * @param oldv
     */
    @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;
    }

    /**
     * 适配器
     * @param adapter
     */
    @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布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.administrator.viewpagercarousel.MainActivity">

    <widget.ViewFlow
        android:id="@+id/viewflow"
        android:layout_width="fill_parent"
        android:layout_height="170dip"
        />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/viewflow"
        android:layout_gravity="bottom"
        android:background="@color/back"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="5dip">


        <widget.CircleFlowIndicator
            android:id="@+id/viewflowindic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginTop="3dp"
            android:padding="2dip"
            app:activeColor="#222"
            app:activeType="fill"
            app:circleSeparation="20dip"
            app:inactiveColor="#ffffff"
            app:inactiveType="fill"
            app:radius="4dip"
            />
    </LinearLayout>

</RelativeLayout>
  • 主程序代码

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

import java.util.ArrayList;
import java.util.List;

import adapter.FlowAdapter;
import bean.Ad;
import widget.CircleFlowIndicator;
import widget.ViewFlow;

public class MainActivity extends Activity {
    private ViewFlow viewFlow;
    private List<Ad> adlist;
    private CircleFlowIndicator indic;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViewpager();
    }

    private void initViewpager() {
        //找控件
        viewFlow = (ViewFlow) findViewById(R.id.viewflow);
        indic = (CircleFlowIndicator) findViewById(R.id.viewflowindic);

        //初始化数据
        adlist = new ArrayList<Ad>();
        adlist.add(new Ad(R.mipmap.b));
        adlist.add(new Ad(R.mipmap.c));
        adlist.add(new Ad(R.mipmap.d));
        adlist.add(new Ad(R.mipmap.e));

        //为viewFlow送数据
        viewFlow.setAdapter(new FlowAdapter(this, adlist));
        viewFlow.setmSideBuffer(adlist.size()); // 实际图片张数
        viewFlow.setFlowIndicator(indic); //与圆点联动
        viewFlow.setTimeSpan(4000); //自动轮播的时间
        viewFlow.setSelection(3 * 1000);    //设置初始位置
        viewFlow.startAutoFlowTimer();  //启动自动播放
    }
}


  • viewFlow所需要的适配器
package adapter;

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.widget.BaseAdapter;
import android.widget.ImageView;

import com.example.administrator.viewpagercarousel.R;
import com.example.administrator.viewpagercarousel.StepActivity;

import java.util.List;

import bean.Ad;

/**
 * viewFlow的适配器
 */
public class FlowAdapter extends BaseAdapter {

    private final LayoutInflater mInflater;
    private Context mContext;
    private List<Ad> mAdList;

    public FlowAdapter(Context context, List<Ad> adList) {
        mContext = context;
        mAdList = adList;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    }

    @Override
    public int getCount() {
        return Integer.MAX_VALUE; // 最大,无限循环
    }

    @Override
    public Object getItem(int position) {
        return mAdList.get(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.item_image, null);
        }
        ((ImageView) convertView.findViewById(R.id.image)).setImageResource(mAdList.get(position % mAdList.size()).getImages()); //设置显示的图片
        //给整张图片加监听
        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //跳转页面
                Intent intent = new Intent(mContext, StepActivity.class);
                Bundle bundle = new Bundle();
                bundle.putInt("image_id", mAdList.get(position % mAdList.size()).getImages());
                intent.putExtras(bundle);
                mContext.startActivity(intent);
            }
        });
        return convertView;
    }
}
  • 适配器需要的item布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@mipmap/b"
        />

</LinearLayout>

完成了整体的效果,有需要代码的我已经上传了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值