Walkthrough tutorial upon app launch

pull/14/head
Andrew Shaw 14 years ago committed by Sam Bosley
parent 859bc80dc2
commit d1e9b4c7c0

@ -29,5 +29,6 @@
<classpathentry exported="true" kind="lib" path="libs/google-api-services-tasks-v1-1.2.5-beta.jar"/> <classpathentry exported="true" kind="lib" path="libs/google-api-services-tasks-v1-1.2.5-beta.jar"/>
<classpathentry kind="lib" path="libs/crittercism_v1_1_3.jar"/> <classpathentry kind="lib" path="libs/crittercism_v1_1_3.jar"/>
<classpathentry kind="lib" path="libs/jchronic-0.2.3.jar"/> <classpathentry kind="lib" path="libs/jchronic-0.2.3.jar"/>
<classpathentry kind="lib" path="libs/android-support-v4.jar"/>
<classpathentry kind="output" path="bin/classes"/> <classpathentry kind="output" path="bin/classes"/>
</classpath> </classpath>

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timsu.astrid" package="com.timsu.astrid"
android:versionName="3.9.1.1" android:versionName="3.9.2"
android:versionCode="206"> android:versionCode="207">
<!-- widgets, alarms, and services will break if Astrid is installed on SD card --> <!-- widgets, alarms, and services will break if Astrid is installed on SD card -->
<!-- android:installLocation="internalOnly"> --> <!-- android:installLocation="internalOnly"> -->
@ -649,6 +649,9 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<activity android:name="com.todoroo.astrid.welcome.tutorial.WelcomeWalkthrough"
android:windowSoftInputMode="stateHidden"
android:screenOrientation="portrait"></activity>
</application> </application>

@ -142,7 +142,7 @@
replace="\1 true;" /> replace="\1 true;" />
</target> </target>
<target name="obfuscate"> <target name="obfuscate" depends="-setup">
<property name="proguard.enabled" value="true" /> <property name="proguard.enabled" value="true" />
<antcall target="-obfuscate" /> <antcall target="-obfuscate" />
</target> </target>

@ -8,15 +8,6 @@
package com.localytics.android; package com.localytics.android;
import android.Manifest.permission;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -27,6 +18,15 @@ import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import android.Manifest.permission;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.Log;
/** /**
* Provides a number of static functions to aid in the collection and formatting of datapoints. * Provides a number of static functions to aid in the collection and formatting of datapoints.
* <p> * <p>

@ -1,9 +1,9 @@
package com.localytics.android; package com.localytics.android;
import android.Manifest.permission;
import org.json.JSONArray; import org.json.JSONArray;
import android.Manifest.permission;
/** /**
* Set of constants for building JSON objects that get sent to the Localytics web service. * Set of constants for building JSON objects that get sent to the Localytics web service.
*/ */

