Android ViewPager圆点指示器

两个values文件

vpi__defaults.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
   <bool name="default_circle_indicator_centered">true</bool>
   <color name="default_circle_indicator_fill_color">#FFFFFFFF</color>
   <color name="default_circle_indicator_page_color">#00000000</color>
   <integer name="default_circle_indicator_orientation">0</integer>
   <dimen name="default_circle_indicator_radius">4dp</dimen>
   <bool name="default_circle_indicator_snap">false</bool>
   <color name="default_circle_indicator_stroke_color">#FFDDDDDD</color>
   <dimen name="default_circle_indicator_stroke_width">1dp</dimen>

   <dimen name="default_line_indicator_line_width">12dp</dimen>
   <dimen name="default_line_indicator_gap_width">4dp</dimen>
   <dimen name="default_line_indicator_stroke_width">1dp</dimen>
   <color name="default_line_indicator_selected_color">#FF33B5E5</color>
   <color name="default_line_indicator_unselected_color">#FFBBBBBB</color>
   <bool name="default_line_indicator_centered">true</bool>

   <dimen name="default_title_indicator_clip_padding">4dp</dimen>
   <color name="default_title_indicator_footer_color">#FF33B5E5</color>
   <dimen name="default_title_indicator_footer_line_height">2dp</dimen>
   <integer name="default_title_indicator_footer_indicator_style">2</integer>
   <dimen name="default_title_indicator_footer_indicator_height">4dp</dimen>
   <dimen name="default_title_indicator_footer_indicator_underline_padding">20dp</dimen>
   <dimen name="default_title_indicator_footer_padding">7dp</dimen>
   <integer name="default_title_indicator_line_position">0</integer>
   <color name="default_title_indicator_selected_color">#FFFFFFFF</color>
   <bool name="default_title_indicator_selected_bold">true</bool>
   <color name="default_title_indicator_text_color">#BBFFFFFF</color>
   <dimen name="default_title_indicator_text_size">15dp</dimen>
   <dimen name="default_title_indicator_title_padding">5dp</dimen>
   <dimen name="default_title_indicator_top_padding">7dp</dimen>

   <bool name="default_underline_indicator_fades">true</bool>
   <integer name="default_underline_indicator_fade_delay">300</integer>
   <integer name="default_underline_indicator_fade_length">400</integer>
   <color name="default_underline_indicator_selected_color">#FF33B5E5</color>
</resources>

attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="border_width" format="dimension"/>
        <attr name="border_color" format="color"/>
        <attr name="border_overlay" format="boolean"/>
    </declare-styleable>

    <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>
    <declare-styleable name="RedTipTextView">
        <attr name="redTipsVisibility">
            <enum name="invisible" value="0"></enum>
            <enum name="visible" value="1"></enum>
            <enum name="gone" value="2"></enum>
        </attr>
    </declare-styleable>
    <declare-styleable name="roundedimageview">
        <attr name="border_thickness" format="dimension"/>
        <attr name="border_inside_color" format="color"/>
        <attr name="border_outside_color" format="color"></attr>
    </declare-styleable>


    <declare-styleable name="ContactItemView">
        <attr name="contactItemImage" format="reference"/>
        <attr name="contactItemName" format="string"/>
    </declare-styleable>
    <declare-styleable name="MLImageView">
        <attr name="press_alpha" format="integer"/>
        <attr name="press_color" format="color"/>
        <attr name="radiuss" format="dimension"/>
        <attr name="border_widthh" format="dimension"/>
        <attr name="border_colorr" format="color"/>
        <attr name="shape_type" format="enum">
            <enum name="round" value="0"/>
            <enum name="rectangle" value="1"/>
        </attr>
    </declare-styleable>
    <declare-styleable name="AmountView">
        <!-- 左右2边+-按钮的宽度 -->
        <attr name="btnWidth" format="dimension"/>
        <!-- 中间TextView的宽度 -->
        <attr name="tvWidth" format="dimension"/>
        <!--<attr name="tvColor" format="color"/>-->
        <attr name="tvTextSize" format="dimension"/>
        <attr name="btnTextSize" format="dimension"/>
    </declare-styleable>

    <declare-styleable name="ViewPagerIndicator">
        <!-- Style of the circle indicator. -->
        <attr name="vpiCirclePageIndicatorStyle" format="reference"/>
        <!-- Style of the icon indicator's views. -->
        <attr name="vpiIconPageIndicatorStyle" format="reference"/>
        <!-- Style of the line indicator. -->
        <attr name="vpiLinePageIndicatorStyle" format="reference"/>
        <!-- Style of the title indicator. -->
        <attr name="vpiTitlePageIndicatorStyle" format="reference"/>
        <!-- Style of the tab indicator's tabs. -->
        <attr name="vpiTabPageIndicatorStyle" format="reference"/>
        <!-- Style of the underline indicator. -->
        <attr name="vpiUnderlinePageIndicatorStyle" format="reference"/>
    </declare-styleable>

    <attr name="selectedColor" format="color"/>
    <attr name="strokeWidth" format="dimension"/>
    <attr name="unselectedColor" format="color"/>

    <declare-styleable name="CirclePageIndicator">
        <!-- Whether or not the indicators should be centered. -->
        <attr name="centered"/>
        <!-- Color of the filled circle that represents the current page. -->
        <attr name="fillColor" format="color"/>
        <!-- Color of the filled circles that represents pages. -->
        <attr name="pageColor" format="color"/>
        <!-- Orientation of the indicator. -->
        <attr name="android:orientation"/>
        <!-- Radius of the circles. This is also the spacing between circles. -->
        <attr name="radius1" format="dimension"/>
        <!-- Whether or not the selected indicator snaps to the circles. -->
        <attr name="snap" format="boolean"/>
        <!-- Color of the open circles. -->
        <attr name="strokeColor" format="color"/>
        <!-- Width of the stroke used to draw the circles. -->
        <attr name="strokeWidth"/>
        <!-- View background -->
        <attr name="android:background"/>
    </declare-styleable>

    <declare-styleable name="LinePageIndicator">
        <!-- Whether or not the indicators should be centered. -->
        <attr name="centered"/>
        <!-- Color of the unselected lines that represent the pages. -->
        <attr name="unselectedColor"/>
        <!-- Color of the selected line that represents the current page. -->
        <attr name="selectedColor"/>
        <!-- Width of each indicator line. -->
        <attr name="lineWidth" format="dimension"/>
        <!-- Width of each indicator line's stroke. -->
        <attr name="strokeWidth"/>
        <!-- Width of the gap between each indicator line. -->
        <attr name="gapWidth" format="dimension"/>
        <!-- View background -->
        <attr name="android:background"/>
    </declare-styleable>

    <declare-styleable name="TitlePageIndicator">
        <!-- Screen edge padding. -->
        <attr name="clipPadding" format="dimension"/>
        <!-- Color of the footer line and indicator. -->
        <attr name="footerColor" format="color"/>
        <!-- Height of the footer line. -->
        <attr name="footerLineHeight" format="dimension"/>
        <!-- Style of the indicator. Default is triangle. -->
        <attr name="footerIndicatorStyle">
            <enum name="none" value="0"/>
            <enum name="triangle" value="1"/>
            <enum name="underline" value="2"/>
        </attr>
        <!-- Height of the indicator above the footer line. -->
        <attr name="footerIndicatorHeight" format="dimension"/>
        <!-- Left and right padding of the underline indicator. -->
        <attr name="footerIndicatorUnderlinePadding" format="dimension"/>
        <!-- Padding between the bottom of the title and the footer. -->
        <attr name="footerPadding" format="dimension"/>
        <!-- Position of the line. -->
        <attr name="linePosition">
            <enum name="bottom" value="0"/>
            <enum name="top" value="1"/>
        </attr>
        <!-- Color of the selected title. -->
        <attr name="selectedColor"/>
        <!-- Whether or not the selected item is displayed as bold. -->
        <attr name="selectedBold" format="boolean"/>
        <!-- Color of regular titles. -->
        <attr name="android:textColor"/>
        <!-- Size of title text. -->
        <attr name="android:textSize"/>
        <!-- Padding between titles when bumping into each other. -->
        <attr name="titlePadding" format="dimension"/>
        <!-- Padding between titles and the top of the View. -->
        <attr name="topPadding" format="dimension"/>
        <!-- View background -->
        <attr name="android:background"/>
    </declare-styleable>

    <declare-styleable name="UnderlinePageIndicator">
        <!-- Whether or not the selected indicator fades. -->
        <attr name="fades" format="boolean"/>
        <!-- Length of the delay to fade the indicator. -->
        <attr name="fadeDelay" format="integer"/>
        <!-- Length of the indicator fade to transparent. -->
        <attr name="fadeLength" format="integer"/>
        <!-- Color of the selected line that represents the current page. -->
        <attr name="selectedColor"/>
        <!-- View background -->
        <attr name="android:background"/>
    </declare-styleable>
    <!-- Defines the custom XML attributes supported for a HorizontalListView -->
    <declare-styleable name="HorizontalListView">
        <attr name="dividerWidth" format="dimension" />
        <attr name="android:divider" />
        <attr name="android:requiresFadingEdge" />
        <attr name="android:fadingEdgeLength" />
    </declare-styleable>
