diff options
Diffstat (limited to 'src/com/viewpagerindicator/LinePageIndicator.java')
-rw-r--r-- | src/com/viewpagerindicator/LinePageIndicator.java | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/src/com/viewpagerindicator/LinePageIndicator.java b/src/com/viewpagerindicator/LinePageIndicator.java new file mode 100644 index 0000000..a009cbf --- /dev/null +++ b/src/com/viewpagerindicator/LinePageIndicator.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2012 Jake Wharton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.viewpagerindicator; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewConfigurationCompat; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.FloatMath; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +/** + * Draws a line for each page. The current page line is colored differently + * than the unselected page lines. + */ +public class LinePageIndicator extends View implements PageIndicator { + private static final int INVALID_POINTER = -1; + + private final Paint mPaintUnselected = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mPaintSelected = new Paint(Paint.ANTI_ALIAS_FLAG); + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private int mCurrentPage; + private boolean mCentered; + private float mLineWidth; + private float mGapWidth; + + private int mTouchSlop; + private float mLastMotionX = -1; + private int mActivePointerId = INVALID_POINTER; + private boolean mIsDragging; + + + public LinePageIndicator(Context context) { + this(context, null); + } + + public LinePageIndicator(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.vpiLinePageIndicatorStyle); + } + + public LinePageIndicator(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + if (isInEditMode()) return; + + final Resources res = getResources(); + + //Load defaults from resources + final int defaultSelectedColor = res.getColor(R.color.default_line_indicator_selected_color); + final int defaultUnselectedColor = res.getColor(R.color.default_line_indicator_unselected_color); + final float defaultLineWidth = res.getDimension(R.dimen.default_line_indicator_line_width); + final float defaultGapWidth = res.getDimension(R.dimen.default_line_indicator_gap_width); + final float defaultStrokeWidth = res.getDimension(R.dimen.default_line_indicator_stroke_width); + final boolean defaultCentered = res.getBoolean(R.bool.default_line_indicator_centered); + + //Retrieve styles attributes + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LinePageIndicator, defStyle, 0); + + mCentered = a.getBoolean(R.styleable.LinePageIndicator_centered, defaultCentered); + mLineWidth = a.getDimension(R.styleable.LinePageIndicator_lineWidth, defaultLineWidth); + mGapWidth = a.getDimension(R.styleable.LinePageIndicator_gapWidth, defaultGapWidth); + setStrokeWidth(a.getDimension(R.styleable.LinePageIndicator_strokeWidth, defaultStrokeWidth)); + mPaintUnselected.setColor(a.getColor(R.styleable.LinePageIndicator_unselectedColor, defaultUnselectedColor)); + mPaintSelected.setColor(a.getColor(R.styleable.LinePageIndicator_selectedColor, defaultSelectedColor)); + + Drawable background = a.getDrawable(R.styleable.LinePageIndicator_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 setUnselectedColor(int unselectedColor) { + mPaintUnselected.setColor(unselectedColor); + invalidate(); + } + + public int getUnselectedColor() { + return mPaintUnselected.getColor(); + } + + public void setSelectedColor(int selectedColor) { + mPaintSelected.setColor(selectedColor); + invalidate(); + } + + public int getSelectedColor() { + return mPaintSelected.getColor(); + } + + public void setLineWidth(float lineWidth) { + mLineWidth = lineWidth; + invalidate(); + } + + public float getLineWidth() { + return mLineWidth; + } + + public void setStrokeWidth(float lineHeight) { + mPaintSelected.setStrokeWidth(lineHeight); + mPaintUnselected.setStrokeWidth(lineHeight); + invalidate(); + } + + public float getStrokeWidth() { + return mPaintSelected.getStrokeWidth(); + } + + public void setGapWidth(float gapWidth) { + mGapWidth = gapWidth; + invalidate(); + } + + public float getGapWidth() { + return mGapWidth; + } + + @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; + } + + final float lineWidthAndGap = mLineWidth + mGapWidth; + final float indicatorWidth = (count * lineWidthAndGap) - mGapWidth; + final float paddingTop = getPaddingTop(); + final float paddingLeft = getPaddingLeft(); + final float paddingRight = getPaddingRight(); + + float verticalOffset = paddingTop + ((getHeight() - paddingTop - getPaddingBottom()) / 2.0f); + float horizontalOffset = paddingLeft; + if (mCentered) { + horizontalOffset += ((getWidth() - paddingLeft - paddingRight) / 2.0f) - (indicatorWidth / 2.0f); + } + + //Draw stroked circles + for (int i = 0; i < count; i++) { + float dx1 = horizontalOffset + (i * lineWidthAndGap); + float dx2 = dx1 + mLineWidth; + canvas.drawLine(dx1, verticalOffset, dx2, verticalOffset, (i == mCurrentPage) ? mPaintSelected : mPaintUnselected); + } + } + + public boolean onTouchEvent(android.view.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 viewPager) { + if (mViewPager == viewPager) { + return; + } + if (mViewPager != null) { + //Clear us from the old pager. + mViewPager.setOnPageChangeListener(null); + } + if (viewPager.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + mViewPager = viewPager; + 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) { + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageSelected(int position) { + mCurrentPage = position; + invalidate(); + + if (mListener != null) { + mListener.onPageSelected(position); + } + } + + @Override + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mListener = listener; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(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 measureWidth(int measureSpec) { + float 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 = getPaddingLeft() + getPaddingRight() + (count * mLineWidth) + ((count - 1) * mGapWidth); + //Respect AT_MOST value if that was what is called for by measureSpec + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + return (int)FloatMath.ceil(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 measureHeight(int measureSpec) { + float 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 = mPaintSelected.getStrokeWidth() + getPaddingTop() + getPaddingBottom(); + //Respect AT_MOST value if that was what is called for by measureSpec + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + return (int)FloatMath.ceil(result); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState)state; + super.onRestoreInstanceState(savedState.getSuperState()); + mCurrentPage = 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 Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +}
\ No newline at end of file |