@ -1,5 +1,13 @@
package com.localytics.android; package com.localytics.android;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -10,14 +18,6 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import android.util.Log; import android.util.Log;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/** /**
* Implements the storage mechanism for the Localytics library. The interface and implementation are similar to a ContentProvider * Implements the storage mechanism for the Localytics library. The interface and implementation are similar to a ContentProvider
* but modified to be better suited to a library. The interface is table-oriented, rather than Uri-oriented. * but modified to be better suited to a library. The interface is table-oriented, rather than Uri-oriented.

@ -8,23 +8,6 @@
package com.localytics.android; package com.localytics.android;
import android.Manifest.permission;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorJoiner;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -51,6 +34,23 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import android.Manifest.permission;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorJoiner;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import com.localytics.android.JsonObjects.BlobHeader; import com.localytics.android.JsonObjects.BlobHeader;
import com.localytics.android.LocalyticsProvider.ApiKeysDbColumns; import com.localytics.android.LocalyticsProvider.ApiKeysDbColumns;
import com.localytics.android.LocalyticsProvider.AttributesDbColumns; import com.localytics.android.LocalyticsProvider.AttributesDbColumns;

@ -136,6 +136,7 @@ public class ActFmLoginActivity extends Activity implements AuthListener {
ContextManager.setContext(this); ContextManager.setContext(this);
setContentView(getContentViewResource()); setContentView(getContentViewResource());
if(getTitleResource() != 0)
setTitle(getTitleResource()); setTitle(getTitleResource());
rand = new Random(DateUtilities.now()); rand = new Random(DateUtilities.now());
@ -143,15 +144,6 @@ public class ActFmLoginActivity extends Activity implements AuthListener {
noSync = getIntent().getBooleanExtra(EXTRA_DO_NOT_SYNC, false); noSync = getIntent().getBooleanExtra(EXTRA_DO_NOT_SYNC, false);
showToast = getIntent().getBooleanExtra(SHOW_TOAST, true); showToast = getIntent().getBooleanExtra(SHOW_TOAST, true);
facebook = new Facebook(APP_ID);
facebookRunner = new AsyncFacebookRunner(facebook);
errors = (TextView) findViewById(R.id.error);
LoginButton loginButton = (LoginButton) findViewById(R.id.fb_login);
loginButton.init(this, facebook, this, new String[] { "email",
"offline_access", "publish_stream" });
initializeUI(); initializeUI();
getWindow().setFormat(PixelFormat.RGBA_8888); getWindow().setFormat(PixelFormat.RGBA_8888);
@ -195,6 +187,14 @@ public class ActFmLoginActivity extends Activity implements AuthListener {
} }
protected void initializeUI() { protected void initializeUI() {
facebook = new Facebook(APP_ID);
facebookRunner = new AsyncFacebookRunner(facebook);
errors = (TextView) findViewById(R.id.error);
LoginButton loginButton = (LoginButton) findViewById(R.id.fb_login);
loginButton.init(this, facebook, this, new String[] { "email",
"offline_access", "publish_stream" });
findViewById(R.id.gg_login).setOnClickListener(googleListener); findViewById(R.id.gg_login).setOnClickListener(googleListener);
Button pwLogin = (Button) findViewById(R.id.pw_login); Button pwLogin = (Button) findViewById(R.id.pw_login);
pwLogin.setOnClickListener(signUpListener); pwLogin.setOnClickListener(signUpListener);

@ -15,3 +15,4 @@ android.library.reference.2=../greendroid/GreenDroid/
# Project target. # Project target.
target=android-14 target=android-14
apk-configurations= apk-configurations=
android.library.reference.4=../viewPagerIndicator/library

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- xmlns:app="http://schemas.android.com/apk/res/com.viewpagerindicator.sample" -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<com.viewpagerindicator.CirclePageIndicator
android:id="@+id/indicator"
android:padding="15dip"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_alignBottom="@id/pager"
android:background="#00000000"
style="@style/CustomCirclePageIndicator"
/>
<!--
app:radius="10dp"
app:fillColor="#FF888888"
app:strokeColor="#FF000000"
app:strokeWidth="2dp" -->
</RelativeLayout>
</LinearLayout>

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:background="@drawable/welcome_walkthrough_fabric"
android:orientation="vertical">
<TextView
android:id="@+id/logo"
android:layout_width="fill_parent"
android:layout_height="110dip"
android:gravity="center_horizontal"
android:paddingBottom="10dip"
android:layout_marginTop="30dip"
android:text="Connect now\nto get started!"
android:textColor="#444444"
android:textSize="40dip"
android:textStyle="bold"
android:shadowRadius="2"
android:shadowDy="1"
android:shadowColor="@android:color/white" />
<TextView
android:id="@+id/error"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#ff0000"
android:textSize="16sp"
android:textStyle="bold" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="253dp"
android:paddingTop="5dip" >
<TextView
android:id="@+id/login_later"
android:layout_width="fill_parent"
android:layout_height="45dip"
android:gravity="center"
android:layout_alignParentBottom="true"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:paddingBottom="5dip"
android:textSize="16sp"
android:textColor="#ffffff" />
<Button
android:id="@+id/pw_login"
android:layout_width="fill_parent"
android:layout_height="45dip"
android:layout_above="@id/login_later"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:layout_marginLeft="15dip"
android:layout_marginRight="15dip"
android:textSize="16sp"
android:text="@string/welcome_login_pw"/>
<com.facebook.android.LoginButton
android:id="@+id/fb_login"
android:layout_width="fill_parent"
android:layout_height="45dip"
android:layout_above="@id/pw_login"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:layout_marginLeft="15dip"
android:layout_marginRight="15dip"
android:textSize="16sp"
android:drawableLeft="@drawable/facebook"
android:text="@string/actfm_ALA_fb_login" />
<Button
android:id="@+id/gg_login"
android:layout_width="fill_parent"
android:layout_height="45dip"
android:layout_above="@id/fb_login"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:layout_marginLeft="15dip"
android:layout_marginRight="15dip"
android:textSize="16sp"
android:drawableLeft="@drawable/google"
android:text="@string/actfm_ALA_gg_login" />
<TextView
android:id="@+id/tos"
android:layout_width="fill_parent"
android:layout_height="35dip"
android:layout_above="@id/gg_login"
android:gravity="center"
android:paddingLeft="20dip"
android:paddingRight="20dip"
android:textSize="12.5sp"
android:textColor="#ffffff" />
</RelativeLayout>
</LinearLayout>

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/welcome_walkthrough_fabric"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:src="@drawable/welcome_walkthrough_fabric" />
<TextView
android:id="@+id/welcome_walkthrough_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="27dp"
android:gravity="center_horizontal"
android:text="Hello"
android:textColor="#444444"
android:textSize="40dip"
android:textStyle="bold"
android:shadowRadius="2"
android:shadowDy="1"
android:shadowColor="@android:color/white" />
<ImageView
android:id="@+id/welcome_walkthrough_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:src="@drawable/welcome_walkthrough_1" />
<TextView
android:id="@+id/welcome_walkthrough_body"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="75dp"
android:gravity="center_horizontal"
android:text="Hello"
android:textColor="#444444"
android:textSize="20dip"
android:textStyle="bold"
android:shadowRadius="2"
android:shadowDy="1"
android:shadowColor="@android:color/white" />
</RelativeLayout>
</LinearLayout>

@ -185,10 +185,10 @@
share, and delegate with others.</string> share, and delegate with others.</string>
<!-- share login: Sharing Login FB Prompt --> <!-- share login: Sharing Login FB Prompt -->
<string name="actfm_ALA_fb_login">Login with Facebook</string> <string name="actfm_ALA_fb_login">Connect with Facebook</string>
<!-- share login: Sharing Login GG Prompt --> <!-- share login: Sharing Login GG Prompt -->
<string name="actfm_ALA_gg_login">Login with Google</string> <string name="actfm_ALA_gg_login">Connect with Google</string>
<!-- share login: Sharing Footer Password Label --> <!-- share login: Sharing Footer Password Label -->
<string name="actfm_ALA_pw_login">Don\'t use Google or Facebook?</string> <string name="actfm_ALA_pw_login">Don\'t use Google or Facebook?</string>

@ -71,7 +71,7 @@
<string name="welcome_login_pw">Login with Username/Password</string> <string name="welcome_login_pw">Login with Username/Password</string>
<string name="welcome_login_later">Login Later</string> <string name="welcome_login_later">Connect Later</string>
<string name="welcome_login_confirm_later_title">Why not sign in?</string> <string name="welcome_login_confirm_later_title">Why not sign in?</string>

@ -231,5 +231,13 @@
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
<item name="android:textColor">@android:color/black</item> <item name="android:textColor">@android:color/black</item>
</style> </style>
<style name="CustomCirclePageIndicator">
<item name="fillColor">#FFFFFFFF</item>
<item name="strokeColor">#FF000000</item>
<item name="strokeWidth">2dp</item>
<item name="radius">10dp</item>
<item name="centered">true</item>
<item name="android:background">#FFCCCCCC</item>
</style>
</resources> </resources>

@ -115,6 +115,7 @@ import com.todoroo.astrid.utility.Flags;
import com.todoroo.astrid.voice.VoiceInputAssistant; import com.todoroo.astrid.voice.VoiceInputAssistant;
import com.todoroo.astrid.welcome.HelpInfoPopover; import com.todoroo.astrid.welcome.HelpInfoPopover;
import com.todoroo.astrid.welcome.WelcomeLogin; import com.todoroo.astrid.welcome.WelcomeLogin;
import com.todoroo.astrid.welcome.tutorial.WelcomeWalkthrough;
import com.todoroo.astrid.widget.TasksWidget; import com.todoroo.astrid.widget.TasksWidget;
/** /**
@ -578,8 +579,10 @@ public class TaskListActivity extends ListActivity implements OnScrollListener,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS)); new IntentFilter(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS));
setUpBackgroundJobs(); setUpBackgroundJobs();
//TODO REMOVE THIS GUY
//if (true){
if (!Preferences.getBoolean(WelcomeLogin.KEY_SHOWED_WELCOME_LOGIN, false)) { if (!Preferences.getBoolean(WelcomeLogin.KEY_SHOWED_WELCOME_LOGIN, false)) {
Intent showWelcomeLogin = new Intent(this, WelcomeLogin.class); Intent showWelcomeLogin = new Intent(this, WelcomeWalkthrough.class);
showWelcomeLogin.putExtra(ActFmLoginActivity.SHOW_TOAST, false); showWelcomeLogin.putExtra(ActFmLoginActivity.SHOW_TOAST, false);
startActivity(showWelcomeLogin); startActivity(showWelcomeLogin);
Preferences.setBoolean(WelcomeLogin.KEY_SHOWED_WELCOME_LOGIN, true); Preferences.setBoolean(WelcomeLogin.KEY_SHOWED_WELCOME_LOGIN, true);

@ -40,6 +40,7 @@ import com.todoroo.astrid.utility.AstridPreferences;
public final class UpgradeService { public final class UpgradeService {
public static final int V3_9_2 = 207;
public static final int V3_9_1_1 = 206; public static final int V3_9_1_1 = 206;
public static final int V3_9_1 = 205; public static final int V3_9_1 = 205;
public static final int V3_9_0_2 = 204; public static final int V3_9_0_2 = 204;
@ -170,6 +171,15 @@ public final class UpgradeService {
Preferences.clear(AstridPreferences.P_UPGRADE_FROM); Preferences.clear(AstridPreferences.P_UPGRADE_FROM);
StringBuilder changeLog = new StringBuilder(); StringBuilder changeLog = new StringBuilder();
if (from >= V3_9_1_1 && from < V3_9_2) {
newVersionString(changeLog, "3.9.2 (01/13/12)", new String[] {
"Made selecting dates and times easier:",
"New tutorial walkthrough for new users",
"Stomped on a few bugs",
"Feedback welcomed!"
});
}
if (from >= V3_9_1 && from < V3_9_1_1) { if (from >= V3_9_1 && from < V3_9_1_1) {
newVersionString(changeLog, "3.9.1.1 (01/06/12)", new String[] { newVersionString(changeLog, "3.9.1.1 (01/06/12)", new String[] {
"Fixed a few bugs:", "Fixed a few bugs:",

@ -61,7 +61,7 @@ public class WelcomeLogin extends ActFmLoginActivity implements AuthListener {
setupLoginLater(); setupLoginLater();
} }
private SpannableString getLinkStringWithCustomInterval(String base, String linkComponent, protected SpannableString getLinkStringWithCustomInterval(String base, String linkComponent,
int start, int endOffset, final OnClickListener listener) { int start, int endOffset, final OnClickListener listener) {
SpannableString link = new SpannableString (String.format("%s %s", //$NON-NLS-1$ SpannableString link = new SpannableString (String.format("%s %s", //$NON-NLS-1$
base, linkComponent)); base, linkComponent));
@ -80,7 +80,7 @@ public class WelcomeLogin extends ActFmLoginActivity implements AuthListener {
return link; return link;
} }
private void setupTermsOfService() { protected void setupTermsOfService() {
TextView tos = (TextView)findViewById(R.id.tos); TextView tos = (TextView)findViewById(R.id.tos);
tos.setOnClickListener(showTosListener); tos.setOnClickListener(showTosListener);
@ -90,12 +90,12 @@ public class WelcomeLogin extends ActFmLoginActivity implements AuthListener {
tos.setText(link); tos.setText(link);
} }
private void setupPWLogin() { protected void setupPWLogin() {
Button pwLogin = (Button) findViewById(R.id.pw_login); Button pwLogin = (Button) findViewById(R.id.pw_login);
pwLogin.setOnClickListener(signUpListener); pwLogin.setOnClickListener(signUpListener);
} }
private void setupLoginLater() { protected void setupLoginLater() {
TextView loginLater = (TextView)findViewById(R.id.login_later); TextView loginLater = (TextView)findViewById(R.id.login_later);
loginLater.setOnClickListener(loginLaterListener); loginLater.setOnClickListener(loginLaterListener);
String loginLaterBase = getString(R.string.welcome_login_later); String loginLaterBase = getString(R.string.welcome_login_later);
@ -117,14 +117,14 @@ public class WelcomeLogin extends ActFmLoginActivity implements AuthListener {
// --- event handler // --- event handler
private final OnClickListener showTosListener = new OnClickListener() { protected final OnClickListener showTosListener = new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Eula.showEulaBasic(WelcomeLogin.this); Eula.showEulaBasic(WelcomeLogin.this);
} }
}; };
private final OnClickListener loginLaterListener = new OnClickListener() { protected final OnClickListener loginLaterListener = new OnClickListener() {
@Override @Override
public void onClick(View arg0) { public void onClick(View arg0) {
String title = getString(R.string.welcome_login_confirm_later_title); String title = getString(R.string.welcome_login_confirm_later_title);

@ -0,0 +1,125 @@
package com.todoroo.astrid.welcome.tutorial;
import android.content.Context;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.timsu.astrid.R;
public class ViewPagerAdapter extends PagerAdapter
{
private static int[] images = new int[]
{
R.drawable.welcome_walkthrough_1,
R.drawable.welcome_walkthrough_2,
R.drawable.welcome_walkthrough_3,
R.drawable.welcome_walkthrough_4,
R.drawable.welcome_walkthrough_5,
R.drawable.welcome_walkthrough_6,
R.drawable.welcome_screen
};
private static String[] title = new String[]
{
"Welcome to Astrid!",
"Make lists",
"Share lists",
"Divvy up tasks",
"Provide details",
"Discover",
"Login"
};
private static String[] body = new String[]
{
"The perfect personal\nto-do list that works great\nwith friends",
"Perfect for any list:\nto read, to watch, to buy,\nto visit, to do!",
"Share lists\nwith friends, housemates,\nor your sweetheart!",
"Never wonder who's\nbringing dessert!",
"Tap to add notes,\nset reminders,\nand much more!",
"Additional features,\nproductivity tips, and\nsuggestions from friends",
"Login"
};
private static int[] layouts = new int[]
{
R.layout.welcome_walkthrough_page,
R.layout.welcome_walkthrough_page,
R.layout.welcome_walkthrough_page,
R.layout.welcome_walkthrough_page,
R.layout.welcome_walkthrough_page,
R.layout.welcome_walkthrough_page,
R.layout.welcome_walkthrough_login_page,
};
private final Context context;
public WelcomeWalkthrough parent;
public ViewPagerAdapter( Context context )
{
this.context = context;
}
@Override
public int getCount()
{
return layouts.length;
}
@Override
public Object instantiateItem( View pager, int position )
{
LayoutInflater inflater = LayoutInflater.from(context);
View pageView = inflater.inflate(layouts[position], null, true);
pageView.setLayoutParams( new ViewGroup.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
if (position != getCount()-1){
ImageView imageView = (ImageView) pageView.findViewById(R.id.welcome_walkthrough_image);
imageView.setImageResource(images[position]);
ImageView fabricImage = (ImageView) pageView.findViewById(R.id.welcome_walkthrough_fabric);
fabricImage.setScaleType(ImageView.ScaleType.FIT_XY);
TextView titleView = (TextView) pageView.findViewById(R.id.welcome_walkthrough_title);
titleView.setText(title[position]);
TextView bodyView = (TextView) pageView.findViewById(R.id.welcome_walkthrough_body);
bodyView.setText(body[position]);
}
((ViewPager)pager).addView( pageView, 0 );
parent.pageScrolled(position, pageView);
return pageView;
}
@Override
public void destroyItem( View pager, int position, Object view )
{
((ViewPager)pager).removeView( (View)view );
}
@Override
public boolean isViewFromObject( View view, Object object )
{
return view.equals( object );
}
@Override
public void finishUpdate( View view ) {}
@Override
public void restoreState( Parcelable p, ClassLoader c ) {}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void startUpdate( View view ) {}
}

@ -0,0 +1,178 @@
package com.todoroo.astrid.welcome.tutorial;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import com.timsu.astrid.R;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.actfm.ActFmLoginActivity;
import com.todoroo.astrid.activity.Eula;
import com.todoroo.astrid.welcome.WelcomeLogin;
import com.viewpagerindicator.CirclePageIndicator;
import com.viewpagerindicator.PageIndicator;
public class WelcomeWalkthrough extends ActFmLoginActivity {
ViewPager mPager;
ViewPagerAdapter mAdapter;
PageIndicator mIndicator;
View currentView;
int currentPage;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
//setContentView(R.layout.welcome_walkthrough);
mAdapter = new ViewPagerAdapter(this);
mAdapter.parent = this;
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (CirclePageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
@Override
protected int getContentViewResource() {
return R.layout.welcome_walkthrough;
}
@Override
protected int getTitleResource() {
return 0;
}
public void pageScrolled(int position, View view){
Log.d(null, "Updated ui");
currentView = view;
currentPage = position;
if (position == mAdapter.getCount()-1) {
initializeUI();
}
}
@Override
protected void initializeUI() {
if(mAdapter == null)
return;
if(currentPage == mAdapter.getCount()-1){
super.initializeUI();
setupTermsOfService();
setupLoginLater();
}
}
protected SpannableString getLinkStringWithCustomInterval(String base, String linkComponent,
int start, int endOffset, final OnClickListener listener) {
SpannableString link = new SpannableString (String.format("%s %s", //$NON-NLS-1$
base, linkComponent));
ClickableSpan linkSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
listener.onClick(widget);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setUnderlineText(true);
ds.setColor(Color.rgb(255, 255, 255));
}
};
link.setSpan(linkSpan, start, link.length() + endOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return link;
}
protected void setupTermsOfService() {
TextView tos = (TextView)currentView.findViewById(R.id.tos);
tos.setOnClickListener(showTosListener);
String tosBase = getString(R.string.welcome_login_tos_base);
String tosLink = getString(R.string.welcome_login_tos_link);
SpannableString link = getLinkStringWithCustomInterval(tosBase, tosLink, tosBase.length() + 2, -1, showTosListener);
tos.setText(link);
}
protected void setupPWLogin() {
Button pwLogin = (Button) findViewById(R.id.pw_login);
pwLogin.setOnClickListener(signUpListener);
}
/*
protected void setupWalkthroughLogin() {
Button loginButton = (Button)currentView.findViewById(R.id.walkthrough_login);
loginButton.setOnClickListener(showWalkthroughLoginListener);
}*/
protected void setupLoginLater() {
TextView loginLater = (TextView)currentView.findViewById(R.id.login_later);
loginLater.setOnClickListener(loginLaterListener);
String loginLaterBase = getString(R.string.welcome_login_later);
SpannableString loginLaterLink = new SpannableString(String.format("%s", loginLaterBase)); //$NON-NLS-1$
ClickableSpan laterSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
loginLaterListener.onClick(widget);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setUnderlineText(true);
ds.setColor(Color.rgb(255, 255, 255));
}
};
loginLaterLink.setSpan(laterSpan, 0, loginLaterBase.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
loginLater.setText(loginLaterLink);
}
protected final OnClickListener showTosListener = new OnClickListener() {
@Override
public void onClick(View v) {
Eula.showEulaBasic(WelcomeWalkthrough.this);
}
};
private void showWelcomeLoginActivty() {
Intent showWelcomeLogin = new Intent(this, WelcomeLogin.class);
showWelcomeLogin.putExtra(ActFmLoginActivity.SHOW_TOAST, false);
startActivity(showWelcomeLogin);
}
protected final OnClickListener showWalkthroughLoginListener = new OnClickListener() {
@Override
public void onClick(View v) {
showWelcomeLoginActivty();
}
};
protected final OnClickListener loginLaterListener = new OnClickListener() {
@Override
public void onClick(View arg0) {
String title = getString(R.string.welcome_login_confirm_later_title);
String confirmLater = getString(R.string.welcome_login_confirm_later_dialog);
DialogUtilities.okCancelCustomDialog(WelcomeWalkthrough.this, title, confirmLater,
R.string.welcome_login_confirm_later_ok,
R.string.welcome_login_confirm_later_cancel,
android.R.drawable.ic_dialog_alert,
null, confirmLaterListener);
}
private final DialogInterface.OnClickListener confirmLaterListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
};
};
}