<!--//垂直滚动字幕-->
    <declare-styleable name="VerticalRollingTextView">
        <!--文字颜色-->
        <attr name="android:textColor"/>
        <!--文字大小-->
        <attr name="android:textSize"/>
        <!--滚动动画时长-->
        <attr name="android:duration"/>
        <!--两次动画之间的间隔-->
        <attr name="animInterval" format="integer"/>
    </declare-styleable>
    <declare-styleable name="SwipeBackLayout">
        <attr name="edge_size" format="dimension"/>
        <attr name="edge_flag">
            <enum name="left" value="0" />
            <enum name="right" value="1" />
            <enum name="bottom" value="2" />
            <enum name="all" value="3" />
        </attr>
        <attr name="shadow_left" format="reference"/>
        <attr name="shadow_right" format="reference"/>
        <attr name="shadow_bottom" format="reference"/>
    </declare-styleable>
    <declare-styleable name="MarqueeViewStyle">
        <attr name="mvInterval" format="integer|reference"/>
        <attr name="mvAnimDuration" format="integer|reference"/>
        <attr name="mvTextSize" format="dimension|reference"/>
        <attr name="mvTextColor" format="color|reference"/>
        <attr name="mvSingleLine" format="boolean"/>
        <attr name="mvGravity">
            <enum name="left" value="0"/>
            <enum name="center" value="1"/>
            <enum name="right" value="2"/>
        </attr>
        <attr name="mvDirection">
            <enum name="bottom_to_top" value="0"/>
            <enum name="top_to_bottom" value="1"/>
            <enum name="right_to_left" value="2"/>
            <enum name="left_to_right" value="3"/>
        </attr>
    </declare-styleable>
    <attr name="SwipeBackLayoutStyle" format="reference"/>
    <declare-styleable name="TwoWayAbsListView">
        <!-- Drawable used to indicate the currently selected item in the list. -->
        <attr name="listSelector" format="color|reference" />
        <!-- When set to true, the selector will be drawn over the selected item.
             Otherwise the selector is drawn behind the selected item. The default
             value is false. -->
        <attr name="drawSelectorOnTop" format="boolean" />
        <!-- Used by ListView and GridView to stack their content from the bottom. -->
        <attr name="stackFromBottom" format="boolean" />
        <!-- When set to true, the list uses a drawing cache during scrolling.
             This makes the rendering faster but uses more memory. The default
             value is true. -->
        <attr name="scrollingCache" format="boolean" />
        <!-- When set to true, the list will filter results as the user types. The
             List's adapter must support the Filterable interface for this to work. -->
        <!-- <attr name="textFilterEnabled" format="boolean" /> -->
        <!-- Sets the transcript mode for the list. In transcript mode, the list
             scrolls to the bottom to make new items visible when they are added. -->
        <attr name="transcriptMode">
            <!-- Disables transcript mode. This is the default value. -->
            <enum name="disabled" value="0"/>
            <!-- The list will automatically scroll to the bottom when
                 a data set change notification is received and only if the last item is
                 already visible on screen. -->
            <enum name="normal" value="1" />
            <!-- The list will automatically scroll to the bottom, no matter what items
                 are currently visible. -->
            <enum name="alwaysScroll" value="2" />
        </attr>
        <!-- Indicates that this list will always be drawn on top of solid, single-color
             opaque background. This allows the list to optimize drawing. -->
        <attr name="cacheColorHint" format="color" />
        <!-- Enables the fast scroll thumb that can be dragged to quickly scroll through
             the list. -->
        <!-- <attr name="fastScrollEnabled" format="boolean" /> -->
        <!-- When set to true, the list will use a more refined calculation
             method based on the pixels height of the items visible on screen. This
             property is set to true by default but should be set to false if your adapter
             will display items of varying heights. When this property is set to true and
             your adapter displays items of varying heights, the scrollbar thumb will
             change size as the user scrolls through the list. When set to fale, the list
             will use only the number of items in the adapter and the number of items visible
             on screen to determine the scrollbar's properties. -->
        <attr name="smoothScrollbar" format="boolean" />

        <!-- Use this attribute to control which direction the GridView scrolls when in
             portrait orientation -->
        <attr name="scrollDirectionPortrait">
            <!-- Scroll up vertically. This is the default value. -->
            <enum name="vertical" value="0"/>
            <!-- Scroll horizontally. -->
            <enum name="horizontal" value="1" />
        </attr>
        <attr name="scrollDirectionLandscape">
            <!-- Scroll up vertically. This is the default value. -->
            <enum name="vertical" value="0"/>
            <!-- Scroll horizontally. -->
            <enum name="horizontal" value="1" />
        </attr>
    </declare-styleable>

    <declare-styleable name="TwoWayGridView">
        <attr name="horizontalSpacing" format="dimension" />
        <attr name="verticalSpacing" format="dimension" />
        <attr name="stretchMode">
            <enum name="none" value="0"/>
            <enum name="spacingWidth" value="1" />
            <enum name="columnWidth" value="2" />
            <enum name="spacingWidthUniform" value="3" />
        </attr>
        <attr name="columnWidth" format="dimension" />
        <attr name="rowHeight" format="dimension" />
        <attr name="numColumns" format="integer" min="0">
            <enum name="auto_fit" value="-1" />
        </attr>
        <attr name="numRows" format="integer" min="0">
            <enum name="auto_fit" value="-1" />
        </attr>
        <attr name="gravity" />
    </declare-styleable>
    <declare-styleable name="FillableLoader">
        <attr name="fl_fillColor"
            format="color"/>
        <attr name="fl_strokeColor"
            format="color"/>
        <attr name="fl_strokeWidth"
            format="dimension"/>
        <attr name="fl_originalWidth"
            format="integer"/>
        <attr name="fl_originalHeight"
            format="integer"/>
        <attr name="fl_strokeDrawingDuration"
            format="integer"/>
        <attr name="fl_fillDuration"
            format="integer"/>
        <attr name="fl_fillPercentage"
            format="integer"/>
        <attr name="fl_clippingTransform"
            format="enum">
            <enum name="plain"
                value="0"/>
            <enum name="spikes"
                value="1"/>
            <enum name="rounded"
                value="2"/>
            <enum name="waves"
                value="3"/>
            <enum name="squares"
                value="4"/>
            <enum name="bites"
                value="5"/>
        </attr>
    </declare-styleable>

    // X5Webview 是否支持默认进度条
    <declare-styleable name="X5WebView">
        <attr name="defaultProgress" format="boolean" />
    </declare-styleable>
