Control Keys

move to next slide (also Enter or Spacebar).
move to previous slide.
 d  enable/disable drawing on slides
 p  toggles between print and presentation view
CTRL  +  zoom in
CTRL  -  zoom out
CTRL  0  reset zoom

Slides can also be advanced by clicking on the left or right border of the slide.

Android

androidicon
  • Android is a Linux-based operating system from Google with very large market share on mobile devices (88% of all smartphones in Q2/2018*)
  • Own applications (Apps) can be created in Java using the Android Software Development Kit (SDK), which contains all the necessary tools and libraries
  • The SDK is available for all major platforms (Windows, Mac OS and Linux)
  • On the Android Developer webpage detailed guidance and assistance can be found

Installing Android Studio

  • Google has ended the support for the Stand-alone SDK Tools and the Android Eclipse Tools
  • Developers now have to work with Android Studio:

Creating an Android project directory

  • The source code of an app contains not just Java files, but is composed from a directory structure with various XML files, image files for icons, etc.
  • Android Studio can be used to create an initial directory structure
    (instructions)

AndroidManifest.xml

  • The manifest file AndroidManifest.xml generated within the directory structure is particularly important
  • It specifies how the application is called, which icon is used, which Activity 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>

AndroidManifest.xml

  • Later, when the app will run on different devices, the manifest file specifies, which android version must be implemented at least on the device (android:minSdkVersion) and what is the highest version of Android that the developer has tested (android:targetSdkVersion)
  • To that end, the following code row is inserted:
<?xml version="1.0" encoding="utf-8"?>
<manifest ....>
   <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="20" />
...
</manifest>
  • Within Google Play the app is then only available to those devices that meet the requirements
  • A complete list of Google Play filters can be found here

The first GUI

The first GUI

  • Using an Activity as a screen-filling top-level container
  • Displaying a text with TextView
gui1 gui1

Source code of the example: HelloGUI.zip

The first GUI

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);
    }
}

Adding a Menu

  • Creating a menu with Menu and SubMenu
menubar


Source code of the example: MyMenuBar.zip

Adding a Menu

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;
  }
}  

Inheritance hierarchy ("is a") for the Android View class

class_hierarchy
Source: extracted from the Android API

Layout-Manager

  • The function setContentView of an Activity can only be passed one View reference
  • If several components must be placed, a layout manager must be used
  • A layout manager automates the placement of components
  • The layout is automatically adjusted when the window size is changed
  • For Android, all layouts and container are derived from the View.ViewGroup class

Adding a button

  • Create a button with the Button class
button


Source code of the example: MyButton.zip

Adding a button

public 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);
  }
 ...
}

Composition for the example hierarchy ("is part of")

containment_hierarchy
button

XML Description

XML description for GUI components

  • Instead of instantiating the GUI components in the program code (as this is was shown previously), the composition hierarchy can also be described using resource files in XML format
  • Advantages
    • The graphical layout editors from integrated development environments can read and write XML easily
    • Different layouts for different languages, screen sizes or the orientation of a device can be provided
    • The XML description decouples the GUI design from the programming of the functionality
  • Disadvantages
    • More files
    • More complex build system, because the resource files must be compiled separately
    • Many similar components can not be instantiated by loops in the program

Resource files

  • The resource files are stored in a directory structure within the directory ./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
  • Additional identifiers encoded in the directory names that describe resources for different devices, e.g.,
    • ./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)
  • During the build process a file named R.java is generated from the resources. The file contains the class R, which can be accessed in the program code.

The previous example with XML resources

button


Source code of the example: MyButtonAlt.zip

The previous example with XML resources

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>

The previous example with XML resources

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>
  • The '@' marker indicates references to other XML resources
  • To reference an item unambiguously in code, a unique identifier (ID) is required. Using the '+' marker this ID is created automatically.

The previous example with XML resources

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;
  }
}

Event Processing

Event Processing

  • Event: Something has been activated, changed, moved, etc.
  • Event Source: The component that is directly affected by the event, for example, a pressed AbstractButton
  • Event Listener: The class that would like to be informed about the event

