diff --git a/README b/README index 6132324..a205a44 100644 --- a/README +++ b/README @@ -37,6 +37,11 @@ Facebook: http://www.facebook.com/pages/Yaaic/359902798214 Yaaic includes the PircBot IRC API written by Paul Mutton available under the GNU General Public License (GPL). http://www.jibble.org +Yaaic uses the ViewPagerIndicator widget written by Patrik +Ã…kerfeldt, Francisco Figueiredo Jr. and Jake Wharton. Licensed +under the Apache License 2.0. +https://github.com/JakeWharton/Android-ViewPagerIndicator + The Yaaic icon was designed by http://www.androidicons.com Some icons are part of the Silk icon set designed by Mark James diff --git a/libs/ViewPagerIndicator/.classpath b/libs/ViewPagerIndicator/.classpath new file mode 100644 index 0000000..c5c5afd --- /dev/null +++ b/libs/ViewPagerIndicator/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/libs/ViewPagerIndicator/.project b/libs/ViewPagerIndicator/.project new file mode 100644 index 0000000..02921eb --- /dev/null +++ b/libs/ViewPagerIndicator/.project @@ -0,0 +1,33 @@ + + + ViewPagerIndicator + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/libs/ViewPagerIndicator/AndroidManifest.xml b/libs/ViewPagerIndicator/AndroidManifest.xml new file mode 100644 index 0000000..ff69fba --- /dev/null +++ b/libs/ViewPagerIndicator/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/libs/ViewPagerIndicator/checkstyle.xml b/libs/ViewPagerIndicator/checkstyle.xml new file mode 100644 index 0000000..5c3e7da --- /dev/null +++ b/libs/ViewPagerIndicator/checkstyle.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/ViewPagerIndicator/libs/android-support-v4.jar b/libs/ViewPagerIndicator/libs/android-support-v4.jar new file mode 100644 index 0000000..b254ef5 Binary files /dev/null and b/libs/ViewPagerIndicator/libs/android-support-v4.jar differ diff --git a/libs/ViewPagerIndicator/lint.xml b/libs/ViewPagerIndicator/lint.xml new file mode 100644 index 0000000..ee0eead --- /dev/null +++ b/libs/ViewPagerIndicator/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/libs/ViewPagerIndicator/pom.xml b/libs/ViewPagerIndicator/pom.xml new file mode 100644 index 0000000..ed61520 --- /dev/null +++ b/libs/ViewPagerIndicator/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + com.viewpagerindicator + library + Android-ViewPagerIndicator + apklib + + + com.viewpagerindicator + parent + 2.2.2 + ../pom.xml + + + + + android + android + provided + + + + android.support + compatibility-v4 + provided + + + + + src + + + + com.jayway.maven.plugins.android.generation2 + maven-android-plugin + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ${project.basedir}/checkstyle.xml + + + + verify + + checkstyle + + + + + + + diff --git a/libs/ViewPagerIndicator/project.properties b/libs/ViewPagerIndicator/project.properties new file mode 100644 index 0000000..96db742 --- /dev/null +++ b/libs/ViewPagerIndicator/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library=true +# Project target. +target=android-4 diff --git a/libs/ViewPagerIndicator/res/color/vpi__dark_theme.xml b/libs/ViewPagerIndicator/res/color/vpi__dark_theme.xml new file mode 100644 index 0000000..3e7a08f --- /dev/null +++ b/libs/ViewPagerIndicator/res/color/vpi__dark_theme.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/libs/ViewPagerIndicator/res/color/vpi__light_theme.xml b/libs/ViewPagerIndicator/res/color/vpi__light_theme.xml new file mode 100644 index 0000000..f955db7 --- /dev/null +++ b/libs/ViewPagerIndicator/res/color/vpi__light_theme.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_focused_holo.9.png b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_focused_holo.9.png new file mode 100644 index 0000000..673e3bf Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_focused_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_holo.9.png b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_holo.9.png new file mode 100644 index 0000000..d57df98 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_pressed_holo.9.png b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_pressed_holo.9.png new file mode 100644 index 0000000..6278eef Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_selected_pressed_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_focused_holo.9.png b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_focused_holo.9.png new file mode 100644 index 0000000..294991d Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_focused_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_holo.9.png b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_holo.9.png new file mode 100644 index 0000000..19532ab Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_pressed_holo.9.png b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_pressed_holo.9.png new file mode 100644 index 0000000..aadc6f8 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-hdpi/vpi__tab_unselected_pressed_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_focused_holo.9.png b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_focused_holo.9.png new file mode 100644 index 0000000..c9972e7 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_focused_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_holo.9.png b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_holo.9.png new file mode 100644 index 0000000..587337c Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_pressed_holo.9.png b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_pressed_holo.9.png new file mode 100644 index 0000000..155c4fc Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_selected_pressed_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_focused_holo.9.png b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_focused_holo.9.png new file mode 100644 index 0000000..f0cecd1 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_focused_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_holo.9.png b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_holo.9.png new file mode 100644 index 0000000..a2dbf42 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_pressed_holo.9.png b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_pressed_holo.9.png new file mode 100644 index 0000000..b1223fe Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-mdpi/vpi__tab_unselected_pressed_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_focused_holo.9.png b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_focused_holo.9.png new file mode 100644 index 0000000..03cfb09 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_focused_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_holo.9.png b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_holo.9.png new file mode 100644 index 0000000..e4229f2 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_pressed_holo.9.png b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_pressed_holo.9.png new file mode 100644 index 0000000..e862cb1 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_selected_pressed_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_focused_holo.9.png b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_focused_holo.9.png new file mode 100644 index 0000000..f3a5cbd Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_focused_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_holo.9.png b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_holo.9.png new file mode 100644 index 0000000..9465173 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_pressed_holo.9.png b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_pressed_holo.9.png new file mode 100644 index 0000000..f1eb673 Binary files /dev/null and b/libs/ViewPagerIndicator/res/drawable-xhdpi/vpi__tab_unselected_pressed_holo.9.png differ diff --git a/libs/ViewPagerIndicator/res/drawable/vpi__tab_indicator.xml b/libs/ViewPagerIndicator/res/drawable/vpi__tab_indicator.xml new file mode 100644 index 0000000..520d08c --- /dev/null +++ b/libs/ViewPagerIndicator/res/drawable/vpi__tab_indicator.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/ViewPagerIndicator/res/layout/vpi__tab.xml b/libs/ViewPagerIndicator/res/layout/vpi__tab.xml new file mode 100644 index 0000000..980bec4 --- /dev/null +++ b/libs/ViewPagerIndicator/res/layout/vpi__tab.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/libs/ViewPagerIndicator/res/values/vpi__attrs.xml b/libs/ViewPagerIndicator/res/values/vpi__attrs.xml new file mode 100644 index 0000000..9e47b07 --- /dev/null +++ b/libs/ViewPagerIndicator/res/values/vpi__attrs.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/ViewPagerIndicator/res/values/vpi__colors.xml b/libs/ViewPagerIndicator/res/values/vpi__colors.xml new file mode 100644 index 0000000..c0d958f --- /dev/null +++ b/libs/ViewPagerIndicator/res/values/vpi__colors.xml @@ -0,0 +1,26 @@ + + + + + #ff000000 + #fff3f3f3 + @color/vpi__background_holo_light + @color/vpi__background_holo_dark + #ff4c4c4c + #ffb2b2b2 + @color/vpi__bright_foreground_holo_light + @color/vpi__bright_foreground_holo_dark + diff --git a/libs/ViewPagerIndicator/res/values/vpi__defaults.xml b/libs/ViewPagerIndicator/res/values/vpi__defaults.xml new file mode 100644 index 0000000..6a27089 --- /dev/null +++ b/libs/ViewPagerIndicator/res/values/vpi__defaults.xml @@ -0,0 +1,39 @@ + + + + + true + #FFFFFFFF + 0 + 3dp + false + #FFDDDDDD + 1dp + + 4dp + #FF6899FF + 2dp + 2 + 4dp + 20dp + 7dp + #FFFFFFFF + true + #BBFFFFFF + 15sp + 5dp + 7dp + \ No newline at end of file diff --git a/libs/ViewPagerIndicator/res/values/vpi__styles.xml b/libs/ViewPagerIndicator/res/values/vpi__styles.xml new file mode 100644 index 0000000..f6fa7b1 --- /dev/null +++ b/libs/ViewPagerIndicator/res/values/vpi__styles.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + diff --git a/libs/ViewPagerIndicator/src/com/viewpagerindicator/CirclePageIndicator.java b/libs/ViewPagerIndicator/src/com/viewpagerindicator/CirclePageIndicator.java new file mode 100644 index 0000000..bc4f898 --- /dev/null +++ b/libs/ViewPagerIndicator/src/com/viewpagerindicator/CirclePageIndicator.java @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 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.Paint.Style; +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 circles (one for each view). The current view position is filled and + * others are only stroked. + */ +public class CirclePageIndicator extends View implements PageIndicator { + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + private float mRadius; + private final Paint mPaintStroke; + private final Paint mPaintFill; + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private int mCurrentPage; + private int mSnapPage; + private int mCurrentOffset; + private int mScrollState; + private int mPageSize; + private int mOrientation; + private boolean mCentered; + private boolean mSnap; + + private static final int INVALID_POINTER = -1; + + 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); + + //Load defaults from resources + final Resources res = getResources(); + 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, R.style.Widget_CirclePageIndicator); + + mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered); + mOrientation = a.getInt(R.styleable.CirclePageIndicator_orientation, defaultOrientation); + mPaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintStroke.setStyle(Style.STROKE); + mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor)); + mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth)); + mPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); + 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); + + 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 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; + updatePageSize(); + 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; + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onDraw(android.graphics.Canvas) + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mViewPager == null) { + return; + } + final int count = mViewPager.getAdapter().getCount(); + if (count == 0) { + 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; + 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; + + //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; + } + canvas.drawCircle(dX, dY, mRadius, mPaintStroke); + } + + //Draw the filled circle according to the current scroll + float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius; + if (!mSnap && (mPageSize != 0)) { + cx += (mCurrentOffset * 1.0f / mPageSize) * threeRadius; + } + if (mOrientation == HORIZONTAL) { + dX = longOffset + cx; + dY = shortOffset; + } else { + dX = shortOffset; + dY = longOffset + cx; + } + canvas.drawCircle(dX, dY, mRadius, mPaintFill); + } + + public boolean onTouchEvent(android.view.MotionEvent ev) { + if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { + return false; + } + + final int action = ev.getAction(); + + switch (action & MotionEventCompat.ACTION_MASK) { + 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) { + if (!mViewPager.isFakeDragging()) { + mViewPager.beginFakeDrag(); + } + + mLastMotionX = x; + + 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)) { + mViewPager.setCurrentItem(mCurrentPage - 1); + return true; + } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) { + 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); + final float x = MotionEventCompat.getX(ev, index); + mLastMotionX = x; + 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 (view.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + mViewPager = view; + mViewPager.setOnPageChangeListener(this); + updatePageSize(); + invalidate(); + } + + private void updatePageSize() { + if (mViewPager != null) { + mPageSize = (mOrientation == HORIZONTAL) ? mViewPager.getWidth() : mViewPager.getHeight(); + } + } + + @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; + mCurrentOffset = positionOffsetPixels; + updatePageSize(); + 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 = 0; + 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 = 0; + 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); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/libs/ViewPagerIndicator/src/com/viewpagerindicator/PageIndicator.java b/libs/ViewPagerIndicator/src/com/viewpagerindicator/PageIndicator.java new file mode 100644 index 0000000..26414d8 --- /dev/null +++ b/libs/ViewPagerIndicator/src/com/viewpagerindicator/PageIndicator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 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.support.v4.view.ViewPager; + +/** + * 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 + */ + public void setViewPager(ViewPager view); + + /** + * Bind the indicator to a ViewPager. + * + * @param view + * @param initialPosition + */ + public void setViewPager(ViewPager view, int initialPosition); + + /** + *

