- Anlegen einer View
- Einbindung einer View
- Zeichnungen in der Methode onDraw
- Ereignisse
- Größenermittlung der View: onMeasure
Anlegen einer View
Eine View ist eine Klasse, die sich von der Klasse android.view.View ableitet. Man kann die Klasse einfach selbst erstellen.- Man klickt in Android-Studio den Eintrag app/java mit der rechten Maustaste an.
- Man wählt New | Java-Class.
- Als Ziel wählt man ../app/src/main/java aus.
- Im nächsten Dialog New Java Class gibt man den Klassennamen an.
- Anschließend verschiebt man die neue Klasse in das Package, in das es gehört.
package de.willemer.testview; public class MeineView { }Zunächst wird an den Klassennamen extends View angehängt, was eine View ja schließlich ausmacht. Dafür wird der entsprechende Import von android.view.View benötigt. Die Basisklasse View besitzt keinen Standardkonstruktor. Darum mus mindestens einer der Konstruktoren mit Parametern überschrieben werden.
Der Parameter ist ein Context. Typischerweise liefert den die Activity, in die die View eingebunden ist.
package de.willemer.testview; import android.content.Context; import android.view.View; public class MeineView extends View { public MeineView(Context context) { super(context); } }Soll die View später auch in eine XML-Datei eingebunden werden, muss sie einen weiteren Parameter AttributeSet bekommen. Der dritte Konstruktor hat als weiteren Parameter eine int-Variable für den defStyle, die benötigt wird, wenn innerhalb der XML-Datei ein Attribut style vergeben wird. Im folgenden Beispiel überschreiben wir alle drei denkbaren Konstruktoren und rufen die Gegenstücke der Basisklasse auf.
package de.willemer.testview; import android.content.Context; import android.view.View; public class MeineView extends View { public MeineView(Context context) { super(context); } public MeineView(Context context, AttributeSet attrs) { super(context); } public MeineView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } }Um die View erkennen zu können, zeichnen wir eine Diagonale:
public class MeineView extends View { // ... @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); canvas.drawLine(0, 0, getWidth(), getHeight(), paint); } }
Einbindung einer View
Die View benötigt eine Activity, um dargestellt zu werden.Einbindung in das Layout der Activity
Die eigene View kann wie ein normales Kontrollelement in die Layout-XML-Datei einer Activity eingebunden werden.Unter app/res/layout wird die Datei activity_main.xml doppelt angeklickt. Auf der rechten Seite erscheint XML in einem Editor (Code), eine Darstellung des Displays in zwei Rechtecken (Design) oder eine Zwischenform (Split). Zwischen den drei Zuständen kann rechts oben umgestaltet werden.
Im Modus Design wird auf der linken Seite die Palette sichtbar. Hier zieht man einfach ein beliebiges Element, beispielsweise eine TextView auf das Feld und ändert es im Modus Code in eine View. Dazu wird der Bezeichner TextView in den Klassennamen der View inklusive des Packages aufgeführt.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... <TextView ... /> ... <de.willemer.testview.MeineView android:id="@+id/bermudaview" android:layout_width="match_parent" android:layout_height="match_parent" /> ... </LinearLayout>Die Änderung von layout_width und layout_height von match_parent auf wrap_content ist nicht sinnvoll, da die View in der Regel nicht weiß, welchen Raum sie benötigt.
Parameter der XML-Datei
In der Layout-XML-Datei können Parameter für die View angegeben werden, die von der View in Java gelesen werden können.
<de.willemer.testview.MeineView style="@style/Widget.Theme.Testview.MyView" android:layout_width="300dp" android:layout_height="300dp" android:paddingLeft="20dp" android:paddingBottom="40dp" app:exampleDimension="24sp" app:exampleDrawable="@android:drawable/ic_menu_add" app:exampleString="Hello, MineSweeperView" />Die angegebenen Werte können aus der View folgendermaßen gelesen werden.
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MineSweeperView, defStyle, 0); mExampleString = a.getString(R.styleable.MineSweeperView_exampleString); mExampleColor = a.getColor(R.styleable.MineSweeperView_exampleColor, mExampleColor); mExampleDimension = a.getDimension(R.styleable.MineSweeperView_exampleDimension, mExampleDimension); if (a.hasValue(R.styleable. MineSweeperView_exampleDrawable)) { mExampleDrawable = a.getDrawable(R.styleable. MineSweeperView_exampleDrawable); mExampleDrawable.setCallback(this); }
Eine View vollflächig
Die Kontrollelemente wie Eingabefelder oder Datumspicker sind von Android vorgefertigte Views. Sie haben einen Bereich, in dem sie sich darstellen und in dem sie die Ereignisse überwachen.Wird außer der View auf dem Display kein weiteres Eingabeelement benötigt, kann die View als Content in der Activity statt der XML-Datei angegeben werden. Dies kommt hauptsächlich bei Spielen in Betracht, die außer dem Spielfeld in der Activity keine Eingabeelemente benötigen.
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); // nicht mehr nötig! setContentView(new MeineView(this)); } }Durch diese Änderung wird die Datei activity_main.xml nicht mehr eingelesen und kann also auch entfernt werden.
Zeichnungen in der Methode onDraw
Die View ist dafür verantwortlich, ihren Inhalt jederzeit darstellen zu können. Dazu ruft das System die Methode onDraw auf. Will die eigene View mehr als einen weißen Bildschirm zeigen, muss sie die Methode onDraw überschreiben.@Override public void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); // Darstellung der View }Als Parameter erhält sie ein Objekt von Canvas. Das Canvas stellt die Zeichenfläche dar. Über sie werden die Zeichen-Methoden aufgerufen. Die Zeichenprimitive benötigen neben dem Canvas aber auch ein Objekt der Klasse Paint. Das Paint-Objekt legen Sie selbst an. Es bestimmt die Art der Zeichnung, beispielsweise die Farbe, ob Elemente gefüllt oder umrahmt werden.
float posx = 100, posy = 100; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(Color.RED); canvas.drawCircle(posx, posy, 5, paint); }
Ereignisse
Wenn Sie eine eigene View schreiben, werden Sie auch auf Ereignisse reagieren wollen.Ereignisse führen häufig dazu, dass sich auf dem Bildschirm Dinge verändern sollen. Ein direktes Zeichnen ist in solchen Fällen nicht möglich, da dies durch die Methode onDraw umgesetzt wird. Stattdessen werden die Datenstrukturen für onDraw vorgereitet und durch den Aufruf von invalidate ein Neuzeichnen ausgelöst.
Berührung: onTouchEvent
Das typische Ereignis ist die Berührung des Displays mit dem Finger oder einem Stift.@Override public boolean onTouchEvent(MotionEvent event) { // Lese das Auslösungselement des Events int actionPerformed = event.getAction(); int x = event.getX(); int y = event.getY(); // Aktionen aufgrund des Events return true; // wenn Ereignis behandelt wurde }Eine Berührung liefert in der Regel zwei Ereignisse. Die erste ist das Berühren und das zweite ist das Entfernen des Fingers von der Scheibe. Haben beide Ereignisse unterschiedliche Koordinaten, wollte der Benutzer vermutlich ein Objekt über den Bildschirm verschieben.
- int x = getX();
Liefert die X-Koordinate des Ereignisses - int x = getX();
Liefert die X-Koordinate des Ereignisses - int action = event.getAction()
Der Wert von action wird mit MotionEvent.ACTION_UP oder mit MotionEvent.ACTION_DOWN verglichen, um festzustellen, ob der Finger gehoben oder gesenkt wurde.
Ein kleiner Test
Der folgende Programmausschnitt erzeugt bei jeder Berührung einen Punkt auf dem Display. Da der ganze Bildschirm neu gezeichnet wird, ist immer nur der letzte Berührungspunkt zu sehen. Um Sommersprossen zeigen zu können, müssten die Ereignisse in einer Liste oder einem Array gespeichert werden.// globale Variable für den Datenaustausch zwischen onDraw und onTouchEvent float posx = -1, posy = -1; @Override public boolean onTouchEvent(MotionEvent event) { posx = event.getX(); posy = event.getY(); invalidate(); // Zwingt zum Neuzeichnen der View return true; // Verarbeitung ist hier erfolgt! } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); // Kontrolle des Eingabepunkts if (posx>=0 && posy>=0) { canvas.drawCircle(posx, posy, 5, paint); } }
Tasten
Wenn das Android-Gerät über eine Tastatur verfügt, können auch diese ausgewertet werden.@Override public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { // Aktionen beim Niederdrücken einer Taste return true; // wenn Ereignis behandelt wurde } @Override public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { // Aktionen beim Loslassen einer niedergedrückten Taste return true; // wenn Ereignis behandelt wurde }
Größenermittlung der View: onMeasure
Das Ereignis onMeasure wird ausgelöst, wenn das System wissen will, welche Platzansprüche die View hat. Das Grundgerüst eine onMeasure-Methode:@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int w = MeasureSpec.getSize(widthMeasureSpec); int h = MeasureSpec.getSize(heightMeasureSpec); int myW = getMeineBreite(w); int myH = getMeineHoehe(h); setMeasuredDimension(myW, myH); }Das System ruft onMeasure auf, um die View zu fragen, welche Größe sie haben will. Die beiden Parameter sind keineswegs Breite und Höhe des Displays. Um den verfügbaren Platz zu ermitteln, werden diese als Parameter an die Methode MeasureSpec.getSize übergeben, die dann je nach Parameter Höhe und Breite ermittelt.
Die View errechnet nun anhand des zur Verfügung stehenden Platzes die eigenen Bedürfnissen. Diese Ausdehnung gibt sie als Parameter an die Methode setMeasuredDimension, die typischerweise immer am Schluss der Methode onMeasure aufgerufen wird.
Ausdehnungsmodus
Android kann verschiedene Anforderungen an die Ausdehnung der View haben. So kann sie vorschreiben, dass der angegebene Platz exakt einzuhalten ist oder sie kann angeben, dass dies der maximal verfügbare Platz ist. Diese Information liefert die Methode MeasureSpec.getMode und sie kann sich für die Höhe und die Breite unterscheiden.@Override protected void onMeasure(int wMeasureSpec, int hMeasureSpec) { // berechne die Breite und Hoehe int w = getMeineBreite(wMeasureSpec); int h = getMeineHoehe(hMeasureSpec); // onMeasure ruft immer diese Methode setMeasuredDimension( w, h ); }Die Methoden getMeineBreite und getMeineHoehe werden lokal implementiert, wobei sie die in den Spec übergebenen Vorgaben aus dem Layout auswerten.
private int getMeineBreite(int measureSpec) { int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); int width = specSize; if (specMode == MeasureSpec.AT_MOST) { } else if (specMode == MeasureSpec.EXACTLY) { } else if (specMode == MeasureSpec.UNSPECIFIED) { // hier eine Vorgabe machen, wenn Layout nichts spezifiziert } return width; } private int getMeineHoehe(int measureSpec) { int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); int height = specSize; if (specMode == MeasureSpec.AT_MOST) { } else if (specMode == MeasureSpec.EXACTLY) { } else if (specMode == MeasureSpec.UNSPECIFIED) { // hier eine Vorgabe machen, wenn Layout nichts spezifiziert } return height; }