diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index e206d70..f758959 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,5 +1,6 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 8c3735b..a87d8d3 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -3,8 +3,10 @@
-
+
+
+
@@ -12,8 +14,8 @@
+
-
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 4ae8d54..8909776 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -22,5 +46,4 @@
-
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 69a2920..343f97c 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ The default toasts are ugly and don't really provide much more than a short mess
#### Gradle
```groovy
dependencies {
- compile 'net.steamcrafted:load-toast:1.0.5'
+ compile 'net.steamcrafted:load-toast:1.0.12'
}
```
@@ -31,6 +31,8 @@ Change the displayed text:
lt.setText("Sending Reply...");
```
+If you don't have a message to display, the toast will shrink to only show the circular loader.
+
Then proceed to show the toast:
```java
@@ -45,9 +47,12 @@ lt.success();
// Or this method if it failed
lt.error();
+
+// Or if no feedback is desired you can simply hide the toast
+lt.hide();
```
-If you are using translucent actionbar in a full screen activity it will appear over the actionbar, fortunately there is a method to change the y translation:
+To properly position the toast use the following method to adjust the Y offset:
```java
lt.setTranslationY(100); // y offset in pixels
@@ -59,8 +64,27 @@ You can also change the colors of the different toast elements:
lt.setTextColor(Color.RED).setBackgroundColor(Color.GREEN).setProgressColor(Color.BLUE);
```
+In some situations a border might be desired for increased visibility, by default it is transparent:
+
+```java
+// Change the border color
+lt.setBorderColor(int color);
+
+// Change the border width
+lt.setBorderWidthPx(int widthPx);
+lt.setBorderWidthDp(int widthDp);
+lt.setBorderWidthRes(int resourceId);
+```
+
+When displaying a message in a RTL language you can force the text to marquee from left to right instead of the default right to left:
+
+```java
+// pass in false for RTL text, true for LTR text
+lt.setTextDirection(boolean isLeftToRight);
+```
+
These can be chained as you can see.
-#License
+# License
Released under the [Apache 2.0 License](https://github.com/code-mc/loadtoast/blob/master/license.md)
diff --git a/app/app.iml b/app/app.iml
index 9f752ab..c53829f 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -1,5 +1,5 @@
-
+
@@ -9,12 +9,11 @@
-
-
-
-
+
+ generateDebugSources
+
@@ -23,72 +22,91 @@
-
+
-
+
+
-
+
+
-
+
-
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
+
+
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index f338fed..7daaad7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,23 +1,17 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 22
- buildToolsVersion "21.1.1"
+ compileSdkVersion 25
+ buildToolsVersion '25.0.3'
lintOptions {
abortOnError false
}
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_6
- targetCompatibility JavaVersion.VERSION_1_6
- }
-
defaultConfig {
applicationId "net.steamcrafted.gesturetrackerlib"
- minSdkVersion 8
- targetSdkVersion 22
+ minSdkVersion 9
+ targetSdkVersion 25
versionCode 1
versionName "1.0"
}
@@ -32,4 +26,6 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':loadtoast')
+// compile 'net.steamcrafted:load-toast:1.0.10'
+ compile 'com.android.support:appcompat-v7:25.3.1'
}
diff --git a/app/src/main/java/net/steamcrafted/loadtoastlib/MainActivity.java b/app/src/main/java/net/steamcrafted/loadtoastlib/MainActivity.java
index 62ba024..1cb1f1c 100644
--- a/app/src/main/java/net/steamcrafted/loadtoastlib/MainActivity.java
+++ b/app/src/main/java/net/steamcrafted/loadtoastlib/MainActivity.java
@@ -4,12 +4,17 @@
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
+import android.os.Handler;
+import android.support.v7.app.AppCompatActivity;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
import net.steamcrafted.loadtoast.LoadToast;
+import net.steamcrafted.loadtoast.MaterialProgressDrawable;
-public class MainActivity extends Activity {
+public class MainActivity extends AppCompatActivity {
// Example activity
@@ -18,8 +23,19 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- final String text = "Sending reply...";
- final LoadToast lt = new LoadToast(this).setText(text).show();
+ final String text = "dhfbsd kjsdfjnskdfs dfs";
+ final LoadToast lt = new LoadToast(this)
+ .setProgressColor(Color.RED)
+ .setText(text)
+ .setTranslationY(100)
+ .setBorderColor(Color.LTGRAY)
+ .show();
+ //lt.success();
+ final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+
+ View v = new View(this);
+ v.setBackgroundColor(Color.RED);
+ //root.addView(v, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 400));
findViewById(R.id.show).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -41,10 +57,19 @@ public void onClick(View view) {
findViewById(R.id.refresh).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- Intent intent = getIntent();
- finish();
- startActivity(intent);
+ View v = new View(MainActivity.this);
+ v.setBackgroundColor(Color.rgb((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)));
+ root.addView(v, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 400));
}
});
+ findViewById(R.id.hide).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ lt.hide();
+ }
+ });
+
+ ImageView progressView = ((ImageView) findViewById(R.id.progressdrawable));
+ MaterialProgressDrawable drawable = new MaterialProgressDrawable(this, progressView);
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index b8c8212..4fa3906 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -6,6 +6,11 @@
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
android:id="@+id/main_root">
+
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index c58ed25..374a7b4 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,8 +1,9 @@
-
diff --git a/build.gradle b/build.gradle
index f935f4c..1f602d4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.1.1'
+ classpath 'com.android.tools.build:gradle:2.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gestureTrackerLib.iml b/gestureTrackerLib.iml
index 0bb6048..54f87fa 100644
--- a/gestureTrackerLib.iml
+++ b/gestureTrackerLib.iml
@@ -1,13 +1,14 @@
-
+
+
-
+
@@ -15,5 +16,4 @@
-
-
+
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c71e76..47a1472 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Tue Oct 03 16:59:17 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/loadtoast/build.gradle b/loadtoast/build.gradle
index ac082ea..254b2fd 100644
--- a/loadtoast/build.gradle
+++ b/loadtoast/build.gradle
@@ -3,16 +3,16 @@ apply plugin: 'com.android.library'
ext {
PUBLISH_GROUP_ID = 'net.steamcrafted'
PUBLISH_ARTIFACT_ID = 'load-toast'
- PUBLISH_VERSION = '1.0.5'
+ PUBLISH_VERSION = '1.0.12'
}
android {
- compileSdkVersion 22
- buildToolsVersion "21.1.1"
+ compileSdkVersion 23
+ buildToolsVersion '25.0.3'
defaultConfig {
minSdkVersion 8
- targetSdkVersion 22
+ targetSdkVersion 23
versionCode 1
versionName "1.0"
}
diff --git a/loadtoast/loadtoast.iml b/loadtoast/loadtoast.iml
index d207d12..9143de9 100644
--- a/loadtoast/loadtoast.iml
+++ b/loadtoast/loadtoast.iml
@@ -1,5 +1,5 @@
-
+
@@ -9,90 +9,99 @@
-
-
-
-
+
+ generateDebugSources
+
-
+
-
+
-
+
+
-
+
+
-
+
-
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
+
-
+
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
-
+
+
-
+
-
-
+
\ No newline at end of file
diff --git a/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToast.java b/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToast.java
index 7d6d955..1d06293 100644
--- a/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToast.java
+++ b/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToast.java
@@ -2,10 +2,13 @@
import android.app.Activity;
import android.content.Context;
+import android.content.res.Resources;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
@@ -13,27 +16,33 @@
* Created by Wannes2 on 23/04/2015.
*/
public class LoadToast {
-
private String mText = "";
private LoadToastView mView;
+ private ViewGroup mParentView;
private int mTranslationY = 0;
private boolean mShowCalled = false;
+ private boolean mToastCanceled = false;
private boolean mInflated = false;
+ private boolean mVisible = false;
+ private boolean mReAttached = false;
public LoadToast(Context context){
mView = new LoadToastView(context);
- final ViewGroup vg = (ViewGroup) ((Activity) context).getWindow().getDecorView().findViewById(android.R.id.content);
- vg.addView(mView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- ViewHelper.setAlpha(mView, 0);
- vg.postDelayed(new Runnable() {
- @Override
- public void run() {
- ViewHelper.setTranslationX(mView, (vg.getWidth() - mView.getWidth()) / 2);
- ViewHelper.setTranslationY(mView, -mView.getHeight() + mTranslationY);
- mInflated = true;
- if(mShowCalled) show();
+ mParentView = (ViewGroup) ((Activity) context).getWindow().getDecorView();
+ }
+
+ private void cleanup() {
+ int childCount = mParentView.getChildCount();
+ for(int i = childCount; i >= 0; i--){
+ if(mParentView.getChildAt(i) instanceof LoadToastView){
+ LoadToastView ltv = (LoadToastView) mParentView.getChildAt(i);
+ ltv.cleanup();
+ mParentView.removeViewAt(i);
}
- },1);
+ }
+
+ mInflated = false;
+ mToastCanceled = false;
}
public LoadToast setTranslationY(int pixels){
@@ -62,35 +71,122 @@ public LoadToast setProgressColor(int color){
return this;
}
+ public LoadToast setTextDirection(boolean isLeftToRight){
+ mView.setTextDirection(isLeftToRight);
+ return this;
+ }
+
+ public LoadToast setBorderColor(int color){
+ mView.setBorderColor(color);
+ return this;
+ }
+
+ public LoadToast setBorderWidthPx(int width){
+ mView.setBorderWidthPx(width);
+ return this;
+ }
+
+ public LoadToast setBorderWidthRes(int resourceId){
+ mView.setBorderWidthRes(resourceId);
+ return this;
+ }
+
+ public LoadToast setBorderWidthDp(int width){
+ mView.setBorderWidthDp(width);
+ return this;
+ }
+
public LoadToast show(){
- if(!mInflated){
- mShowCalled = true;
- return this;
- }
+ mShowCalled = true;
+ attach();
+ return this;
+ }
+
+ private void showInternal(){
mView.show();
+ ViewHelper.setTranslationX(mView, (mParentView.getWidth() - mView.getWidth()) / 2);
ViewHelper.setAlpha(mView, 0f);
ViewHelper.setTranslationY(mView, -mView.getHeight() + mTranslationY);
//mView.setVisibility(View.VISIBLE);
ViewPropertyAnimator.animate(mView).alpha(1f).translationY(25 + mTranslationY)
.setInterpolator(new DecelerateInterpolator())
+ .setListener(null)
.setDuration(300).setStartDelay(0).start();
- return this;
+
+ mVisible = true;
+ }
+
+ private void attach() {
+ cleanup();
+
+ mReAttached = true;
+
+ mParentView.addView(mView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ ViewHelper.setAlpha(mView, 0);
+ mParentView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ ViewHelper.setTranslationX(mView, (mParentView.getWidth() - mView.getWidth()) / 2);
+ ViewHelper.setTranslationY(mView, -mView.getHeight() + mTranslationY);
+ mInflated = true;
+ if(!mToastCanceled && mShowCalled) showInternal();
+ }
+ },1);
}
public void success(){
- mView.success();
- slideUp();
+ if(!mInflated){
+ mToastCanceled = true;
+ return;
+ }
+ if(mReAttached){
+ mView.success();
+ slideUp();
+ }
}
public void error(){
- mView.error();
- slideUp();
+ if(!mInflated){
+ mToastCanceled = true;
+ return;
+ }
+ if(mReAttached){
+ mView.error();
+ slideUp();
+ }
+ }
+
+ public void hide(){
+ if(!mInflated){
+ mToastCanceled = true;
+ return;
+ }
+ if(mReAttached){
+ slideUp(0);
+ }
}
private void slideUp(){
- ViewPropertyAnimator.animate(mView).setStartDelay(1000).alpha(0f)
+ slideUp(1000);
+ }
+
+ private void slideUp(int startDelay){
+ mReAttached = false;
+
+ ViewPropertyAnimator.animate(mView).setStartDelay(startDelay).alpha(0f)
.translationY(-mView.getHeight() + mTranslationY)
.setInterpolator(new AccelerateInterpolator())
- .setDuration(300).start();
+ .setDuration(300)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if(!mReAttached){
+ cleanup();
+ }
+ }
+ })
+ .start();
+
+ mVisible = false;
}
}
diff --git a/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToastView.java b/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToastView.java
index 5381872..736f7ad 100644
--- a/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToastView.java
+++ b/loadtoast/src/main/java/net/steamcrafted/loadtoast/LoadToastView.java
@@ -2,6 +2,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -16,14 +17,17 @@
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
+import android.widget.ImageView;
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;
/**
* Created by Wannes2 on 23/04/2015.
*/
-public class LoadToastView extends View {
+public class LoadToastView extends ImageView {
private String mText = "";
@@ -33,6 +37,7 @@ public class LoadToastView extends View {
private Paint loaderPaint = new Paint();
private Paint successPaint = new Paint();
private Paint errorPaint = new Paint();
+ private Paint borderPaint = new Paint();
private Rect iconBounds;
private Rect mTextBounds = new Rect();
@@ -42,7 +47,11 @@ public class LoadToastView extends View {
private int BASE_TEXT_SIZE = 20;
private int IMAGE_WIDTH = 40;
private int TOAST_HEIGHT = 48;
- private float WIDTH_SCALE = 0f;
+ private int LINE_WIDTH = 3;
+ private float WIDTH_SCALE = 0f;
+ private int MARQUE_STEP = 1;
+
+ private long prevUpdate = 0;
private Drawable completeicon;
private Drawable failedicon;
@@ -51,9 +60,14 @@ public class LoadToastView extends View {
private ValueAnimator cmp;
private boolean success = true;
+ private boolean outOfBounds = false;
+ private boolean mLtr = true;
private Path toastPath = new Path();
private AccelerateDecelerateInterpolator easeinterpol = new AccelerateDecelerateInterpolator();
+ private MaterialProgressDrawable spinnerDrawable;
+
+ private int borderOffset = dpToPx(1);
public LoadToastView(Context context) {
super(context);
@@ -72,6 +86,11 @@ public LoadToastView(Context context) {
loaderPaint.setColor(fetchPrimaryColor());
loaderPaint.setStyle(Paint.Style.STROKE);
+ borderPaint.setAntiAlias(true);
+ borderPaint.setStrokeWidth(borderOffset * 2);
+ borderPaint.setColor(Color.TRANSPARENT);
+ borderPaint.setStyle(Paint.Style.STROKE);
+
successPaint.setColor(getResources().getColor(R.color.color_success));
errorPaint.setColor(getResources().getColor(R.color.color_error));
successPaint.setAntiAlias(true);
@@ -81,6 +100,8 @@ public LoadToastView(Context context) {
BASE_TEXT_SIZE = dpToPx(BASE_TEXT_SIZE);
IMAGE_WIDTH = dpToPx(IMAGE_WIDTH);
TOAST_HEIGHT = dpToPx(TOAST_HEIGHT);
+ LINE_WIDTH = dpToPx(LINE_WIDTH);
+ MARQUE_STEP = dpToPx(MARQUE_STEP);
int padding = (TOAST_HEIGHT - IMAGE_WIDTH)/2;
iconBounds = new Rect(TOAST_HEIGHT + MAX_TEXT_WIDTH - padding, padding, TOAST_HEIGHT + MAX_TEXT_WIDTH - padding + IMAGE_WIDTH, IMAGE_WIDTH + padding);
@@ -105,13 +126,44 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) {
va.setInterpolator(new LinearInterpolator());
va.start();
+ initSpinner();
+
calculateBounds();
}
+ private void initSpinner(){
+ spinnerDrawable = new MaterialProgressDrawable(getContext(), this);
+
+ spinnerDrawable.setStartEndTrim(0, .5f);
+ spinnerDrawable.setProgressRotation(.5f);
+
+ int mDiameter = TOAST_HEIGHT;
+ int mProgressStokeWidth = LINE_WIDTH;
+ spinnerDrawable.setSizeParameters(mDiameter, mDiameter,
+ (mDiameter - mProgressStokeWidth * 2) / 4,
+ mProgressStokeWidth,
+ mProgressStokeWidth * 4,
+ mProgressStokeWidth * 2);
+
+ spinnerDrawable.setBackgroundColor(Color.TRANSPARENT);
+ spinnerDrawable.setColorSchemeColors(loaderPaint.getColor());
+ spinnerDrawable.setVisible(true, false);
+ spinnerDrawable.setAlpha(255);
+
+ setImageDrawable(null);
+ setImageDrawable(spinnerDrawable);
+
+ spinnerDrawable.start();
+ }
+
public void setTextColor(int color){
textPaint.setColor(color);
}
+ public void setTextDirection(boolean isLeftToRight){
+ mLtr = isLeftToRight;
+ }
+
public void setBackgroundColor(int color){
backPaint.setColor(color);
iconBackPaint.setColor(color);
@@ -119,11 +171,35 @@ public void setBackgroundColor(int color){
public void setProgressColor(int color){
loaderPaint.setColor(color);
+ spinnerDrawable.setColorSchemeColors(color);
+ }
+
+ public void setBorderColor(int color){
+ borderPaint.setColor(color);
+ }
+
+ public void setBorderWidthPx(int widthPx){
+ borderOffset = widthPx / 2;
+ borderPaint.setStrokeWidth(borderOffset * 2);
+ }
+
+ public void setBorderWidthRes(int resourceId){
+ setBorderWidthPx(getResources().getDimensionPixelSize(resourceId));
+ }
+
+ public void setBorderWidthDp(int width){
+ setBorderWidthPx(dpToPx(width));
}
public void show(){
+ spinnerDrawable.stop();
+ spinnerDrawable.start();
+
WIDTH_SCALE = 0f;
- if(cmp != null) cmp.removeAllUpdateListeners();
+ if(cmp != null){
+ cmp.removeAllUpdateListeners();
+ cmp.removeAllListeners();
+ }
}
public void success(){
@@ -147,22 +223,35 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) {
postInvalidate();
}
});
+ cmp.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ cleanup();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ cleanup();
+ }
+ });
cmp.setInterpolator(new DecelerateInterpolator());
cmp.start();
}
private int fetchPrimaryColor() {
+ int color = Color.rgb(155, 155, 155);
+
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
TypedValue typedValue = new TypedValue();
- TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[] { android.R.attr.colorAccent });
- int color = a.getColor(0, 0);
+ TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{android.R.attr.colorAccent});
+ color = a.getColor(0, color);
a.recycle();
-
- return color;
}
- return Color.rgb(155,155,155);
+ return color;
}
private int dpToPx(int dp){
@@ -175,6 +264,9 @@ public void setText(String text) {
}
private void calculateBounds() {
+ outOfBounds = false;
+ prevUpdate = 0;
+
textPaint.setTextSize(BASE_TEXT_SIZE);
textPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
if(mTextBounds.width() > MAX_TEXT_WIDTH){
@@ -186,19 +278,23 @@ private void calculateBounds() {
textPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
}
if(mTextBounds.width() > MAX_TEXT_WIDTH){
+ outOfBounds = true;
+ /**
float keep = (float)MAX_TEXT_WIDTH / (float)mTextBounds.width();
int charcount = (int)(mText.length() * keep);
//Log.d("calc", "keep " + charcount + " per " + keep + " len " + mText.length());
mText = mText.substring(0, charcount);
textPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
+ **/
}
}
}
@Override
protected void onDraw(Canvas c){
- super.onDraw(c);
-
float ws = Math.max(1f - WIDTH_SCALE, 0f);
+ // If there is nothing to display, just draw a circle
+ if(mText.length() == 0) ws = 0;
+
float translateLoad = (1f-ws)*(IMAGE_WIDTH+MAX_TEXT_WIDTH);
float leftMargin = translateLoad/2;
float textOpactity = Math.max(0, ws * 10f - 9f);
@@ -212,6 +308,8 @@ protected void onDraw(Canvas c){
int iconoffset = (int)(IMAGE_WIDTH*2*(Math.sqrt(2)-1)/3);
int iw = IMAGE_WIDTH;
+ float totalWidth = leftMargin * 2 + th + ws*(IMAGE_WIDTH + MAX_TEXT_WIDTH) - translateLoad;
+
toastPath.reset();
toastPath.moveTo(leftMargin + th / 2, 0);
toastPath.rLineTo(ws*(IMAGE_WIDTH + MAX_TEXT_WIDTH), 0);
@@ -230,10 +328,23 @@ protected void onDraw(Canvas c){
toastPath.rCubicTo(0, -circleOffset, -circleOffset + th / 2, -th / 2, th / 2, -th / 2);
c.drawCircle(spinnerRect.centerX(), spinnerRect.centerY(), iconBounds.height() / 1.9f, backPaint);
- //loadicon.draw(c);
+
c.drawPath(toastPath, backPaint);
- int yPos = (int) ((th / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
- c.drawText(mText, 0, mText.length(), th / 2 + (MAX_TEXT_WIDTH - mTextBounds.width()) / 2, yPos, textPaint);
+
+ int thb = th - borderOffset*2;
+
+ toastPath.reset();
+ toastPath.moveTo(leftMargin + th / 2, borderOffset);
+ toastPath.rLineTo(ws*(IMAGE_WIDTH + MAX_TEXT_WIDTH), 0);
+ toastPath.rCubicTo(circleOffset, 0, thb / 2, thb / 2 - circleOffset, thb / 2, thb / 2);
+
+ toastPath.rCubicTo(0, circleOffset, circleOffset - thb / 2, thb / 2, -thb / 2, thb / 2);
+ toastPath.rLineTo(ws*(-IMAGE_WIDTH - MAX_TEXT_WIDTH), 0);
+ toastPath.rCubicTo(-circleOffset, 0, -thb / 2, -thb / 2 + circleOffset, -thb / 2, -thb / 2);
+ toastPath.rCubicTo(0, -circleOffset, -circleOffset + thb / 2, -thb / 2, thb / 2, -thb / 2);
+
+ c.drawPath(toastPath, borderPaint);
+ toastPath.reset();
float prog = va.getAnimatedFraction() * 6.0f;
float progrot = prog % 2.0f;
@@ -242,12 +353,17 @@ protected void onDraw(Canvas c){
proglength = .75f - (prog % 3f - 1.5f);
progrot += (prog % 3f - 1.5f)/1.5f * 2f;
}
- //Log.d("spin", "rot " + progrot + " len " + proglength);
- toastPath.reset();
- toastPath.arcTo(spinnerRect, 180 * progrot, Math.min((200 / .75f) * proglength + 1 + 560*(1f-ws),359.9999f));
- loaderPaint.setAlpha((int)(255 * ws));
- c.drawPath(toastPath, loaderPaint);
+ if(mText.length() == 0){
+ ws = Math.max(1f - WIDTH_SCALE, 0f);
+ }
+
+ c.save();
+
+ c.translate((totalWidth - TOAST_HEIGHT)/2, 0);
+ super.onDraw(c);
+
+ c.restore();
if(WIDTH_SCALE > 1f){
Drawable icon = (success) ? completeicon : failedicon;
@@ -262,8 +378,33 @@ protected void onDraw(Canvas c){
c.rotate(90*(1f-circleProg), leftMargin + TOAST_HEIGHT/2, TOAST_HEIGHT/2);
icon.draw(c);
c.restore();
+
+ prevUpdate = 0;
+ return;
+ }
+
+ int yPos = (int) ((th / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
+
+ if(outOfBounds){
+ float shift = 0;
+ if(prevUpdate == 0){
+ prevUpdate = System.currentTimeMillis();
+ }else{
+ shift = ((float)(System.currentTimeMillis() - prevUpdate) / 16f) * MARQUE_STEP;
+
+ if(shift - MAX_TEXT_WIDTH > mTextBounds.width()){
+ prevUpdate = 0;
+ }
+ }
+ c.clipRect(th / 2, 0, th/2 + MAX_TEXT_WIDTH, TOAST_HEIGHT);
+ if (!mLtr || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && getTextDirection() == TEXT_DIRECTION_ANY_RTL)) {
+ c.drawText(mText, th / 2 - mTextBounds.width() + shift, yPos, textPaint);
+ }else{
+ c.drawText(mText, th / 2 - shift + MAX_TEXT_WIDTH, yPos, textPaint);
+ }
+ }else{
+ c.drawText(mText, 0, mText.length(), th / 2 + (MAX_TEXT_WIDTH - mTextBounds.width()) / 2, yPos, textPaint);
}
- //c.drawArc(spinnerRect, 360 * progrot, 200 * proglength + 1, true, loaderPaint);
}
@Override
@@ -322,4 +463,24 @@ private int measureHeight(int measureSpec) {
return result;
}
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ Log.d(getClass().getSimpleName(), "detached");
+ cleanup();
+ }
+
+ public void cleanup(){
+ if(cmp != null){
+ cmp.removeAllUpdateListeners();
+ cmp.removeAllListeners();
+ }
+
+ if(va != null){
+ va.removeAllUpdateListeners();
+ va.removeAllListeners();
+ }
+
+ spinnerDrawable.stop();
+ }
}
diff --git a/loadtoast/src/main/java/net/steamcrafted/loadtoast/MaterialProgressDrawable.java b/loadtoast/src/main/java/net/steamcrafted/loadtoast/MaterialProgressDrawable.java
new file mode 100644
index 0000000..9e20770
--- /dev/null
+++ b/loadtoast/src/main/java/net/steamcrafted/loadtoast/MaterialProgressDrawable.java
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package net.steamcrafted.loadtoast;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+
+import java.util.ArrayList;
+
+public class MaterialProgressDrawable extends Drawable implements Animatable {
+ // Maps to ProgressBar.Large style
+ public static final int LARGE = 0;
+ // Maps to ProgressBar default style
+ public static final int DEFAULT = 1;
+ private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+ private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
+ private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
+ private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
+ // Maps to ProgressBar default style
+ private static final int CIRCLE_DIAMETER = 40;
+ private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width
+ private static final float STROKE_WIDTH = 2.5f;
+ // Maps to ProgressBar.Large style
+ private static final int CIRCLE_DIAMETER_LARGE = 56;
+ private static final float CENTER_RADIUS_LARGE = 12.5f;
+ static final float STROKE_WIDTH_LARGE = 3f;
+ /**
+ * The duration of a single progress spin in milliseconds.
+ */
+ private static final int ANIMATION_DURATION = 1000 * 80 / 60;
+ /**
+ * The number of points in the progress "star".
+ */
+ private static final float NUM_POINTS = 5f;
+ /**
+ * Layout info for the arrowhead in dp
+ */
+ private static final int ARROW_WIDTH = 10;
+ private static final int ARROW_HEIGHT = 5;
+ private static final float ARROW_OFFSET_ANGLE = 0;
+ /**
+ * Layout info for the arrowhead for the large spinner in dp
+ */
+ static final int ARROW_WIDTH_LARGE = 12;
+ static final int ARROW_HEIGHT_LARGE = 6;
+ private static final float MAX_PROGRESS_ARC = .8f;
+ private final int[] COLORS = new int[]{
+ Color.BLACK
+ };
+ /**
+ * The list of animators operating on this drawable.
+ */
+ private final ArrayList mAnimators = new ArrayList();
+ /**
+ * The indicator ring, used to manage animation state.
+ */
+ private final Ring mRing;
+ private final Callback mCallback = new Callback() {
+ @Override
+ public void invalidateDrawable(Drawable d) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable d, Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable d, Runnable what) {
+ unscheduleSelf(what);
+ }
+ };
+ boolean mFinishing;
+ /**
+ * Canvas rotation in degrees.
+ */
+ private float mRotation;
+ private Resources mResources;
+ private View mAnimExcutor;
+ private Animation mAnimation;
+ private float mRotationCount;
+ private double mWidth;
+ private double mHeight;
+ private boolean mShowArrowOnFirstStart = false;
+
+ public MaterialProgressDrawable(Context context, View animExcutor) {
+ mAnimExcutor = animExcutor;
+ mResources = context.getResources();
+
+ mRing = new Ring(mCallback);
+ mRing.setColors(COLORS);
+
+ updateSizes(DEFAULT);
+ setupAnimators();
+ }
+
+ public void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
+ double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
+ final Ring ring = mRing;
+ mWidth = progressCircleWidth;
+ mHeight = progressCircleHeight ;
+ ring.setStrokeWidth((float) strokeWidth );
+ ring.setCenterRadius(centerRadius);
+ ring.setColorIndex(0);
+ ring.setArrowDimensions(arrowWidth , arrowHeight );
+ ring.setInsets((int) mWidth, (int) mHeight);
+ }
+
+ /**
+ * Set the overall size for the progress spinner. This updates the radius
+ * and stroke width of the ring.
+ *
+
+ */
+ public void updateSizes(@ProgressDrawableSize int size) {
+ final DisplayMetrics metrics = mResources.getDisplayMetrics();
+ final float screenDensity = metrics.density;
+
+ if (size == LARGE) {
+ setSizeParameters(CIRCLE_DIAMETER_LARGE*screenDensity, CIRCLE_DIAMETER_LARGE*screenDensity, CENTER_RADIUS_LARGE*screenDensity,
+ STROKE_WIDTH_LARGE*screenDensity, ARROW_WIDTH_LARGE*screenDensity, ARROW_HEIGHT_LARGE*screenDensity);
+ } else {
+ setSizeParameters(CIRCLE_DIAMETER*screenDensity, CIRCLE_DIAMETER*screenDensity, CENTER_RADIUS*screenDensity, STROKE_WIDTH*screenDensity,
+ ARROW_WIDTH*screenDensity, ARROW_HEIGHT*screenDensity);
+ }
+ }
+
+ /**
+ * @param show Set to true to display the arrowhead on the progress spinner.
+ */
+ public void showArrow(boolean show) {
+ mRing.setShowArrow(show);
+ }
+
+ /**
+ * @param scale Set the scale of the arrowhead for the spinner.
+ */
+ public void setArrowScale(float scale) {
+ mRing.setArrowScale(scale);
+ }
+
+ /**
+ * Set the start and end trim for the progress spinner arc.
+ *
+ * @param startAngle start angle
+ * @param endAngle end angle
+ */
+ public void setStartEndTrim(float startAngle, float endAngle) {
+ mRing.setStartTrim(startAngle);
+ mRing.setEndTrim(endAngle);
+ }
+
+ /**
+ * Set the amount of rotation to apply to the progress spinner.
+ *
+ * @param rotation Rotation is from [0..1]
+ */
+ public void setProgressRotation(float rotation) {
+ mRing.setRotation(rotation);
+ }
+
+ /**
+ * Update the background color of the circle image view.
+ */
+ public void setBackgroundColor(int color) {
+ mRing.setBackgroundColor(color);
+ }
+
+ /**
+ * Set the colors used in the progress animation from color resources.
+ * The first color will also be the color of the bar that grows in response
+ * to a user swipe gesture.
+ *
+ * @param colors
+ */
+ public void setColorSchemeColors(int... colors) {
+ mRing.setColors(colors);
+ mRing.setColorIndex(0);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return (int) mHeight;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return (int) mWidth;
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ final Rect bounds = getBounds();
+ final int saveCount = c.save();
+ c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
+ mRing.draw(c, bounds);
+ c.restoreToCount(saveCount);
+ }
+
+ public int getAlpha() {
+ return mRing.getAlpha();
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mRing.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mRing.setColorFilter(colorFilter);
+ }
+
+ @SuppressWarnings("unused")
+ private float getRotation() {
+ return mRotation;
+ }
+
+ @SuppressWarnings("unused")
+ void setRotation(float rotation) {
+ mRotation = rotation;
+ invalidateSelf();
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return !this.mAnimation.hasEnded();
+ }
+
+ @Override
+ public void start() {
+ mAnimation.reset();
+ mRing.storeOriginals();
+ mRing.setShowArrow(mShowArrowOnFirstStart);
+
+ // Already showing some part of the ring
+ if (mRing.getEndTrim() != mRing.getStartTrim()) {
+ mFinishing = true;
+ mAnimation.setDuration(ANIMATION_DURATION / 2);
+ mAnimExcutor.startAnimation(mAnimation);
+ } else {
+ mRing.setColorIndex(0);
+ mRing.resetOriginals();
+ mAnimation.setDuration(ANIMATION_DURATION);
+ mAnimExcutor.startAnimation(mAnimation);
+ }
+ }
+
+ @Override
+ public void stop() {
+ mAnimExcutor.clearAnimation();
+ setRotation(0);
+ mRing.setShowArrow(false);
+ mRing.setColorIndex(0);
+ mRing.resetOriginals();
+ }
+
+ private void applyFinishTranslation(float interpolatedTime, Ring ring) {
+ // shrink back down and complete a full rotation before
+ // starting other circles
+ // Rotation goes between [0..1].
+ float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
+ + 1f);
+ final float startTrim = ring.getStartingStartTrim()
+ + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime;
+ ring.setStartTrim(startTrim);
+ final float rotation = ring.getStartingRotation()
+ + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
+ ring.setRotation(rotation);
+ }
+
+ private void setupAnimators() {
+ final Ring ring = mRing;
+ final Animation animation = new Animation() {
+ @Override
+ public void applyTransformation(float interpolatedTime, Transformation t) {
+ if (mFinishing) {
+ applyFinishTranslation(interpolatedTime, ring);
+ } else {
+ // The minProgressArc is calculated from 0 to create an
+ // angle that
+ // matches the stroke width.
+ final float minProgressArc = (float) Math.toRadians(
+ ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
+ final float startingEndTrim = ring.getStartingEndTrim();
+ final float startingTrim = ring.getStartingStartTrim();
+ final float startingRotation = ring.getStartingRotation();
+
+ // Offset the minProgressArc to where the endTrim is
+ // located.
+ final float minArc = MAX_PROGRESS_ARC - minProgressArc;
+ float endTrim = startingEndTrim + (minArc
+ * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
+ float startTrim = startingTrim + (MAX_PROGRESS_ARC
+ * END_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
+
+ final float sweepTrim = endTrim-startTrim;
+ //Avoid the ring to be a full circle
+ if(Math.abs(sweepTrim)>=1){
+ endTrim = startTrim+0.5f;
+ }
+
+ ring.setEndTrim(endTrim);
+
+ ring.setStartTrim(startTrim);
+
+ final float rotation = startingRotation + (0.25f * interpolatedTime);
+ ring.setRotation(rotation);
+
+ float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
+ + (720.0f * (mRotationCount / NUM_POINTS));
+ setRotation(groupRotation);
+ }
+ }
+ };
+ animation.setRepeatCount(Animation.INFINITE);
+ animation.setRepeatMode(Animation.RESTART);
+ animation.setInterpolator(LINEAR_INTERPOLATOR);
+ animation.setAnimationListener(new Animation.AnimationListener() {
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ mRotationCount = 0;
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // do nothing
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ ring.storeOriginals();
+ ring.goToNextColor();
+ ring.setStartTrim(ring.getEndTrim());
+ if (mFinishing) {
+ // finished closing the last ring from the swipe gesture; go
+ // into progress mode
+ mFinishing = false;
+ animation.setDuration(ANIMATION_DURATION);
+ ring.setShowArrow(false);
+ } else {
+ mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
+ }
+ }
+ });
+ mAnimation = animation;
+ }
+
+ public void showArrowOnFirstStart(boolean showArrowOnFirstStart) {
+ this.mShowArrowOnFirstStart = showArrowOnFirstStart;
+ }
+
+
+ public @interface ProgressDrawableSize {
+ }
+
+ private static class Ring {
+ private final RectF mTempBounds = new RectF();
+ private final Paint mPaint = new Paint();
+ private final Paint mArrowPaint = new Paint();
+
+ private final Callback mCallback;
+ private final Paint mCirclePaint = new Paint();
+ private float mStartTrim = 0.0f;
+ private float mEndTrim = 0.0f;
+ private float mRotation = 0.0f;
+ private float mStrokeWidth = 5.0f;
+ private float mStrokeInset = 2.5f;
+ private int[] mColors;
+ // mColorIndex represents the offset into the available mColors that the
+ // progress circle should currently display. As the progress circle is
+ // animating, the mColorIndex moves by one to the next available color.
+ private int mColorIndex;
+ private float mStartingStartTrim;
+ private float mStartingEndTrim;
+ private float mStartingRotation;
+ private boolean mShowArrow;
+ private Path mArrow;
+ private float mArrowScale;
+ private double mRingCenterRadius;
+ private int mArrowWidth;
+ private int mArrowHeight;
+ private int mAlpha;
+ private int mBackgroundColor;
+
+ public Ring(Callback callback) {
+ mCallback = callback;
+
+ mPaint.setStrokeCap(Paint.Cap.SQUARE);
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Style.STROKE);
+
+ mArrowPaint.setStyle(Style.FILL);
+ mArrowPaint.setAntiAlias(true);
+ }
+
+ public void setBackgroundColor(int color) {
+ mBackgroundColor = color;
+ }
+
+ /**
+ * Set the dimensions of the arrowhead.
+ *
+ * @param width Width of the hypotenuse of the arrow head
+ * @param height Height of the arrow point
+ */
+ public void setArrowDimensions(float width, float height) {
+ mArrowWidth = (int) width;
+ mArrowHeight = (int) height;
+ }
+
+ /**
+ * Draw the progress spinner
+ */
+ public void draw(Canvas c, Rect bounds) {
+ final RectF arcBounds = mTempBounds;
+ arcBounds.set(bounds);
+ arcBounds.inset(mStrokeInset, mStrokeInset);
+
+ final float startAngle = (mStartTrim + mRotation) * 360;
+ final float endAngle = (mEndTrim + mRotation) * 360;
+ float sweepAngle = endAngle - startAngle;
+ mPaint.setColor(mColors[mColorIndex]);
+ c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
+
+ drawTriangle(c, startAngle, sweepAngle, bounds);
+
+ if (mAlpha < 255) {
+ mCirclePaint.setColor(mBackgroundColor);
+ mCirclePaint.setAlpha(255 - mAlpha);
+ c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
+ mCirclePaint);
+ }
+ }
+
+ private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
+ if (mShowArrow) {
+ if (mArrow == null) {
+ mArrow = new Path();
+ mArrow.setFillType(Path.FillType.EVEN_ODD);
+ } else {
+ mArrow.reset();
+ }
+
+ // Adjust the position of the triangle so that it is inset as
+ // much as the arc, but also centered on the arc.
+ float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
+ float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
+
+ // Update the path each time. This works around an issue in SKIA
+ // where concatenating a rotation matrix to a scale matrix
+ // ignored a starting negative rotation. This appears to have
+ // been fixed as of API 21.
+ mArrow.moveTo(0, 0);
+ mArrow.lineTo((mArrowWidth) * mArrowScale, 0);
+ mArrow.lineTo(((mArrowWidth) * mArrowScale / 2), (mArrowHeight
+ * mArrowScale));
+ mArrow.offset(x-((mArrowWidth) * mArrowScale / 2), y);
+ mArrow.close();
+ // draw a triangle
+ mArrowPaint.setColor(mColors[mColorIndex]);
+ //when sweepAngle < 0 adjust the position of the arrow
+ c.rotate(startAngle + (sweepAngle<0?0:sweepAngle) - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
+ bounds.exactCenterY());
+ c.drawPath(mArrow, mArrowPaint);
+ }
+ }
+
+ /**
+ * Set the colors the progress spinner alternates between.
+ *
+ * @param colors Array of integers describing the colors. Must be non-null.
+ */
+ public void setColors(int[] colors) {
+ mColors = colors;
+ // if colors are reset, make sure to reset the color index as well
+ setColorIndex(0);
+ }
+
+ /**
+ * @param index Index into the color array of the color to display in
+ * the progress spinner.
+ */
+ public void setColorIndex(int index) {
+ mColorIndex = index;
+ }
+
+ /**
+ * Proceed to the next available ring color. This will automatically
+ * wrap back to the beginning of colors.
+ */
+ public void goToNextColor() {
+ mColorIndex = (mColorIndex + 1) % (mColors.length);
+ }
+
+ public void setColorFilter(ColorFilter filter) {
+ mPaint.setColorFilter(filter);
+ invalidateSelf();
+ }
+
+ /**
+ * @return Current alpha of the progress spinner and arrowhead.
+ */
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ /**
+ * @param alpha Set the alpha of the progress spinner and associated arrowhead.
+ */
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ }
+
+ @SuppressWarnings("unused")
+ public float getStrokeWidth() {
+ return mStrokeWidth;
+ }
+
+ /**
+ * @param strokeWidth Set the stroke width of the progress spinner in pixels.
+ */
+ public void setStrokeWidth(float strokeWidth) {
+ mStrokeWidth = strokeWidth;
+ mPaint.setStrokeWidth(strokeWidth);
+ invalidateSelf();
+ }
+
+ @SuppressWarnings("unused")
+ public float getStartTrim() {
+ return mStartTrim;
+ }
+
+ @SuppressWarnings("unused")
+ public void setStartTrim(float startTrim) {
+ mStartTrim = startTrim;
+ invalidateSelf();
+ }
+
+ public float getStartingStartTrim() {
+ return mStartingStartTrim;
+ }
+
+ public float getStartingEndTrim() {
+ return mStartingEndTrim;
+ }
+
+ @SuppressWarnings("unused")
+ public float getEndTrim() {
+ return mEndTrim;
+ }
+
+ @SuppressWarnings("unused")
+ public void setEndTrim(float endTrim) {
+ mEndTrim = endTrim;
+ invalidateSelf();
+ }
+
+ @SuppressWarnings("unused")
+ public float getRotation() {
+ return mRotation;
+ }
+
+ @SuppressWarnings("unused")
+ public void setRotation(float rotation) {
+ mRotation = rotation;
+ invalidateSelf();
+ }
+
+ public void setInsets(int width, int height) {
+ final float minEdge = (float) Math.min(width, height);
+ float insets;
+ if (mRingCenterRadius <= 0 || minEdge < 0) {
+ insets = (float) Math.ceil(mStrokeWidth / 2.0f);
+ } else {
+ insets = (float) (minEdge / 2.0f - mRingCenterRadius);
+ }
+ mStrokeInset = insets;
+ }
+
+ @SuppressWarnings("unused")
+ public float getInsets() {
+ return mStrokeInset;
+ }
+
+ public double getCenterRadius() {
+ return mRingCenterRadius;
+ }
+
+ /**
+ * @param centerRadius Inner radius in px of the circle the progress
+ * spinner arc traces.
+ */
+ public void setCenterRadius(double centerRadius) {
+ mRingCenterRadius = centerRadius;
+ }
+
+ /**
+ * @param show Set to true to show the arrow head on the progress spinner.
+ */
+ public void setShowArrow(boolean show) {
+ if (mShowArrow != show) {
+ mShowArrow = show;
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * @param scale Set the scale of the arrowhead for the spinner.
+ */
+ public void setArrowScale(float scale) {
+ if (scale != mArrowScale) {
+ mArrowScale = scale;
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * @return The amount the progress spinner is currently rotated, between [0..1].
+ */
+ public float getStartingRotation() {
+ return mStartingRotation;
+ }
+
+ /**
+ * If the start / end trim are offset to begin with, store them so that
+ * animation starts from that offset.
+ */
+ public void storeOriginals() {
+ mStartingStartTrim = mStartTrim;
+ mStartingEndTrim = mEndTrim;
+ mStartingRotation = mRotation;
+ }
+
+ /**
+ * Reset the progress spinner to default rotation, start and end angles.
+ */
+ public void resetOriginals() {
+ mStartingStartTrim = 0;
+ mStartingEndTrim = 0;
+ mStartingRotation = 0;
+ setStartTrim(0);
+ setEndTrim(0);
+ setRotation(0);
+ }
+
+ private void invalidateSelf() {
+ mCallback.invalidateDrawable(null);
+ }
+ }
+
+ /**
+ * Squishes the interpolation curve into the second half of the animation.
+ */
+ private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
+ @Override
+ public float getInterpolation(float input) {
+ return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
+ }
+ }
+
+ /**
+ * Squishes the interpolation curve into the first half of the animation.
+ */
+ private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
+ @Override
+ public float getInterpolation(float input) {
+ return super.getInterpolation(Math.min(1, input * 2.0f));
+ }
+ }
+}