/* * Copyright (C) 2011 Cyril Mottier (http://www.cyrilmottier.com) * * 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 greendroid.widget; import com.cyrilmottier.android.greendroid.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; /** * Visual indicator of a paged content. The {@link PageIndicator} usually * displays a line of dots. Each dot represents a page. {@link PageIndicator} * supports two types of dots. In {@link DotType#SINGLE} mode, all dots are * drawn but only one is is the selected state and represents the selected page, * In {@link DotType#MULTIPLE} the selected page is actually represented by the * amount of dots currently drawn. other dots are drawn. * * @author Cyril Mottier */ public class PageIndicator extends View { /** * Constant that may be used to select none of the dots in the * {@link PageIndicator} */ public static final int NO_ACTIVE_DOT = -1; /** * Interface containing of dot types supported by the {@link PageIndicator} * class. * * @author Cyril Mottier */ public interface DotType { /** * Represents the single dot type. Only one selected dot may be drawn. */ int SINGLE = 0; /** * Represents the multiple dot type. Several selected dot may be drawn. */ int MULTIPLE = 1; } private static final int MIN_DOT_COUNT = 1; private static Rect sInRect = new Rect(); private static Rect sOutRect = new Rect(); private int mGravity; private int mDotSpacing; private Drawable mDotDrawable; private int mDotCount; private int mDotType; private int mActiveDot; private int[] mExtraState; private boolean mInitializing; public PageIndicator(Context context) { this(context, null); } public PageIndicator(Context context, AttributeSet attrs) { this(context, attrs, R.attr.gdPageIndicatorStyle); } public PageIndicator(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initPageIndicator(); mInitializing = true; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PageIndicator, defStyle, 0); setDotCount(a.getInt(R.styleable.PageIndicator_dotCount, mDotCount)); setActiveDot(a.getInt(R.styleable.PageIndicator_activeDot, mActiveDot)); setDotDrawable(a.getDrawable(R.styleable.PageIndicator_dotDrawable)); setDotSpacing(a.getDimensionPixelSize(R.styleable.PageIndicator_dotSpacing, mDotSpacing)); setGravity(a.getInt(R.styleable.PageIndicator_gravity, mGravity)); setDotType(a.getInt(R.styleable.PageIndicator_dotType, mDotType)); a.recycle(); mInitializing = false; } private void initPageIndicator() { mDotCount = MIN_DOT_COUNT; mGravity = Gravity.CENTER; mActiveDot = 0; mDotSpacing = 0; mDotType = DotType.SINGLE; mExtraState = onCreateDrawableState(1); mergeDrawableStates(mExtraState, SELECTED_STATE_SET); } /** * How many dots are drawn by this {@link PageIndicator} * * @return The maximum number of dots. */ public int getDotCount() { return mDotCount; } /** * Sets the number of dots. * * @param dotCount The number oF dots * @see #getDotCount() */ public void setDotCount(int dotCount) { if (dotCount < MIN_DOT_COUNT) { dotCount = MIN_DOT_COUNT; } if (mDotCount != dotCount) { mDotCount = dotCount; requestLayout(); invalidate(); } } /** * Return the current active dot. Depending on the current dot type of this * {@link PageIndicator} the current active dot may be the number of * displayed dots or the index of the selected dot * * @return The current active dot index or dots count * @see #setActiveDot(int) */ public int getActiveDot() { return mActiveDot; } /** * Sets the index of the active dot or the number of active dots. Depending * on the current dot type of this {@link PageIndicator} the current active * dot may be the number of displayed dots or the index of the selected dot * * @param activeDot The number/index of (the) active dot(s) * @see #getActiveDot() */ public void setActiveDot(int activeDot) { if (activeDot < 0) { activeDot = NO_ACTIVE_DOT; } switch (mDotType) { case DotType.SINGLE: if (activeDot > mDotCount - 1) { activeDot = NO_ACTIVE_DOT; } break; case DotType.MULTIPLE: if (activeDot > mDotCount) { activeDot = NO_ACTIVE_DOT; } } mActiveDot = activeDot; invalidate(); } /** * Returns the drawable currently used for each dot. * * @return The drawable used to draw each dot. */ public Drawable getDotDrawable() { return mDotDrawable; } /** * Sets the {@link Drawable} used for each dot. The given {@link Drawable} * may be a {@link StateListDrawable} in order to take advantage of the * selection system. If your {@link StateListDrawable} contains a * {@link android.R.attr.state_selected} state, the {@link Drawable} will be * used to represent a selected dot. * <em><strong>Note :</strong> this methods does not support {@link Drawable} * that has no intrinsic dimensions.</em> * * @param dotDrawable The {@link Drawable} used to represents a dot */ public void setDotDrawable(Drawable dotDrawable) { if (dotDrawable != mDotDrawable) { mDotDrawable = dotDrawable; if (dotDrawable != null) { if (dotDrawable.getIntrinsicHeight() == -1 || dotDrawable.getIntrinsicWidth() == -1) { // Do not accept Drawable with no intrinsic dimensions. return; } dotDrawable.setBounds(0, 0, dotDrawable.getIntrinsicWidth(), dotDrawable.getIntrinsicHeight()); dotDrawable.setCallback(this); if (dotDrawable.isStateful()) { dotDrawable.setState(getDrawableState()); } } requestLayout(); invalidate(); } } /** * The spacing between each dot * * @return The spacing between each dot */ public int getDotSpacing() { return mDotSpacing; } /** * Sets the spacing between each dot * * @param dotSpacing The spacing between each dot. */ public void setDotSpacing(int dotSpacing) { if (dotSpacing != mDotSpacing) { mDotSpacing = dotSpacing; requestLayout(); invalidate(); } } /** * Returns the gravity * * @return The current gravity */ public int getGravity() { return mGravity; } /** * Specifies how to align the dots by the view's x- and/or y-axis when the * space taken by the dots is smaller than the view. * * @param gravity The gravity */ public void setGravity(int gravity) { if (mGravity != gravity) { mGravity = gravity; invalidate(); } } /** * The current dot type * * @return The dot type of this {@link PageIndicator} * @see DotType */ public int getDotType() { return mDotType; } /** * Specifies the type of dot actually drawn by this {@link PageIndicator} * * @param dotType The dot type to use * @see DotType */ public void setDotType(int dotType) { if (dotType == DotType.SINGLE || dotType == DotType.MULTIPLE) { if (mDotType != dotType) { mDotType = dotType; invalidate(); } } } @Override public void requestLayout() { if (!mInitializing) { super.requestLayout(); } } @Override public void invalidate() { if (!mInitializing) { super.invalidate(); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mDotDrawable; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); mExtraState = onCreateDrawableState(1); mergeDrawableStates(mExtraState, SELECTED_STATE_SET); invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Drawable d = mDotDrawable; int width = 0; int height = 0; if (d != null) { width = mDotCount * (d.getIntrinsicWidth() + mDotSpacing) - mDotSpacing; height = d.getIntrinsicHeight(); } width += getPaddingRight() + getPaddingLeft(); height += getPaddingBottom() + getPaddingTop(); setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); } @Override protected void onDraw(Canvas canvas) { final Drawable d = mDotDrawable; if (d != null) { final int count = mDotType == DotType.SINGLE ? mDotCount : mActiveDot; if (count <= 0) { return; } final int h = d.getIntrinsicHeight(); final int w = Math.max(0, count * (d.getIntrinsicWidth() + mDotSpacing) - mDotSpacing); final int pRight = getPaddingRight(); final int pLeft = getPaddingLeft(); final int pTop = getPaddingTop(); final int pBottom = getPaddingBottom(); sInRect.set(pLeft, pTop, getWidth() - pRight, getHeight() - pBottom); Gravity.apply(mGravity, w, h, sInRect, sOutRect); canvas.save(); canvas.translate(sOutRect.left, sOutRect.top); for (int i = 0; i < count; i++) { if (d.isStateful()) { int[] state = getDrawableState(); if (mDotType == DotType.MULTIPLE || i == mActiveDot) { state = mExtraState; } d.setState(state); } d.draw(canvas); canvas.translate(mDotSpacing + d.getIntrinsicWidth(), 0); } canvas.restore(); } } static class SavedState extends BaseSavedState { int activeDot; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); activeDot = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(activeDot); } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.activeDot = mActiveDot; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); mActiveDot = ss.activeDot; } }