@ -0,0 +1,17 @@
#Android generated
bin
gen
#Eclipse
.project
.classpath
.settings
#IntelliJ IDEA
.idea
*.iml
#Maven
target
release.properties
pom.xml.*

@ -0,0 +1,88 @@
Change Log
==========
Version 2.2.2 *(2011-12-31)*
----------------------------
* Fix incorrect `R.java` imports in all of the sample activities.
Version 2.2.1 *(2011-12-31)*
----------------------------
* New `setTypeface(Typeface)` and `getTypeface()` methods for title indicator.
(Thanks Dimitri Fedorov)
* Added styled tab indicator sample.
* Support for widths other than those that could be measured exactly.
Version 2.2.0 *(2011-12-13)*
----------------------------
* Default title indicator style is now 'underline'.
* Title indicator now allows specifying an `OnCenterItemClickListener` which
will give you callbacks when the current item title has been clicked.
(Thanks Chris Banes)
Version 2.1.0 *(2011-11-30)*
----------------------------
* Indicators now have a `notifyDataSetChanged` method which should be called
when changes are made to the adapter.
* Fix: Avoid `NullPointerException`s when the `ViewPager` is not immediately
bound to the indicator.
Version 2.0.0 *(2011-11-20)*
----------------------------
* New `TabPageIndicator`! Uses the Ice Cream Sandwich-style action bar tabs
which fill the width of the view when there are only a few tabs or provide
horizontal animated scrolling when there are many.
* Update to link against ACLv4r4. This will now be required in all implementing
applications.
* Allow dragging the title and circle indicators to drag the pager.
* Remove orientation example as the DirectionalViewPager library has not been
updated to ACLv4r4.
Version 1.2.1 *(2011-10-20)*
----------------------------
Maven 3 is now required when building from the command line.
* Update to support ADT 14.
Version 1.2.0 *(2011-10-04)*
----------------------------
* Move to `com.viewpagerindicator` package.
* Move maven group and artifact to `com.viewpagerindicator:library`.
Version 1.1.0 *(2011-10-02)*
----------------------------
* Package changed from `com.jakewharton.android.viewpagerindicator` to
`com.jakewharton.android.view`.
* Add vertical orientation support to the circle indicator.
* Fix: Corrected drawing bug where a single frame would be drawn as if the
pager had completed its scroll when in fact it was still scrolling.
(Thanks SimonVT!)
Version 1.0.1 *(2011-09-15)*
----------------------------
* Fade selected title color to normal text color during the swipe to and from
the center position.
* Fix: Ensure both the indicator and footer line are updated when changing the
footer color via the `setFooterColor` method.
Version 1.0.0 *(2011-08-07)*
----------------------------
Initial release.

