Grafikprogrammierung
GUIs mit Java (Android)
Thorsten Thormählen
22. Oktober 2018
Teil 2, Kapitel 3
Thorsten Thormählen
22. Oktober 2018
Teil 2, Kapitel 3
Dies ist die Druck-Ansicht.
Weiterschalten der Folien durch die → Taste oder
durch das Klicken auf den rechten Folienrand.
Das Weiterschalten der Folien kann ebenfalls durch das Klicken auf den rechten bzw. linken Folienrand erfolgen.
AndroidManifest.xml
innerhalb der erzeugten Verzeichnisstruktur ist besonders wichtigActivity
beim Start des Programms ausgeführt wird, welche Styles verwendet werden, usw.<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.unimarburg.helloguiapp" android:versionCode="1" android:versionName="1.0"> <application android:label="HelloGUIApp"> <activity android:name="HelloGUI" android:label="HelloGUIApp"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>;
android:minSdkVersion
) und was
die höchste Android-Version ist, die der Entwickler getestet hat (android:targetSdkVersion
)<?xml version="1.0" encoding="utf-8"?> <manifest ....> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="20" /> ... </manifest>
Activity
als Bildschirm füllender Top-Level-ContainerTextView
Quelltext des Beispiels: HelloGUI.zip
package de.unimarburg.helloguiapp; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class HelloGUI extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setText("Hello World"); setContentView(tv); } }
package de.unimarburg.mymenubarapp; import android.app.Activity; ... public class MyMenuBar extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setText("Hello World"); setContentView(tv); } public boolean onCreateOptionsMenu(Menu menu) { SubMenu subMenu = menu.addSubMenu("File"); subMenu.add("Open"); subMenu.add("Close"); return true; } }
setContentView
einer Activity
kann nur ein View
übergeben werdenView.ViewGroup
abgeleitetpublic class MyButton extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); TextView tv = new TextView(this); tv.setText("Hello World"); layout.addView(tv); Button b = new Button(this); b.setText("Button Text"); layout.addView(b); LinearLayout.LayoutParams lp; lp = (LinearLayout.LayoutParams) b.getLayoutParams(); lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; this.setContentView(layout); } ... }
./res/
abgelegt
./res/layout/
für die XML-Layouts./res/menu/
für Menü-Strukturen./res/values/
für konstante Werte für Strings, Farben, Zahlen, Arrays, etc../res/drawable/
für Bilddaten./res/values-de/
für deutsche Strings, usw../res/drawable-ldpi/
für Bilddaten (niedrige Auflösung)./res/drawable-mdpi/
für Bilddaten (mittlere Auflösung)./res/drawable-hdpi/
für Bilddaten (hohe Auflösung)./res/drawable-xhdpi/
für Bilddaten (besonders hohe Auflösung)R.java
erzeugt, die die Klasse R
enthält, auf die im Programm-Code zugegriffen werden kannDatei: ./res/values/mybuttonstrings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="textViewString">Hello World</string> <string name="buttonString">Button Text</string> <string name="menuFile">File</string> <string name="menuOpen">Open</string> <string name="menuClose">Close</string> </resources>
Datei: ./res/menu/mybuttonmenu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/file" android:title="@string/menuFile" > <menu> <item android:id="@+id/open" android:title="@string/menuOpen" /> <item android:id="@+id/close" android:title="@string/menuClose" /> </menu> </item> </menu>
Datei ./res/layout/mybuttonlayout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/textViewString" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/buttonString" /> </LinearLayout>
Der eigentliche Java-Quelltext wird durch Verwendung der XML-Beschreibung häufig kürzer:
package de.unimarburg.mybuttonaltapp; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; public class MyButtonAlt extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.mybuttonlayout); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.mybuttonmenu, menu); return true; } }
View
oder dessen Kinderklassen
abgeleitet werden und die entsprechenden Funktionen, wie z.B. onKeyDown(int keyCode, KeyEvent event)
, überschrieben
werdenOnKeyListener kl = new OnKeyListener() { @Override public boolean onKey (View clickedView, int keyCode, KeyEvent event) { ... } };Diese Event-Listener-Instanz kann anschließend an eine oder mehrere Ereignisquellen übergeben werden durch
View.setOnKeyListener(kl)
.
TextView
ausQuelltext des Beispiels: ActionButton.zip
package de.unimarburg.actionbuttonapp; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.SubMenu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.LinearLayout; public class ActionButton extends Activity { private int counter = 0; TextView tv; private OnClickListener cl = new OnClickListener() { @Override public void onClick(View v) { counter++; tv.setText("Click #" + Integer.toString(counter)); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); tv = new TextView(this); tv.setText("Hello World"); layout.addView(tv); Button b = new Button(this); b.setText("Increment"); b.setOnClickListener(cl); layout.addView(b); LinearLayout.LayoutParams lp; lp = (LinearLayout.LayoutParams) b.getLayoutParams(); lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; this.setContentView(layout); } @Override public boolean onCreateOptionsMenu(Menu menu) { SubMenu subMenu = menu.addSubMenu("File"); subMenu.add("Open"); subMenu.add("Close"); return true; } }
public class ActionMenu extends Activity { ... private final int ID_MENU_INC = 1; private final int ID_MENU_RST = 2; @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, ID_MENU_INC, Menu.NONE, "Increment"); menu.add(Menu.NONE, ID_MENU_RST, Menu.NONE, "Reset"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { counter++; if(item.getItemId() == ID_MENU_RST) counter = 0; tv.setText("Click #" + Integer.toString(counter)); return true; } }
MotionEvent
dient dazu, Berührungen und Bewegungen mit Finger, Stift oder Maus zu beschreibenGestureDetector.SimpleOnGestureListener
weitergeleitet werden, um bestimmte Gesten zu erkennen:
onDown(MotionEvent e)
: Benutzer hat den Bildschirm berührtonFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
: Benutzer hat eine Wischbewegung ausgeführtonLongPress(MotionEvent e)
: Benutzer hat den Bildschirm lange berührtonScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY):
Benutzer hat eine Scrollbewegung ausgeführtonShowPress(MotionEvent e)
: Benutzer hat den Bildschirm angetippt und weder bewegt noch losgelassenonSingleTapUp(MotionEvent e)
: Benutzer hat den Bildschirm angetippt und wieder losgelassenTextView
ausgegeben
Quelltext des Beispiels: MyTouchEvent.zip
package de.unimarburg.mytoucheventapp; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import android.view.GestureDetector; import android.view.MotionEvent; public class MyTouchEvent extends Activity { private GestureDetector gestDetector; private TextView tv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); tv = new TextView(this); tv.setText("Position: 0, 0"); setContentView(tv); MyGestureListener gl = new MyGestureListener(); gestDetector = new GestureDetector(this, gl); } @Override public boolean onTouchEvent(MotionEvent e) { this.gestDetector.onTouchEvent(e); return super.onTouchEvent(e); } class MyGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { tv.setText("onFDown; Position=" + Float.toString(e.getX()) + ", " + Float.toString(e.getY())); return true; } @Override public void onLongPress(MotionEvent e) { tv.setText("onLongPress; Position=" + Float.toString(e.getX()) + ", " + Float.toString(e.getY())); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { tv.setText("onFling: from=" + Float.toString(e1.getX()) + ", " + Float.toString(e1.getY()) + " to=" + Float.toString(e2.getX()) + ", " + Float.toString(e2.getY())); return true; } } }
onPause()
aufgerufen. Die
bisherige Activity ist aber eventuell noch sichtbaronStop()
aufgerufen.onPause()
zu speichern (z.B. Benutzer-spezifische Einstellungen, Datenbankeinträge, usw.)onPause()
natürlich alle aufwendigen Berechnungen der Activity gestoppt werden, damit
Rechenleistung nicht unnötig vergeudet wirdonSaveInstanceState(Bundle state)
und onRestoreInstanceState(Bundle state)
, die
es erlauben, den aktuellen Zustand dieser speziellen Instanz der Activity in einem Bundle
zu speichern und
nach einem Neustart daraus wieder herzustellenBundle
auch der Funktion onCreate(Bundle state)
übergeben, so dass die Wiederherstellung des
Zustands auch dort stattfinden kannonPause()
in den SharedPreferences
gespeichert und beim nochmaligen Starten
der App entsprechend gesetztonSaveInstanceState()
der aktuelle Zustand des Zähler abgespeichert und bei einem vom Betriebsystem initiierten
Neustart in onCreate(Bundle state)
wiederhergestelltQuelltext des Beispiels: PausedActivity.zip
... public class PausedActivity extends Activity { private int counter = 0; // this value should be maintained per instance private int minDigits = 1; // this preference should be maintained globally private TextView tv; private EditText et; private void updateTextView() { tv.setText("Click #" + String.format("%0" + minDigits + "d", counter)); } private OnClickListener cl = new OnClickListener() { @Override public void onClick(View v) { counter++; updateTextView(); } }; private TextWatcher tw = new TextWatcher() { @Override public void afterTextChanged(Editable s) { try { minDigits = Integer.parseInt(s.toString()); } catch (NumberFormatException e) { minDigits = 1; } if (minDigits > 255) { minDigits = 255; } if (minDigits < 1) { minDigits = 1; } updateTextView(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); tv = new TextView(this); tv.setText("Hello World"); layout.addView(tv); Button b = new Button(this); b.setText("Increment"); b.setOnClickListener(cl); layout.addView(b); LinearLayout.LayoutParams lp; lp = (LinearLayout.LayoutParams) b.getLayoutParams(); lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; TextView label = new TextView(this); label.setText("Choose your preferred minimal number of digits:"); layout.addView(label); et = new EditText(this); et.setInputType(InputType.TYPE_CLASS_NUMBER); et.setText("1"); et.addTextChangedListener(tw); layout.addView(et); lp = (LinearLayout.LayoutParams) et.getLayoutParams(); lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; // if available, restore minDigits from preferences SharedPreferences prefs = getPreferences(MODE_PRIVATE); if (prefs.contains("minDigitsVal")) { minDigits = prefs.getInt("minDigitsVal", 1); et.setText(Integer.toString(minDigits)); } // if available, restore counter from the savedInstanceState if (savedInstanceState != null && savedInstanceState.containsKey("counterVal")) { counter = savedInstanceState.getInt("counterVal"); updateTextView(); } else { tv.setText("Hello World"); } this.setContentView(layout); } @Override protected void onPause() { super.onPause(); // write persistent data to preferences SharedPreferences prefs = getPreferences(MODE_PRIVATE); SharedPreferences.Editor e = prefs.edit(); e.putInt("minDigitsVal", minDigits); e.commit(); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { // the superclass handles the state of the view classes in the layout super.onSaveInstanceState(savedInstanceState); // store member variables of this instance to the savedInstanceState savedInstanceState.putInt("counterVal", counter); } ... }
View
abgeleitet werden und deren onDraw(Canvas canvas)
Methode überschrieben werdenCanvas
enthält diverse Methoden zum Zeichnen von 2D Primitiven, wie Linien, Rechtecken, Kreise, etc.Quelltext des Beispiels: LineRectCircle.zip
public class MyView extends View { Paint paint; public MyView(Context context) { super(context); paint = new Paint(); paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.STROKE); } @Override public void onDraw(Canvas canvas) { canvas.drawLine(20.0f, 50.0f, 50.0f, 200.0f, paint); canvas.drawRect(100.0f, 50.0f, 60.0f, 80.0f, paint); canvas.drawCircle(200.0f, 100.0f, 80.0f, paint); } }
public class MyView extends View { Paint paint; Path path; public MyView(Context context) { super(context); paint = new Paint(); paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.STROKE); //paint.setStyle(Paint.Style.FILL); path = new Path(); path.moveTo( 50, 50); // start here path.lineTo( 50, 70); // going down path.lineTo(100, 70); // going right path.lineTo(100, 180); // going down path.lineTo(120, 180); // going right path.lineTo(120, 70); // going up path.lineTo(170, 70); // going right path.lineTo(170, 50); // going up path.close(); // going left (back to start) } @Override public void onDraw(Canvas canvas) { canvas.drawPath(path, paint); } }
Anregungen oder Verbesserungsvorschläge können auch gerne per E-mail an mich gesendet werden: Kontakt