Event Processing

  • In Android there are two ways to implement an event processing:
    • Event Handlers: This requires derivation from the View class or its child classes and overwriting of corresponding functions, such as onKeyDown(int keyCode, KeyEvent event)
    • Event Listeners: This requires to implement an Interface, e.g.:
      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).

Adding an event processing for the button

  • A click on the button increments a counter and displays the number in a TextView
actionbutton

Source code of the example: ActionButton.zip

Adding an event processing for the button

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;
  }
}

Adding an event processing for the menu

  • Selecting the MenuItem "Reset" resets the counter.
gui_action_menu


Source code of the example: ActionMenu.zip

Adding an event processing for the menu

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;
  }
}

Touches, movements, and gestures

  • The class MotionEvent is used to describe touches and movements with finger, stylus, or mouse
  • MotionEvents can for example be passed to a GestureDetector.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 time
    • onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY): The user performed a scroll motion
    • onShowPress(MotionEvent e): The user has touched the screen and neither moved nor released it
    • onSingleTapUp(MotionEvent e): The user has touched the screen and released it again

Example: TouchEvent

  • On touching the screen, the position is displayed in a TextView
gui_motionevent

Source code of the example: MyTouchEvent.zip

Example: TouchEvent

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;
    }
  }
}

Life cycle of an Activity

Life cycle of an Activity and data management

  • Mobile devices will often switch between Activities. This is done at times without explicit user action, e.g., for an incoming call
  • The Activities are organized as a stack, only the top "Foreground"-Activity accepts user inputs
  • If another Activity becomes the "Foreground"-Activity, the previous Activity pauses and its member function onPause() is called. However, the previous activity is possibly still visible.
  • Only when the previous activity is no longer visible, it is stopped and the member function onStop() is called.
  • Stopped or paused Activities can be completely closed by the operating system to free up memory or other resources
  • Therefore, it is necessary to store all data that require permanent storage to be saved at the latest in the member function onPause() (e.g., user-specific settings, database records, etc.))
  • In addition, all computationally expensive calculations should be stopped in the member function onPause(), such that computing power is not unnecessarily wasted.

Life cycle of an Activity

activity_lifecycle
Source: based on Android API Guides

Activity Instance State

  • When the operating system completely closes an activity to free resources and later restarts the activity, it should be in the same state in which the user has left it
  • This is the expected behavior because the user has never actively initiated the closing of the Activity
  • Therefore, the Activity class provides the member functions
    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
  • Alternatively, the Bundle is also passed to the function onCreate(Bundle state), so that the restoration of the state may also take place there

Activity Instance State

activity_instancestate
Source: based on Android API Guides

Example: SharedPreferences and InstanceState

android_paused_small
  • The previous "counter" example is extended by a text field into which the user can enter his preference for the minimum number of digits for the counter value
  • This preference is stored in the function onPause() in the SharedPreferences and is set accordingly after a restarting of the app
  • In addition, on a restart initiated by the operating system, the current state of the counter is stored in the function 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

Example: SharedPreferences and InstanceState

...

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);
  }
  ...
}

Canvas

Canvas

  • To draw 2-dimensional objects an own class can be derived from the View class and its onDraw(Canvas canvas) method can be overwritten
  • The Canvas class contains various methods for drawing 2D primitives such as lines, rectangles, circles, etc.

Example: Line, Rectangle, and Circle

  • The example draws a line, a rectangle, and a circle
gui_linerectcircle

Source code of the example: LineRectCircle.zip

Example: Line, Rectangle, and Circle

 
  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);
    }
  }

Example: Drawing a general path

  • The example draws a "T" with the help of the Path class
gui_generalpath

Source code of the example: MyGeneralPath.zip

Example: Drawing a general path

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);
  }
}

Are there any questions?

questions

Please notify me by e-mail if you have questions, suggestions for improvement, or found typos: Contact

More lecture slides

Slides in German (Folien auf Deutsch)