@ -0,0 +1,152 @@
Android ViewPagerIndicator
==========================
Port of [Patrik Åkerfeldt][1]'s paging indicators that are compatible with the
ViewPager from the [Android Compatibility Library][2] and
[ActionBarSherlock][3].
Try out the sample application [on the Android Market][10].
![ViewPagerIndicator Sample Screenshots][9]
Usage
=====
*For a working implementation of this project see the `sample/` folder.*
1. Include one of the widgets in your view. This should usually be placed
adjacent to the `ViewPager` it represents.
<com.viewpagerindicator.TitlePageIndicator
android:id="@+id/titles"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
2. In your `onCreate` method (or `onCreateView` for a fragment), bind the
indicator to the `ViewPager`.
//Set the pager with an adapter
ViewPager pager = (ViewPager)findViewById(R.id.pager);
pager.setAdapter(new TestAdapter(getSupportFragmentManager()));
//Bind the title indicator to the adapter
TitlePageIndicator titleIndicator = (TitlePageIndicator)findViewById(R.id.titles);
titleIndicator.setViewPager(pager);
*Note*: If you are using `TitlePageIndicator` your adapter must implement
`TitleProvider`.
3. *(Optional)* If you use an `OnPageChangeListener` with your view pager
you should set it in the indicator rather than on the pager directly.
//continued from above
titleIndicator.setOnPageChangeListener(mPageChangeListener);
Theming
-------
There are three ways to style the look of the indicators.
1. **Theme XML**. An attribute for each type of indicator is provided in which
you can specify a custom style.
2. **Layout XML**. Through the use of a custom namespace you can include any
desired styles.
3. **Object methods**. Both styles have getters and setters for each style
attribute which can be changed at any point.
Each indicator has a demo which creates the same look using each of these
methods.
Including In Your Project
-------------------------
Android-ViewPagerIndicator is presented as an [Android library project][7]. A
standalone JAR is not possible due to the theming capabilities offered by the
indicator widgets.
You can include this project by [referencing it as a library project][8] in
Eclipse or ant.
If you are a Maven user you can easily include the library by specifying it as
a dependency:
<dependency>
<groupId>com.viewpagerindicator</groupId>
<artifactId>library</artifactId>
<version>2.2.2</version>
<type>apklib</type>
</dependency>
You must also include the following repository:
<repository>
<id>com.jakewharton</id>
<url>http://r.jakewharton.com/maven/release</url>
</repository>
This project depends on the `ViewPager` class which is available in the
[Android Compatibility Library][2] or [ActionBarSherlock][3]. Details for
including one of those libraries is available on their respecitve web sites.
Developed By
============
* Jake Wharton - <jakewharton@gmail.com>
Credits
-------
* [Patrik Åkerfeldt][1] - Author of [ViewFlow][4], a precursor to the ViewPager,
which supports paged views and is the original source of both the title
and circle indicators.
* [Francisco Figueiredo Jr.][5] - Idea and [first implementation][6] for
fragment support via ViewPager.
License
=======
Copyright 2011 Patrik Åkerfeldt
Copyright 2011 Francisco Figueiredo Jr.
Copyright 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.
[1]: https://github.com/pakerfeldt
[2]: http://developer.android.com/sdk/compatibility-library.html
[3]: http://actionbarsherlock.com
[4]: https://github.com/pakerfeldt/android-viewflow
[5]: https://github.com/franciscojunior
[6]: https://gist.github.com/1122947
[7]: http://developer.android.com/guide/developing/projects/projects-eclipse.html
[8]: http://developer.android.com/guide/developing/projects/projects-eclipse.html#ReferencingLibraryProject
[9]: https://raw.github.com/JakeWharton/Android-ViewPagerIndicator/master/sample/screens.png
[10]: https://market.android.com/details?id=com.viewpagerindicator.sample

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.viewpagerindicator"
android:versionCode="26"
android:versionName="2.2.2">
<uses-sdk android:minSdkVersion="4" />
</manifest>

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="library" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
unless="sdk.dir"
/>
<!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets -->
<!--
<target name="-pre-build">
</target>
<target name="-pre-compile">
</target>
/* This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir} */
<target name="-post-compile">
</target>
-->
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

