Lesson 141. Drawing. Access to Canvas
Android Lessons

Lesson 141. Drawing. Access to Canvas


In this lesson:

– access to Canvas

Finally, we begin the cycle of lessons on the schedule in Android. It’s been two and a half years since the site was created.

For starters, let’s consider the usual 2D drawing.

Canvas object is used for drawing. Immediately agree that I will call it “Canvas.” Moreover, in Russian there is even such a word, known in narrow circles to be cross stitched. You can, of course, translate Canvas as a “canvas” or “canvas”, but somehow it turns out pathetically. Canvas is easier and more convenient for me.

Immediately say that the canvas is just a tool for drawing. And the whole result is saved to Bitmap. We can’t ask Bitmap directly to draw a line or a circle, so the outline acts as an intermediary and helps us draw what we need.

In this tutorial, we will explore two ways to gain access to a canvas.

The first way is through the View class heir. We just need to override his onDraw method and he will give us access to the canvas. The code here is a minimum and everything is extremely simple. But there is a drawback – all the drawing is done in the main thread. It will roll if you have a static picture or not very dynamic animation.

The second way is through SurfaceView. This method is suitable if you plan to draw something heavy and dynamic. A separate stream will be highlighted here. This is a little more difficult to implement than the first way.

Let’s create a project:

Project name: P1411_CanvasView
Build Target: Android 2.3.3
Application name: CanvasView
Package name: ru.startandroid.develop.p1411canvasview
Create Activity: MainActivity

MainActivity.java code:

package ru.startandroid.develop.p1411canvasview;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new DrawView(this));
  }

  class DrawView extends View {

    public DrawView(Context context) {
      super(context);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
      canvas.drawColor(Color.GREEN);
    }
    
  }
  
}

IN onCreate we are not passing the layout file, as usual, into our setContentView method, but our DrawView view component. It will take up all the activity content.

class DrawView is the heir to View and overrides its onDraw method. And this method gives us access to the Canvas object. So far, we will not draw anything special, but just paint everything green with the drawColor method.

Actually, everything. Finish the first application that draws something on the screen.

We all save, run and see the result.

The screen is green, as we asked.

The onDraw method was called by the system when it became necessary to draw a View component on the screen. This will also happen, for example, if you turn off the screen. Try to put in the onDraw log and see the result.

If you need some animation on the canvas, you need to constantly redraw the screen when your changes are ready for display. To do this, use the invalidate method. You call him and he in turn will call onDraw. There are also implementations of the invalidate method, which allows not only the entire component to be redrawn, but only part of it, specifying the coordinates.

If looping is required, you can place the invalidate method directly onDraw and View will be redrawn constantly. In some lessons, I think we will, but only to simplify the code. And in reality, this is not a good practice because it will all go down the drain. And it will be more correct to implement such constant redrawing through SurfaceView.

Let’s see how this is done.

SurfaceView

rewrite MainActivity.java:

package ru.startandroid.develop.p1411canvasview;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new DrawView(this));
  }

  class DrawView extends SurfaceView implements SurfaceHolder.Callback {

    private DrawThread drawThread;

    public DrawView(Context context) {
      super(context);
      getHolder().addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
      drawThread = new DrawThread(getHolder());
      drawThread.setRunning(true);
      drawThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
      boolean retry = true;
      drawThread.setRunning(false);
      while (retry) {
        try {
          drawThread.join();
          retry = false;
        } catch (InterruptedException e) {
        }
      }
    }

    class DrawThread extends Thread {

      private boolean running = false;
      private SurfaceHolder surfaceHolder;

      public DrawThread(SurfaceHolder surfaceHolder) {
        this.surfaceHolder = surfaceHolder;
      }

      public void setRunning(boolean running) {
        this.running = running;
      }

      @Override
      public void run() {
        Canvas canvas;
        while (running) {
          canvas = null;
          try {
            canvas = surfaceHolder.lockCanvas(null);
            if (canvas == null)
              continue;
            canvas.drawColor(Color.GREEN);
          } finally {
            if (canvas != null) {
              surfaceHolder.unlockCanvasAndPost(canvas);
            }
          }
        }
      }
    }

  }

}

It got a little tricky, right? ) Now let’s see what is what.

method onCreateActually, it has not changed at all. We also pass our DrawView object to the setContentView method.

look DrawView. It is the heir to SurfaceView and at the same time implements the interface of SurfaceHolder.Callback. I am reminded that with SurfaceView we have already worked in a lesson about the camera (Lesson 132). This component only displays content. And work with him is through the SurfaceHolder handler.

IN designers DrawView we receive SurfaceHolder and inform him that we will handle his events. There are three such events:

surfaceChanged – SurfaceView format or size changed

surfaceCreated – SurfaceView created and ready to display information

surfaceDestroyed – Called before SurfaceView is destroyed

IN surfaceCreated we create our drawing stream (about it a little later), pass it to SurfaceHolder. By calling the setRunning (true) method, we label it that it can work and start it.

IN surfaceDestroyed we tell our stream (setRunning (false)) that its work should be stopped because SurfaceView will now be destroyed. Next, we run a loop that waits until our drawing flow is complete. The wait is a must, otherwise the stream may try to draw something on the destroyed SurfaceView.

DrawThread, Heir to the Thread, is our stream of drawing. It will be drawing.

IN designer pass SurfaceHolder. We need it to get to the canvas.

method setRunning puts a work tag that tells the thread if it can work.

method run. In it, we see a loop that runs as long as the running tag allows. In the loop, we zero the canvas variable, then from SurfaceHolder we get the canvas using the lockCanvas method. Just in case, we check that the canvas is not null, and it is possible to draw: again it is simply painted over in green. After drawing what we like, we return the canvas of the SurfaceHolder object using the unlockCanvasAndPost method in the finally (required) section, and SurfaceView will display our art.

Accordingly, when in surfaceDestroyed the setRunning (false) method is called, the loop in the method is exited run and the flow completes.

We all save, run and see the result.

The screen is green.

When we were considering the first way to get a canvas (via onDraw), I mentioned that I had to call myself invalidate if a constant redraw was needed. In the second way, there is nothing else to do. We now have a constant redraw in the loop.

We conclude this introductory lesson. We looked at two ways to get an outline. In the following lessons, I will use the first method, because it is simpler, the code in it is much smaller and it will be possible to focus directly on working with the canvas and drawing.

In the next lesson:

– draw figures
– output text




Discuss in the forum [21 replies]

Leave a Reply

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