Ver Fonte

Custom progress drawables are used for Android 4.0+

Aidan Follestad há 10 anos atrás
pai
commit
af2b56258c
18 ficheiros alterados com 1735 adições e 213 exclusões
  1. 22 7
      core/src/main/java/com/afollestad/materialdialogs/DialogInit.java
  2. 184 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/Animators.java
  3. 130 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/HorizontalProgressDrawable.java
  4. 170 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/IndeterminateHorizontalProgressDrawable.java
  5. 160 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/IndeterminateProgressDrawable.java
  6. 84 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/IndeterminateProgressDrawableBase.java
  7. 157 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/Interpolators.java
  8. 155 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/ObjectAnimatorCompat.java
  9. 119 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/ObjectAnimatorCompatBase.java
  10. 48 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/ObjectAnimatorCompatLollipop.java
  11. 192 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/ProgressDrawableBase.java
  12. 123 0
      core/src/main/java/com/afollestad/materialdialogs/internal/progress/SingleHorizontalProgressDrawable.java
  13. 0 206
      core/src/main/java/com/afollestad/materialdialogs/progress/CircularProgressDrawable.java
  14. 10 0
      core/src/main/java/com/afollestad/materialdialogs/util/DialogUtils.java
  15. 56 0
      core/src/main/res/layout-v14/md_stub_progress.xml
  16. 33 0
      core/src/main/res/layout-v14/md_stub_progress_indeterminate.xml
  17. 24 0
      core/src/main/res/layout-v14/md_stub_progress_indeterminate_horizontal.xml
  18. 68 0
      core/src/main/res/values-v14/styles.xml

+ 22 - 7
core/src/main/java/com/afollestad/materialdialogs/DialogInit.java

@@ -25,7 +25,9 @@ import com.afollestad.materialdialogs.internal.MDAdapter;
 import com.afollestad.materialdialogs.internal.MDButton;
 import com.afollestad.materialdialogs.internal.MDRootLayout;
 import com.afollestad.materialdialogs.internal.MDTintHelper;
-import com.afollestad.materialdialogs.progress.CircularProgressDrawable;
+import com.afollestad.materialdialogs.internal.progress.HorizontalProgressDrawable;
+import com.afollestad.materialdialogs.internal.progress.IndeterminateHorizontalProgressDrawable;
+import com.afollestad.materialdialogs.internal.progress.IndeterminateProgressDrawable;
 import com.afollestad.materialdialogs.util.DialogUtils;
 
 import java.util.ArrayList;
@@ -352,12 +354,25 @@ class DialogInit {
             dialog.mProgress = (ProgressBar) dialog.view.findViewById(android.R.id.progress);
             if (dialog.mProgress == null) return;
 
-            if (builder.indeterminateProgress && !builder.indeterminateIsHorizontalProgress &&
-                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
-                    Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-                dialog.mProgress.setIndeterminateDrawable(new CircularProgressDrawable(
-                        builder.widgetColor, builder.context.getResources().getDimension(R.dimen.circular_progress_border)));
-                MDTintHelper.setTint(dialog.mProgress, builder.widgetColor, true);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                if (builder.indeterminateProgress) {
+                    if (builder.indeterminateIsHorizontalProgress) {
+                        IndeterminateHorizontalProgressDrawable d = new IndeterminateHorizontalProgressDrawable(builder.getContext());
+                        d.setTint(builder.widgetColor);
+                        dialog.mProgress.setProgressDrawable(d);
+                        dialog.mProgress.setIndeterminateDrawable(d);
+                    } else {
+                        IndeterminateProgressDrawable d = new IndeterminateProgressDrawable(builder.getContext());
+                        d.setTint(builder.widgetColor);
+                        dialog.mProgress.setProgressDrawable(d);
+                        dialog.mProgress.setIndeterminateDrawable(d);
+                    }
+                } else {
+                    HorizontalProgressDrawable d = new HorizontalProgressDrawable(builder.getContext());
+                    d.setTint(builder.widgetColor);
+                    dialog.mProgress.setProgressDrawable(d);
+                    dialog.mProgress.setIndeterminateDrawable(d);
+                }
             } else {
                 MDTintHelper.setTint(dialog.mProgress, builder.widgetColor);
             }

+ 184 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/Animators.java

@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.graphics.Path;
+import android.os.Build;
+
+/**
+ * Animators backported for Drawables in this library.
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+class Animators {
+
+    private Animators() {
+    }
+
+    // M -522.59998,0
+    // c 48.89972,0 166.02656,0 301.21729,0
+    // c 197.58128,0 420.9827,0 420.9827,0
+    private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X;
+
+    static {
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X = new Path();
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.moveTo(-522.59998f, 0);
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.rCubicTo(48.89972f, 0, 166.02656f,
+                0, 301.21729f, 0);
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.rCubicTo(197.58128f, 0, 420.9827f,
+                0, 420.9827f, 0);
+    }
+
+    // M 0 0.1
+    // L 1 0.826849212646
+    // L 2 0.1
+    private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X;
+
+    static {
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X = new Path();
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.moveTo(0, 0.1f);
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.lineTo(1, 0.826849212646f);
+        PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.lineTo(2, 0.1f);
+    }
+
+    // M -197.60001,0
+    // c 14.28182,0 85.07782,0 135.54689,0
+    // c 54.26191,0 90.42461,0 168.24331,0
+    // c 144.72154,0 316.40982,0 316.40982,0
+    private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X;
+
+    static {
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X = new Path();
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.moveTo(-197.60001f, 0);
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.rCubicTo(14.28182f, 0, 85.07782f, 0,
+                135.54689f, 0);
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.rCubicTo(54.26191f, 0, 90.42461f, 0,
+                168.24331f, 0);
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.rCubicTo(144.72154f, 0, 316.40982f, 0,
+                316.40982f, 0);
+    }
+
+    // M 0.0,0.1
+    // L 1.0,0.571379510698
+    // L 2.0,0.909950256348
+    // L 3.0,0.1
+    private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X;
+
+    static {
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X = new Path();
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.moveTo(0, 0.1f);
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.lineTo(1, 0.571379510698f);
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.lineTo(2, 0.909950256348f);
+        PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.lineTo(3, 0.1f);
+    }
+
+    /**
+     * Create a backported Animator for
+     * {@code @android:anim/progress_indeterminate_horizontal_rect1}.
+     *
+     * @param target The object whose properties are to be animated.
+     * @return An Animator object that is set up to behave the same as the its native counterpart.
+     */
+    public static Animator createIndeterminateHorizontalRect1(Object target) {
+
+        ObjectAnimator translateXAnimator = ObjectAnimatorCompat.ofFloat(target, "translateX", null,
+                PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X);
+        translateXAnimator.setDuration(2000);
+        translateXAnimator.setInterpolator(
+                Interpolators.INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.INSTANCE);
+        translateXAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        ObjectAnimator scaleXAnimator = ObjectAnimatorCompat.ofFloat(target, null, "scaleX",
+                PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X);
+        scaleXAnimator.setDuration(2000);
+        scaleXAnimator.setInterpolator(
+                Interpolators.INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.INSTANCE);
+        scaleXAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(translateXAnimator, scaleXAnimator);
+        return animatorSet;
+    }
+
+    /**
+     * Create a backported Animator for
+     * {@code @android:anim/progress_indeterminate_horizontal_rect2}.
+     *
+     * @param target The object whose properties are to be animated.
+     * @return An Animator object that is set up to behave the same as the its native counterpart.
+     */
+    public static Animator createIndeterminateHorizontalRect2(Object target) {
+
+        ObjectAnimator translateXAnimator = ObjectAnimatorCompat.ofFloat(target, "translateX", null,
+                PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X);
+        translateXAnimator.setDuration(2000);
+        translateXAnimator.setInterpolator(
+                Interpolators.INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.INSTANCE);
+        translateXAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        ObjectAnimator scaleXAnimator = ObjectAnimatorCompat.ofFloat(target, null, "scaleX",
+                PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X);
+        scaleXAnimator.setDuration(2000);
+        scaleXAnimator.setInterpolator(
+                Interpolators.INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.INSTANCE);
+        scaleXAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(translateXAnimator, scaleXAnimator);
+        return animatorSet;
+    }
+
+    /**
+     * Create a backported Animator for {@code @android:anim/progress_indeterminate_material}.
+     *
+     * @param target The object whose properties are to be animated.
+     * @return An Animator object that is set up to behave the same as the its native counterpart.
+     */
+    public static Animator createIndeterminate(Object target) {
+
+        ObjectAnimator trimPathStartAnimator = ObjectAnimator.ofFloat(target, "trimPathStart", 0,
+                0.75f);
+        trimPathStartAnimator.setDuration(1333);
+        trimPathStartAnimator.setInterpolator(Interpolators.TRIM_PATH_START.INSTANCE);
+        trimPathStartAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        ObjectAnimator trimPathEndAnimator = ObjectAnimator.ofFloat(target, "trimPathEnd", 0,
+                0.75f);
+        trimPathEndAnimator.setDuration(1333);
+        trimPathEndAnimator.setInterpolator(Interpolators.TRIM_PATH_END.INSTANCE);
+        trimPathEndAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        ObjectAnimator trimPathOffsetAnimator = ObjectAnimator.ofFloat(target, "trimPathOffset", 0,
+                0.25f);
+        trimPathOffsetAnimator.setDuration(1333);
+        trimPathOffsetAnimator.setInterpolator(Interpolators.LINEAR.INSTANCE);
+        trimPathOffsetAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(trimPathStartAnimator, trimPathEndAnimator,
+                trimPathOffsetAnimator);
+        return animatorSet;
+    }
+
+    /**
+     * Create a backported Animator for
+     * {@code @android:anim/progress_indeterminate_rotation_material}.
+     *
+     * @param target The object whose properties are to be animated.
+     * @return An Animator object that is set up to behave the same as the its native counterpart.
+     */
+    public static Animator createIndeterminateRotation(Object target) {
+        ObjectAnimator rotationAnimator = ObjectAnimator.ofFloat(target, "rotation", 0, 720);
+        rotationAnimator.setDuration(6665);
+        rotationAnimator.setInterpolator(Interpolators.LINEAR.INSTANCE);
+        rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        return rotationAnimator;
+    }
+}

