Commits

Scott Baar committed 3228f0a

bar animate

Comments (0)

Files changed (7)

HoloGraphLibrary/src/com/echo/holographlibrary/Bar.java

     private int mValueColor = Color.WHITE;
     private String mName = null;
     private float mValue;
+    private float mOldValue;
+    private float mGoalValue;
     private String mValueString = null;
+    private String mValuePrefix = null;
+    private String mValueSuffix = null;
 
     public int getColor() {
         return mColor;
         mValue = value;
     }
 
+    public float getOldValue() {
+        return mOldValue;
+    }
+
+    public void setOldValue(float oldValue) { mOldValue = oldValue; }
+
+    public float getGoalValue() {
+        return mGoalValue;
+    }
+
+    public void setGoalValue(float goalValue) { mGoalValue = goalValue; }
+
     public String getValueString() {
         if (mValueString != null) {
             return mValueString;
     public void setValueString(final String valueString) {
         mValueString = valueString;
     }
+    public String getValuePrefix() {return mValuePrefix;}
+
+    public void setValuePrefix(String valuePrefix) { mValuePrefix = valuePrefix; }
+
+    public String getValueSuffix() {return mValueSuffix;}
+
+    public void setValueSuffix(String valueSuffix) { mValueSuffix = valueSuffix; }
+
+    public void makeValueString(int decimalPrecision){
+        String base = String.format("%." + String.valueOf(decimalPrecision)+"f",mValue);
+        if (getValuePrefix() != null) base = getValuePrefix() + base;
+        if (getValueSuffix() != null) base = base + getValueSuffix();
+        setValueString(base);
+    }
 
     public Path getPath() {
         return mPath;

HoloGraphLibrary/src/com/echo/holographlibrary/BarGraph.java

 
 package com.echo.holographlibrary;
 
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.NinePatchDrawable;
+import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
 
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 
-public class BarGraph extends View {
+public class BarGraph extends View implements HoloGraphAnimate {
 
     private static final int VALUE_FONT_SIZE = 30;
     private static final int AXIS_LABEL_FONT_SIZE = 15;
     private OnBarClickedListener mListener;
     private int mAxisColor;
 
+    private int mDuration = 300;//in ms
+    private Interpolator mInterpolator;
+    private Animator.AnimatorListener mAnimationListener;
+    private ValueAnimator mValueAnimator;
+    private float mMaxValue;            //max value to use when animating
+    private float mOldMaxValue;
+    private float mGoalMaxValue;
+    private long mLastTimeValueStringsUpdated;
+    private long mValueStringUpdateInterval = 200;//ms; how often to update the value strings when animating
+    private int mValueStringPrecision = 0;//how many decimals to put in the value string when animating; 0 for integers
+
     public BarGraph(Context context) {
         this(context, null);
     }
         }
         float barWidth = (getWidth() - (padding * 2) * mBars.size()) / mBars.size();
 
-        // Maximum y value = sum of all values.
+        // Maximum y value = Max(highest current value, highest goal value when animation finishes)
         for (final Bar bar : mBars) {
             if (bar.getValue() > maxValue) {
                 maxValue = bar.getValue();
             }
         }
+
         if (maxValue == 0) {
             maxValue = 1;
         }
+        if (isAnimating()){
+            maxValue = mMaxValue;
+        }
 
         int count = 0;
 
         mListener = listener;
     }
 
+
+    @Override
+    public int getDuration() {
+        return mDuration;
+    }
+
+    @Override
+    public void setDuration(int duration) {mDuration = duration;}
+
+    @Override
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Make sure your interpolator ends at a value within .01 of 1 or else call makeValueString() on each bar + invalidate()
+     * in onAnimationEnd in a custom listener to make sure each valueString reflects the goalValue.
+     * Most end at 1.0 but some, like BounceInterpolator, do not. Be careful when using custom interpolators.
+     * @param interpolator
+     */
+    @Override
+    public void setInterpolator(Interpolator interpolator) {mInterpolator = interpolator;}
+
+    public int getmValueStringPrecision() {
+        return mValueStringPrecision;
+    }
+
+    public void setValueStringPrecision(int valueStringPrecision) {mValueStringPrecision = valueStringPrecision;}
+
+    public long getValueStringUpdateInterval() {
+        return mValueStringUpdateInterval;
+    }
+
+    public void setValueStringUpdateInterval(long valueStringUpdateInterval) {mValueStringUpdateInterval = valueStringUpdateInterval;}
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public boolean isAnimating() {
+        if(mValueAnimator != null)
+            return mValueAnimator.isRunning();
+        return false;
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public boolean cancelAnimating() {
+        if (mValueAnimator != null)
+            mValueAnimator.cancel();
+        return false;
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public void animateToGoalValues() {
+        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1){
+            Log.e("HoloGraphLibrary compatibility error", "Animation not supported on api level 11 and below. Returning without animating.");
+            return;
+        }
+        if (mValueAnimator != null)
+            mValueAnimator.cancel();
+
+        mOldMaxValue = 0;
+        mGoalMaxValue = 0;
+        for (Bar b : mBars) {
+            b.setOldValue(b.getValue());
+            mOldMaxValue = Math.max(mOldMaxValue, b.getValue());
+            mGoalMaxValue = Math.max(mGoalMaxValue, b.getGoalValue());
+        }
+        mMaxValue = mOldMaxValue;
+        ValueAnimator va = ValueAnimator.ofFloat(0,1);
+        mValueAnimator = va;
+        va.setDuration(getDuration());
+        if (mInterpolator == null) mInterpolator = new LinearInterpolator();
+        va.setInterpolator(mInterpolator);
+        if (mAnimationListener != null) va.addListener(mAnimationListener);
+        mLastTimeValueStringsUpdated = System.currentTimeMillis();
+        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float f = animation.getAnimatedFraction();
+                // Log.d("f", String.valueOf(f));
+
+                for (Bar b : mBars) {
+                    float x = b.getGoalValue() - b.getOldValue();
+                    b.setValue(b.getOldValue() + (x * f));
+                    float xMax = mGoalMaxValue - mOldMaxValue;
+                    mMaxValue = mOldMaxValue + (xMax * f);
+                }
+                long now = System.currentTimeMillis();
+                //check to see if f is close to 1f because some interpolators like BounceInterpolator don't quite end at 1f.
+                if ((mLastTimeValueStringsUpdated + mValueStringUpdateInterval < now) || (Math.round(f*100)==100f))
+                {
+                    for (Bar b : mBars)
+                        b.makeValueString(mValueStringPrecision);
+                    mLastTimeValueStringsUpdated = now;
+                }
+                postInvalidate();
+            }});
+        va.start();
+
+    }
+
+
+    @Override
+    public void setAnimationListener(Animator.AnimatorListener animationListener) {
+        mAnimationListener = animationListener;
+
+    }
+
     public interface OnBarClickedListener {
         abstract void onClick(int index);
     }

HoloGraphLibrary/src/com/echo/holographlibrary/HoloGraphAnimate.java

     Interpolator getInterpolator();
     void setInterpolator(Interpolator interpolator);
 
+    boolean isAnimating();
+    boolean cancelAnimating();
     void animateToGoalValues();
     void setAnimationListener(Animator.AnimatorListener animationListener);
 }

