| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- /*
- * 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.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)Math.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)Math.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];
- }
- };
- }
- }
|