Stand: 08.09.2018

Bisher kennen Sie "Grafik" in zwei Formen: zum einen in Form von vorgegebenen UI-Komponenten wie Buttons oder Textfelder, zum anderen in Form von Bitmaps, die Sie frei platzieren können.

In diesem Kapitel geht es darum, selbst Grafik durch verschiedene Zeichenbefehle zu erzeugen und gegebenenfalls interaktiv neu zu zeichnen.

11.1 Zeichnen

11.1.1 Eigene View-Klasse

Um einfache Grundformen wie Linien, Kreise und Rechecke zu zeichnen, erstellt man eine Unterklasse der Klasse View. Dort überschreibt man die Methode onDraw, um eigene Objekte zu zeichnen.

Üblicherweise erstellt man die eigenen View-Klasse innerhalb der jeweiligen Aktivität, damit man gegebenenfalls über Instanzvariablen Infomationen mit der aktuellen Aktivität austauschen kann oder Übergänge per Intent realisieren kann.

Hier sehen wir das minimale Grundgerüst einer eigenen Klasse DrawingView mit Konstruktor und der Methode onDraw.

public class MainActivity extends AppCompatActivity {

    ...

    class DrawingView extends View {

        public DrawingView(Context context) {
            super(context);
        }

        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // Code zum Zeichnen
        }
    }
}

Jetzt müssen wir unsere neue View noch einbinden. In onCreate erzeugen Sie dann eine Instanz dieser Klasse und sagen der Activity, dass dies "ihre View" ist:

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  final DrawingView view = new DrawingView(this);
  setContentView(view);
}

Das heißt natürlich auch, dass Ihre Layout-Datei activity_main.xml nicht mehr "zuständig" ist und ignoriert wird.

11.1.2 Auf die Canvas zeichnen

In der Methode onDraw, die wir ja überschreiben, bekommen wir ein Objekt vom Typ Canvas (engl. für Leinwand). Man kann sich ein Canvas-Objekt tatsächlich wie ein Leinwand vorstellen, auf der man zeichnet. Die Befehle zum Zeichnen sind Methoden der Klasse Canvas.

API Canvas →

Grundformen

Sie können dann Grundformen wie Linien, Rechtecke, Ellipsen und Kreise zeichnen mit den Methoden drawLine, drawRect, drawOval und drawCircle. Dabei übergeben Sie die jeweils erforderlichen Koordinaten und Größenangaben und dann noch ein Objekt vom Typ Paint, auf das wir im nächsten Abschnitt eingehen.

Hier ein Beispiel:

public class MainActivity extends AppCompatActivity {

    ...

    class DrawingView extends View {

        public DrawingView(Context context) {
            super(context);
        }

        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawCircle(400, 400, 200, new Paint());
            canvas.drawRect(800, 200, 1200, 600, new Paint());
        }
    }
}

Sie sollten sehen:

Kreis

Styling und Text

Zum Styling verwendet man die Klasse Paint. Es empfiehlt sich, Paint-Objekte als Instanzvariablen zu deklarieren, damit sie nicht bei jedem der häufigen Aufrufe von onDraw erst hergestellt werden müssen.

API Paint →

class DrawingView extends View {

    private Paint paint1 = new Paint();
    private Paint paint2 = new Paint();
    private Paint paintText = new Paint();

    ...

    protected void onDraw(Canvas canvas) {
        ...
    }
}

In onDraw kann man die Paint-Objekte anschließend konfigurieren und gibt sie dann den Zeichenbefehlen mit.

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    paint1.setStyle(Paint.Style.STROKE);
    paint1.setStrokeWidth(10);
    paint1.setColor(Color.RED);
    canvas.drawCircle(400, 400, 200, paint1);

    canvas.drawLine(700, 200, 700, 600, paint1);

    paint2.setStyle(Paint.Style.FILL);
    paint2.setColor(Color.BLUE);
    canvas.drawRect(800, 200, 1200, 600, paint2);
    canvas.drawOval(1300, 200, 1500, 600, paint2);

    paintText.setTextSize(60);
    paintText.setFakeBoldText(true);
    paintText.setColor(Color.GRAY);
    paintText.setTextAlign(Paint.Align.CENTER);
    canvas.drawText("Grundformen", canvas.getWidth()/2, 100, paintText);
}

Grundformen mit Styling

Sie sehen, dass man die Kontour (Stroke) und die Füllung (Fill) bestimmen kann.

Text zeichnen Sie mit drawText. Beim Styling kann man Größe, Alignierung und "fake bold" (fett) angeben ("fake" deshalb, weil einfach die Dicke vergrößert wird und nicht ein dafür angefertigter Zeichensatz).

Bitmaps

Sie können auch Bitmaps, also vorgegebene Grafikdateien (JPG, GIF, PNG etc.) einbinden. Wir nehmen dazu an, Sie haben eine Datei "smiley.jpg" im Ressourcenverzeichnis "drawable" abgelegt.

Dann können Sie die Bitmap laden und anschließend mit drawBitmap auf die Canvas zeichnen.

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.smiley);
canvas.drawBitmap(bitmap, (canvas.getWidth() - bitmap.getWidth())/2,
    (canvas.getHeight() - bitmap.getHeight())/2, new Paint());

Aus Performance-Gründen würde man die obere Zeile in onCreate ausführen und die Bitmap-Variable als Instanzvariable anlegen.

11.1.3 Shape als Ressource

Sie können eine Form auch in XML definieren und direkt ins Layout einbinden, ohne eine Zeile Java-Code zu schreiben.

Zunächst spezifizieren wir unsere Form im Ordner (mit beispielhaftem Dateinamen):

res/drawable/shape.xml

Zum Beispiel einen roten Kreis:

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#FF0000" />
    <size android:width="200dp" android:height="200dp"/>
</shape>

Dieser wird mit Hilfe eines ImageView-Objekts in das Layout eingefügt:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/shape" />

Kreis

Hier ein Beispiel für einen Kreis nur mit Kontour und ohne Füllfarbe:

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <stroke android:color="#000000" android:width="3dp"/>
    <size android:width="200dp" android:height="200dp"/>
</shape>

11.2 Interaktion

Möchten Sie den Kreis immer an die Position eines Touch hängen, müssen Sie einen Touch-Listener hinzufügen, der die Position des Kreises verändert.

Am Ende des Listeners müssen Sie ein invalidate() einfügen, das dafür sorgt, dass das onDraw() aufgerufen und der Kreis somit neu gezeichnet wird.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final DrawingView view = new DrawingView(this);
    setContentView(view);

    view.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent motionEvent) {
            view.setPos(motionEvent.getX(), motionEvent.getY());
            invalidate();
            return true;
        }
    });
}

class DrawingView extends View {

    private Paint paint = new Paint();
    private float x;
    private float y;

    public DrawingView(Context context) {
        super(context);
    }

    public void setPos(float x, float y) {
        this.x = x;
        this.y = y;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GREEN);
        canvas.drawCircle(x, y, 200, paint);
    }
}