Update ViewPagerIndicator to 2.3.1.

This commit is contained in:
Sebastian Kaspari 2012-05-21 21:40:32 +02:00
parent 6045cd0f91
commit f713466468
16 changed files with 1074 additions and 371 deletions

View File

@ -33,14 +33,13 @@ import android.support.v4.view.ViewPager;
import android.view.View;
import com.viewpagerindicator.TitlePageIndicator;
import com.viewpagerindicator.TitleProvider;
/**
* Adapter for displaying a pager of conversations.
*
* @author Sebastian Kaspari <sebastian@yaaic.org>
*/
public class ConversationPagerAdapter extends PagerAdapter implements TitleProvider
public class ConversationPagerAdapter extends PagerAdapter
{
private final Server server;
private LinkedList<ConversationInfo> conversations;
@ -267,7 +266,7 @@ public class ConversationPagerAdapter extends PagerAdapter implements TitleProvi
* Get the title for the given position. Used by the {@link TitlePageIndicator}.
*/
@Override
public String getTitle(int position)
public String getPageTitle(int position)
{
Conversation conversation = getItem(position);

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.viewpagerindicator"
android:versionCode="30"
android:versionName="2.2.3">
android:versionCode="50"
android:versionName="2.3.1">
<uses-sdk android:minSdkVersion="4" />
</manifest>

View File

@ -1,115 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
<!--module name="NewlineAtEndOfFile"/-->
<module name="FileLength"/>
<module name="FileTabCharacter"/>
<!-- Trailing spaces -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<module name="TreeWalker">
<property name="cacheFile" value="${checkstyle.cache.file}"/>
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<!--module name="JavadocMethod"/-->
<!--module name="JavadocType"/-->
<!--module name="JavadocVariable"/-->
<!--module name="JavadocStyle"/-->
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sf.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sf.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<!--module name="LineLength"/-->
<!--module name="MethodLength"/-->
<!--module name="ParameterNumber"/-->
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<!--module name="EmptyForIteratorPad"/-->
<!--module name="MethodParamPad"/-->
<!--module name="NoWhitespaceAfter"/-->
<!--module name="NoWhitespaceBefore"/-->
<!--module name="OperatorWrap"/-->
<!--module name="ParenPad"/-->
<!--module name="TypecastParenPad"/-->
<!--module name="WhitespaceAfter"/-->
<!--module name="WhitespaceAround"/-->
<!-- Modifier Checks -->
<!-- See http://checkstyle.sf.net/config_modifiers.html -->
<!--module name="ModifierOrder"/-->
<!--module name="RedundantModifier"/-->
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<!--module name="AvoidNestedBlocks"/-->
<!--module name="EmptyBlock"/-->
<!--module name="LeftCurly"/-->
<!--module name="NeedBraces"/-->
<!--module name="RightCurly"/-->
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sf.net/config_coding.html -->
<!--module name="AvoidInlineConditionals"/-->
<!--module name="DoubleCheckedLocking"/--> <!-- MY FAVOURITE -->
<!--module name="EmptyStatement"/-->
<!--module name="EqualsHashCode"/-->
<!--module name="HiddenField"/-->
<!--module name="IllegalInstantiation"/-->
<!--module name="InnerAssignment"/-->
<!--module name="MagicNumber"/-->
<!--module name="MissingSwitchDefault"/-->
<!--module name="RedundantThrows"/-->
<!--module name="SimplifyBooleanExpression"/-->
<!--module name="SimplifyBooleanReturn"/-->
<!-- Checks for class design -->
<!-- See http://checkstyle.sf.net/config_design.html -->
<!--module name="DesignForExtension"/-->
<!--module name="FinalClass"/-->
<!--module name="HideUtilityClassConstructor"/-->
<!--module name="InterfaceIsType"/-->
<!--module name="VisibilityModifier"/-->
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<!--module name="ArrayTypeStyle"/-->
<!--module name="FinalParameters"/-->
<!--module name="TodoComment"/-->
<!--module name="UpperEll"/-->
</module>
</module>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>com.viewpagerindicator</groupId>
<artifactId>parent</artifactId>
<version>2.2.3</version>
<version>2.3.1</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -23,8 +23,8 @@
</dependency>
<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v4</artifactId>
<groupId>com.google.android</groupId>
<artifactId>support-v4</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
@ -39,19 +39,11 @@
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<configLocation>${project.basedir}/checkstyle.xml</configLocation>
<configLocation>../checkstyle.xml</configLocation>
</configuration>
<executions>
<execution>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<view
xmlns:android="http://schemas.android.com/apk/res/android"
class="com.viewpagerindicator.TabPageIndicator$TabView"
style="?attr/vpiTabPageIndicatorStyle">
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
style="?attr/vpiTabTextStyle" />
</view>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 Patrik Åkerfeldt
Copyright (C) 2011 Jake Wharton
<!-- Copyright (C) 2012 Jake Wharton
Copyright (C) 2011 Patrik Åkerfeldt
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -19,28 +19,30 @@
<declare-styleable name="ViewPagerIndicator">
<!-- Style of the circle indicator. -->
<attr name="vpiCirclePageIndicatorStyle" format="reference"/>
<!-- Style of the line indicator. -->
<attr name="vpiLinePageIndicatorStyle" format="reference"/>
<!-- Style of the title indicator. -->
<attr name="vpiTitlePageIndicatorStyle" format="reference"/>
<!-- Style of the tab indicator. -->
<!-- Style of the tab indicator's tabs. -->
<attr name="vpiTabPageIndicatorStyle" format="reference"/>
<!-- Style of the text in a tab. -->
<attr name="vpiTabTextStyle" format="reference"/>
<!-- Style of the underline indicator. -->
<attr name="vpiUnderlinePageIndicatorStyle" format="reference"/>
</declare-styleable>
<attr name="centered" format="boolean" />
<attr name="selectedColor" format="color" />
<attr name="strokeWidth" format="dimension" />
<attr name="unselectedColor" format="color" />
<declare-styleable name="CirclePageIndicator">
<!-- Whether or not the indicators should be centered. -->
<attr name="centered" format="boolean" />
<attr name="centered" />
<!-- Color of the filled circle that represents the current page. -->
<attr name="fillColor" format="color" />
<!-- Color of the filled circles that represents pages. -->
<attr name="pageColor" format="color" />
<!-- Orientation of the indicator. -->
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
<attr name="android:orientation"/>
<!-- Radius of the circles. This is also the spacing between circles. -->
<attr name="radius" format="dimension" />
<!-- Whether or not the selected indicator snaps to the circles. -->
@ -48,7 +50,22 @@
<!-- Color of the open circles. -->
<attr name="strokeColor" format="color" />
<!-- Width of the stroke used to draw the circles. -->
<attr name="strokeWidth" format="dimension" />
<attr name="strokeWidth" />
</declare-styleable>
<declare-styleable name="LinePageIndicator">
<!-- Whether or not the indicators should be centered. -->
<attr name="centered" />
<!-- Color of the unselected lines that represent the pages. -->
<attr name="unselectedColor" />
<!-- Color of the selected line that represents the current page. -->
<attr name="selectedColor" />
<!-- Width of each indicator line. -->
<attr name="lineWidth" format="dimension" />
<!-- Width of each indicator line's stroke. -->
<attr name="strokeWidth" />
<!-- Width of the gap between each indicator line. -->
<attr name="gapWidth" format="dimension" />
</declare-styleable>
<declare-styleable name="TitlePageIndicator">
@ -71,16 +88,27 @@
<!-- Padding between the bottom of the title and the footer. -->
<attr name="footerPadding" format="dimension" />
<!-- Color of the selected title. -->
<attr name="selectedColor" format="color" />
<attr name="selectedColor" />
<!-- Whether or not the selected item is displayed as bold. -->
<attr name="selectedBold" format="boolean" />
<!-- Color of regular titles. -->
<attr name="textColor" format="color" />
<attr name="android:textColor" />
<!-- Size of title text. -->
<attr name="textSize" format="dimension" />
<attr name="android:textSize" />
<!-- Padding between titles when bumping into each other. -->
<attr name="titlePadding" format="dimension" />
<!-- Padding between titles and the top of the View. -->
<attr name="topPadding" format="dimension" />
</declare-styleable>
<declare-styleable name="UnderlinePageIndicator">
<!-- Whether or not the selected indicator fades. -->
<attr name="fades" format="boolean" />
<!-- Length of the delay to fade the indicator. -->
<attr name="fadeDelay" format="integer" />
<!-- Length of the indicator fade to transparent. -->
<attr name="fadeLength" format="integer" />
<!-- Color of the selected line that represents the current page. -->
<attr name="selectedColor" />
</declare-styleable>
</resources>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
<!-- 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.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 Jake Wharton
<!-- 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.
@ -24,8 +24,15 @@
<color name="default_circle_indicator_stroke_color">#FFDDDDDD</color>
<dimen name="default_circle_indicator_stroke_width">1dp</dimen>
<dimen name="default_line_indicator_line_width">12dp</dimen>
<dimen name="default_line_indicator_gap_width">4dp</dimen>
<dimen name="default_line_indicator_stroke_width">1dp</dimen>
<color name="default_line_indicator_selected_color">#FF33B5E5</color>
<color name="default_line_indicator_unselected_color">#FFBBBBBB</color>
<bool name="default_line_indicator_centered">true</bool>
<dimen name="default_title_indicator_clip_padding">4dp</dimen>
<color name="default_title_indicator_footer_color">#FF6899FF</color>
<color name="default_title_indicator_footer_color">#FF33B5E5</color>
<dimen name="default_title_indicator_footer_line_height">2dp</dimen>
<integer name="default_title_indicator_footer_indicator_style">2</integer>
<dimen name="default_title_indicator_footer_indicator_height">4dp</dimen>
@ -37,4 +44,9 @@
<dimen name="default_title_indicator_text_size">15dp</dimen>
<dimen name="default_title_indicator_title_padding">5dp</dimen>
<dimen name="default_title_indicator_top_padding">7dp</dimen>
<bool name="default_underline_indicator_fades">true</bool>
<integer name="default_underline_indicator_fade_delay">300</integer>
<integer name="default_underline_indicator_fade_length">400</integer>
<color name="default_underline_indicator_selected_color">#FF33B5E5</color>
</resources>

View File

@ -16,58 +16,26 @@
<resources>
<style name="Theme.PageIndicatorDefaults" parent="android:Theme">
<item name="vpiCirclePageIndicatorStyle">@style/Widget.CirclePageIndicator</item>
<item name="vpiTitlePageIndicatorStyle">@style/Widget.TitlePageIndicator</item>
<item name="vpiTabPageIndicatorStyle">@style/Widget.TabPageIndicator</item>
<item name="vpiTabTextStyle">@style/Widget.TabPageIndicator.Text</item>
</style>
<style name="Widget"></style>
<style name="Widget.CirclePageIndicator" parent="Widget">
<item name="centered">@bool/default_circle_indicator_centered</item>
<item name="fillColor">@color/default_circle_indicator_fill_color</item>
<item name="pageColor">@color/default_circle_indicator_page_color</item>
<item name="orientation">@integer/default_circle_indicator_orientation</item>
<item name="radius">@dimen/default_circle_indicator_radius</item>
<item name="snap">@bool/default_circle_indicator_snap</item>
<item name="strokeColor">@color/default_circle_indicator_stroke_color</item>
<item name="strokeWidth">@dimen/default_circle_indicator_stroke_width</item>
</style>
<style name="Widget.TitlePageIndicator" parent="Widget">
<item name="clipPadding">@dimen/default_title_indicator_clip_padding</item>
<item name="footerColor">@color/default_title_indicator_footer_color</item>
<item name="footerLineHeight">@dimen/default_title_indicator_footer_line_height</item>
<item name="footerIndicatorStyle">@integer/default_title_indicator_footer_indicator_style</item>
<item name="footerIndicatorHeight">@dimen/default_title_indicator_footer_indicator_height</item>
<item name="footerIndicatorUnderlinePadding">@dimen/default_title_indicator_footer_indicator_underline_padding</item>
<item name="footerPadding">@dimen/default_title_indicator_footer_padding</item>
<item name="selectedColor">@color/default_title_indicator_selected_color</item>
<item name="selectedBold">@bool/default_title_indicator_selected_bold</item>
<item name="textColor">@color/default_title_indicator_text_color</item>
<item name="textSize">@dimen/default_title_indicator_text_size</item>
<item name="titlePadding">@dimen/default_title_indicator_title_padding</item>
<item name="topPadding">@dimen/default_title_indicator_top_padding</item>
<style name="Widget">
</style>
<style name="Widget.TabPageIndicator" parent="Widget">
<item name="android:gravity">center_horizontal</item>
<item name="android:gravity">center</item>
<item name="android:background">@drawable/vpi__tab_indicator</item>
<item name="android:paddingLeft">22dip</item>
<item name="android:paddingRight">22dip</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
</style>
<style name="Widget.TabPageIndicator.Text" parent="Widget">
<item name="android:textAppearance">@style/TextAppearance.TabPageIndicator</item>
<item name="android:textColor">@color/vpi__dark_theme</item>
<item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item>
<item name="android:maxLines">1</item>
</style>
<style name="TextAppearance.TabPageIndicator" parent="Widget">
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/vpi__dark_theme</item>
</style>
</resources>

View File

@ -32,31 +32,31 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
/**
* 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 static final int INVALID_POINTER = -1;
private float mRadius;
private final Paint mPaintPageFill;
private final Paint mPaintStroke;
private final Paint mPaintFill;
private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
private ViewPager mViewPager;
private ViewPager.OnPageChangeListener mListener;
private int mCurrentPage;
private int mSnapPage;
private int mCurrentOffset;
private float mPageOffset;
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;
@ -73,6 +73,7 @@ public class CirclePageIndicator extends View implements PageIndicator {
public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) return;
//Load defaults from resources
final Resources res = getResources();
@ -86,18 +87,15 @@ public class CirclePageIndicator extends View implements PageIndicator {
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);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);
mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
mOrientation = a.getInt(R.styleable.CirclePageIndicator_orientation, defaultOrientation);
mPaintPageFill = new Paint(Paint.ANTI_ALIAS_FLAG);
mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
mPaintPageFill.setStyle(Style.FILL);
mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
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);
@ -142,7 +140,6 @@ public class CirclePageIndicator extends View implements PageIndicator {
case HORIZONTAL:
case VERTICAL:
mOrientation = orientation;
updatePageSize();
requestLayout();
break;
@ -191,11 +188,6 @@ public class CirclePageIndicator extends View implements PageIndicator {
return mSnap;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@ -267,8 +259,8 @@ public class CirclePageIndicator extends View implements PageIndicator {
//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 (!mSnap) {
cx += mPageOffset * threeRadius;
}
if (mOrientation == HORIZONTAL) {
dX = longOffset + cx;
@ -308,13 +300,10 @@ public class CirclePageIndicator extends View implements PageIndicator {
}
if (mIsDragging) {
if (!mViewPager.isFakeDragging()) {
mViewPager.beginFakeDrag();
}
mLastMotionX = x;
mViewPager.fakeDragBy(deltaX);
if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
mViewPager.fakeDragBy(deltaX);
}
}
break;
@ -366,21 +355,20 @@ public class CirclePageIndicator extends View implements PageIndicator {
@Override
public void setViewPager(ViewPager view) {
if (mViewPager == view) {
return;
}
if (mViewPager != null) {
mViewPager.setOnPageChangeListener(null);
}
if (view.getAdapter() == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
mViewPager = view;
mViewPager.setOnPageChangeListener(this);
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);
@ -414,8 +402,7 @@ public class CirclePageIndicator extends View implements PageIndicator {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mCurrentPage = position;
mCurrentOffset = positionOffsetPixels;
updatePageSize();
mPageOffset = positionOffset;
invalidate();
if (mListener != null) {

View File

@ -0,0 +1,439 @@
/*
* 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.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));
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();
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) {
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)) {
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 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 = 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 = 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 = 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 = 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);
}
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];
}
};
}
}

View File

@ -29,7 +29,7 @@ public interface PageIndicator extends ViewPager.OnPageChangeListener {
*
* @param view
*/
public void setViewPager(ViewPager view);
void setViewPager(ViewPager view);
/**
* Bind the indicator to a ViewPager.
@ -37,7 +37,7 @@ public interface PageIndicator extends ViewPager.OnPageChangeListener {
* @param view
* @param initialPosition
*/
public void setViewPager(ViewPager view, int initialPosition);
void setViewPager(ViewPager view, int initialPosition);
/**
* <p>Set the current page of both the ViewPager and indicator.</p>
@ -47,17 +47,17 @@ public interface PageIndicator extends ViewPager.OnPageChangeListener {
*
* @param item
*/
public void setCurrentItem(int item);
void setCurrentItem(int item);
/**
* Set a page change listener which will receive forwarded events.
*
* @param listener
*/
public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);
void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);
/**
* Notify the indicator that the fragment list has changed.
*/
public void notifyDataSetChanged();
void notifyDataSetChanged();
}