HoloGraphLibrary/src/com/echo/holographlibrary/PieGraph.java

     private boolean mDrawCompleted = false;
     private RectF mRectF = new RectF();
 
+
+    private int mDuration = 300;//in ms
+    private Interpolator mInterpolator;
+    private Animator.AnimatorListener mAnimationListener;
+    private ValueAnimator mValueAnimator;
+
     public PieGraph(Context context) {
         this(context, null);
     }
         postInvalidate();
     }
 
-    int mDuration = 300;
-    Interpolator mInterpolator;
-    Animator.AnimatorListener mAnimationListener;
     @Override
     public int getDuration() {
         return mDuration;
     @Override
     public void setInterpolator(Interpolator interpolator) {mInterpolator = interpolator;}
 
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public boolean isAnimating() {
+        if(mValueAnimator != null)
+            return mValueAnimator.isRunning();
+        return false;
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public boolean cancelAnimating() {
+        if (mValueAnimator != null)
+            mValueAnimator.cancel();
+        return false;
+    }
+
+
+    /**
+     * Stops running animation and starts a new one, animating each slice from their current to goal value.
+     * If removing a slice, consider animating to 0 then removing in onAnimationEnd listener.
+     * Default inerpolator is linear; constant speed.
+     */
     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
     @Override
     public void animateToGoalValues() {
             Log.e("HoloGraphLibrary compatibility error", "Animation not supported on api level 12 and below. Returning without animating.");
             return;
         }
+        if (mValueAnimator != null)
+            mValueAnimator.cancel();
+
         for (PieSlice s : mSlices)
             s.setOldValue(s.getValue());
         ValueAnimator va = ValueAnimator.ofFloat(0,1);
+        mValueAnimator = va;
         va.setDuration(getDuration());
         if (mInterpolator == null) mInterpolator = new LinearInterpolator();
         va.setInterpolator(mInterpolator);

HoloGraphLibrarySample/res/layout/fragment_bargraph.xml

 
     <com.echo.holographlibrary.BarGraph
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
         android:id="@+id/bargraph"
         android:layout_margin="@dimen/default_margin"
         app:barAxisColor="@color/transparent_blue"
         app:barShowText="true"
         app:barShowAxis="true"/>
 
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Animate to random values"
+        android:id="@+id/animateBarButton"
+        android:layout_weight="0"/>
+
 </LinearLayout>

HoloGraphLibrarySample/src/com/echo/holographlibrarysample/BarFragment.java

 
 package com.echo.holographlibrarysample;
 
+import android.animation.Animator;
+import android.annotation.TargetApi;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnticipateOvershootInterpolator;
+import android.view.animation.BounceInterpolator;
+import android.view.animation.CycleInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Button;
 import android.widget.Toast;
 
 import com.echo.holographlibrary.Bar;
         bar.setValue(2000);
         bar.setValueString("$2,000");
         aBars.add(bar);
+        bar = new Bar();
+        bar.setColor(resources.getColor(R.color.purple));
+        bar.setName("Test3");
+        bar.setValue(1500);
+        bar.setValueString("$1,500");
+        aBars.add(bar);
 
-        BarGraph barGraph = (BarGraph) v.findViewById(R.id.bargraph);
+        final BarGraph barGraph = (BarGraph) v.findViewById(R.id.bargraph);
         barGraph.setBars(aBars);
 
         barGraph.setOnBarClickedListener(new OnBarClickedListener() {
             @Override
             public void onClick(int index) {
                 Toast.makeText(getActivity(),
-                        "Bar " + index + " clicked",
+                        "Bar " + index + " clicked " + String.valueOf(barGraph.getBars().get(index).getValue()),
                         Toast.LENGTH_SHORT)
                         .show();
             }
         });
+        Button animateBarButton = (Button) v.findViewById(R.id.animateBarButton);
+        animateBarButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                for (Bar b : barGraph.getBars()) {
+                    b.setGoalValue((float) Math.random() * 1000);
+                    b.setValuePrefix("$");//display the prefix throughout the animation
+                    Log.d("goal val", String.valueOf(b.getGoalValue()));
+                }
+                barGraph.setDuration(1500);//default if unspecified is 300 ms
+                barGraph.setInterpolator(new BounceInterpolator());//IMPORTANT: Read source comment before using
+                barGraph.setAnimationListener(getAnimationListener());
+                barGraph.animateToGoalValues();//animation will always overwrite. Pass true to call the onAnimationCancel Listener with onAnimationEnd
 
+            }
+        });
         return v;
     }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    public Animator.AnimatorListener getAnimationListener(){
+        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
+            return new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {//consider calling makeValueS
+                    Log.d("piefrag", "anim end");
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    Log.d("piefrag", "anim cancel");
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+
+                }
+            };
+        else return null;
+
+    }
 }

