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