public class LoopingCirclePageIndicator 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;
private float mExtraSpacing;
public LoopingCirclePageIndicator(Context context) {
this(context, null); }
public LoopingCirclePageIndicator(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.vpiCirclePageIndicatorStyle); }
public LoopingCirclePageIndicator(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(Style.FILL); mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor)); mPaintStroke.setStyle(Style.STROKE); mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor)); mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth)); mPaintFill.setStyle(Style.FILL); mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor)); mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, 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 setExtraSpacing(float extraSpacing) {
mExtraSpacing = extraSpacing; }
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 = getRealCount(); 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 * 3 + mExtraSpacing; final float shortOffset = shortPaddingBefore + mRadius; float longOffset = longPaddingBefore + mRadius; if (mCentered) {
final float circleSize = mRadius * 2; final float width = count * circleSize + ((count - 1) * mExtraSpacing); longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - (width / 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 || getRealCount() == 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 = getRealCount(); 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 = getRealPage(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 = getRealPage(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 = getRealPage(position); mSnapPage = getRealPage(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 = getRealCount(); result = (int) (getPaddingLeft() + getPaddingRight()
+ (count * 2 * mRadius) + (count - 1) * (mRadius + mExtraSpacing) + 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 = getRealPage(savedState.currentPage); mSnapPage = getRealPage(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 CreatorCREATOR = new Creator () {
@Override public SavedState createFromParcel(Parcel in) {
return new SavedState(in); }
@Override public SavedState[] newArray(int size) {
return new SavedState[size]; }
}; }
public int getRealCount() {
if (mViewPager.getAdapter() instanceof LoopingPagerAdapter) {
return ((LoopingPagerAdapter) mViewPager.getAdapter()).getRealCount(); } else {
return mViewPager.getAdapter().getCount(); }
}
public int getRealPage(int page) {
return page % getRealCount(); }
}
------------------------------------------------------------------------------------------------------
xml version="1.0" encoding="utf-8"?>name="ViewPagerIndicator">
name="vpiCirclePageIndicatorStyle" format="reference"/>
name="vpiIconPageIndicatorStyle" format="reference"/>
name="vpiLinePageIndicatorStyle" format="reference"/>
name="vpiTitlePageIndicatorStyle" format="reference"/>
name="vpiTabPageIndicatorStyle" format="reference"/>
name="vpiUnderlinePageIndicatorStyle" format="reference"/>
name="centered" format="boolean" /> name="selectedColor" format="color" /> name="strokeWidth" format="dimension" /> name="unselectedColor" format="color" />
name="CirclePageIndicator">
name="centered" />
name="fillColor" format="color" />
name="pageColor" format="color" />
name="android:orientation"/>
name="radius" format="dimension" />
name="snap" format="boolean" />
name="strokeColor" format="color" />
name="strokeWidth" />
name="android:background"/>
name="LinePageIndicator">
name="centered" />
name="unselectedColor" />
name="selectedColor" />
name="lineWidth" format="dimension" />
name="strokeWidth" />
name="gapWidth" format="dimension" />
name="android:background"/>
name="TitlePageIndicator">
name="clipPadding" format="dimension" />
name="footerColor" format="color" />
name="footerLineHeight" format="dimension" />
name="footerIndicatorStyle"> name="none" value="0" /> name="triangle" value="1" /> name="underline" value="2" />
name="footerIndicatorHeight" format="dimension" />
name="footerIndicatorUnderlinePadding" format="dimension" />
name="footerPadding" format="dimension" />
name="linePosition"> name="bottom" value="0"/> name="top" value="1"/>
name="selectedColor" />
name="selectedBold" format="boolean" />
name="android:textColor" />
name="android:textSize" />
name="titlePadding" format="dimension" />
name="topPadding" format="dimension" />
name="android:background"/>
name="UnderlinePageIndicator">
name="fades" format="boolean" />
name="fadeDelay" format="integer" />
name="fadeLength" format="integer" />
name="selectedColor" />
name="android:background"/>
-------------------------------------------------------------------xml version="1.0" encoding="utf-8"?>
---------------------------------------------------------------------------xml version="1.0" encoding="utf-8"?>
-----------------------------------------------------------------------------------------xml version="1.0" encoding="utf-8"?>
-------------------------------------------------------------------------------------
No comments:
Post a Comment