Graphics Programming
GUIs with Java (Android)
Thorsten Thormählen
October 22, 2018
Part 2, Chapter 3
Thorsten Thormählen
October 22, 2018
Part 2, Chapter 3
This is the print version of the slides.
Advance slides with the → key or
by clicking on the right border of the slide
Slides can also be advanced by clicking on the left or right border of the slide.
AndroidManifest.xml
generated within the directory structure is particularly importantActivity
is run at startup, which styles are used, etc.<?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
)
and what is the highest version of Android that the developer has tested (android:targetSdkVersion
)<?xml version="1.0" encoding="utf-8"?> <manifest ....> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="20" /> ... </manifest>
Activity
as a screen-filling top-level container TextView
Source code of the example: 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
of an Activity
can only be passed one View
referenceView.ViewGroup
classpublic 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/
./res/layout/
for XML layouts ./res/menu/
for menu structures ./res/values/
for constant values, such as strings, colors, numbers, arrays./res/drawable/
for image data./res/values-de/
for German strings, etc. ./res/drawable-ldpi/
for image data (low resolution) ./res/drawable-mdpi/
for image data (medium resolution) ./res/drawable-hdpi/
for image data (high resolution) ./res/drawable-xhdpi/
for image data (especially high resolution) R.java
is generated from the resources.
The file contains the class R
, which can be accessed in the program code.File: ./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>
File: ./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>
File: ./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>
By using the XML description the resulting Java source code often gets shorter:
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; } }
AbstractButton
View
class or its child classes
and overwriting of corresponding functions, such as onKeyDown(int keyCode, KeyEvent event)
OnKeyListener kl = new OnKeyListener() { @Override public boolean onKey (View clickedView, int keyCode, KeyEvent event) { ... } };This event listener instance can then be passed to one or more event sources by
View.setOnKeyListener(kl)
.
TextView
Source code of the example: 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
is used to describe touches and movements with finger, stylus, or mouseGestureDetector.SimpleOnGestureListener
to recognize certain gestures:
onDown(MotionEvent e)
: The user has touched the screen onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
: The user has done a swiping motion onLongPress(MotionEvent e)
: The user has touched the screen for a long timeonScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY):
The user performed a scroll motiononShowPress(MotionEvent e)
: The user has touched the screen and neither moved nor released itonSingleTapUp(MotionEvent e)
: The user has touched the screen and released it againTextView
Source code of the example: 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()
is called.
However, the previous activity is possibly still visible.onStop()
is called.onPause()
(e.g., user-specific settings, database records, etc.))onPause()
, such that computing power
is not unnecessarily wasted.onSaveInstanceState(Bundle state)
and onRestoreInstanceState(Bundle state)
,
which allow to save the current state of this particular instance of the Activity in a Bundle
and restore it after a restart Bundle
is also passed to the function onCreate(Bundle state)
, so that the
restoration of the state may also take place thereonPause()
in the SharedPreferences
and is set accordingly after a restarting of the app onSaveInstanceState()
and is restored
in onCreate(Bundle state)
.
A restart may be enforced by rotating the device (CTRL + F11 or CTRL + F12 in the emulator)Source code of the example: 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
class
and its onDraw(Canvas canvas)
method can be overwritten Canvas
class contains various methods for drawing 2D primitives such as lines, rectangles, circles, etc. 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); } }
Path
class
Source code of the example: MyGeneralPath.zip
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); } }
Please notify me by e-mail if you have questions, suggestions for improvement, or found typos: Contact