Commits

Daniel Nadeau  committed 071c88b Merge

Merged in sbaar/holographlibrarypoll (pull request #48)

Bar graph animate

  • Participants
  • Parent commits f17aa8d, b357689

Comments (0)

Files changed (10)

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

     private int mLabelColor = -1;
     private int mSelectedColor = -1;
     private int mValueColor = Color.WHITE;
+    private int mColorAlpha = 255;//no transparency by default. Used in animations to transition to a final alpha.
     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 mAnimateSpecial = HoloGraphAnimate.ANIMATE_NORMAL;//add getter setter
 
     public int getColor() {
         return mColor;
     }
 
+    public int getColorAlpha(){return mColorAlpha;}
+
     public void setColor(int color) {
         mColor = color;
+        mColorAlpha = Color.alpha(color);
     }
 
     public int getLabelColor() {
         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;

File 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);
     }
                     getHeight() - bottomPadding + 10 * resources.getDisplayMetrics().density,
                     mPaint);
         }
-        float barWidth = (getWidth() - (padding * 2) * mBars.size()) / mBars.size();
+        //Determine ideal bar size with number of bars at end not deleted
 
-        // Maximum y value = sum of all values.
-        for (final Bar bar : mBars) {
-            if (bar.getValue() > maxValue) {
-                maxValue = bar.getValue();
-            }
-        }
-        if (maxValue == 0) {
-            maxValue = 1;
+        int insertCount = 0;
+        int deleteCount = 0;
+        for (Bar bar :mBars)
+        {
+            if (bar.mAnimateSpecial ==ANIMATE_INSERT)
+                insertCount++;
+
+            if (bar.mAnimateSpecial ==ANIMATE_DELETE)
+                deleteCount++;
         }
+        int specialCount = insertCount + deleteCount;
+        float barWidthHelper = (getWidth() - (padding * 2) * (mBars.size()-insertCount)) / (mBars.size()-insertCount);
+        float insertHelper = (getWidth() - (padding * 2) * (mBars.size()-deleteCount)) / (mBars.size()-deleteCount);
+        float specialWidthTotal = 0;
 
         int count = 0;
+        float barWidths [] = new float[mBars.size()];
+        for (final Bar bar : mBars) {   //calculate total widths of bars being inserted/deleted
+            if (bar.mAnimateSpecial == ANIMATE_INSERT) {
+                barWidths[count] =  (int) (getAnimatedFractionSafe() * insertHelper);
+                specialWidthTotal +=  barWidths[count];
+
+            }
+            else if (bar.mAnimateSpecial == ANIMATE_DELETE){
+                barWidths[count]  = (int) ( (1-getAnimatedFractionSafe()) * barWidthHelper);
+                specialWidthTotal +=  barWidths[count];
+            }
+            count++;
+        }
+        specialWidthTotal += (deleteCount * (padding *2 *(1-getAnimationFraction())));
+        specialWidthTotal += (insertCount * (padding *2 *getAnimationFraction()));
+        int normalCount = mBars.size() - specialCount;
+        //calculate the width of the exsisting normal bars
+        float barWidth = (getWidth() - specialWidthTotal - (padding * 2 *normalCount)) / (normalCount);//calculate regular widths
+        float defaultBarWidth = barWidth;
+        for (int i = 0; i <mBars.size(); i++) if (mBars.get(i).mAnimateSpecial == ANIMATE_NORMAL) barWidths[i] = defaultBarWidth;
+
+        //if animating, the max value is calculated for us
+        if (isAnimating()){
+            maxValue = mMaxValue;
+        }
+        else {
+            for (final Bar bar : mBars) {
+                if (bar.getValue() > maxValue) {
+                    maxValue = bar.getValue();
+                }
+            }
+            if (maxValue == 0) {
+                maxValue = 1;
+            }
+        }
 
-        // Calculate the maximum text size for all the axis labels
+        count = 0;
+        // Calculate the maximum text size for all the axis labels without regard to animation state so text doesn't jitter.
+        // TODO there's probably a better way to do this.
         mPaint.setTextSize(AXIS_LABEL_FONT_SIZE
                 * resources.getDisplayMetrics().scaledDensity);
         for (final Bar bar : mBars) {
             int left = (int) ((padding * 2) * count + padding + barWidth * count);
             int right = (int) ((padding * 2) * count + padding + barWidth * (count + 1));
+            int width = (int)(defaultBarWidth + (padding *2));
             float textWidth = mPaint.measureText(bar.getName());
             // Decrease text size to fit and not overlap with other labels.
-            while (right - left + (padding * LABEL_PADDING_MULTIPLIER) < textWidth) {
+            while (right -left + (padding * LABEL_PADDING_MULTIPLIER) < textWidth) {
                 mPaint.setTextSize(mPaint.getTextSize() - 1);
-                textWidth = mPaint.measureText(bar.getName());
+                float newTextWidth = mPaint.measureText(bar.getName());
+                if (textWidth == newTextWidth) break;
+                textWidth =newTextWidth;
             }
             count++;
         }
         float labelTextSize = mPaint.getTextSize();
 
         count = 0;
+        int oldright = (int) (padding *-1);
+        int alpha = 255;//for bar color. Max values is bar.getColorAlpha
+        int popupAlpha = 255;// for bar popup and text. Max value is 255;
         SparseArray<Float> valueTextSizes = new SparseArray<Float>();
         for (final Bar bar : mBars) {
+            //Set alpha and width percentage if inserting or deleting
+            if (isAnimating()){
+                if (bar.mAnimateSpecial == ANIMATE_INSERT) {
+                    alpha = ((int) (getAnimatedFractionSafe() * bar.getColorAlpha()));
+                    popupAlpha = ((int) (getAnimatedFractionSafe() * 255));
+                }
+                else if (bar.mAnimateSpecial == ANIMATE_DELETE){
+                    alpha = ((int) ((1 - getAnimatedFractionSafe()) * bar.getColorAlpha()));
+                    popupAlpha = ((int) ((1- getAnimatedFractionSafe()) * 255));
+            }
+                else {
+                    alpha = bar.getColorAlpha();
+                    popupAlpha = 255;
+                }
+            }
+            else {
+                mPaint.setAlpha(bar.getColorAlpha());
+                popupAlpha = 255;
+            }
+            barWidth = barWidths[count];
             // Set bar bounds
-            int left = (int) ((padding * 2) * count + padding + barWidth * count);
+            int left = (int) (oldright + (padding * 2 *
+                    (bar.mAnimateSpecial ==ANIMATE_DELETE ? 1-getAnimationFraction()://scale padding by animation time same as bar width
+                    bar.mAnimateSpecial == ANIMATE_INSERT ? getAnimationFraction()
+                    :1)));
             int top = (int) (getHeight() - bottomPadding
                     - (usableHeight * (bar.getValue() / maxValue)));
-            int right = (int) ((padding * 2) * count + padding + barWidth * (count + 1));
+            int right = (int) (left + barWidth );
             int bottom = (int) (getHeight() - bottomPadding);
+            oldright = right;
             mBoundsRect.set(left, top, right, bottom);
 
             // Draw bar
             } else {
                 mPaint.setColor(bar.getColor());
             }
+            if (isAnimating()) mPaint.setAlpha(alpha);
             canvas.drawRect(mBoundsRect, mPaint);
 
             // Create selection region
             if (mShowAxisLabel) {
                 mPaint.setColor(bar.getLabelColor());
                 mPaint.setTextSize(labelTextSize);
+                if (isAnimating()) mPaint.setAlpha(alpha);
                 float textWidth = mPaint.measureText(bar.getName());
                 int x = (int) (((mBoundsRect.left + mBoundsRect.right) / 2) - (textWidth / 2));
                 int y = (int) (getHeight() - 3 * resources.getDisplayMetrics().scaledDensity);
                 mPaint.setTextSize(VALUE_FONT_SIZE
                         * resources.getDisplayMetrics().scaledDensity);
                 mPaint.setColor(bar.getValueColor());
+                if (isAnimating()) mPaint.setAlpha(popupAlpha);
                 mPaint.getTextBounds(bar.getValueString(), 0, 1, mTextRect);
 
                 int boundLeft = (int) (((mBoundsRect.left + mBoundsRect.right) / 2)
                 }
 
                 if (mShowPopup) {
+                    if (isAnimating()) popup.setAlpha(popupAlpha);
                     popup.setBounds(boundLeft, boundTop, boundRight, mBoundsRect.top);
                     popup.draw(canvas);
                 }
 
-                // Check cache to see if we've done this calculation before
-                if (0 > valueTextSizes.indexOfKey(bar.getValueString().length())) {
-                    while (mPaint.measureText(bar.getValueString()) > boundRight - boundLeft) {
-                        mPaint.setTextSize(mPaint.getTextSize() - (float) 1);
+                // Check cache to see if we've calculated value text size for bars that aren't being inserted/deleted.
+                // Not 100% accurate with non monospace fonts but close enough
+                // This is actually performance critical for 10+ bars.
+                if (bar.mAnimateSpecial == ANIMATE_NORMAL) {
+                    if (0 > valueTextSizes.indexOfKey(bar.getValueString().length())) {//cache miss
+                        while (mPaint.measureText(bar.getValueString()) > boundRight - boundLeft) {
+                            mPaint.setTextSize(mPaint.getTextSize() - (float) 1);
+                        }
+                        valueTextSizes.put(bar.getValueString().length(), mPaint.getTextSize());//cache save
+                    } else {//cache hit
+                        mPaint.setTextSize(valueTextSizes.get(bar.getValueString().length()));
                     }
-                    valueTextSizes.put(bar.getValueString().length(), mPaint.getTextSize());
-                } else {
-                    mPaint.setTextSize(valueTextSizes.get(bar.getValueString().length()));
+                }   //for bars inserting/deleting, do the math everytime without a cache.
+                    else
+                        while (mPaint.measureText(bar.getValueString()) > boundRight - boundLeft) {
+                            mPaint.setTextSize(mPaint.getTextSize() - (float) 1);
                 }
+
+
+                if (isAnimating()) mPaint.setAlpha(popupAlpha);
                 canvas.drawText(bar.getValueString(),
                         (int) (((mBoundsRect.left + mBoundsRect.right) / 2)
                                 - (mPaint.measureText(bar.getValueString())) / 2),
         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 exactly 1.0 (Bounce interpolator doesnt)
+     * or else call makeValueString(int precision) on each bar in onAnimationEnd  so the value label will be the goal value.
+     * @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)
+    private float getAnimationFraction(){
+        if (mValueAnimator != null && isAnimating())
+            return mValueAnimator.getAnimatedFraction();
+        else return 1f;
+    }
+    private float getAnimatedFractionSafe(){
+        float f = getAnimationFraction();
+        if (f >1) return 1;
+        if (f < 0) return 0;
+        else return f;
+    }
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public boolean cancelAnimating() {
+        if (mValueAnimator != null)
+            mValueAnimator.cancel();
+        return false;
+    }
+
+    /**
+     * Cancels then starts an animation to goal values, inserting or deleting bars.
+     */
+    @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();
+                //change value string after interval or when animation is at end point.
+                if ((mLastTimeValueStringsUpdated + mValueStringUpdateInterval < now) || f==1f)
+                {
+                    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);
     }

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

  */
 public interface HoloGraphAnimate {
 
+    final int ANIMATE_NORMAL = 0;
+    final int ANIMATE_INSERT = 1;
+    final int ANIMATE_DELETE = 2;
     int getDuration();
     void setDuration(int duration);
 
     Interpolator getInterpolator();
     void setInterpolator(Interpolator interpolator);
 
+    boolean isAnimating();
+    boolean cancelAnimating();
     void animateToGoalValues();
     void setAnimationListener(Animator.AnimatorListener animationListener);
 }

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

     private boolean mBackgroundImageCenter = false;
 
 
+
+    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);

File HoloGraphLibrarySample/build.gradle

 
 dependencies {
     compile project(':HoloGraphLibrary')
-    compile 'com.android.support:support-v4:+'
-    compile 'com.android.support:appcompat-v7:+'
+    compile 'com.android.support:support-v4:19.1.0'
+    compile 'com.android.support:appcompat-v7:19.1.0'
 }
 

File 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"/>
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_margin="@dimen/default_margin"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Animate change"
+            android:id="@+id/animateBarButton"
+            android:layout_weight="0"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Insert"
+            android:id="@+id/animateInsertBarButton"
+            android:layout_weight="0"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Delete"
+            android:id="@+id/animateDeleteBarButton"
+            android:layout_weight="0"/>
+
+    </LinearLayout>
 
 </LinearLayout>

File 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.graphics.Color;
+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.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Button;
 import android.widget.Toast;
 
 import com.echo.holographlibrary.Bar;
 import com.echo.holographlibrary.BarGraph;
 import com.echo.holographlibrary.BarGraph.OnBarClickedListener;
+import com.echo.holographlibrary.HoloGraphAnimate;
 
+import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.Random;
 
 public class BarFragment extends Fragment {
 
+    BarGraph bg;
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
         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);
+        bg = 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);
+        Button animateInsertBarButton = (Button) v.findViewById(R.id.animateInsertBarButton);
+        Button animateDelteBarButton = (Button) v.findViewById(R.id.animateDeleteBarButton);
+
+        //animate to random values
+        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(1200);//default if unspecified is 300 ms
+                barGraph.setInterpolator(new AccelerateDecelerateInterpolator());//Only use over/undershoot  when not inserting/deleting
+                barGraph.setAnimationListener(getAnimationListener());
+                barGraph.animateToGoalValues();
+
+            }
+        });
 