Set the current page of both the ViewPager and indicator.

+ * + *

This must be used if you need to set the page before + * the views are drawn on screen (e.g., default start page).

+ * + * @param item + */ + public void setCurrentItem(int item); + + /** + * Set a page change listener which will receive forwarded events. + * + * @param listener + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); + + /** + * Notify the indicator that the fragment list has changed. + */ + public void notifyDataSetChanged(); +} diff --git a/libs/ViewPagerIndicator/src/com/viewpagerindicator/TabPageIndicator.java b/libs/ViewPagerIndicator/src/com/viewpagerindicator/TabPageIndicator.java new file mode 100644 index 0000000..9fba053 --- /dev/null +++ b/libs/ViewPagerIndicator/src/com/viewpagerindicator/TabPageIndicator.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2011 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.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * This widget implements the dynamic action bar tab behavior that can change + * across different configurations or circumstances. + */ +public class TabPageIndicator extends HorizontalScrollView implements PageIndicator { + Runnable mTabSelector; + + private OnClickListener mTabClickListener = new OnClickListener() { + public void onClick(View view) { + TabView tabView = (TabView)view; + mViewPager.setCurrentItem(tabView.getIndex()); + } + }; + + private LinearLayout mTabLayout; + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + + private LayoutInflater mInflater; + + int mMaxTabWidth; + private int mSelectedTabIndex; + + public TabPageIndicator(Context context) { + this(context, null); + } + + public TabPageIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + setHorizontalScrollBarEnabled(false); + + mInflater = LayoutInflater.from(context); + + mTabLayout = new LinearLayout(getContext()); + addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY; + setFillViewport(lockedExpanded); + + final int childCount = mTabLayout.getChildCount(); + if (childCount > 1 && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) { + if (childCount > 2) { + mMaxTabWidth = (int)(MeasureSpec.getSize(widthMeasureSpec) * 0.4f); + } else { + mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2; + } + } else { + mMaxTabWidth = -1; + } + + final int oldWidth = getMeasuredWidth(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + final int newWidth = getMeasuredWidth(); + + if (lockedExpanded && oldWidth != newWidth) { + // Recenter the tab display if we're at a new (scrollable) size. + setCurrentItem(mSelectedTabIndex); + } + } + + private void animateToTab(final int position) { + final View tabView = mTabLayout.getChildAt(position); + if (mTabSelector != null) { + removeCallbacks(mTabSelector); + } + mTabSelector = new Runnable() { + public void run() { + final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2; + smoothScrollTo(scrollPos, 0); + mTabSelector = null; + } + }; + post(mTabSelector); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mTabSelector != null) { + // Re-post the selector we saved + post(mTabSelector); + } + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mTabSelector != null) { + removeCallbacks(mTabSelector); + } + } + + private void addTab(String text, int index) { + //Workaround for not being able to pass a defStyle on pre-3.0 + final TabView tabView = (TabView)mInflater.inflate(R.layout.vpi__tab, null); + tabView.init(this, text, index); + tabView.setFocusable(true); + tabView.setOnClickListener(mTabClickListener); + + mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, LayoutParams.FILL_PARENT, 1)); + } + + @Override + public void onPageScrollStateChanged(int arg0) { + if (mListener != null) { + mListener.onPageScrollStateChanged(arg0); + } + } + + @Override + public void onPageScrolled(int arg0, float arg1, int arg2) { + if (mListener != null) { + mListener.onPageScrolled(arg0, arg1, arg2); + } + } + + @Override + public void onPageSelected(int arg0) { + setCurrentItem(arg0); + if (mListener != null) { + mListener.onPageSelected(arg0); + } + } + + @Override + public void setViewPager(ViewPager view) { + final PagerAdapter adapter = view.getAdapter(); + if (adapter == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + if (!(adapter instanceof TitleProvider)) { + throw new IllegalStateException("ViewPager adapter must implement TitleProvider to be used with TitlePageIndicator."); + } + mViewPager = view; + view.setOnPageChangeListener(this); + notifyDataSetChanged(); + } + + public void notifyDataSetChanged() { + mTabLayout.removeAllViews(); + TitleProvider adapter = (TitleProvider)mViewPager.getAdapter(); + final int count = ((PagerAdapter)adapter).getCount(); + for (int i = 0; i < count; i++) { + addTab(adapter.getTitle(i), i); + } + if (mSelectedTabIndex > count) { + mSelectedTabIndex = count - 1; + } + setCurrentItem(mSelectedTabIndex); + requestLayout(); + } + + @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."); + } + mSelectedTabIndex = item; + final int tabCount = mTabLayout.getChildCount(); + for (int i = 0; i < tabCount; i++) { + final View child = mTabLayout.getChildAt(i); + final boolean isSelected = (i == item); + child.setSelected(isSelected); + if (isSelected) { + animateToTab(item); + } + } + } + + @Override + public void setOnPageChangeListener(OnPageChangeListener listener) { + mListener = listener; + } + + public static class TabView extends LinearLayout { + private TabPageIndicator mParent; + private int mIndex; + + public TabView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void init(TabPageIndicator parent, String text, int index) { + mParent = parent; + mIndex = index; + + TextView textView = (TextView)findViewById(android.R.id.text1); + textView.setText(text); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Re-measure if we went beyond our maximum size. + if (mParent.mMaxTabWidth > 0 && getMeasuredWidth() > mParent.mMaxTabWidth) { + super.onMeasure(MeasureSpec.makeMeasureSpec(mParent.mMaxTabWidth, MeasureSpec.EXACTLY), + heightMeasureSpec); + } + } + + public int getIndex() { + return mIndex; + } + } +} diff --git a/libs/ViewPagerIndicator/src/com/viewpagerindicator/TitlePageIndicator.java b/libs/ViewPagerIndicator/src/com/viewpagerindicator/TitlePageIndicator.java new file mode 100644 index 0000000..b4eb8ad --- /dev/null +++ b/libs/ViewPagerIndicator/src/com/viewpagerindicator/TitlePageIndicator.java @@ -0,0 +1,776 @@ +/* + * Copyright (C) 2011 Jake Wharton + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 Francisco Figueiredo Jr. + * + * 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 java.util.ArrayList; +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.Path; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.PagerAdapter; +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; + +/** + * A TitlePageIndicator is a PageIndicator which displays the title of left view + * (if exist), the title of the current select view (centered) and the title of + * the right view (if exist). When the user scrolls the ViewPager then titles are + * also scrolled. + */ +public class TitlePageIndicator extends View implements PageIndicator { + /** + * Percentage indicating what percentage of the screen width away from + * center should the underline be fully faded. A value of 0.25 means that + * halfway between the center of the screen and an edge. + */ + private static final float SELECTION_FADE_PERCENTAGE = 0.25f; + + /** + * Percentage indicating what percentage of the screen width away from + * center should the selected text bold turn off. A value of 0.05 means + * that 10% between the center and an edge. + */ + private static final float BOLD_FADE_PERCENTAGE = 0.05f; + + /** + * Interface for a callback when the center item has been clicked. + */ + public static interface OnCenterItemClickListener { + /** + * Callback when the center item has been clicked. + * + * @param position Position of the current center item. + */ + public void onCenterItemClick(int position); + } + + public enum IndicatorStyle { + None(0), Triangle(1), Underline(2); + + public final int value; + + private IndicatorStyle(int value) { + this.value = value; + } + + public static IndicatorStyle fromValue(int value) { + for (IndicatorStyle style : IndicatorStyle.values()) { + if (style.value == value) { + return style; + } + } + return null; + } + } + + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private TitleProvider mTitleProvider; + private int mCurrentPage; + private int mCurrentOffset; + private int mScrollState; + private final Paint mPaintText = new Paint(); + private boolean mBoldText; + private int mColorText; + private int mColorSelected; + private Path mPath; + private final Paint mPaintFooterLine = new Paint(); + private IndicatorStyle mFooterIndicatorStyle; + private final Paint mPaintFooterIndicator = new Paint(); + private float mFooterIndicatorHeight; + private float mFooterIndicatorUnderlinePadding; + private float mFooterPadding; + private float mTitlePadding; + private float mTopPadding; + /** Left and right side padding for not active view titles. */ + private float mClipPadding; + private float mFooterLineHeight; + + private static final int INVALID_POINTER = -1; + + private int mTouchSlop; + private float mLastMotionX = -1; + private int mActivePointerId = INVALID_POINTER; + private boolean mIsDragging; + + private OnCenterItemClickListener mCenterItemClickListener; + + + public TitlePageIndicator(Context context) { + this(context, null); + } + + public TitlePageIndicator(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.vpiTitlePageIndicatorStyle); + } + + public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + //Load defaults from resources + final Resources res = getResources(); + final int defaultFooterColor = res.getColor(R.color.default_title_indicator_footer_color); + final float defaultFooterLineHeight = res.getDimension(R.dimen.default_title_indicator_footer_line_height); + final int defaultFooterIndicatorStyle = res.getInteger(R.integer.default_title_indicator_footer_indicator_style); + final float defaultFooterIndicatorHeight = res.getDimension(R.dimen.default_title_indicator_footer_indicator_height); + final float defaultFooterIndicatorUnderlinePadding = res.getDimension(R.dimen.default_title_indicator_footer_indicator_underline_padding); + final float defaultFooterPadding = res.getDimension(R.dimen.default_title_indicator_footer_padding); + final int defaultSelectedColor = res.getColor(R.color.default_title_indicator_selected_color); + final boolean defaultSelectedBold = res.getBoolean(R.bool.default_title_indicator_selected_bold); + final int defaultTextColor = res.getColor(R.color.default_title_indicator_text_color); + final float defaultTextSize = res.getDimension(R.dimen.default_title_indicator_text_size); + final float defaultTitlePadding = res.getDimension(R.dimen.default_title_indicator_title_padding); + final float defaultClipPadding = res.getDimension(R.dimen.default_title_indicator_clip_padding); + final float defaultTopPadding = res.getDimension(R.dimen.default_title_indicator_top_padding); + + //Retrieve styles attributes + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, R.style.Widget_TitlePageIndicator); + + //Retrieve the colors to be used for this view and apply them. + mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight); + mFooterIndicatorStyle = IndicatorStyle.fromValue(a.getInteger(R.styleable.TitlePageIndicator_footerIndicatorStyle, defaultFooterIndicatorStyle)); + mFooterIndicatorHeight = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorHeight, defaultFooterIndicatorHeight); + mFooterIndicatorUnderlinePadding = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorUnderlinePadding, defaultFooterIndicatorUnderlinePadding); + mFooterPadding = a.getDimension(R.styleable.TitlePageIndicator_footerPadding, defaultFooterPadding); + mTopPadding = a.getDimension(R.styleable.TitlePageIndicator_topPadding, defaultTopPadding); + mTitlePadding = a.getDimension(R.styleable.TitlePageIndicator_titlePadding, defaultTitlePadding); + mClipPadding = a.getDimension(R.styleable.TitlePageIndicator_clipPadding, defaultClipPadding); + mColorSelected = a.getColor(R.styleable.TitlePageIndicator_selectedColor, defaultSelectedColor); + mColorText = a.getColor(R.styleable.TitlePageIndicator_textColor, defaultTextColor); + mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold); + + final float textSize = a.getDimension(R.styleable.TitlePageIndicator_textSize, defaultTextSize); + final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor); + mPaintText.setTextSize(textSize); + mPaintText.setAntiAlias(true); + mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE); + mPaintFooterLine.setStrokeWidth(mFooterLineHeight); + mPaintFooterLine.setColor(footerColor); + mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE); + mPaintFooterIndicator.setColor(footerColor); + + a.recycle(); + + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); + } + + + public int getFooterColor() { + return mPaintFooterLine.getColor(); + } + + public void setFooterColor(int footerColor) { + mPaintFooterLine.setColor(footerColor); + mPaintFooterIndicator.setColor(footerColor); + invalidate(); + } + + public float getFooterLineHeight() { + return mFooterLineHeight; + } + + public void setFooterLineHeight(float footerLineHeight) { + mFooterLineHeight = footerLineHeight; + mPaintFooterLine.setStrokeWidth(mFooterLineHeight); + invalidate(); + } + + public float getFooterIndicatorHeight() { + return mFooterIndicatorHeight; + } + + public void setFooterIndicatorHeight(float footerTriangleHeight) { + mFooterIndicatorHeight = footerTriangleHeight; + invalidate(); + } + + public float getFooterIndicatorPadding() { + return mFooterPadding; + } + + public void setFooterIndicatorPadding(float footerIndicatorPadding) { + mFooterPadding = footerIndicatorPadding; + invalidate(); + } + + public IndicatorStyle getFooterIndicatorStyle() { + return mFooterIndicatorStyle; + } + + public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) { + mFooterIndicatorStyle = indicatorStyle; + invalidate(); + } + + public int getSelectedColor() { + return mColorSelected; + } + + public void setSelectedColor(int selectedColor) { + mColorSelected = selectedColor; + invalidate(); + } + + public boolean isSelectedBold() { + return mBoldText; + } + + public void setSelectedBold(boolean selectedBold) { + mBoldText = selectedBold; + invalidate(); + } + + public int getTextColor() { + return mColorText; + } + + public void setTextColor(int textColor) { + mPaintText.setColor(textColor); + mColorText = textColor; + invalidate(); + } + + public float getTextSize() { + return mPaintText.getTextSize(); + } + + public void setTextSize(float textSize) { + mPaintText.setTextSize(textSize); + invalidate(); + } + + public float getTitlePadding() { + return this.mTitlePadding; + } + + public void setTitlePadding(float titlePadding) { + mTitlePadding = titlePadding; + invalidate(); + } + + public float getTopPadding() { + return this.mTopPadding; + } + + public void setTopPadding(float topPadding) { + mTopPadding = topPadding; + invalidate(); + } + + public float getClipPadding() { + return this.mClipPadding; + } + + public void setClipPadding(float clipPadding) { + mClipPadding = clipPadding; + invalidate(); + } + + public void setTypeface(Typeface typeface) { + mPaintText.setTypeface(typeface); + invalidate(); + } + + public Typeface getTypeface() { + return mPaintText.getTypeface(); + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onDraw(android.graphics.Canvas) + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mViewPager == null) { + return; + } + final int count = mViewPager.getAdapter().getCount(); + if (count == 0) { + return; + } + + //Calculate views bounds + ArrayList bounds = calculateAllBounds(mPaintText); + + //Make sure we're on a page that still exists + if (mCurrentPage >= bounds.size()) { + setCurrentItem(bounds.size()-1); + } + + final int countMinusOne = count - 1; + final float halfWidth = getWidth() / 2f; + final int left = getLeft(); + final float leftClip = left + mClipPadding; + final int width = getWidth(); + final int height = getHeight(); + final int right = left + width; + final float rightClip = right - mClipPadding; + + int page = mCurrentPage; + float offsetPercent; + if (mCurrentOffset <= halfWidth) { + offsetPercent = 1.0f * mCurrentOffset / width; + } else { + page += 1; + offsetPercent = 1.0f * (width - mCurrentOffset) / width; + } + final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE); + final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE); + final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE; + + //Verify if the current view must be clipped to the screen + RectF curPageBound = bounds.get(mCurrentPage); + float curPageWidth = curPageBound.right - curPageBound.left; + if (curPageBound.left < leftClip) { + //Try to clip to the screen (left side) + clipViewOnTheLeft(curPageBound, curPageWidth, left); + } + if (curPageBound.right > rightClip) { + //Try to clip to the screen (right side) + clipViewOnTheRight(curPageBound, curPageWidth, right); + } + + //Left views starting from the current position + if (mCurrentPage > 0) { + for (int i = mCurrentPage - 1; i >= 0; i--) { + RectF bound = bounds.get(i); + //Is left side is outside the screen + if (bound.left < leftClip) { + float w = bound.right - bound.left; + //Try to clip to the screen (left side) + clipViewOnTheLeft(bound, w, left); + //Except if there's an intersection with the right view + RectF rightBound = bounds.get(i + 1); + //Intersection + if (bound.right + mTitlePadding > rightBound.left) { + bound.left = rightBound.left - w - mTitlePadding; + bound.right = bound.left + w; + } + } + } + } + //Right views starting from the current position + if (mCurrentPage < countMinusOne) { + for (int i = mCurrentPage + 1 ; i < count; i++) { + RectF bound = bounds.get(i); + //If right side is outside the screen + if (bound.right > rightClip) { + float w = bound.right - bound.left; + //Try to clip to the screen (right side) + clipViewOnTheRight(bound, w, right); + //Except if there's an intersection with the left view + RectF leftBound = bounds.get(i - 1); + //Intersection + if (bound.left - mTitlePadding < leftBound.right) { + bound.left = leftBound.right + mTitlePadding; + bound.right = bound.left + w; + } + } + } + } + + //Now draw views + for (int i = 0; i < count; i++) { + //Get the title + RectF bound = bounds.get(i); + //Only if one side is visible + if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) { + final boolean currentPage = (i == page); + //Only set bold if we are within bounds + mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText); + + //Draw text as unselected + mPaintText.setColor(mColorText); + canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); + + //If we are within the selected bounds draw the selected text + if (currentPage && currentSelected) { + mPaintText.setColor(mColorSelected); + mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent)); + canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); + } + } + } + + //Draw the footer line + mPath = new Path(); + mPath.moveTo(0, height - mFooterLineHeight / 2f); + mPath.lineTo(width, height - mFooterLineHeight / 2f); + mPath.close(); + canvas.drawPath(mPath, mPaintFooterLine); + + switch (mFooterIndicatorStyle) { + case Triangle: + mPath = new Path(); + mPath.moveTo(halfWidth, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.lineTo(halfWidth + mFooterIndicatorHeight, height - mFooterLineHeight); + mPath.lineTo(halfWidth - mFooterIndicatorHeight, height - mFooterLineHeight); + mPath.close(); + canvas.drawPath(mPath, mPaintFooterIndicator); + break; + + case Underline: + if (!currentSelected) { + break; + } + + RectF underlineBounds = bounds.get(page); + mPath = new Path(); + mPath.moveTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); + mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); + mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.lineTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.close(); + + mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent)); + canvas.drawPath(mPath, mPaintFooterIndicator); + mPaintFooterIndicator.setAlpha(0xFF); + break; + } + } + + public boolean onTouchEvent(android.view.MotionEvent ev) { + if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { + return false; + } + + final int action = ev.getAction(); + + switch (action & MotionEventCompat.ACTION_MASK) { + 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) { + if (!mViewPager.isFakeDragging()) { + mViewPager.beginFakeDrag(); + } + + mLastMotionX = x; + + 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; + final float leftThird = halfWidth - sixthWidth; + final float rightThird = halfWidth + sixthWidth; + final float eventX = ev.getX(); + + if (eventX < leftThird) { + if (mCurrentPage > 0) { + mViewPager.setCurrentItem(mCurrentPage - 1); + return true; + } + } else if (eventX > rightThird) { + if (mCurrentPage < count - 1) { + mViewPager.setCurrentItem(mCurrentPage + 1); + return true; + } + } else { + //Middle third + if (mCenterItemClickListener != null) { + mCenterItemClickListener.onCenterItemClick(mCurrentPage); + } + } + } + + mIsDragging = false; + mActivePointerId = INVALID_POINTER; + if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag(); + break; + + case MotionEventCompat.ACTION_POINTER_DOWN: { + final int index = MotionEventCompat.getActionIndex(ev); + final float x = MotionEventCompat.getX(ev, index); + mLastMotionX = x; + 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; + }; + + /** + * Set bounds for the right textView including clip padding. + * + * @param curViewBound + * current bounds. + * @param curViewWidth + * width of the view. + */ + private void clipViewOnTheRight(RectF curViewBound, float curViewWidth, int right) { + curViewBound.right = right - mClipPadding; + curViewBound.left = curViewBound.right - curViewWidth; + } + + /** + * Set bounds for the left textView including clip padding. + * + * @param curViewBound + * current bounds. + * @param curViewWidth + * width of the view. + */ + private void clipViewOnTheLeft(RectF curViewBound, float curViewWidth, int left) { + curViewBound.left = left + mClipPadding; + curViewBound.right = mClipPadding + curViewWidth; + } + + /** + * Calculate views bounds and scroll them according to the current index + * + * @param paint + * @param currentIndex + * @return + */ + private ArrayList calculateAllBounds(Paint paint) { + ArrayList list = new ArrayList(); + //For each views (If no values then add a fake one) + final int count = mViewPager.getAdapter().getCount(); + final int width = getWidth(); + final int halfWidth = width / 2; + for (int i = 0; i < count; i++) { + RectF bounds = calcBounds(i, paint); + float w = (bounds.right - bounds.left); + float h = (bounds.bottom - bounds.top); + bounds.left = (halfWidth) - (w / 2) - mCurrentOffset + ((i - mCurrentPage) * width); + bounds.right = bounds.left + w; + bounds.top = 0; + bounds.bottom = h; + list.add(bounds); + } + + return list; + } + + /** + * Calculate the bounds for a view's title + * + * @param index + * @param paint + * @return + */ + private RectF calcBounds(int index, Paint paint) { + //Calculate the text bounds + RectF bounds = new RectF(); + bounds.right = paint.measureText(mTitleProvider.getTitle(index)); + bounds.bottom = paint.descent() - paint.ascent(); + return bounds; + } + + @Override + public void setViewPager(ViewPager view) { + final PagerAdapter adapter = view.getAdapter(); + if (adapter == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + if (!(adapter instanceof TitleProvider)) { + throw new IllegalStateException("ViewPager adapter must implement TitleProvider to be used with TitlePageIndicator."); + } + mViewPager = view; + mViewPager.setOnPageChangeListener(this); + mTitleProvider = (TitleProvider)adapter; + invalidate(); + } + + @Override + public void setViewPager(ViewPager view, int initialPosition) { + setViewPager(view); + setCurrentItem(initialPosition); + } + + @Override + public void notifyDataSetChanged() { + invalidate(); + } + + /** + * Set a callback listener for the center item click. + * + * @param listener Callback instance. + */ + public void setOnCenterItemClickListener(OnCenterItemClickListener listener) { + mCenterItemClickListener = listener; + } + + @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 onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mCurrentPage = position; + mCurrentOffset = positionOffsetPixels; + invalidate(); + + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + 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) { + //Measure our width in whatever mode specified + final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + + //Determine our height + float height = 0; + final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + if (heightSpecMode == MeasureSpec.EXACTLY) { + //We were told how big to be + height = MeasureSpec.getSize(heightMeasureSpec); + } else { + //Calculate the text bounds + RectF bounds = new RectF(); + bounds.bottom = mPaintText.descent()-mPaintText.ascent(); + height = bounds.bottom - bounds.top + mFooterLineHeight + mFooterPadding + mTopPadding; + if (mFooterIndicatorStyle != IndicatorStyle.None) { + height += mFooterIndicatorHeight; + } + } + final int measuredHeight = (int)height; + + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @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); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/libs/ViewPagerIndicator/src/com/viewpagerindicator/TitleProvider.java b/libs/ViewPagerIndicator/src/com/viewpagerindicator/TitleProvider.java new file mode 100644 index 0000000..2a04b65 --- /dev/null +++ b/libs/ViewPagerIndicator/src/com/viewpagerindicator/TitleProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * + * 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; + +/** + * A TitleProvider provides the title to display according to a view. + */ +public interface TitleProvider { + /** + * Returns the title of the view at position + * @param position + * @return + */ + public String getTitle(int position); +}