Mittwoch, 7. Mai 2014

Android: Per Finger auf den Bildschirm zeichnen

Im heutigen Post möchte ich zeigen, wie man mit C# eine Android Applikation programmiert, bei der man auf den Bildschirm bzw. in das Fenster der App zeichnen kann. Quasi wie bei einem Mal- oder Handschriftenprogramm.

Dafür erstellen wir uns eine eigene View, die wir dann natürlich in der MainActivity als Layout verwenden.
In der View wird bei Start der Anwendung bzw. bei Größenänderung die Zeichenfläche festgelegt:

protected override void OnSizeChanged (int w, int h, int oldw, int oldh)
{
     base.OnSizeChanged (w, h, oldw, oldh);
     canvasBitmap = Bitmap.CreateBitmap (w, h, Bitmap.Config.Argb8888);
     drawCanvas = new Canvas (canvasBitmap);
}

canvasBitmap ist eine Bitmap Variable, die das bisher gezeichnete Bild enthält. drawCanvas eine Canvas (Leinwand) Variable, die mit dem Bild verknüpft wird.

Weiterhin benutzen wir einen Zeichenpfad (Path). Mit diesem speichern und zeichnen wir Pfade, die der Benutzer auf das Display malt.
In der Funktion OnTouchEvent(), die bei Berührungen des Bildschirms aufgerufen wird, verfolgen wir entsprechende Bewegungen des Benutzers:

public override  bool OnTouchEvent (MotionEvent e)
{
     float touchX = e.GetX ();
     float touchY = e.GetY ();
     switch (e.Action) {
     case MotionEventActions.Down:
          drawPath.MoveTo (touchX, touchY);
          break;
     case MotionEventActions.Move:
          drawPath.LineTo (touchX, touchY);
          break;
     case MotionEventActions.Up:
          drawCanvas.DrawPath (drawPath, drawPaint);
          drawPath.Reset ();
          break;
     default:
          return false;
     }
     Invalidate ();
     return true;
}

In der Funktion wird zuerst die entsprechende Aktion bestimmt: Berührt der Benutzer das Display (für diesen Pfad) erstmalig, wird der Zeichenpfad auf die entsprechende Stelle geführt ohne zu zeichnen. Zieht der Benutzer per Finger von diesem Punkt ausgehend weiter, wird gezeichnet. Beim Beenden des Zeichenvorgangs wird der Pfad in drawCanvas gespeichert. Da dies mit canvasBitmap verknüpft ist, wird der Pfad so auch später noch gezeichnet, wenn er schon abgeschlossen ist. Per Invalidate() wird das Neuzeichnen des Screens erzwungen.

Abschließend müssen wir noch die OnDraw() Funktion überschreiben:
protected override void OnDraw (Canvas canvas)
{
     canvas.DrawBitmap (canvasBitmap, 0, 0, canvasPaint);
     canvas.DrawPath (drawPath, drawPaint);
}

In dieser wird einmal canvasBitmap gezeichnet um das komplette bisherige Bild zu zeichnen, sowie drawPath, um den aktuellen, noch nicht beendeten Pfad direkt anzuzeigen.
Anfänglich muss noch die Funktion start() aufgerufen werden, in der verschiedene Werte, wie Zeichenfarbe, Hintergrundfarbe, Zeichendicke etc. festgelegt werden.
Ich denke bei Ansicht des kompletten Quellcodes sollte die Funktionsweise verständlich sein:

MainActivity.cs:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace Draw
{
     [Activity (Label = "Draw", MainLauncher = true)]
     public class MainActivity : Activity
     {
          int count = 1;

          protected override void OnCreate (Bundle bundle)
          {
               base.OnCreate (bundle);

               DrawView test = new DrawView(this);
               test.start ();

               SetContentView (test);
          }
     }
}

DrawView.cs:
using System;
using Android.Views;
using Android.Graphics;
using System.Collections.Generic;
using Android.Content;
using Java.Lang;

namespace Draw
{
     public class DrawView : View
     {
          public DrawView (Context context) : base (context)
          {

          }

          private Path drawPath;
          private Paint drawPaint, canvasPaint;
          private uint paintColor = 0xFF660000;
          private Canvas drawCanvas;
          private Bitmap canvasBitmap;


          public void start ()
          {
               drawPath = new Path ();
               drawPaint = new Paint ();
               drawPaint.Color = new Color ((int)paintColor);
               drawPaint.AntiAlias = true;
               drawPaint.StrokeWidth = 20;
               drawPaint.SetStyle (Paint.Style.Stroke);
               drawPaint.StrokeJoin = Paint.Join.Round;
               drawPaint.StrokeCap = Paint.Cap.Round;
               canvasPaint = new Paint ();
               canvasPaint.Dither = true;
          }

          protected override void OnSizeChanged (int w, int h, int oldw, int oldh)
          {
               base.OnSizeChanged (w, h, oldw, oldh);
               canvasBitmap = Bitmap.CreateBitmap (w, h, Bitmap.Config.Argb8888);
               drawCanvas = new Canvas (canvasBitmap);
          }

          protected override void OnDraw (Canvas canvas)
          {
               canvas.DrawBitmap (canvasBitmap, 0, 0, canvasPaint);
               canvas.DrawPath (drawPath, drawPaint);
          }
              

          public override  bool OnTouchEvent (MotionEvent e)
          {
               float touchX = e.GetX ();
               float touchY = e.GetY ();
               switch (e.Action) {
               case MotionEventActions.Down:
                    drawPath.MoveTo (touchX, touchY);
                    break;
               case MotionEventActions.Move:
                    drawPath.LineTo (touchX, touchY);
                    break;
               case MotionEventActions.Up:
                    drawCanvas.DrawPath (drawPath, drawPaint);
                    drawPath.Reset ();
                    break;
               default:
                    return false;
               }
               Invalidate ();
               return true;
          }
     }
}

Abschließend noch ein Bild:

Keine Kommentare:

Kommentar veröffentlichen