Android Pie Chart Part 2. Adding state feedback to the view

Published on: July 23, 2013 Written by: Thokozani Mhlongo

This is our second post of our pie chart series, In our last post we done the drawing part of our pie chart. Now we will add some interaction to our chart. As with most components either on the web or on desktop applications, even standard android components support state. It might not seem important, but not doing this will seem as if the component is...well...dead. It might entice them to press the component twice because there's no drawing feedback presented.

Adding touch state to the view

We will start by adding two additional instance variables to our pie chart view class:

public class PieChartView extends View {
    private Paint slicePaint;
    private int[] sliceClrs = { Color.GREEN, Color.BLUE, Color.RED, Color.YELLOW };
    private RectF rectf; // Our box
    private float[] datapoints; // Our values
    
    private int alphaIndex = -1;
    private Bitmap canvasBitmap;
}

 

Let's modify our onDraw() so that it caters for our alpha index. When this alpha index is set to zero or more we will alter the paint object alpha property by using setAlpha(). So when you press on your pie chart our view's touch method (that we'll define in a moment) will get where your finger was and tell onDraw() where the change needs to be made. With that said here is what our updated onDraw() method looks like:

@Override
protected void onDraw(Canvas canvas) {
    if (this.datapoints != null) {
        int startTop = 0;
        int startLeft = 0;
        int endBottom = getWidth();
        int endRight = endBottom; // To make this an equal square
        // Create the box
        rectf = new RectF(startLeft, startTop, endRight, endBottom); // Creating the box
            
        float[] scaledValues = scale(); // Get the scaled values
        float sliceStartPoint = 0;
        for (int i = 0; i < scaledValues.length; i++) {
            slicePaint.setColor(sliceClrs[i]); //Remember to set the paint color first
                
            //Let's check the alphaIndex if it is greater than or q 0 first
            if(alphaIndex > -1 && alphaIndex == i) slicePaint.setAlpha(150); //Then slice at i was pressed
            else slicePaint.setAlpha(255);
                
            canvas.drawArc(rectf, sliceStartPoint, scaledValues[i], true, slicePaint); // Draw slice
            sliceStartPoint += scaledValues[i]; // Update starting point of the next slice
        }
            
        //Build and get what's drawn on the canvas as a bitmap
        buildDrawingCache(true);
        canvasBitmap = getDrawingCache(true);
    }
 }

 

You might also notice that we have used two methods called buildDrawingCache(true) and getDrawingCache(true). What these methods merely do is generate a bitmap image based on what's drawn on the canvas so that we can determine color pixels when we touch the pie slices. Alright, next is to override the onTouchEvent() method that's inherited from our View class. It is within this method that we will determine which pixel the finger was on and it's where we will tell the view to redraw itself when the finger is on a pie slice and when it's off. Here is our method:

@Override
public boolean onTouchEvent(MotionEvent event) {
    //If the finger is on the screen
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        //Get x and y coordinates of where the finger touched
        int pixelX = (int) (Math.floor(event.getX()));
        int pixelY = (int) (Math.floor(event.getY()));

        if(canvasBitmap != null) {
            //Get the pixel color
            int pixel = canvasBitmap.getPixel(pixelX, pixelY);

            //Convert the pixel into a hex color string
            String pixelHex = Integer.toHexString(pixel); //This will return a hexa-decimal of the bitmap

            //Set the alpha index so that we know which slice to change the opacity of
            setAlphaIndex(pixelHex);
        }       
   
        invalidate();
    }

    //If the finger is off the screen
    if(event.getAction() == MotionEvent.ACTION_UP) {
        alphaIndex = -1; //This is a flag to reset the view alphas to normal
        invalidate();
    }

    return true;
}

private void setAlphaIndex(String pixelHex) {
    for(int i = 0; i < sliceClrs.length; i++) {
        String tempHex = Integer.toHexString(sliceClrs[i]);
        if(pixelHex.equals(tempHex)) {
            alphaIndex = i; //This is the slice index to tweak the opacity of
            break;
        }
    }
}

 