+ 130 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/HorizontalProgressDrawable.java

@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+
+import com.afollestad.materialdialogs.util.DialogUtils;
+
+/**
+ * A backported {@code Drawable} for determinate horizontal {@code ProgressBar}.
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class HorizontalProgressDrawable extends LayerDrawable {
+
+    private int mSecondaryAlpha;
+    private SingleHorizontalProgressDrawable mTrackDrawable;
+    private SingleHorizontalProgressDrawable mSecondaryProgressDrawable;
+    private SingleHorizontalProgressDrawable mProgressDrawable;
+
+    /**
+     * Create a new {@code HorizontalProgressDrawable}.
+     *
+     * @param context the {@code Context} for retrieving style information.
+     */
+    public HorizontalProgressDrawable(Context context) {
+        super(new Drawable[]{
+                new SingleHorizontalProgressDrawable(context),
+                new SingleHorizontalProgressDrawable(context),
+                new SingleHorizontalProgressDrawable(context)
+        });
+
+        setId(0, android.R.id.background);
+        mTrackDrawable = (SingleHorizontalProgressDrawable) getDrawable(0);
+
+        setId(1, android.R.id.secondaryProgress);
+        mSecondaryProgressDrawable = (SingleHorizontalProgressDrawable) getDrawable(1);
+        float disabledAlpha = DialogUtils.resolveFloat(context, android.R.attr.disabledAlpha);
+        mSecondaryAlpha = Math.round(disabledAlpha * 0xFF);
+        mSecondaryProgressDrawable.setAlpha(mSecondaryAlpha);
+        mSecondaryProgressDrawable.setShowTrack(false);
+
+        setId(2, android.R.id.progress);
+        mProgressDrawable = (SingleHorizontalProgressDrawable) getDrawable(2);
+        mProgressDrawable.setShowTrack(false);
+    }
+
+    /**
+     * Get whether this {@code Drawable} is showing a track. The default is true.
+     *
+     * @return Whether this {@code Drawable} is showing a track.
+     */
+    public boolean getShowTrack() {
+        return mTrackDrawable.getShowTrack();
+    }
+
+    /**
+     * Set whether this {@code Drawable} should show a track. The default is true.
+     */
+    public void setShowTrack(boolean showTrack) {
+        if (mTrackDrawable.getShowTrack() != showTrack) {
+            mTrackDrawable.setShowTrack(showTrack);
+            // HACK: Make alpha act as if composited.
+            mSecondaryProgressDrawable.setAlpha(showTrack ? mSecondaryAlpha : 2 * mSecondaryAlpha);
+        }
+    }
+
+    /**
+     * Get whether this {@code Drawable} is using an intrinsic padding. The default is true.
+     *
+     * @return Whether this {@code Drawable} is using an intrinsic padding.
+     */
+    public boolean getUseIntrinsicPadding() {
+        return mTrackDrawable.getUseIntrinsicPadding();
+    }
+
+    /**
+     * Set whether this {@code Drawable} should use an intrinsic padding. The default is true.
+     */
+    public void setUseIntrinsicPadding(boolean useIntrinsicPadding) {
+
+        mTrackDrawable.setUseIntrinsicPadding(useIntrinsicPadding);
+        mSecondaryProgressDrawable.setUseIntrinsicPadding(useIntrinsicPadding);
+        mProgressDrawable.setUseIntrinsicPadding(useIntrinsicPadding);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    // Rewrite for compatibility and workaround lint.
+    @Override
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public void setTint(int tint) {
+        mTrackDrawable.setTint(tint);
+        mSecondaryProgressDrawable.setTint(tint);
+        mProgressDrawable.setTint(tint);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    // Rewrite for compatibility and workaround lint.
+    @Override
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public void setTintList(ColorStateList tint) {
+        mTrackDrawable.setTintList(tint);
+        mSecondaryProgressDrawable.setTintList(tint);
+        mProgressDrawable.setTintList(tint);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    // Rewrite for compatibility and workaround lint.
+    @Override
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mTrackDrawable.setTintMode(tintMode);
+        mSecondaryProgressDrawable.setTintMode(tintMode);
+        mProgressDrawable.setTintMode(tintMode);
+    }
+}

+ 170 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/IndeterminateHorizontalProgressDrawable.java

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.os.Build;
+import android.support.annotation.Keep;
+
+import com.afollestad.materialdialogs.util.DialogUtils;
+
+/**
+ * A backported {@code Drawable} for indeterminate horizontal {@code ProgressBar}.
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class IndeterminateHorizontalProgressDrawable extends IndeterminateProgressDrawableBase {
+
+    private static final float PROGRESS_INTRINSIC_HEIGHT_DP = 3.2f;
+    private static final float PADDED_INTRINSIC_HEIGHT_DP = 16;
+    private static final RectF RECT_BOUND = new RectF(-180, -1, 180, 1);
+    private static final RectF RECT_PADDED_BOUND = new RectF(-180, -5, 180, 5);
+    private static final RectF RECT_PROGRESS = new RectF(-144, -1, 144, 1);
+    private static final RectTransformX RECT_1_TRANSFORM_X = new RectTransformX(-522.6f, 0.1f);
+    private static final RectTransformX RECT_2_TRANSFORM_X = new RectTransformX(-197.6f, 0.1f);
+
+    private int mProgressIntrinsicHeight;
+    private int mPaddedIntrinsicHeight;
+    private boolean mShowTrack = true;
+    private float mTrackAlpha;
+
+    private RectTransformX mRect1TransformX = new RectTransformX(RECT_1_TRANSFORM_X);
+    private RectTransformX mRect2TransformX = new RectTransformX(RECT_2_TRANSFORM_X);
+
+    /**
+     * Create a new {@code IndeterminateHorizontalProgressDrawable}.
+     *
+     * @param context the {@code Context} for retrieving style information.
+     */
+    public IndeterminateHorizontalProgressDrawable(Context context) {
+        super(context);
+
+        float density = context.getResources().getDisplayMetrics().density;
+        mProgressIntrinsicHeight = Math.round(PROGRESS_INTRINSIC_HEIGHT_DP * density);
+        mPaddedIntrinsicHeight = Math.round(PADDED_INTRINSIC_HEIGHT_DP * density);
+
+        mTrackAlpha = DialogUtils.resolveFloat(context, android.R.attr.disabledAlpha);
+
+        mAnimators = new Animator[] {
+                Animators.createIndeterminateHorizontalRect1(mRect1TransformX),
+                Animators.createIndeterminateHorizontalRect2(mRect2TransformX)
+        };
+    }
+
+    /**
+     * Get whether this {@code Drawable} is showing a track. The default is true.
+     *
+     * @return Whether this {@code Drawable} is showing a track.
+     */
+    public boolean getShowTrack() {
+        return mShowTrack;
+    }
+
+    /**
+     * Set whether this {@code Drawable} should show a track. The default is true.
+     */
+    public void setShowTrack(boolean showTrack) {
+        if (mShowTrack != showTrack) {
+            mShowTrack = showTrack;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getIntrinsicHeight() {
+        return mUseIntrinsicPadding ? mPaddedIntrinsicHeight : mProgressIntrinsicHeight;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getOpacity() {
+        if (mAlpha == 0) {
+            return PixelFormat.TRANSPARENT;
+        } else if (mAlpha == 0xFF && (!mShowTrack || mTrackAlpha == 1)) {
+            return PixelFormat.OPAQUE;
+        } else {
+            return PixelFormat.TRANSLUCENT;
+        }
+    }
+
+    @Override
+    protected void onPreparePaint(Paint paint) {
+        paint.setStyle(Paint.Style.FILL);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas, int width, int height, Paint paint) {
+
+        if (mUseIntrinsicPadding) {
+            canvas.scale(width / RECT_PADDED_BOUND.width(), height / RECT_PADDED_BOUND.height());
+            canvas.translate(RECT_PADDED_BOUND.width() / 2, RECT_PADDED_BOUND.height() / 2);
+        } else {
+            canvas.scale(width / RECT_BOUND.width(), height / RECT_BOUND.height());
+            canvas.translate(RECT_BOUND.width() / 2, RECT_BOUND.height() / 2);
+        }
+
+        if (mShowTrack) {
+            paint.setAlpha(Math.round(mAlpha * mTrackAlpha));
+            drawTrackRect(canvas, paint);
+            paint.setAlpha(mAlpha);
+        }
+        drawProgressRect(canvas, mRect2TransformX, paint);
+        drawProgressRect(canvas, mRect1TransformX, paint);
+    }
+
+    private static void drawTrackRect(Canvas canvas, Paint paint) {
+        canvas.drawRect(RECT_BOUND, paint);
+    }
+
+    private static void drawProgressRect(Canvas canvas, RectTransformX transformX, Paint paint) {
+
+        int saveCount = canvas.save();
+        canvas.translate(transformX.mTranslateX, 0);
+        canvas.scale(transformX.mScaleX, 1);
+
+        canvas.drawRect(RECT_PROGRESS, paint);
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    private static class RectTransformX {
+
+        public float mTranslateX;
+        public float mScaleX;
+
+        public RectTransformX(float translateX, float scaleX) {
+            mTranslateX = translateX;
+            mScaleX = scaleX;
+        }
+
+        public RectTransformX(RectTransformX that) {
+            mTranslateX = that.mTranslateX;
+            mScaleX = that.mScaleX;
+        }
+
+        @Keep
+        @SuppressWarnings("unused")
+        public void setTranslateX(float translateX) {
+            mTranslateX = translateX;
+        }
+
+        @Keep
+        @SuppressWarnings("unused")
+        public void setScaleX(float scaleX) {
+            mScaleX = scaleX;
+        }
+    }
+}

+ 160 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/IndeterminateProgressDrawable.java

@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.os.Build;
+import android.support.annotation.Keep;
+
+/**
+ * A backported {@code Drawable} for indeterminate circular {@code ProgressBar}.
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class IndeterminateProgressDrawable extends IndeterminateProgressDrawableBase {
+
+    private static final float PROGRESS_INTRINSIC_SIZE_DP = 3.2f;
+    private static final float PADDED_INTRINSIC_SIZE_DP = 16;
+    private static final RectF RECT_BOUND = new RectF(-21, -21, 21, 21);
+    private static final RectF RECT_PADDED_BOUND = new RectF(-24, -24, 24, 24);
+    private static final RectF RECT_PROGRESS = new RectF(-19, -19, 19, 19);
+
+    private int mProgressIntrinsicSize;
+    private int mPaddedIntrinsicSize;
+
+    private RingPathTransform mRingPathTransform = new RingPathTransform();
+    private RingRotation mRingRotation = new RingRotation();
+
+    /**
+     * Create a new {@code IndeterminateProgressDrawable}.
+     *
+     * @param context the {@code Context} for retrieving style information.
+     */
+    public IndeterminateProgressDrawable(Context context) {
+        super(context);
+
+        float density = context.getResources().getDisplayMetrics().density;
+        mProgressIntrinsicSize = Math.round(PROGRESS_INTRINSIC_SIZE_DP * density);
+        mPaddedIntrinsicSize = Math.round(PADDED_INTRINSIC_SIZE_DP * density);
+
+        mAnimators = new Animator[] {
+                Animators.createIndeterminate(mRingPathTransform),
+                Animators.createIndeterminateRotation(mRingRotation)
+        };
+    }
+
+    private int getIntrinsicSize() {
+        return mUseIntrinsicPadding ? mPaddedIntrinsicSize : mProgressIntrinsicSize;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getIntrinsicWidth() {
+        return getIntrinsicSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getIntrinsicHeight() {
+        return getIntrinsicSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getOpacity() {
+        if (mAlpha == 0) {
+            return PixelFormat.TRANSPARENT;
+        } else if (mAlpha == 0xFF) {
+            return PixelFormat.OPAQUE;
+        } else {
+            return PixelFormat.TRANSLUCENT;
+        }
+    }
+
+    @Override
+    protected void onPreparePaint(Paint paint) {
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(4);
+        paint.setStrokeCap(Paint.Cap.SQUARE);
+        paint.setStrokeJoin(Paint.Join.MITER);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas, int width, int height, Paint paint) {
+
+        if (mUseIntrinsicPadding) {
+            canvas.scale(width / RECT_PADDED_BOUND.width(), height / RECT_PADDED_BOUND.height());
+            canvas.translate(RECT_PADDED_BOUND.width() / 2, RECT_PADDED_BOUND.height() / 2);
+        } else {
+            canvas.scale(width / RECT_BOUND.width(), height / RECT_BOUND.height());
+            canvas.translate(RECT_BOUND.width() / 2, RECT_BOUND.height() / 2);
+        }
+
+        drawRing(canvas, paint);
+    }
+
+    private void drawRing(Canvas canvas, Paint paint) {
+
+        int saveCount = canvas.save();
+        canvas.rotate(mRingRotation.mRotation);
+
+        // startAngle starts at 3 o'clock on a watch.
+        float startAngle = -90 + 360 * (mRingPathTransform.mTrimPathOffset
+                + mRingPathTransform.mTrimPathStart);
+        float sweepAngle = 360 * (mRingPathTransform.mTrimPathEnd
+                - mRingPathTransform.mTrimPathStart);
+        canvas.drawArc(RECT_PROGRESS, startAngle, sweepAngle, false, paint);
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    private static class RingPathTransform {
+
+        public float mTrimPathStart;
+        public float mTrimPathEnd;
+        public float mTrimPathOffset;
+
+        @Keep
+        @SuppressWarnings("unused")
+        public void setTrimPathStart(float trimPathStart) {
+            mTrimPathStart = trimPathStart;
+        }
+
+        @Keep
+        @SuppressWarnings("unused")
+        public void setTrimPathEnd(float trimPathEnd) {
+            mTrimPathEnd = trimPathEnd;
+        }
+
+        @Keep
+        @SuppressWarnings("unused")
+        public void setTrimPathOffset(float trimPathOffset) {
+            mTrimPathOffset = trimPathOffset;
+        }
+    }
+
+    private static class RingRotation {
+
+        private float mRotation;
+
+        @Keep
+        @SuppressWarnings("unused")
+        public void setRotation(float rotation) {
+            mRotation = rotation;
+        }
+    }
+}

+ 84 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/IndeterminateProgressDrawableBase.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Animatable;
+import android.os.Build;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+abstract class IndeterminateProgressDrawableBase extends ProgressDrawableBase
+        implements Animatable {
+
+    protected Animator[] mAnimators;
+
+    public IndeterminateProgressDrawableBase(Context context) {
+        super(context);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (isStarted()) {
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void start() {
+
+        if (isStarted()) {
+            return;
+        }
+
+        for (Animator animator : mAnimators) {
+            animator.start();
+        }
+        invalidateSelf();
+    }
+
+    private boolean isStarted() {
+        for (Animator animator : mAnimators) {
+            if (animator.isStarted()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void stop() {
+        for (Animator animator : mAnimators) {
+            animator.end();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isRunning() {
+        for (Animator animator : mAnimators) {
+            if (animator.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 157 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/Interpolators.java

@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.annotation.TargetApi;
+import android.graphics.Path;
+import android.os.Build;
+import android.support.v4.view.animation.PathInterpolatorCompat;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * Interpolators backported for Animators in this library.
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+class Interpolators {
+
+    /**
+     * Backported Interpolator for
+     * {@code @android:interpolator/progress_indeterminate_horizontal_rect1_translatex}.
+     */
+    public static class INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X {
+        // M 0.0,0.0
+        // L 0.2 0
+        // C 0.3958333333336,0.0 0.474845090492,0.206797621729 0.5916666666664,0.417082932942
+        // C 0.7151610251224,0.639379624869 0.81625,0.974556908664 1.0,1.0
+        private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X;
+        static {
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X = new Path();
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.moveTo(0, 0);
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.lineTo(0.2f, 0);
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.cubicTo(0.3958333333336f, 0,
+                    0.474845090492f, 0.206797621729f, 0.5916666666664f, 0.417082932942f);
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X.cubicTo(0.7151610251224f,
+                    0.639379624869f, 0.81625f, 0.974556908664f, 1, 1);
+        }
+        public static final Interpolator INSTANCE =
+                PathInterpolatorCompat.create(PATH_INDETERMINATE_HORIZONTAL_RECT1_TRANSLATE_X);
+    }
+
+    /**
+     * Backported Interpolator for
+     * {@code @android:interpolator/progress_indeterminate_horizontal_rect1_scalex}.
+     */
+    public static class INDETERMINATE_HORIZONTAL_RECT1_SCALE_X {
+        // M 0 0
+        // L 0.3665 0
+        // C 0.47252618112021,0.062409910275 0.61541608570164,0.5 0.68325,0.5
+        // C 0.75475061236836,0.5 0.75725829093844,0.814510098964 1.0,1.0
+        private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X;
+        static {
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X = new Path();
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.moveTo(0, 0);
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.lineTo(0.3665f, 0);
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.cubicTo(0.47252618112021f, 0.062409910275f,
+                    0.61541608570164f, 0.5f, 0.68325f, 0.5f);
+            PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X.cubicTo(0.75475061236836f, 0.5f,
+                    0.75725829093844f, 0.814510098964f, 1, 1);
+        }
+        public static final Interpolator INSTANCE =
+                PathInterpolatorCompat.create(PATH_INDETERMINATE_HORIZONTAL_RECT1_SCALE_X);
+    }
+
+    /**
+     * Backported Interpolator for
+     * {@code @android:interpolator/progress_indeterminate_horizontal_rect2_translatex}.
+     */
+    public static class INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X {
+        // M 0.0,0.0
+        // C 0.0375,0.0 0.128764607715,0.0895380946618 0.25,0.218553507947
+        // C 0.322410320025,0.295610602487 0.436666666667,0.417591408114
+        //     0.483333333333,0.489826169306
+        // C 0.69,0.80972296795 0.793333333333,0.950016125212 1.0,1.0
+        private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X;
+        static {
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X = new Path();
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.moveTo(0, 0);
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.cubicTo(0.0375f, 0, 0.128764607715f,
+                    0.0895380946618f, 0.25f, 0.218553507947f);
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.cubicTo(0.322410320025f, 0.295610602487f,
+                    0.436666666667f, 0.417591408114f, 0.483333333333f, 0.489826169306f);
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X.cubicTo(0.69f, 0.80972296795f,
+                    0.793333333333f, 0.950016125212f, 1, 1);
+        }
+        public static final Interpolator INSTANCE =
+                PathInterpolatorCompat.create(PATH_INDETERMINATE_HORIZONTAL_RECT2_TRANSLATE_X);
+    }
+
+    /**
+     * Backported Interpolator for
+     * {@code @android:interpolator/progress_indeterminate_horizontal_rect2_scalex}.
+     */
+    public static class INDETERMINATE_HORIZONTAL_RECT2_SCALE_X {
+        // M 0,0
+        // C 0.06834272400867,0.01992566661414 0.19220331656133,0.15855429260523 0.33333333333333,
+        //     0.34926160892842
+        // C 0.38410433133433,0.41477913453861 0.54945792615267,0.68136029463551 0.66666666666667,
+        //     0.68279962777002
+        // C 0.752586273196,0.68179620963216 0.737253971954,0.878896194318 1,1
+        private static final Path PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X;
+        static {
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X = new Path();
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.moveTo(0, 0);
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.cubicTo(0.06834272400867f, 0.01992566661414f,
+                    0.19220331656133f, 0.15855429260523f, 0.33333333333333f, 0.34926160892842f);
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.cubicTo(0.38410433133433f, 0.41477913453861f,
+                    0.54945792615267f, 0.68136029463551f, 0.66666666666667f, 0.68279962777002f);
+            PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X.cubicTo(0.752586273196f, 0.68179620963216f,
+                    0.737253971954f, 0.878896194318f, 1, 1);
+        }
+        public static final Interpolator INSTANCE =
+                PathInterpolatorCompat.create(PATH_INDETERMINATE_HORIZONTAL_RECT2_SCALE_X);
+    }
+
+    /**
+     * Backported Interpolator for {@code @android:interpolator/trim_start_interpolator}.
+     */
+    public static class TRIM_PATH_START {
+        // L 0.5,0
+        // C 0.7,0 0.6,1 1,1
+        private static final Path PATH_TRIM_PATH_START;
+        static {
+            PATH_TRIM_PATH_START = new Path();
+            PATH_TRIM_PATH_START.lineTo(0.5f, 0);
+            PATH_TRIM_PATH_START.cubicTo(0.7f, 0, 0.6f, 1, 1, 1);
+        }
+        public static final Interpolator INSTANCE =
+                PathInterpolatorCompat.create(PATH_TRIM_PATH_START);
+    }
+
+    /**
+     * Backported Interpolator for {@code @android:interpolator/trim_end_interpolator}.
+     */
+    public static class TRIM_PATH_END {
+        // C 0.2,0 0.1,1 0.5,1
+        // L 1,1
+        private static final Path PATH_TRIM_PATH_END;
+        static {
+            PATH_TRIM_PATH_END = new Path();
+            PATH_TRIM_PATH_END.cubicTo(0.2f, 0, 0.1f, 1, 0.5f, 1);
+            PATH_TRIM_PATH_END.lineTo(1, 1);
+        }
+        public static final Interpolator INSTANCE = PathInterpolatorCompat.create(PATH_TRIM_PATH_END);
+    }
+
+    /**
+     * Lazy-initialized singleton Interpolator for {@code @android:interpolator/linear}.
+     */
+    public static class LINEAR {
+        public static final Interpolator INSTANCE = new LinearInterpolator();
+    }
+
+    private Interpolators() {}
+}

+ 155 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/ObjectAnimatorCompat.java

@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.graphics.Path;
+import android.os.Build;
+import android.util.Property;
+
+/**
+ * Helper for accessing features in {@link ObjectAnimator} introduced after API level 11 (for
+ * {@link android.animation.PropertyValuesHolder}) in a backward compatible fashion.
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class ObjectAnimatorCompat {
+
+    private ObjectAnimatorCompat() {}
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between color values. A single
+     * value implies that that value is the one being animated to. Two values imply starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated. This object should
+     * have a public method on it called <code>setName()</code>, where <code>name</code> is
+     * the value of the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property being animated.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return ObjectAnimatorCompatLollipop.ofArgb(target, propertyName, values);
+        }
+        return ObjectAnimatorCompatBase.ofArgb(target, propertyName, values);
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between color values. A single
+     * value implies that that value is the one being animated to. Two values imply starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated.
+     * @param property The property being animated.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property,
+                                            int... values) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return ObjectAnimatorCompatLollipop.ofArgb(target, property, values);
+        }
+        return ObjectAnimatorCompatBase.ofArgb(target, property, values);
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+     * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+     * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+     * coordinates are floats that are set to separate properties designated by
+     * <code>xPropertyName</code> and <code>yPropertyName</code>.
+     *
+     * @param target The object whose properties are to be animated. This object should
+     *               have public methods on it called <code>setNameX()</code> and
+     *               <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+     *               are the value of the <code>xPropertyName</code> and <code>yPropertyName</code>
+     *               parameters, respectively.
+     * @param xPropertyName The name of the property for the x coordinate being animated.
+     * @param yPropertyName The name of the property for the y coordinate being animated.
+     * @param path The <code>Path</code> to animate values along.
+     * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+     */
+    public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
+                                         Path path) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return ObjectAnimatorCompatLollipop.ofFloat(target, xPropertyName, yPropertyName, path);
+        }
+        return ObjectAnimatorCompatBase.ofFloat(target, xPropertyName, yPropertyName, path);
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+     * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+     * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+     * coordinates are floats that are set to separate properties, <code>xProperty</code> and
+     * <code>yProperty</code>.
+     *
+     * @param target The object whose properties are to be animated.
+     * @param xProperty The property for the x coordinate being animated.
+     * @param yProperty The property for the y coordinate being animated.
+     * @param path The <code>Path</code> to animate values along.
+     * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+     */
+    public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
+                                             Property<T, Float> yProperty, Path path) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return ObjectAnimatorCompatLollipop.ofFloat(target, xProperty, yProperty, path);
+        }
+        return ObjectAnimatorCompatBase.ofFloat(target, xProperty, yProperty, path);
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+     * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+     * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+     * coordinates are integers that are set to separate properties designated by
+     * <code>xPropertyName</code> and <code>yPropertyName</code>.
+     *
+     * @param target The object whose properties are to be animated. This object should
+     *               have public methods on it called <code>setNameX()</code> and
+     *               <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+     *               are the value of <code>xPropertyName</code> and <code>yPropertyName</code>
+     *               parameters, respectively.
+     * @param xPropertyName The name of the property for the x coordinate being animated.
+     * @param yPropertyName The name of the property for the y coordinate being animated.
+     * @param path The <code>Path</code> to animate values along.
+     * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+     */
+    public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
+                                       Path path) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return ObjectAnimatorCompatLollipop.ofInt(target, xPropertyName, yPropertyName, path);
+        }
+        return ObjectAnimatorCompatBase.ofInt(target, xPropertyName, yPropertyName, path);
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+     * using two properties.  A <code>Path</code></> animation moves in two dimensions, animating
+     * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+     * coordinates are integers that are set to separate properties, <code>xProperty</code> and
+     * <code>yProperty</code>.
+     *
+     * @param target The object whose properties are to be animated.
+     * @param xProperty The property for the x coordinate being animated.
+     * @param yProperty The property for the y coordinate being animated.
+     * @param path The <code>Path</code> to animate values along.
+     * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+     */
+    public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
+                                           Property<T, Integer> yProperty, Path path) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return ObjectAnimatorCompatLollipop.ofInt(target, xProperty, yProperty, path);
+        }
+        return ObjectAnimatorCompatBase.ofInt(target, xProperty, yProperty, path);
+    }
+}

+ 119 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/ObjectAnimatorCompatBase.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.TargetApi;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.os.Build;
+import android.support.annotation.Size;
+import android.util.Property;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+class ObjectAnimatorCompatBase {
+
+    private static final int NUM_POINTS = 500;
+
+    private ObjectAnimatorCompatBase() {}
+
+    public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
+        ObjectAnimator animator = ObjectAnimator.ofInt(target, propertyName, values);
+        animator.setEvaluator(new ArgbEvaluator());
+        return animator;
+    }
+
+    public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property,
+                                            int... values) {
+        ObjectAnimator animator = ObjectAnimator.ofInt(target, property, values);
+        animator.setEvaluator(new ArgbEvaluator());
+        return animator;
+    }
+
+    public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
+                                         Path path) {
+
+        float[] xValues = new float[NUM_POINTS];
+        float[] yValues = new float[NUM_POINTS];
+        calculateXYValues(path, xValues, yValues);
+
+        PropertyValuesHolder xPvh = PropertyValuesHolder.ofFloat(xPropertyName, xValues);
+        PropertyValuesHolder yPvh = PropertyValuesHolder.ofFloat(yPropertyName, yValues);
+
+        return ObjectAnimator.ofPropertyValuesHolder(target, xPvh, yPvh);
+    }
+
+    public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
+                                             Property<T, Float> yProperty, Path path) {
+
+        float[] xValues = new float[NUM_POINTS];
+        float[] yValues = new float[NUM_POINTS];
+        calculateXYValues(path, xValues, yValues);
+
+        PropertyValuesHolder xPvh = PropertyValuesHolder.ofFloat(xProperty, xValues);
+        PropertyValuesHolder yPvh = PropertyValuesHolder.ofFloat(yProperty, yValues);
+
+        return ObjectAnimator.ofPropertyValuesHolder(target, xPvh, yPvh);
+    }
+
+    public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
+                                       Path path) {
+
+        int[] xValues = new int[NUM_POINTS];
+        int[] yValues = new int[NUM_POINTS];
+        calculateXYValues(path, xValues, yValues);
+
+        PropertyValuesHolder xPvh = PropertyValuesHolder.ofInt(xPropertyName, xValues);
+        PropertyValuesHolder yPvh = PropertyValuesHolder.ofInt(yPropertyName, yValues);
+
+        return ObjectAnimator.ofPropertyValuesHolder(target, xPvh, yPvh);
+    }
+
+    public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
+                                           Property<T, Integer> yProperty, Path path) {
+
+        int[] xValues = new int[NUM_POINTS];
+        int[] yValues = new int[NUM_POINTS];
+        calculateXYValues(path, xValues, yValues);
+
+        PropertyValuesHolder xPvh = PropertyValuesHolder.ofInt(xProperty, xValues);
+        PropertyValuesHolder yPvh = PropertyValuesHolder.ofInt(yProperty, yValues);
+
+        return ObjectAnimator.ofPropertyValuesHolder(target, xPvh, yPvh);
+    }
+
+    private static void calculateXYValues(Path path, @Size(NUM_POINTS) float[] xValues,
+                                          @Size(NUM_POINTS) float[] yValues) {
+
+        PathMeasure pathMeasure = new PathMeasure(path, false /* forceClosed */);
+        float pathLength = pathMeasure.getLength();
+
+        float[] position = new float[2];
+        for (int i = 0; i < NUM_POINTS; ++i) {
+            float distance = (i * pathLength) / (NUM_POINTS - 1);
+            pathMeasure.getPosTan(distance, position, null /* tangent */);
+            xValues[i] = position[0];
+            yValues[i] = position[1];
+        }
+    }
+
+    private static void calculateXYValues(Path path, @Size(NUM_POINTS) int[] xValues,
+                                          @Size(NUM_POINTS) int[] yValues) {
+
+        PathMeasure pathMeasure = new PathMeasure(path, false /* forceClosed */);
+        float pathLength = pathMeasure.getLength();
+
+        float[] position = new float[2];
+        for (int i = 0; i < NUM_POINTS; ++i) {
+            float distance = (i * pathLength) / (NUM_POINTS - 1);
+            pathMeasure.getPosTan(distance, position, null /* tangent */);
+            xValues[i] = Math.round(position[0]);
+            yValues[i] = Math.round(position[1]);
+        }
+    }
+}

+ 48 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/ObjectAnimatorCompatLollipop.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.graphics.Path;
+import android.os.Build;
+import android.util.Property;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class ObjectAnimatorCompatLollipop {
+
+    private ObjectAnimatorCompatLollipop() {}
+
+    public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
+        return ObjectAnimator.ofArgb(target, propertyName, values);
+    }
+
+    public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property,
+                                            int... values) {
+        return ObjectAnimator.ofArgb(target, property, values);
+    }
+
+    public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
+                                         Path path) {
+        return ObjectAnimator.ofFloat(target, xPropertyName, yPropertyName, path);
+    }
+
+    public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
+                                             Property<T, Float> yProperty, Path path) {
+        return ObjectAnimator.ofFloat(target, xProperty, yProperty, path);
+    }
+
+    public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
+                                       Path path) {
+        return ObjectAnimator.ofInt(target, xPropertyName, yPropertyName, path);
+    }
+
+    public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
+                                           Property<T, Integer> yProperty, Path path) {
+        return ObjectAnimator.ofInt(target, xProperty, yProperty, path);
+    }
+}
+