@ -0,0 +1,115 @@
<?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>

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.viewpagerindicator</groupId>
<artifactId>library</artifactId>
<name>Android-ViewPagerIndicator</name>
<packaging>apklib</packaging>
<parent>
<groupId>com.viewpagerindicator</groupId>
<artifactId>parent</artifactId>
<version>2.2.2</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>android</groupId>
<artifactId>android</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v4</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>maven-android-plugin</artifactId>
<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>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>checkstyle</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,40 @@
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

@ -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

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/vpi__bright_foreground_disabled_holo_dark"/>
<item android:state_window_focused="false" android:color="@color/vpi__bright_foreground_holo_dark"/>
<item android:state_pressed="true" android:color="@color/vpi__bright_foreground_holo_dark"/>
<item android:state_selected="true" android:color="@color/vpi__bright_foreground_holo_dark"/>
<!--item android:state_activated="true" android:color="@color/vpi__bright_foreground_holo_dark"/-->
<item android:color="@color/vpi__bright_foreground_holo_dark"/> <!-- not selected -->
</selector>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/vpi__bright_foreground_disabled_holo_light"/>
<item android:state_window_focused="false" android:color="@color/vpi__bright_foreground_holo_light"/>
<item android:state_pressed="true" android:color="@color/vpi__bright_foreground_holo_light"/>
<item android:state_selected="true" android:color="@color/vpi__bright_foreground_holo_light"/>
<!--item android:state_activated="true" android:color="@color/vpi__bright_foreground_holo_light"/-->
<item android:color="@color/vpi__bright_foreground_holo_light"/> <!-- not selected -->
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Non focused states -->
<item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/vpi__tab_unselected_holo" />
<item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/vpi__tab_selected_holo" />
<!-- Focused states -->
<item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/vpi__tab_unselected_focused_holo" />
<item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/vpi__tab_selected_focused_holo" />
<!-- Pressed -->
<!-- Non focused states -->
<item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/vpi__tab_unselected_pressed_holo" />
<item android:state_focused="false" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/vpi__tab_selected_pressed_holo" />
<!-- Focused states -->
<item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/vpi__tab_unselected_pressed_holo" />
<item android:state_focused="true" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/vpi__tab_selected_pressed_holo" />
</selector>