</resources>

接口类

/**
 * A PageIndicator is responsible to show an visual indicator on the total views
 * number and the current visible view.
 */
public interface PageIndicator extends ViewPager.OnPageChangeListener {
    /**
     * Bind the indicator to a ViewPager.
     *
     * @param view
     */
    void setViewPager(ViewPager view);

    /**
     * Bind the indicator to a ViewPager.
     *
     * @param view
     * @param initialPosition
     */
    void setViewPager(ViewPager view, int initialPosition);

    /**
     * <p>Set the current page of both the ViewPager and indicator.</p>
     * <p/>
     * <p>This <strong>must</strong> be used if you need to set the page before
     * the views are drawn on screen (e.g., default start page).</p>
     *
     * @param item
     */
    void setCurrentItem(int item);

    /**
     * Set a page change listener which will receive forwarded events.
     *
     * @param listener
     */
    void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);

    /**
     * Notify the indicator that the fragment list has changed.
     */
    void notifyDataSetChanged();
}

自定义圆点类

/**
 * Draws circles (one for each view). The current view position is filled and
 * others are only stroked.
 */
public class CirclePageIndicator extends View implements PageIndicator {
    private static final int INVALID_POINTER = -1;

    private float mRadius;
    private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
    private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
    private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mListener;
    private int mCurrentPage;
    private int mSnapPage;
    private float mPageOffset;
    private int mScrollState;
    private int mOrientation;
    private boolean mCentered;
    private boolean mSnap;