+ 192 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/ProgressDrawableBase.java

@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.view.ViewCompat;
+
+import com.afollestad.materialdialogs.R;
+import com.afollestad.materialdialogs.util.DialogUtils;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+abstract class ProgressDrawableBase extends Drawable {
+
+    protected boolean mUseIntrinsicPadding = true;
+    protected int mAlpha = 0xFF;
+    protected ColorFilter mColorFilter;
+    protected ColorStateList mTint;
+    protected PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;
+    protected PorterDuffColorFilter mTintFilter;
+    protected boolean mAutoMirrored = true;
+
+    private Paint mPaint;
+
+    public ProgressDrawableBase(Context context) {
+        int colorControlActivated = DialogUtils.resolveColor(context, R.attr.colorControlActivated);
+        // setTint() has been overridden for compatibility; DrawableCompat won't work because
+        // wrapped Drawable won't be Animatable.
+        setTint(colorControlActivated);
+    }
+
+    /**
+     * Get whether this {@code Drawable} is showing a track. The default is true.
+     *
+     * @return Whether this {@code Drawable} is showing a track.
+     */
+    public boolean getUseIntrinsicPadding() {
+        return mUseIntrinsicPadding;
+    }
+
+    /**
+     * Set whether this {@code Drawable} should show a track. The default is true.
+     */
+    public void setUseIntrinsicPadding(boolean useIntrinsicPadding) {
+        if (mUseIntrinsicPadding != useIntrinsicPadding) {
+            mUseIntrinsicPadding = useIntrinsicPadding;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setAlpha(int alpha) {
+        if (mAlpha != alpha) {
+            mAlpha = alpha;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    // Rewrite for compatibility.
+    @Override
+    public void setTint(int tint) {
+        setTintList(ColorStateList.valueOf(tint));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ColorFilter getColorFilter() {
+        return mColorFilter;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mColorFilter = colorFilter;
+        invalidateSelf();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mTint = tint;
+        mTintFilter = makeTintFilter(tint, mTintMode);
+        invalidateSelf();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mTintMode = tintMode;
+        mTintFilter = makeTintFilter(mTint, tintMode);
+        invalidateSelf();
+    }
+
+    private PorterDuffColorFilter makeTintFilter(ColorStateList tint, PorterDuff.Mode tintMode) {
+
+        if (tint == null || tintMode == null) {
+            return null;
+        }
+
+        int color = tint.getColorForState(getState(), Color.TRANSPARENT);
+        // They made PorterDuffColorFilter.setColor() and setMode() @hide.
+        return new PorterDuffColorFilter(color, tintMode);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAutoMirrored() {
+        return mAutoMirrored;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setAutoMirrored(boolean autoMirrored) {
+        if (mAutoMirrored != autoMirrored) {
+            mAutoMirrored = autoMirrored;
+            invalidateSelf();
+        }
+    }
+
+    private boolean needMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void draw(Canvas canvas) {
+
+        Rect bounds = getBounds();
+        if (bounds.width() == 0 || bounds.height() == 0) {
+            return;
+        }
+
+        if (mPaint == null) {
+            mPaint = new Paint();
+            mPaint.setAntiAlias(true);
+            mPaint.setColor(Color.BLACK);
+            onPreparePaint(mPaint);
+        }
+        mPaint.setAlpha(mAlpha);
+        ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter;
+        mPaint.setColorFilter(colorFilter);
+
+        int saveCount = canvas.save();
+
+        canvas.translate(bounds.left, bounds.top);
+        if (needMirroring()) {
+            canvas.translate(bounds.width(), 0);
+            canvas.scale(-1, 1);
+        }
+
+        onDraw(canvas, bounds.width(), bounds.height(), mPaint);
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    abstract protected void onPreparePaint(Paint paint);
+
+    abstract protected void onDraw(Canvas canvas, int width, int height, Paint paint);
+}

+ 123 - 0
core/src/main/java/com/afollestad/materialdialogs/internal/progress/SingleHorizontalProgressDrawable.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+ * All Rights Reserved.
+ */
+
+package com.afollestad.materialdialogs.internal.progress;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.os.Build;
+
+import com.afollestad.materialdialogs.util.DialogUtils;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+class SingleHorizontalProgressDrawable extends ProgressDrawableBase {
+
+    private static final float PROGRESS_INTRINSIC_HEIGHT_DP = 3.2f;
+    private static final float PADDED_INTRINSIC_HEIGHT_DP = 16;
+    private static final RectF RECT_BOUND = new RectF(-180, -1, 180, 1);
+    private static final RectF RECT_PADDED_BOUND = new RectF(-180, -5, 180, 5);
+    private static final int LEVEL_MAX = 10000;
+
+    private int mProgressIntrinsicHeight;
+    private int mPaddedIntrinsicHeight;
+    private boolean mShowTrack = true;
+    private float mTrackAlpha;
+
+    public SingleHorizontalProgressDrawable(Context context) {
+        super(context);
+
+        float density = context.getResources().getDisplayMetrics().density;
+        mProgressIntrinsicHeight = Math.round(PROGRESS_INTRINSIC_HEIGHT_DP * density);
+        mPaddedIntrinsicHeight = Math.round(PADDED_INTRINSIC_HEIGHT_DP * density);
+
+        mTrackAlpha = DialogUtils.resolveFloat(context, android.R.attr.disabledAlpha);
+    }
+
+    public boolean getShowTrack() {
+        return mShowTrack;
+    }
+
+    public void setShowTrack(boolean showTrack) {
+        if (mShowTrack != showTrack) {
+            mShowTrack = showTrack;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getIntrinsicHeight() {
+        return mUseIntrinsicPadding ? mPaddedIntrinsicHeight : mProgressIntrinsicHeight;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getOpacity() {
+        if (mAlpha == 0) {
+            return PixelFormat.TRANSPARENT;
+        } else if (mAlpha == 0xFF && (!mShowTrack || mTrackAlpha == 1)) {
+            return PixelFormat.OPAQUE;
+        } else {
+            return PixelFormat.TRANSLUCENT;
+        }
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        invalidateSelf();
+        return true;
+    }
+
+    @Override
+    protected void onPreparePaint(Paint paint) {
+        paint.setStyle(Paint.Style.FILL);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas, int width, int height, Paint paint) {
+
+        if (mUseIntrinsicPadding) {
+            canvas.scale(width / RECT_PADDED_BOUND.width(), height / RECT_PADDED_BOUND.height());
+            canvas.translate(RECT_PADDED_BOUND.width() / 2, RECT_PADDED_BOUND.height() / 2);
+        } else {
+            canvas.scale(width / RECT_BOUND.width(), height / RECT_BOUND.height());
+            canvas.translate(RECT_BOUND.width() / 2, RECT_BOUND.height() / 2);
+        }
+
+        if (mShowTrack) {
+            paint.setAlpha(Math.round(mAlpha * mTrackAlpha));
+            drawTrackRect(canvas, paint);
+            paint.setAlpha(mAlpha);
+        }
+        drawProgressRect(canvas, paint);
+    }
+
+    private static void drawTrackRect(Canvas canvas, Paint paint) {
+        canvas.drawRect(RECT_BOUND, paint);
+    }
+
+    private void drawProgressRect(Canvas canvas, Paint paint) {
+
+        int level = getLevel();
+        if (level == 0) {
+            return;
+        }
+
+        int saveCount = canvas.save();
+        canvas.scale((float) level / LEVEL_MAX, 1, RECT_BOUND.left, 0);
+
+        canvas.drawRect(RECT_BOUND, paint);
+
+        canvas.restoreToCount(saveCount);
+    }
+}

+ 0 - 206
core/src/main/java/com/afollestad/materialdialogs/progress/CircularProgressDrawable.java

@@ -1,206 +0,0 @@
-package com.afollestad.materialdialogs.progress;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Property;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-
-@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
-public class CircularProgressDrawable extends Drawable
-        implements Animatable {
-
-    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();
-    private static final Interpolator SWEEP_INTERPOLATOR = new DecelerateInterpolator();
-    private static final int ANGLE_ANIMATOR_DURATION = 2000;
-    private static final int SWEEP_ANIMATOR_DURATION = 600;
-    private static final int MIN_SWEEP_ANGLE = 30;
-    private final RectF fBounds = new RectF();
-
-    private ObjectAnimator mObjectAnimatorSweep;
-    private ObjectAnimator mObjectAnimatorAngle;
-    private boolean mModeAppearing;
-    private final Paint mPaint;
-    private float mCurrentGlobalAngleOffset;
-    private float mCurrentGlobalAngle;
-    private float mCurrentSweepAngle;
-    private final float mBorderWidth;
-    private boolean mRunning;
-
-    public CircularProgressDrawable(int color, float borderWidth) {
-        mBorderWidth = borderWidth;
-
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeWidth(borderWidth);
-        mPaint.setColor(color);
-
-        setupAnimations();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
-        float sweepAngle = mCurrentSweepAngle;
-        if (!mModeAppearing) {
-            startAngle = startAngle + sweepAngle;
-            sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
-        } else {
-            sweepAngle += MIN_SWEEP_ANGLE;
-        }
-        canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mPaint.setAlpha(alpha);
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        mPaint.setColorFilter(cf);
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSPARENT;
-    }
-
-    @Override
-    public void start() {
-        if (isRunning()) {
-            return;
-        }
-        mRunning = true;
-        mObjectAnimatorAngle.start();
-        mObjectAnimatorSweep.start();
-        invalidateSelf();
-    }
-
-    @Override
-    public void stop() {
-        if (!isRunning()) {
-            return;
-        }
-        mRunning = false;
-        mObjectAnimatorAngle.cancel();
-        mObjectAnimatorSweep.cancel();
-        invalidateSelf();
-    }
-
-    @Override
-    public boolean isRunning() {
-        return mRunning;
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        super.onBoundsChange(bounds);
-        fBounds.left = bounds.left + mBorderWidth / 2f + .5f;
-        fBounds.right = bounds.right - mBorderWidth / 2f - .5f;
-        fBounds.top = bounds.top + mBorderWidth / 2f + .5f;
-        fBounds.bottom = bounds.bottom - mBorderWidth / 2f - .5f;
-    }
-
-    public void setCurrentGlobalAngle(float currentGlobalAngle) {
-        mCurrentGlobalAngle = currentGlobalAngle;
-        invalidateSelf();
-    }
-
-    public float getCurrentGlobalAngle() {
-        return mCurrentGlobalAngle;
-    }
-
-    public void setCurrentSweepAngle(float currentSweepAngle) {
-        mCurrentSweepAngle = currentSweepAngle;
-        invalidateSelf();
-    }
-
-    public float getCurrentSweepAngle() {
-        return mCurrentSweepAngle;
-    }
-
-    private void toggleAppearingMode() {
-        mModeAppearing = !mModeAppearing;
-        if (mModeAppearing) {
-            mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
-        }
-    }
-
-    //////////////////////////////////////////////////////////////////////////////
-    ////////////////            Animation
-
-    private final Property<CircularProgressDrawable, Float> mAngleProperty
-            = new Property<CircularProgressDrawable, Float>(Float.class, "angle") {
-        @Override
-        public Float get(CircularProgressDrawable object) {
-            return object.getCurrentGlobalAngle();
-        }
-
-        @Override
-        public void set(CircularProgressDrawable object, Float value) {
-            object.setCurrentGlobalAngle(value);
-        }
-    };
-
-    private final Property<CircularProgressDrawable, Float> mSweepProperty
-            = new Property<CircularProgressDrawable, Float>(Float.class, "arc") {
-        @Override
-        public Float get(CircularProgressDrawable object) {
-            return object.getCurrentSweepAngle();
-        }
-
-        @Override
-        public void set(CircularProgressDrawable object, Float value) {
-            object.setCurrentSweepAngle(value);
-        }
-    };
-
-    private void setupAnimations() {
-        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
-        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
-        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
-        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
-        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
-
-        mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2);
-        mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
-        mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
-        mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);
-        mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
-        mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-                toggleAppearingMode();
-            }
-        });
-    }
-}

+ 10 - 0
core/src/main/java/com/afollestad/materialdialogs/util/DialogUtils.java

@@ -22,6 +22,16 @@ import com.afollestad.materialdialogs.MaterialDialog;
  */
 public class DialogUtils {
 
+    @SuppressWarnings("ConstantConditions")
+    public static float resolveFloat(Context context, int attr) {
+        TypedArray a = context.obtainStyledAttributes(null, new int[]{attr});
+        try {
+            return a.getFloat(0, 0);
+        } finally {
+            a.recycle();
+        }
+    }
+
     public static int adjustAlpha(int color, @SuppressWarnings("SameParameterValue") float factor) {
         int alpha = Math.round(Color.alpha(color) * factor);
         int red = Color.red(color);

+ 56 - 0
core/src/main/res/layout-v14/md_stub_progress.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="UnusedAttribute">
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="20dp"
+        android:layout_marginTop="4dp"
+        android:fontFamily="sans-serif"
+        android:textSize="@dimen/md_content_textsize"
+        tools:text="Message" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <ProgressBar
+            android:id="@android:id/progress"
+            style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignLeft="@android:id/progress"
+            android:layout_alignStart="@android:id/progress"
+            android:layout_below="@android:id/progress"
+            android:gravity="start"
+            android:minWidth="36dp"
+            android:textAlignment="viewStart"
+            android:textSize="@dimen/md_content_textsize"
+            android:textStyle="bold"
+            tools:text="100%" />
+
+        <TextView
+            android:id="@+id/minMax"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignEnd="@android:id/progress"
+            android:layout_alignRight="@android:id/progress"
+            android:layout_below="@android:id/progress"
+            android:gravity="end"
+            android:minWidth="48dp"
+            android:textAlignment="viewEnd"
+            android:textSize="@dimen/md_content_textsize"
+            tools:text="100%" />
+
+    </RelativeLayout>
+
+</merge>

+ 33 - 0
core/src/main/res/layout-v14/md_stub_progress_indeterminate.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="end|center_vertical"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/md_content_padding_top"
+    android:paddingLeft="@dimen/md_dialog_frame_margin"
+    android:paddingRight="@dimen/md_dialog_frame_margin"
+    android:paddingTop="@dimen/md_content_padding_top">
+
+    <ProgressBar
+        android:id="@android:id/progress"
+        style="@style/Widget.MaterialProgressBar.ProgressBar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:indeterminate="true" />
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="sans-serif"
+        android:gravity="start"
+        android:paddingLeft="16dp"
+        android:paddingStart="16dp"
+        android:textAlignment="viewStart"
+        android:textSize="16sp"
+        tools:ignore="NewApi,RtlSymmetry,UnusedAttribute"
+        tools:text="Message" />
+
+</LinearLayout>

+ 24 - 0
core/src/main/res/layout-v14/md_stub_progress_indeterminate_horizontal.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="UnusedAttribute">
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="20dp"
+        android:layout_marginTop="4dp"
+        android:fontFamily="sans-serif"
+        android:textSize="@dimen/md_content_textsize"
+        tools:text="Message" />
+
+    <ProgressBar
+        android:id="@android:id/progress"
+        style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:indeterminate="true" />
+
+</merge>

+ 68 - 0
core/src/main/res/values-v14/styles.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (c) 2015 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
+  ~ All Rights Reserved.
+  -->
+
+<resources>
+
+    <style name="Widget.MaterialProgressBar.ProgressBar.Horizontal" parent="android:Widget.ProgressBar.Horizontal">
+        <!--
+        Disabled for correct behavior on Android 4.x
+        <item name="android:progressDrawable">@null</item>
+        -->
+        <item name="android:indeterminateDrawable">@null</item>
+        <item name="android:minHeight">16dp</item>
+        <item name="android:maxHeight">16dp</item>
+    </style>
+
+    <!--<style name="Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding">-->
+        <!--<item name="android:minHeight">3.2dp</item>-->
+        <!--<item name="android:maxHeight">3.2dp</item>-->
+    <!--</style>-->
+
+    <style name="Widget.MaterialProgressBar.ProgressBar" parent="android:Widget.ProgressBar">
+        <item name="android:indeterminateDrawable">@null</item>
+        <item name="android:minWidth">48dp</item>
+        <item name="android:maxWidth">48dp</item>
+        <item name="android:minHeight">48dp</item>
+        <item name="android:maxHeight">48dp</item>
+    </style>
+
+    <!--<style name="Widget.MaterialProgressBar.ProgressBar.NoPadding">-->
+        <!--<item name="android:minWidth">42dp</item>-->
+        <!--<item name="android:maxWidth">42dp</item>-->
+        <!--<item name="android:minHeight">42dp</item>-->
+        <!--<item name="android:maxHeight">42dp</item>-->
+    <!--</style>-->
+
+    <!--<style name="Widget.MaterialProgressBar.ProgressBar.Large">-->
+        <!--<item name="android:minWidth">76dp</item>-->
+        <!--<item name="android:maxWidth">76dp</item>-->
+        <!--<item name="android:minHeight">76dp</item>-->
+        <!--<item name="android:maxHeight">76dp</item>-->
+    <!--</style>-->
+
+    <!--<style name="Widget.MaterialProgressBar.ProgressBar.Large.NoPadding">-->
+        <!--<item name="android:minWidth">66.5dp</item>-->
+        <!--<item name="android:maxWidth">66.5dp</item>-->
+        <!--<item name="android:minHeight">66.5dp</item>-->
+        <!--<item name="android:maxHeight">66.5dp</item>-->
+    <!--</style>-->
+
+    <!--<style name="Widget.MaterialProgressBar.ProgressBar.Small">-->
+        <!--<item name="android:minWidth">16dp</item>-->
+        <!--<item name="android:maxWidth">16dp</item>-->
+        <!--<item name="android:minHeight">16dp</item>-->
+        <!--<item name="android:maxHeight">16dp</item>-->
+    <!--</style>-->
+
+    <!--<style name="Widget.MaterialProgressBar.ProgressBar.Small.NoPadding">-->
+        <!--<item name="android:minWidth">14dp</item>-->
+        <!--<item name="android:maxWidth">14dp</item>-->
+        <!--<item name="android:minHeight">14dp</item>-->
+        <!--<item name="android:maxHeight">14dp</item>-->
+    <!--</style>-->
+
+</resources>