HoloGraphLibrarySample/src/com/echo/holographlibrarysample/PieFragment.java

                 for (PieSlice s : pg.getSlices())
                     s.setGoalValue((float)Math.random() * 10);
                 pg.setDuration(1000);//default if unspecified is 300 ms
-                pg.setInterpolator(new AccelerateDecelerateInterpolator());//default if unspecified is linear
-                //pg.setAnimationListener(getAnimationListener());
-                pg.animateToGoalValues();
+                pg.setInterpolator(new AccelerateDecelerateInterpolator());//default if unspecified is linear; constant speed
+                pg.setAnimationListener(getAnimationListener());
+                pg.animateToGoalValues();//animation will always overwrite. Pass true to call the onAnimationCancel Listener with onAnimationEnd
 
             }
         });
             }
 
             @Override
-            public void onAnimationEnd(Animator animation) {//you might want to call slice.setvalue(slice.getGoalValue)
+            public void onAnimationEnd(Animator animation) {
+                Log.d("piefrag", "anim end");
             }
 
             @Override
-            public void onAnimationCancel(Animator animation) {
-
+            public void onAnimationCancel(Animator animation) {//you might want to call slice.setvalue(slice.getGoalValue)
+                Log.d("piefrag", "anim cancel");
             }
 
             @Override