View File

@ -21,36 +21,58 @@ 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;
import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* 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;
/** Title text used when no title is provided by the adapter. */
private static final CharSequence EMPTY_TITLE = "";
private OnClickListener mTabClickListener = new OnClickListener() {
/**
* Interface for a callback when the selected tab has been reselected.
*/
public interface OnTabReselectedListener {
/**
* Callback when the selected tab has been reselected.
*
* @param position Position of the current center item.
*/
void onTabReselected(int position);
}
private Runnable mTabSelector;
private final OnClickListener mTabClickListener = new OnClickListener() {
public void onClick(View view) {
TabView tabView = (TabView)view;
mViewPager.setCurrentItem(tabView.getIndex());
final int oldSelected = mViewPager.getCurrentItem();
final int newSelected = tabView.getIndex();
mViewPager.setCurrentItem(newSelected);
if (oldSelected == newSelected && mTabReselectedListener != null) {
mTabReselectedListener.onTabReselected(newSelected);
}
}
};
private LinearLayout mTabLayout;
private final LinearLayout mTabLayout;
private ViewPager mViewPager;
private ViewPager.OnPageChangeListener mListener;
private LayoutInflater mInflater;
int mMaxTabWidth;
private int mMaxTabWidth;
private int mSelectedTabIndex;
private OnTabReselectedListener mTabReselectedListener;
public TabPageIndicator(Context context) {
this(context, null);
}
@ -59,10 +81,12 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
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));
addView(mTabLayout, new ViewGroup.LayoutParams(WRAP_CONTENT, FILL_PARENT));
}
public void setOnTabReselectedListener(OnTabReselectedListener listener) {
mTabReselectedListener = listener;
}
@Override
@ -124,14 +148,14 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
}
}
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);
private void addTab(CharSequence text, int index) {
final TabView tabView = new TabView(getContext());
tabView.mIndex = index;
tabView.setFocusable(true);
tabView.setOnClickListener(mTabClickListener);
tabView.setText(text);
mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, LayoutParams.FILL_PARENT, 1));
mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, FILL_PARENT, 1));
}
@Override
@ -158,13 +182,16 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
@Override
public void setViewPager(ViewPager view) {
if (mViewPager == view) {
return;
}
if (mViewPager != null) {
mViewPager.setOnPageChangeListener(null);
}
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();
@ -172,10 +199,14 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
public void notifyDataSetChanged() {
mTabLayout.removeAllViews();
TitleProvider adapter = (TitleProvider)mViewPager.getAdapter();
final int count = ((PagerAdapter)adapter).getCount();
PagerAdapter adapter = mViewPager.getAdapter();
final int count = adapter.getCount();
for (int i = 0; i < count; i++) {
addTab(adapter.getTitle(i), i);
CharSequence title = adapter.getPageTitle(i);
if (title == null) {
title = EMPTY_TITLE;
}
addTab(title, i);
}
if (mSelectedTabIndex > count) {
mSelectedTabIndex = count - 1;
@ -196,6 +227,8 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
throw new IllegalStateException("ViewPager has not been bound.");
}
mSelectedTabIndex = item;
mViewPager.setCurrentItem(item);
final int tabCount = mTabLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {
final View child = mTabLayout.getChildAt(i);
@ -212,20 +245,11 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
mListener = listener;
}
public static class TabView extends LinearLayout {
private TabPageIndicator mParent;
private class TabView extends TextView {
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);
public TabView(Context context) {
super(context, null, R.attr.vpiTabPageIndicatorStyle);
}
@Override
@ -233,8 +257,8 @@ public class TabPageIndicator extends HorizontalScrollView implements PageIndica
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),
if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
heightMeasureSpec);
}
}