    private int mTouchSlop;
    private float mLastMotionX = -1;
    private int mActivePointerId = INVALID_POINTER;
    private boolean mIsDragging;


    public CirclePageIndicator(Context context) {
        this(context, null);
    }

    public CirclePageIndicator(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.vpiCirclePageIndicatorStyle);
    }

    public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (isInEditMode()) return;

        //Load defaults from resources
        final Resources res = getResources();
        final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
        final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
        final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation);
        final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color);
        final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width);
        final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius);
        final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered);
        final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap);

        //Retrieve styles attributes
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);

        mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
        mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
        mPaintPageFill.setStyle(Paint.Style.FILL);
        mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
        mPaintStroke.setStyle(Paint.Style.STROKE);
        mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor));
        mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth));
        mPaintFill.setStyle(Paint.Style.FILL);
        mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor));
        mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius1, defaultRadius);
        mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap);

        Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background);
        if (background != null) {
            setBackgroundDrawable(background);
        }

        a.recycle();

        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    }


    public void setCentered(boolean centered) {
        mCentered = centered;
        invalidate();
    }

    public boolean isCentered() {
        return mCentered;
    }

    public void setPageColor(int pageColor) {
        mPaintPageFill.setColor(pageColor);
        invalidate();
    }

    public int getPageColor() {
        return mPaintPageFill.getColor();
    }

    public void setFillColor(int fillColor) {
        mPaintFill.setColor(fillColor);
        invalidate();
    }

    public int getFillColor() {
        return mPaintFill.getColor();
    }

    public void setOrientation(int orientation) {
        switch (orientation) {
            case HORIZONTAL:
            case VERTICAL:
                mOrientation = orientation;
                requestLayout();
                break;

            default:
                throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
        }
    }

    public int getOrientation() {
        return mOrientation;
    }

    public void setStrokeColor(int strokeColor) {
        mPaintStroke.setColor(strokeColor);
        invalidate();
    }

    public int getStrokeColor() {
        return mPaintStroke.getColor();
    }

    public void setStrokeWidth(float strokeWidth) {
        mPaintStroke.setStrokeWidth(strokeWidth);
        invalidate();
    }

    public float getStrokeWidth() {
        return mPaintStroke.getStrokeWidth();
    }

    public void setRadius(float radius) {
        mRadius = radius;
        invalidate();
    }

    public float getRadius() {
        return mRadius;
    }

    public void setSnap(boolean snap) {
        mSnap = snap;
        invalidate();
    }

    public boolean isSnap() {
        return mSnap;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mViewPager == null) {
            return;
        }
        final int count = mViewPager.getAdapter().getCount();
        if (count == 0) {
            return;
        }

        if (mCurrentPage >= count) {
            setCurrentItem(count - 1);
            return;
        }

        int longSize;
        int longPaddingBefore;
        int longPaddingAfter;
        int shortPaddingBefore;
        if (mOrientation == HORIZONTAL) {
            longSize = getWidth();
            longPaddingBefore = getPaddingLeft();
            longPaddingAfter = getPaddingRight();
            shortPaddingBefore = getPaddingTop();
        } else {
            longSize = getHeight();
            longPaddingBefore = getPaddingTop();
            longPaddingAfter = getPaddingBottom();
            shortPaddingBefore = getPaddingLeft();
        }

        final float threeRadius = mRadius * 4;
        final float shortOffset = shortPaddingBefore + mRadius;
        float longOffset = longPaddingBefore + mRadius;
        if (mCentered) {
            longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
        }

        float dX;
        float dY;

        float pageFillRadius = mRadius;
        if (mPaintStroke.getStrokeWidth() > 0) {
            pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
        }

        //Draw stroked circles
        for (int iLoop = 0; iLoop < count; iLoop++) {
            float drawLong = longOffset + (iLoop * threeRadius);
            if (mOrientation == HORIZONTAL) {
                dX = drawLong;
                dY = shortOffset;
            } else {
                dX = shortOffset;
                dY = drawLong;
            }
            // Only paint fill if not completely transparent
            if (mPaintPageFill.getAlpha() > 0) {
                canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
            }

            // Only paint stroke if a stroke width was non-zero
            if (pageFillRadius != mRadius) {
                canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
            }
        }

        //Draw the filled circle according to the current scroll
        float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
        if (!mSnap) {
            cx += mPageOffset * threeRadius;
        }
        if (mOrientation == HORIZONTAL) {
            dX = longOffset + cx;
            dY = shortOffset;
        } else {
            dX = shortOffset;
            dY = longOffset + cx;
        }
        canvas.drawCircle(dX, dY, mRadius, mPaintFill);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        if (super.onTouchEvent(ev)) {
            return true;
        }
        if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
            return false;
        }

        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mLastMotionX = ev.getX();
                break;

            case MotionEvent.ACTION_MOVE: {
                final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                final float x = MotionEventCompat.getX(ev, activePointerIndex);
                final float deltaX = x - mLastMotionX;

                if (!mIsDragging) {
                    if (Math.abs(deltaX) > mTouchSlop) {
                        mIsDragging = true;
                    }
                }

                if (mIsDragging) {
                    mLastMotionX = x;
                    if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
                        mViewPager.fakeDragBy(deltaX);
                    }
                }

                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (!mIsDragging) {
                    final int count = mViewPager.getAdapter().getCount();
                    final int width = getWidth();
                    final float halfWidth = width / 2f;
                    final float sixthWidth = width / 6f;

                    if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
                        if (action != MotionEvent.ACTION_CANCEL) {
                            mViewPager.setCurrentItem(mCurrentPage - 1);
                        }
                        return true;
                    } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
                        if (action != MotionEvent.ACTION_CANCEL) {
                            mViewPager.setCurrentItem(mCurrentPage + 1);
                        }
                        return true;
                    }
                }

                mIsDragging = false;
                mActivePointerId = INVALID_POINTER;
                if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
                break;

            case MotionEventCompat.ACTION_POINTER_DOWN: {
                final int index = MotionEventCompat.getActionIndex(ev);
                mLastMotionX = MotionEventCompat.getX(ev, index);
                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                final int pointerIndex = MotionEventCompat.getActionIndex(ev);
                final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
                if (pointerId == mActivePointerId) {
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
                }
                mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
                break;
        }

        return true;
    }

    @Override
    public void setViewPager(ViewPager view) {
        if (mViewPager == view) {
            return;
        }
        if (mViewPager != null) {
            mViewPager.setOnPageChangeListener(null);
        }
        if (view.getAdapter() == null) {
            throw new IllegalStateException("ViewPager does not have adapter instance.");
        }
        mViewPager = view;
        mViewPager.setOnPageChangeListener(this);
        invalidate();
    }

    @Override
    public void setViewPager(ViewPager view, int initialPosition) {
        setViewPager(view);
        setCurrentItem(initialPosition);
    }

    @Override
    public void setCurrentItem(int item) {
        if (mViewPager == null) {
            throw new IllegalStateException("ViewPager has not been bound.");
        }
        mViewPager.setCurrentItem(item);
        mCurrentPage = item;
        invalidate();
    }

    @Override
    public void notifyDataSetChanged() {
        invalidate();
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        mScrollState = state;

        if (mListener != null) {
            mListener.onPageScrollStateChanged(state);
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        mCurrentPage = position;
        mPageOffset = positionOffset;
        invalidate();

        if (mListener != null) {
            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
        }
    }

    @Override
    public void onPageSelected(int position) {
        if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
            mCurrentPage = position;
            mSnapPage = position;
            invalidate();
        }

        if (mListener != null) {
            mListener.onPageSelected(position);
        }
    }

    @Override
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        mListener = listener;
    }

    /*
     * (non-Javadoc)
     *
     * @see android.view.View#onMeasure(int, int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == HORIZONTAL) {
            setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
        } else {
            setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
        }
    }

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

        if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
            //We were told how big to be
            result = specSize;
        } else {
            //Calculate the width according the views count
            final int count = mViewPager.getAdapter().getCount();
            result = (int) (getPaddingLeft() + getPaddingRight()
                    + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
            //Respect AT_MOST value if that was what is called for by measureSpec
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

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

        if (specMode == MeasureSpec.EXACTLY) {
            //We were told how big to be
            result = specSize;
        } else {
            //Measure the height
            result = (int) (2 * mRadius + 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;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        mCurrentPage = savedState.currentPage;
        mSnapPage = savedState.currentPage;
        requestLayout();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState savedState = new SavedState(superState);
        savedState.currentPage = mCurrentPage;
        return savedState;
    }

    static class SavedState extends BaseSavedState {
        int currentPage;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            currentPage = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(currentPage);
        }

        @SuppressWarnings("UnusedDeclaration")
        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}

layout中调用,与ViewPager合用

<android.support.v4.view.ViewPager
    android:id="@+id/activity_viewpager"
    android:layout_width="match_parent"
    android:layout_height="440dp"
    android:layout_marginLeft="12dp"
    android:layout_marginRight="12dp"
    android:layout_marginTop="2dp">
</android.support.v4.view.ViewPager>
<com.yuanxin.clan.core.adapter.indicator.CirclePageIndicator
    android:id="@+id/indicator"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layout_marginTop="2dp"
    android:padding="3dp"
    android:visibility="gone"
    app:fillColor="#00acff"
    app:pageColor="#32000000"
    app:strokeWidth="0dp" />

Java代码

@BindView(R.id.activity_viewpager)
ViewPager activity_viewpager;
@BindView(R.id.indicator)
CirclePageIndicator indicator;
//圆点指示器
indicator.setVisibility(View.VISIBLE);
indicator.setViewPager(activity_viewpager);

猜你喜欢

转载自blog.csdn.net/meixi_android/article/details/81112109