@ -0,0 +1,13 @@
<?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>

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 Patrik Åkerfeldt
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.
-->
<resources>
<declare-styleable name="ViewPagerIndicator">
<!-- Style of the circle indicator. -->
<attr name="vpiCirclePageIndicatorStyle" format="reference"/>
<!-- Style of the title indicator. -->
<attr name="vpiTitlePageIndicatorStyle" format="reference"/>
<!-- Style of the tab indicator. -->
<attr name="vpiTabPageIndicatorStyle" format="reference"/>
<!-- Style of the text in a tab. -->
<attr name="vpiTabTextStyle" format="reference"/>
</declare-styleable>
<declare-styleable name="CirclePageIndicator">
<!-- Whether or not the indicators should be centered. -->
<attr name="centered" format="boolean" />
<!-- Color of the filled circle that represents the current page. -->
<attr name="fillColor" format="color" />
<!-- Orientation of the indicator. -->
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
<!-- 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. -->
<attr name="snap" format="boolean" />
<!-- Color of the open circles. -->
<attr name="strokeColor" format="color" />
<!-- Width of the stroke used to draw the circles. -->
<attr name="strokeWidth" format="dimension" />
</declare-styleable>
<declare-styleable name="TitlePageIndicator">
<!-- Screen edge padding. -->
<attr name="clipPadding" format="dimension" />
<!-- Color of the footer line and indicator. -->
<attr name="footerColor" format="color" />
<!-- Height of the footer line. -->
<attr name="footerLineHeight" format="dimension" />
<!-- Style of the indicator. Default is triangle. -->
<attr name="footerIndicatorStyle">
<enum name="none" value="0" />
<enum name="triangle" value="1" />
<enum name="underline" value="2" />
</attr>
<!-- Height of the indicator above the footer line. -->
<attr name="footerIndicatorHeight" format="dimension" />
<!-- Left and right padding of the underline indicator. -->
<attr name="footerIndicatorUnderlinePadding" format="dimension" />
<!-- 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" />
<!-- Whether or not the selected item is displayed as bold. -->
<attr name="selectedBold" format="boolean" />
<!-- Color of regular titles. -->
<attr name="textColor" format="color" />
<!-- Size of title text. -->
<attr name="textSize" format="dimension" />
<!-- 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>
</resources>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
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.
-->
<resources>
<color name="vpi__background_holo_dark">#ff000000</color>
<color name="vpi__background_holo_light">#fff3f3f3</color>
<color name="vpi__bright_foreground_holo_dark">@color/vpi__background_holo_light</color>
<color name="vpi__bright_foreground_holo_light">@color/vpi__background_holo_dark</color>
<color name="vpi__bright_foreground_disabled_holo_dark">#ff4c4c4c</color>
<color name="vpi__bright_foreground_disabled_holo_light">#ffb2b2b2</color>
<color name="vpi__bright_foreground_inverse_holo_dark">@color/vpi__bright_foreground_holo_light</color>
<color name="vpi__bright_foreground_inverse_holo_light">@color/vpi__bright_foreground_holo_dark</color>
</resources>

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<bool name="default_circle_indicator_centered">true</bool>
<color name="default_circle_indicator_fill_color">#FFFFFFFF</color>
<integer name="default_circle_indicator_orientation">0</integer>
<dimen name="default_circle_indicator_radius">3dp</dimen>
<bool name="default_circle_indicator_snap">false</bool>
<color name="default_circle_indicator_stroke_color">#FFDDDDDD</color>
<dimen name="default_circle_indicator_stroke_width">1dp</dimen>
<dimen name="default_title_indicator_clip_padding">4dp</dimen>
<color name="default_title_indicator_footer_color">#FF6899FF</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>
<dimen name="default_title_indicator_footer_indicator_underline_padding">20dp</dimen>
<dimen name="default_title_indicator_footer_padding">7dp</dimen>
<color name="default_title_indicator_selected_color">#FFFFFFFF</color>
<bool name="default_title_indicator_selected_bold">true</bool>
<color name="default_title_indicator_text_color">#BBFFFFFF</color>
<dimen name="default_title_indicator_text_size">15sp</dimen>
<dimen name="default_title_indicator_title_padding">5dp</dimen>
<dimen name="default_title_indicator_top_padding">7dp</dimen>
</resources>

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<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="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>
<style name="Widget.TabPageIndicator" parent="Widget">
<item name="android:gravity">center_horizontal</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">
</style>
</resources>