Let's look at the code above. First we determine what type of motion action it is (MotionEvent.ACTION_DOWN for example). We then get the touch coordinates using getX() and getY() from the View class and using these values we get the pixel from the canvas bitmap so we can determine the color of that pixel. Our method setAlphaIndex(String) is then called which sets the alpha index that will be used by our onDraw() method to determine which slice needs to be a bit faded out.

Here is the entire class:

package com.example.customview.component;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class PieChartView extends View {
    private Paint slicePaint;
    private int[] sliceClrs = { Color.GREEN, Color.BLUE, Color.RED, Color.YELLOW };
    private RectF rectf; // Our box
    private float[] datapoints; // Our values
    
    private int alphaIndex = -1;
    private Bitmap canvasBitmap;

    public PieChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        slicePaint = new Paint();
        slicePaint.setAntiAlias(true);
        slicePaint.setDither(true);
        slicePaint.setStyle(Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (this.datapoints != null) {
            int startTop = 0;
            int startLeft = 0;
            int endBottom = getWidth();
            int endRight = endBottom; // To make this an equal square
            // Create the box
            rectf = new RectF(startLeft, startTop, endRight, endBottom); // Creating the box
            
            float[] scaledValues = scale(); // Get the scaled values
            float sliceStartPoint = 0;
            for (int i = 0; i < scaledValues.length; i++) {
                slicePaint.setColor(sliceClrs[i]); //Remember to set the paint color first
                
                //Let's check the alphaIndex if it is greater than or equal to 0 first
                if(alphaIndex > -1 && alphaIndex == i) slicePaint.setAlpha(150); //Then slice at i was pressed
                else slicePaint.setAlpha(255);
                
                canvas.drawArc(rectf, sliceStartPoint, scaledValues[i], true, slicePaint); // Draw slice
                sliceStartPoint += scaledValues[i]; // Update starting point of the next slice
            }
            
            //Build and get what's drawn on the canvas as a bitmap
            buildDrawingCache(true);
            canvasBitmap = getDrawingCache(true);
        }
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //If the finger is on the screen
        if(event.getAction() == MotionEvent.ACTION_DOWN ) {
            //Get x and y coordinates of where the finger touched
            int pixelX = (int) (Math.floor(event.getX()));
            int pixelY = (int) (Math.floor(event.getY()));
            
            if(canvasBitmap != null) {
                //Get the pixel color
                int pixel = canvasBitmap.getPixel(pixelX, pixelY);
                
                //Convert the pixel into a hex color string
                String pixelHex = Integer.toHexString(pixel); //This will return a hexa-decimal of the bitmap
                
                //Set the alpha index so that we know which slice to change the opacity of
                setAlphaIndex(pixelHex);
            }
            
            invalidate();
        }
        
        //If the finger is off the screen
        if(event.getAction() == MotionEvent.ACTION_UP) {
            alphaIndex = -1; //This is a flag to reset the view alphas to normal
            invalidate();
        }
        
        return true;
    }

    public void setDataPoints(float[] datapoints) {
        this.datapoints = datapoints;
        invalidate(); // Tells the chart to redraw itself
    }
    
    private void setAlphaIndex(String pixelHex) {
        for(int i = 0; i < sliceClrs.length; i++) {
            String tempHex = Integer.toHexString(sliceClrs[i]);
            if(pixelHex.equals(tempHex)) {
                alphaIndex = i; //This is the slice index to tweak the opacity of
                break;
            }
        }
    }

    private float[] scale() {
        float[] scaledValues = new float[this.datapoints.length];
        float total = getTotal(); // Total all values supplied to the chart
        for (int i = 0; i < this.datapoints.length; i++) {
            scaledValues[i] = (this.datapoints[i] / total) * 360; // Scale each value
        }
        return scaledValues;
    }

    private float getTotal() {
        float total = 0;
        for (float val : this.datapoints)
            total += val;
        return total;
    }
}

Conclusion

That's it! We have completed two out of three parts of our chart in this series. Next step is adding callback functions to our custom pie chart view

Comments