View File

@ -24,7 +24,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
@ -58,16 +58,21 @@ public class TitlePageIndicator extends View implements PageIndicator {
*/
private static final float BOLD_FADE_PERCENTAGE = 0.05f;
/**
* Title text used when no title is provided by the adapter.
*/
private static final String EMPTY_TITLE = "";
/**
* Interface for a callback when the center item has been clicked.
*/
public static interface OnCenterItemClickListener {
public interface OnCenterItemClickListener {
/**
* Callback when the center item has been clicked.
*
* @param position Position of the current center item.
*/
public void onCenterItemClick(int position);
void onCenterItemClick(int position);
}
public enum IndicatorStyle {
@ -91,15 +96,16 @@ public class TitlePageIndicator extends View implements PageIndicator {
private ViewPager mViewPager;
private ViewPager.OnPageChangeListener mListener;
private TitleProvider mTitleProvider;
private int mCurrentPage;
private int mCurrentOffset;
private PagerAdapter mPagerAdapter;
private int mCurrentPage = -1;
private float mPageOffset;
private int mScrollState;
private final Paint mPaintText = new Paint();
private boolean mBoldText;
private int mColorText;
private int mColorSelected;
private Path mPath;
private Path mPath = new Path();
private final Rect mBounds = new Rect();
private final Paint mPaintFooterLine = new Paint();
private IndicatorStyle mFooterIndicatorStyle;
private final Paint mPaintFooterIndicator = new Paint();
@ -132,6 +138,7 @@ public class TitlePageIndicator extends View implements PageIndicator {
public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) return;
//Load defaults from resources
final Resources res = getResources();
@ -150,7 +157,7 @@ public class TitlePageIndicator extends View implements PageIndicator {
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);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, 0);
//Retrieve the colors to be used for this view and apply them.
mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight);
@ -162,10 +169,10 @@ public class TitlePageIndicator extends View implements PageIndicator {
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);
mColorText = a.getColor(R.styleable.TitlePageIndicator_android_textColor, defaultTextColor);
mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold);
final float textSize = a.getDimension(R.styleable.TitlePageIndicator_textSize, defaultTextSize);
final float textSize = a.getDimension(R.styleable.TitlePageIndicator_android_textSize, defaultTextSize);
final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor);
mPaintText.setTextSize(textSize);
mPaintText.setAntiAlias(true);
@ -319,8 +326,11 @@ public class TitlePageIndicator extends View implements PageIndicator {
return;
}
// mCurrentPage is -1 on first start and after orientation changed. If so, retrieve the correct index from viewpager.
if(mCurrentPage == -1 && mViewPager != null) mCurrentPage = mViewPager.getCurrentItem();
//Calculate views bounds
ArrayList<RectF> bounds = calculateAllBounds(mPaintText);
ArrayList<Rect> bounds = calculateAllBounds(mPaintText);
final int boundsSize = bounds.size();
//Make sure we're on a page that still exists
@ -340,18 +350,18 @@ public class TitlePageIndicator extends View implements PageIndicator {
int page = mCurrentPage;
float offsetPercent;
if (mCurrentOffset <= halfWidth) {
offsetPercent = 1.0f * mCurrentOffset / width;
if (mPageOffset <= 0.5) {
offsetPercent = mPageOffset;
} else {
page += 1;
offsetPercent = 1.0f * (width - mCurrentOffset) / width;
offsetPercent = 1 - mPageOffset;
}
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);
Rect curPageBound = bounds.get(mCurrentPage);
float curPageWidth = curPageBound.right - curPageBound.left;
if (curPageBound.left < leftClip) {
//Try to clip to the screen (left side)
@ -365,17 +375,17 @@ public class TitlePageIndicator extends View implements PageIndicator {
//Left views starting from the current position
if (mCurrentPage > 0) {
for (int i = mCurrentPage - 1; i >= 0; i--) {
RectF bound = bounds.get(i);
Rect bound = bounds.get(i);
//Is left side is outside the screen
if (bound.left < leftClip) {
float w = bound.right - bound.left;
int 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);
Rect rightBound = bounds.get(i + 1);
//Intersection
if (bound.right + mTitlePadding > rightBound.left) {
bound.left = rightBound.left - w - mTitlePadding;
bound.left = (int) (rightBound.left - w - mTitlePadding);
bound.right = bound.left + w;
}
}
@ -384,17 +394,17 @@ public class TitlePageIndicator extends View implements PageIndicator {
//Right views starting from the current position
if (mCurrentPage < countMinusOne) {
for (int i = mCurrentPage + 1 ; i < count; i++) {
RectF bound = bounds.get(i);
Rect bound = bounds.get(i);
//If right side is outside the screen
if (bound.right > rightClip) {
float w = bound.right - bound.left;
int 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);
Rect leftBound = bounds.get(i - 1);
//Intersection
if (bound.left - mTitlePadding < leftBound.right) {
bound.left = leftBound.right + mTitlePadding;
bound.left = (int) (leftBound.right + mTitlePadding);
bound.right = bound.left + w;
}
}
@ -405,10 +415,12 @@ public class TitlePageIndicator extends View implements PageIndicator {
int colorTextAlpha = mColorText >>> 24;
for (int i = 0; i < count; i++) {
//Get the title
RectF bound = bounds.get(i);
Rect 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);
final CharSequence pageTitle = getTitle(i);
//Only set bold if we are within bounds
mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText);
@ -418,19 +430,19 @@ public class TitlePageIndicator extends View implements PageIndicator {
//Fade out/in unselected text as the selected text fades in/out
mPaintText.setAlpha(colorTextAlpha - (int)(colorTextAlpha * selectedPercent));
}
canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText);
canvas.drawText(pageTitle, 0, pageTitle.length(), 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);
canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText);
}
}
}
//Draw the footer line
mPath = new Path();
mPath.reset();
mPath.moveTo(0, height - mFooterLineHeight / 2f);
mPath.lineTo(width, height - mFooterLineHeight / 2f);
mPath.close();
@ -438,7 +450,7 @@ public class TitlePageIndicator extends View implements PageIndicator {
switch (mFooterIndicatorStyle) {
case Triangle:
mPath = new Path();
mPath.reset();
mPath.moveTo(halfWidth, height - mFooterLineHeight - mFooterIndicatorHeight);
mPath.lineTo(halfWidth + mFooterIndicatorHeight, height - mFooterLineHeight);
mPath.lineTo(halfWidth - mFooterIndicatorHeight, height - mFooterLineHeight);
@ -451,8 +463,8 @@ public class TitlePageIndicator extends View implements PageIndicator {
break;
}
RectF underlineBounds = bounds.get(page);
mPath = new Path();
Rect underlineBounds = bounds.get(page);
mPath.reset();
mPath.moveTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight);
mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight);
mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight);
@ -494,13 +506,10 @@ public class TitlePageIndicator extends View implements PageIndicator {
}
if (mIsDragging) {
if (!mViewPager.isFakeDragging()) {
mViewPager.beginFakeDrag();
}
mLastMotionX = x;
mViewPager.fakeDragBy(deltaX);
if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
mViewPager.fakeDragBy(deltaX);
}
}
break;
@ -570,9 +579,9 @@ public class TitlePageIndicator extends View implements PageIndicator {
* @param curViewWidth
* width of the view.
*/
private void clipViewOnTheRight(RectF curViewBound, float curViewWidth, int right) {
curViewBound.right = right - mClipPadding;
curViewBound.left = curViewBound.right - curViewWidth;
private void clipViewOnTheRight(Rect curViewBound, float curViewWidth, int right) {
curViewBound.right = (int) (right - mClipPadding);
curViewBound.left = (int) (curViewBound.right - curViewWidth);
}
/**
@ -583,29 +592,28 @@ public class TitlePageIndicator extends View implements PageIndicator {
* @param curViewWidth
* width of the view.
*/
private void clipViewOnTheLeft(RectF curViewBound, float curViewWidth, int left) {
curViewBound.left = left + mClipPadding;
curViewBound.right = mClipPadding + curViewWidth;
private void clipViewOnTheLeft(Rect curViewBound, float curViewWidth, int left) {
curViewBound.left = (int) (left + mClipPadding);
curViewBound.right = (int) (mClipPadding + curViewWidth);
}
/**
* Calculate views bounds and scroll them according to the current index
*
* @param paint
* @param currentIndex
* @return
*/
private ArrayList<RectF> calculateAllBounds(Paint paint) {
ArrayList<RectF> list = new ArrayList<RectF>();
private ArrayList<Rect> calculateAllBounds(Paint paint) {
ArrayList<Rect> list = new ArrayList<Rect>();
//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);
Rect bounds = calcBounds(i, paint);
int w = bounds.right - bounds.left;
int h = bounds.bottom - bounds.top;
bounds.left = (int)(halfWidth - (w / 2f) + ((i - mCurrentPage - mPageOffset) * width));
bounds.right = bounds.left + w;
bounds.top = 0;
bounds.bottom = h;
@ -622,26 +630,29 @@ public class TitlePageIndicator extends View implements PageIndicator {
* @param paint
* @return
*/
private RectF calcBounds(int index, Paint paint) {
private Rect 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();
Rect bounds = new Rect();
CharSequence title = getTitle(index);
bounds.right = (int) paint.measureText(title, 0, title.length());
bounds.bottom = (int) (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 (mViewPager == view) {
return;
}
if (!(adapter instanceof TitleProvider)) {
throw new IllegalStateException("ViewPager adapter must implement TitleProvider to be used with TitlePageIndicator.");
if (mViewPager != null) {
mViewPager.setOnPageChangeListener(null);
}
mPagerAdapter = view.getAdapter();
if (mPagerAdapter == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
mViewPager = view;
mViewPager.setOnPageChangeListener(this);
mTitleProvider = (TitleProvider)adapter;
invalidate();
}
@ -687,7 +698,7 @@ public class TitlePageIndicator extends View implements PageIndicator {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mCurrentPage = position;
mCurrentOffset = positionOffsetPixels;
mPageOffset = positionOffset;
invalidate();
if (mListener != null) {
@ -725,9 +736,9 @@ public class TitlePageIndicator extends View implements PageIndicator {
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;
mBounds.setEmpty();
mBounds.bottom = (int) (mPaintText.descent() - mPaintText.ascent());
height = mBounds.bottom - mBounds.top + mFooterLineHeight + mFooterPadding + mTopPadding;
if (mFooterIndicatorStyle != IndicatorStyle.None) {
height += mFooterIndicatorHeight;
}
@ -783,4 +794,12 @@ public class TitlePageIndicator extends View implements PageIndicator {
}
};
}
private CharSequence getTitle(int i) {
CharSequence title = mPagerAdapter.getPageTitle(i);
if (title == null) {
title = EMPTY_TITLE;
}
return title.toString();
}
}

View File

@ -1,28 +0,0 @@
/*
* 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);
}

View File

@ -0,0 +1,391 @@
/*
* 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.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 UnderlinePageIndicator extends View implements PageIndicator {
private static final int INVALID_POINTER = -1;
private static final int FADE_FRAME_MS = 30;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean mFades;
private int mFadeDelay;
private int mFadeLength;
private int mFadeBy;
private ViewPager mViewPager;
private ViewPager.OnPageChangeListener mListener;
private int mScrollState;
private int mCurrentPage;
private float mPositionOffset;
private int mTouchSlop;
private float mLastMotionX = -1;
private int mActivePointerId = INVALID_POINTER;
private boolean mIsDragging;
private final Runnable mFadeRunnable = new Runnable() {
@Override public void run() {
final int alpha = Math.max(mPaint.getAlpha() - mFadeBy, 0);
mPaint.setAlpha(alpha);
invalidate();
if (alpha > 0) {
postDelayed(this, FADE_FRAME_MS);
}
}
};
public UnderlinePageIndicator(Context context) {
this(context, null);
}
public UnderlinePageIndicator(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.vpiUnderlinePageIndicatorStyle);
}
public UnderlinePageIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) return;
final Resources res = getResources();
//Load defaults from resources
final boolean defaultFades = res.getBoolean(R.bool.default_underline_indicator_fades);
final int defaultFadeDelay = res.getInteger(R.integer.default_underline_indicator_fade_delay);
final int defaultFadeLength = res.getInteger(R.integer.default_underline_indicator_fade_length);
final int defaultSelectedColor = res.getColor(R.color.default_underline_indicator_selected_color);
//Retrieve styles attributes
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UnderlinePageIndicator, defStyle, 0);
setFades(a.getBoolean(R.styleable.UnderlinePageIndicator_fades, defaultFades));
setSelectedColor(a.getColor(R.styleable.UnderlinePageIndicator_selectedColor, defaultSelectedColor));
setFadeDelay(a.getInteger(R.styleable.UnderlinePageIndicator_fadeDelay, defaultFadeDelay));
setFadeLength(a.getInteger(R.styleable.UnderlinePageIndicator_fadeLength, defaultFadeLength));
a.recycle();
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}
public boolean getFades() {
return mFades;
}
public void setFades(boolean fades) {
if (fades != mFades) {
mFades = fades;
if (fades) {
post(mFadeRunnable);
} else {
removeCallbacks(mFadeRunnable);
mPaint.setAlpha(0xFF);
invalidate();
}
}
}
public int getFadeDelay() {
return mFadeDelay;
}
public void setFadeDelay(int fadeDelay) {
mFadeDelay = fadeDelay;
}
public int getFadeLength() {
return mFadeLength;
}
public void setFadeLength(int fadeLength) {
mFadeLength = fadeLength;
mFadeBy = 0xFF / (mFadeLength / FADE_FRAME_MS);
}
public int getSelectedColor() {
return mPaint.getColor();
}
public void setSelectedColor(int selectedColor) {
mPaint.setColor(selectedColor);
invalidate();
}
@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 int paddingLeft = getPaddingLeft();
final float pageWidth = (getWidth() - paddingLeft - getPaddingRight()) / (1f * count);
final float left = paddingLeft + pageWidth * (mCurrentPage + mPositionOffset);
final float right = left + pageWidth;
final float top = getPaddingTop();
final float bottom = getHeight() - getPaddingBottom();
canvas.drawRect(left, top, right, bottom, mPaint);
}
public boolean onTouchEvent(MotionEvent ev) {
if (super.onTouchEvent(ev)) {
return true;
}
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) {
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)) {
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 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();
post(new Runnable() {
@Override public void run() {
if (mFades) {
post(mFadeRunnable);
}
}
});
}
@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;
mPositionOffset = positionOffset;
if (mFades) {
if (positionOffsetPixels > 0) {
removeCallbacks(mFadeRunnable);
mPaint.setAlpha(0xFF);
} else if (mScrollState != ViewPager.SCROLL_STATE_DRAGGING) {
postDelayed(mFadeRunnable, mFadeDelay);
}
}
invalidate();
if (mListener != null) {
mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(int position) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
mCurrentPage = position;
mPositionOffset = 0;
invalidate();
mFadeRunnable.run();
}
if (mListener != null) {
mListener.onPageSelected(position);
}
}
@Override
public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
mListener = listener;
}
@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 Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}