@ -0,0 +1,526 @@
/*
* 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 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 - getResources().getDimension(R.dimen.default_circle_indicator_stroke_width)/2
, mPaintFill);
//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);
}
}
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<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];
}
};
}
}

@ -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);
/**
* <p>Set the current page of both the ViewPager and indicator.</p>
*
* <p>This <strong>must</strong> be used if you need to set the page before
* the views are drawn on screen (e.g., default start page).</p>
*
* @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();
}

@ -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;
}
}
}

@ -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<RectF> 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<RectF> calculateAllBounds(Paint paint) {
ArrayList<RectF> list = new ArrayList<RectF>();
//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<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];
}
};
}
}

@ -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);
}

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.viewpagerindicator</groupId>
<artifactId>parent</artifactId>
<packaging>pom</packaging>
<version>2.2.2</version>
<name>Android-ViewPagerIndicator (Parent)</name>
<description>Android library for.</description>
<url>https://github.com/JakeWharton/Android-ViewPagerIndicator</url>
<inceptionYear>2011</inceptionYear>
<modules>
<module>library</module>
<module>sample</module>
</modules>
<scm>
<url>http://github.com/JakeWharton/Android-ViewPagerIndicator/</url>
<connection>scm:git:git://github.com/JakeWharton/Android-ViewPagerIndicator.git</connection>
<developerConnection>scm:git:git@github.com:JakeWharton/Android-ViewPagerIndicator.git</developerConnection>
</scm>
<developers>
<developer>
<name>Jake Wharton</name>
<email>jakewharton@gmail.com</email>
<id>jakewharton</id>
<url>http://jakewharton.com</url>
<timezone>-5</timezone>
<roles>
<role>developer</role>
</roles>
</developer>
</developers>
<licenses>
<license>
<name>Apache License Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<distributionManagement>
<repository>
<id>personal-repository</id>
<name>JakeWharton.com Maven Repository</name>
<url>scp://r.jakewharton.com/home/jakewharton_repository/r.jakewharton.com/maven/release/</url>
</repository>
<snapshotRepository>
<id>personal-repository</id>
<name>JakeWharton.com Maven Repository</name>
<url>scp://r.jakewharton.com/home/jakewharton_repository/r.jakewharton.com/maven/snapshot/</url>
</snapshotRepository>
</distributionManagement>
<organization>
<name>Jake Wharton</name>
<url>http://jakewharton.com</url>
</organization>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/JakeWharton/Android-ViewPagerIndicator/issues</url>
</issueManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.6</java.version>
<android.version>1.6_r3</android.version>
<android.platform>4</android.platform>
<android.support.version>r6</android.support.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>android</groupId>
<artifactId>android</artifactId>
<version>${android.version}</version>
</dependency>
<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v4</artifactId>
<version>${android.support.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>com.jakewharton</id>
<url>http://r.jakewharton.com/maven/release</url>
</repository>
</repositories>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<version>3.0.0-alpha-13</version>
<configuration>
<sdk>
<platform>${android.platform}</platform>
</sdk>
<undeployBeforeDeploy>true</undeployBeforeDeploy>
</configuration>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.6</version>
<configuration>
<failsOnError>true</failsOnError>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.1</version>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.6</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>1.0-beta-7</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
Loading…
Cancel
Save