Draw an animated bar chart on Android

How I drew a live bar chart in my sound proofing and STC app available on google play

Fast Fourier transform FFT android


The above picture is a screenshot of my android app where I needed to live display over 100 data points onto my screen. Here is how I did just that.

Important to know is that these data points came in the form of double array of [156] from my fast Fourier transform. With these 156 data point I needed to do the following;

  1. Draw a background and figure out how much of this data was needed to fill in my chart
  2. Grab that data asynchronously from a separate thread doing the calculations
  3. Take my data point and figure how wide and tall they need to be.
  4. Draw the previous record and then draw the new total over the top of it.
  5. Do it 30 times per second and post back to the UI


So let me start with item #2… how the data was getting there. The background thread that was giving me the 156 data points was refreshing very fast. I didn’t need all the data for the purpose of the user display and I chose to simply post it to an array repeatedly. This way it could post as frequently as it wanted and I could grab the data as frequently as a wanted without interruption to either. Loosely coupled might be the appropriate terminology.


This is my class wide field I posted too.

private double[][] uiChartBuffer;

This is what I call a controlling method. I have several inner classes within my audiocontrol class which do the work on different worker threads.
My controlling methods allow the classes utilizing the audiocontrol object to make its inner classes perform their functions without actually touching them. Here you
can see that I start recording and I instantiate the inner class drawchart and call its resume method. These inner classes are 100% accessed by their own controlling methods as well. It may
also be important to mention that the audiocontrol's controlling methods are affected by the android lifecycle. Killing the inner classes automatically.

So if the user gets a phone call and moves out of the app there are no leaked resources:
 Fragment.onPause() -> audioControl.onPause(this call a bunch of methods such as the one below) -> drawChart.pause and null -> recordAudio.pause() and null;
public void startRecording(File file)
 Log.i(TAG, "startRecording: ");
 running = !running;

 drawChart = new DrawChart(context);

 fileToWork = file;

 recordAudio = new RecordAudio();



At the very bottom is my entire inner drawchart class. There is a resume and pause method, a method to check if the screen update is ready and a method that just loops over and over trying to update the screen.
Anything that doesn't have to do with the user interaction should be in a background thread. This is no exception however android doesn't allow background threads to touch the UI so you have to us a workaround.
In this case I used a surface view which is a very typical way of doing this.

</pre><pre>private class DrawChart extends SurfaceView implements Runnable
    SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private Canvas canvas;
    private Paint paint;
    Thread thread = null;
    TextView textViewDecibelShow;
    int highestDeciebelFromSample;

    private int mBarColorF;
    private int mBarColorS;
    private Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.newhzdb);
    BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),bitmap);

    private long nextFrameDue;
    private long frameLength = 100;
    int width, height;
    int barWidth, seqS, seqF,  mod;
    double eWidth, reverse, bHeight;
    float bh;

    public DrawChart(Context context) {

        surfaceView = myFragmentView.findViewById(R.id.surfaceView);
        textViewDecibelShow = myFragmentView.findViewById(R.id.textViewliveDB);
        surfaceHolder = surfaceView.getHolder();

        nextFrameDue = System.currentTimeMillis();

        mBarColorF = ResourcesCompat.getColor(getResources(),R.color.colorBarChartFluctuate,null);
        mBarColorS = ResourcesCompat.getColor(getResources(),R.color.colorBarChartStay, null);
        paint = new Paint();

        width = 0;


    public void run() {

        while  (running || playing)

            if (thread.isInterrupted())

            if (updateRequired())

                highestDeciebelFromSample = 0;

                if (surfaceHolder.getSurface().isValid() &amp;&amp; uiChartBuffer != null) {

                    canvas = surfaceHolder.lockCanvas();
                    // Log.i(TAG, "run: in midst of draws while loop");

                    if (width == 0){

                        width =  surfaceView.getWidth();    //screen dimensions 540
                        height = surfaceView.getHeight();   //screen dimensions 600

                        int almostwidth = (int) Math.round( width *.89) ;
                        barWidth = (int) almostwidth/reqBars;                       //4?
                        int netwidth = reqBars * barWidth;                         //464?
                        int grossWidth = (int) Math.round(netwidth/.90);            //515

                        width = grossWidth;

                        mod = getMod(width);
                        bHeight = height * .965;             // random number i found to keep max reading within chart
                        bh = (float) bHeight;


                        //scaledBitmap = Bitmap.createScaledBitmap(bitmap,width,height,true);


                    for (int i = 1; i &lt; reqBars ; i++) {

                        seqF = i * barWidth + mod; //right
                        seqS = seqF - barWidth; //left

                        int bottom = reverseandCalc(uiChartBuffer[0][i], false);

                        //Log.i(TAG, "run: " + String.valueOf(bottom));

                        if (bottom &gt; highestDeciebelFromSample){
                            highestDeciebelFromSample = bottom;

                        if (bottom &gt; highestDecibelByBar[i])
                        {// if this data is higher then add to record
                            highestDecibelByBar[i] = bottom;
                        }else {
                            //if not then draw prev record behind it

                            int top = (int) bh - highestDecibelByBar[i];

                            canvas.drawRect(seqS, top, seqF, bh, paint);      //draw prev record redbar underneath

                        int top = (int) bh - bottom;

                        canvas.drawRect(seqS, top, seqF, bh, paint); //draw green bar on top



                //averageDecibelForView = average(highestDeciebelFromSample);





    public void resume()
        highestDecibelByBar = new int[reqBars];
        thread = new Thread(this);
        Log.i(TAG, "resume: draw thread");


    public void destroy()

    // TODO: 5/1/2018 update based not on time but on fft method calls
    public boolean updateRequired()

        if (nextFrameDue &lt;= System.currentTimeMillis()){

            nextFrameDue = System.currentTimeMillis() + frameLength;

            return true;
        return false;






Leave a comment

Your email address will not be published. Required fields are marked *