+        //insert a bar
+        animateInsertBarButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                barGraph.cancelAnimating(); //must clear existing to call onAnimationEndListener cleanup BEFORE adding new bars
+                int newPosition = barGraph.getBars().size() == 0 ? 0 : new Random().nextInt(barGraph.getBars().size());
+                Bar bar = new Bar();
+                bar.setColor(Color.parseColor("#AA0000FF"));
+                bar.setName("Insert bar " + String.valueOf(barGraph.getBars().size()));
+                bar.setValue(0);
+                bar.mAnimateSpecial = HoloGraphAnimate.ANIMATE_INSERT;
+                barGraph.getBars().add(newPosition,bar);
+                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(1200);//default if unspecified is 300 ms
+                barGraph.setInterpolator(new AccelerateDecelerateInterpolator());//Don't use over/undershoot interpolator for insert/delete
+                barGraph.setAnimationListener(getAnimationListener());
+                barGraph.animateToGoalValues();
+
+            }
+        });
+
+        //delete a bar
+        animateDelteBarButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                barGraph.cancelAnimating(); //must clear existing to call onAnimationEndListener cleanup BEFORE adding new bars
+                if (barGraph.getBars().size() == 0) return;
+
+                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()));
+                }
+                int newPosition = new Random().nextInt(barGraph.getBars().size());
+                Bar bar = barGraph.getBars().get(newPosition);
+                bar.mAnimateSpecial = HoloGraphAnimate.ANIMATE_DELETE;
+                bar.setGoalValue(0);//animate to 0 then delete
+                barGraph.setDuration(1200);//default if unspecified is 300 ms
+                barGraph.setInterpolator(new AccelerateInterpolator());//Don't use over/undershoot interpolator for insert/delete
+                barGraph.setAnimationListener(getAnimationListener());
+                barGraph.animateToGoalValues();
+
+            }
+        });
         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) {
+                    ArrayList<Bar> newBars = new ArrayList<Bar>();
+                    //Keep bars that were not deleted
+                    for (Bar b : bg.getBars()){
+                        if (b.mAnimateSpecial != HoloGraphAnimate.ANIMATE_DELETE){
+                            b.mAnimateSpecial = HoloGraphAnimate.ANIMATE_NORMAL;
+                            newBars.add(b);
+                        }
+                    }
+                    bg.setBars(newBars);
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+
+                }
+            };
+        else return null;
+
+    }
 }

File 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

File build.gradle

   }
 
   dependencies {
-    classpath 'com.android.tools.build:gradle:0.11.+'
+    classpath 'com.android.tools.build:gradle:0.12.+'
   }
 }
 

File gradle/wrapper/gradle-wrapper.properties

-#Wed Mar 12 23:50:05 CET 2014
+#Tue Jul 01 17:59:36 PDT 2014
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip