name: inverse layout: true class: center, middle, inverse --- # Essential Geometry and View Updates Jennifer Mankoff CSE 340 Spring 2019 --- layout: false .title[Getting Help] .body[ Before asking for help (even on Piazza), there are some key things you need to do: 1. Read the spec code through 2. Specify the error that is occuring including the text of the error 3. Explain what you have done to isolate and debug 4. If it has to do with things not showing up visually, have already used the live inspector; and include a screen shot; and have tried printouts (logs) 5. If it has to do with the app crashing, include the (relevant) error message and at least one thing you've logged to try to debug the error ] --- .title[Getting Help] .body[ Office hours (virtual or in person) can be requested any time, but 24 hours warning will be needed to schedule. We will post a link on Piazza when these are scheduled. Captain obvious is a really helpful thing. You can do this for each other. If you don't know anyone in the class, email us, and we will assign ‘buddies’ who can play ‘captain obvious’ to each other Piazza monitoring times will be made more explicit Other suggestions from the class? ] --- .left-column[ #Input Event Handling Review ] .right-column[ Device Independence - Want / need device independence - Need a uniform and higher level abstraction for input Component Independence - Given a model for representing input, how do we get inputs delivered to the right component? Model View Control for Components - How do components deal with events (Essential Behavior) ] ??? --- .left-column[ ## Essential Behavior Quiz question PPS Parts? - Action - Input modifier - Input Event - Query field (Condition) - State ] .right-column[ ![:img Picture of first quiz question asking "What sort of interaction does this PPS support?" and showing a state machine for drawing a line with updates to the line on every move event. Most people correctly understood a line with rubber banding., 100%](img/quizqs.png) ] --- [//]: # (Outline Slide) .title[Today's goals] .body[ - Understand more about __Event Listeners__ - Understanding more about __Activities__ - Understand how to save _State_ and data persistence - Understand Essential Geometry - Understand View Updates ] --- template: inverse ## Event Listeners ??? - Implementation strategy - Basic goal: notify something of a change --- layout: false .left-column[ ##Basics of Event Listeners More listed in the [API Documentation](https://developer.android.com/guide/topics/ui/ui-events.html) ] .right-column[ - The set of Android's provided interfaces include: ```java // User tapped a view public static interface View.OnClickListener { ... } // User long pressed on a View public static interface View.OnLongClickListener { ... } // State of view has changed // (e.g. user clicked outside a EditText input box) public static interface View.OnFocusChangeListener { ... } ``` ] --- .title[Declaring an Event Listener] .body[ - Two ways to use declare an event listener: - Create an Anonymous Class - Implement an Interface ] --- .left-column[ ## Create an Anonymous Class ] .right-column[ ```java public class ExampleActivity extends AppCompatActivity { private Button mButton; private int mButtonClicks = 0; // As a member variable in an Activity private View.OnClickListener mClickListener = new View.OnClickListener() { public void OnClick(View v) { if (mButton != v) { // Do nothing if its not the right view return; } // Increment the click count mButtonClicks += 1; } }; protected void onCreate(Bundle savedState) { Button mButton = (Button) findViewById(R.id.button_test); if (mButton != null) { // 'this' refers to the activity itself here // When the button clicked, Android will trigger // the Activity's OnClick method mButton.setOnClickListener(mClickListener); } } } ``` ] --- .left-column[ ## Implement the Interface ] .right-column[ ```java // Start by implementing the interface public class ExampleActivity extends AppCompatActivity implements View.OnClickListener { // Necessary to implement private Button mButton; private int mButtonClicks = 0; protected void onCreate(Bundle savedState) { Button mButton = (Button) findViewById(R.id.button_test); if (mButton != null) { // 'this' refers to the activity itself here // When the button clicked, Android will trigger // the Activity's OnClick method mButton.setOnClickListener(this); } } // Here's where you do the implementation of your listener public void OnClick(View v) { if (mButton != v) { // Do nothing if its not the right view return; } // Increment the click count mButtonClicks += 1; } } ``` ] --- .left-column[ ## Registering a Listener (1/2) - Once a listener is created ] .right-column[ ```java View.OnClickListener buttonListener = new View.OnClickListener({ public void OnClick(View v) { if (mButton != v) { // Do nothing if its not the right view return; } // Increment the click count mButtonClicks += 1; } }); ``` ] --- .left-column[ ## Registering a Listener (1/2) - Once a listener is created - Set it on the view: ] .right-column[ ```java Button mButton = (Button) findViewById(R.id.button_test); if (mButton != null) { mButton.setOnClickListener(buttonListener); } ``` ] --- .left-column[ ## Registering a Listener (2/2) ] .right-column[ - `View` has a series of `set*Listeners(...)`, for example: ```java View v = new View(); v.setOnClickListener(...); v.setLongClickListener(...); ``` ] --- .left-column[ ## Marking an event as handled ] .right-column[ - Many listeners return a `boolean` value that indicate the event has been handled - Returning `false` causes the event to bubble up to the next possible view that can handle it ```java /** * onLongClick - triggered when a view is long clicked * @param v - the view long pressed * @return - true if the callback consumed the long click, false otherwise. **/ boolean onLongClick (View v) { ... }; ``` ] .footnote[[API Documentation for Long Click](https://developer.android.com/reference/android/view/View.OnLongClickListener.html) ] --- .left-column[ ## Review: How Android Triggers Listeners] .right-column[ - Android traverses the view hierarchy (starting at root) - "Picks" `Views` that respond to an input event - Loops through the list of `View` objects, checks if they will "capture" the event - If none capture the event, the event "bubbles" back up the `View` object list ] --- [//]: # (Outline Slide) .title[Today's goals] .body[ - Understand more about Event Listeners - **Understand how to save _State_ and data persistence** - Understand Essential Geometry - Understand View Updates ] --- template: inverse # Saving State --- layout: false .left-column[## When to save state? (1/3)] .right-column[ - Activity `A` spawns Activity `B` - E.g. Typing a Facebook post, then you select 'Add a Location' ] -- .right-column[ - Android kills `A` to reclaim resources while the user is interacting with `B` ] -- .right-column[ - User finishes with `B`, returns to `A` but the post is gone! ] --- .left-column[## Activity Lifecycle] .right-column[![:img Android activity lifecycle, 50%](img/activity_lifecycle.png)] --- .left-column[## When to save state? (2/3)] .right-column[![:img Android activity state diagram, 50%](img/android-activity-states-01.png)] --- .left-column[## When to save state? (3/3)] .right-column[![:img Android activity state diagram -- process killed, 50%](img/android-activity-states-02.png)] --- .title[ Maintaining Activity State] .body[ - We can fix it by saving the state in a `Bundle` before Android closes the `Activity` - We then restore the state when we return to the `Activity` later ] --- .title[# Bundle it up] .body[ - `Bundle`: A hash map (dictionary) of String keys to Primitive, Parcelable, Serializable values ] -- .body[ - Used to: (1) Save/restore state (2) Transfer data between different `Activity` in an app ] -- .body[ - Primitive are `int`, `float`, `boolean`, `double` (the usual suspects) ] --- .left-column[ ## Saving in the Bundle ] .right-column[ Methods for _saving_ items out of a Bundle - Ints: `putInt(String key, int value)` - Floats: `putFloat(String key, float value)` - Characters: `putChar(String key, char value)` - Strings: `putString(String key, String value)` - Serializable Objects: `putSerializable(String key, Serializable value)` - Parcelable Objects: `putParcelable(String key, Parcelable value)` - _Others in the documentation (e.g. Arrays, Bytes, etc).._ ] .footnote[[Bundle Documentation]( https://developer.android.com/reference/android/os/Bundle.html )] --- .left-column[ ## Retrieving from the Bundle ] .right-column[ Methods for getting items out of a Bundle - Ints: `getInt(String key)` - Floats: `getFloat(String key)` - Characters: `getChar(String key)` - Strings: `getString(String key)` - Serializable Objects: `getSerializable(String key)` - Parcelable Objects: `getParcelable(String key)` - _Others in the documentation_ (e.g. Arrays, Bytes, etc).. ] .footnote[[Bundle Documentation]( https://developer.android.com/reference/android/os/Bundle.html )] --- .left-column[ ## Saving Activity State (1/3)] .right-column[ - Android provides the `onSaveInstanceState(Bundle savedInstanceState)` callback method for you to save the state of an `Activity` ```java private static final USERNAME_KEY = "USER_NAME_KEY"; private static final FIB_SUM_KEY = "FIB_SUM_KEY"; private int mFibSumValue; // Set to 10295 by our user private String mUsername; // Set to "Prabha" by our user @Override public void onSaveInstanceState(Bundle outState) { // Always call super - your super classes could be saving state for you! super.onSaveInstanceState(outState); // Now outState.putInt(FIB_SUM_KEY, mFibSumValue); outState.putString(USERNAME_KEY, mUsername); } ``` ] --- .left-column[ ## Restoring Activity State (1/2) ] .right-column[ - We saved, and now our user goes off to some other app ] -- .right-column[ - Android kills our `Activity` ] -- .right-column[ - Now how do we get the _saved_ state back? ] --- .left-column[ ## Restoring Activity State (2/2)] .right-column[ - Two ways: - `onCreate(Bundle savedInstanceState)` - `onRestoreInstanceState(Bundle savedInstanceState)` ] --- .left-column[ ## Restoring in `onCreate` vs. `onRestoreInstanceState` ] .right-column[ - `onRestoreInstanceState` - Guarantees you will never have a non-null `Bundle` when called - Lets subclasses override the behavior of restoring the state - `onCreate` - Lets you focus on doing all of your initialization in one place - In the assignment we use onCreate ] .footnote[[Stackoverflow conversation on this](http://stackoverflow.com/questions/36408776/using-oncreate-vs-onrestoreinstancestate) ] --- [//]: # (Outline Slide) .title[Today's goals] .body[ - Understand more about Event Listeners - Understand how to save _State_ and data persistence - **Understand Essential Geometry** - **Understand View Updates** ] --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - Supports feedback to user ![:img google doc with scrollbar, 50%](img/window.png) ] ??? Examples? --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - Supports feedback to user ![:img google doc with scrollbar, 50%](img/window-with-menu.png) ] --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - Supports feedback to user ![:img google doc with scrollbar, 50%](img/window-with-menu-highlight.png) ] --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - Supports feedback to user ![:img google doc with scrollbar, 50%](img/window-with-menu-highlight2.png) ] ??? What is the essential geometry of this menu? --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - Supports feedback to user - Requires redraw on toolkit side ] ??? why? --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - What should be redrawn? ![:img google doc with scrollbar, 50%](img/window.png) ] --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - What should be redrawn? ![:img google doc with scrollbar, 50%](img/window-with-menu.png) ] --- .left-column[ ## View Update: .red[Based on Essential Geometry] ] .right-column[ - What should be redrawn? ![:img google doc with scrollbar, 50%](img/combined.png) ] --- .title[Essential Geometry for a scrollbar?] --- .left-column[ ## View Update: .red[Damage Mechanism] ] .right-column[ - How does the toolkit know what to redraw? - Let the component report - Aggregate - Usually calculate bounding box ] --- .left-column[ ## View Update: .red[Draw/Redraw Mechanism] ] .right-column[ - Virtual device abstraction provided by windowing system ] ??? Allows each program to (mostly) pretend that it has the screen (frame buffer) to itself --- .left-column[ ## View Update: .red[Draw/Redraw Mechanism] ] .right-column[ - Virtual device abstraction provided by windowing system - Component abstraction provided by toolkit ] ??? Allows each component to (mostly) pretend that it has the screen to itself --- .left-column[ ## View Update: .red[Draw/Redraw Mechanism] ] .right-column[ - Virtual device abstraction provided by windowing system - Component abstraction provided by toolkit - Enforced using clipping - Supported using coordinate system transformations ] --- .left-column[ ## View Update: .red[Value of component abstraction] ] .right-column[ Each component knows how to draw itself Based on what it is and its internal state Includes some common properties (inherited from a base class for all components), E.g. - position (x,y relative to parent) - size (w,h) - visible - Enabled Typically includes some specialized properties (declared in subclass) ] --- .left-column[ ## View Update: .red[Redraw Mechanism] ] .right-column[ Damage caused by drawing (or other things) ] ??? Can you think about other things? - Window hidden and re-exposed - Resizing - redrawing --- .left-column[ ## View Update: .red[Redraw Mechanism] ] .right-column[ Damage caused by drawing (or other things) Naive approach to redraw -- problems? ] --- .left-column[ ## View Update: .red[Redraw Mechanism] ] .right-column[ Damage caused by drawing (or other things) Naive approach to redraw -- problems? ] ![:img pic of original screen and changed screen, 72%](img/original-new.png) ??? XXX TODO ADD pic like this using divs? - Can be slow (redrawing everything unecessary) - Can cause flickering - double buffering is better, - hence the 'Canvas' abstraction or equivalent - can then switch which FB is displayed (very fast) --- .left-column[ ## View Update: .red[Redraw Mechanism] ] .right-column[ Damage caused by drawing (or other things) Naive approach to redraw -- problems? ] ![:img pic of original screen and changed screen, 72%](img/original-new.png) ![:img pic with double buffering included, 70%](img/full-solution-buffering.png) --- .left-column[ ## View Update: .red[Redraw Mechanism] ] .right-column[ Damage caused by drawing (or other things) Naive approach to redraw -- problems? ] ![:img pic of original screen and changed screen, 72%](img/original-new.png) ![:img pic with double buffering included, 70%](img/full-solution-buffering.png) -- .right-column[ - **Never** just draw - Why not? ] ??? - Update *state* - Report *damage* (by calling 'repaint()) - Wait for *toolkit to request redraw* (also works if damage comes from elsewhere) - How does it generalize to any cause of damage (always need state!!) --- .left-column[ ## View Update: .red[Propagating Damage] ] .right-column[ How do you decide what is damaged? Trivial reject test ] ??? Integer arithmetic trivial reject test - compare diagonal of damage region (bounding box) - to bounding box of each component --- # Summary --- # End of Deck --- # Basics of Event Listeners (1/3) - Interface on the `View` class that acts as a _callback_ method -- - Must be attached to a particular `View` -- - Android framework triggers the method once the `View` is interacted with by the user -- - A `View` can have listen for different types of interactions - But: the `View` must implement and registering the appropriate listeners --- .left-column[ ## Many Views, Same Listener (1/3)] .right-column[ - Event callbacks are passed the `View` as a parameter ] -- - We can reuse a listener for views that handle the same action (e.g. 3 buttons increment the same click count) --- ## Many Views, Same Listener (2/3) - And we can handle different actions by checking the `View` or its `id`: ```java protected void onCreate(Bundle savedState) { Button mButtonOne = (Button) findViewById(R.id.button_one); if (mButtonOne != null) { mButtonOne.setOnClickListener(mButtonClickListener); } Button mButtonTwo = (Button) findViewById(R.id.button_two); if (mButtonTwo != null) { mButton.setOnClickListener(mButtonClickListener); } } ``` --- ## Many Views, Same Listener (3/3) ```java View.OnClickListener mButtonClickListener = new View.OnClickListener({ public void OnClick(View v) { if (v == null) { return; } int viewId = v.getId(); switch (viewId) { case R.id.button_one: // First Button Log.d("Button Press", "First Button"); break; case R.id.button_two: // Second Button Log.d("Button Press", "Second Button"); break; default: // Someone else is using the listener! Log.d("Button Press", "Invalid Button!"); } } }); ``` --- .left-column[ ## More on Listeners] .right-column[ - Different views can have different handlers - `ListView`: `onItemClick` - Context menus from long clicks: `onCreateContextMenu` - Keystrokes: `onKey` - And others... ] -- - Check documentation for specifics --- # Saving state: advanced material --- - Custom objects that implement the `Serializable` or `Parcelable` interfaces - You will need to make any custom classes that you want to save into a `Bundle` implement the `Parcelable` interface --- # Parcelable vs. Serializable - Both are for the same purpose of saving but `Serializable` makes implementations much easier... -- - **EXCEPT, .red[you should almost always use `Parcelable`]** -- - Why? - `Serializable` uses introspection ==> more memory, and CPU cycles - `Parcelable` **is optimized for mobile devices** --- ## Implementing `Parcelable` (1/3) -- You are required to implement the following methods for the `Parcelable` interface: ```java public class MyPersonModel implements Parcelable { private int mAge; // Required for the interface public int describeContents() { return 0; } // Required for the interface public void writeToParcel(Parcel out, int flags) { out.writeInt(mAge); } // Continued on next slide... ``` --- ## Implementing `Parcelable` (2/3) ```java // Required for the interface // CREATOR is an object that can remark public static final Parcelable.Creator
CREATOR = new Parcelable.Creator
() { public MyPersonModel createFromParcel(Parcel in) { return new MyPersonModel(in); } public MyPersonModel[] newArray(int size) { return new MyPersonModel[size]; } }; // Required for the interface // Private constructor for Android to use with your CREATOR private MyPersonModel(Parcel in) { mAge = in.readInt(); } } ``` --- ## Implementing `Parcelable` (3/3) - **Note**: .red[the order of the values when writing and reading them matters!] - When you write to the parcel using `write*()` methods in `writeToParcel`, - The write order __MUST BE__ the same as when you read them in the private constructor - For more details on implementation: https://developer.android.com/reference/android/os/Parcelable.html --- ## example code for restoring state ```java private static final USERNAME_KEY = "USER_NAME_KEY"; private static final FIB_SUM_KEY = "FIB_SUM_KEY"; private int mFibSumValue = 0; private String mUsername = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMainTextView = (TextView) findViewById(R.id.text_main_title); mSumTextView = (TextView) findViewById(R.id.text_sum); // Check if there was a previously saved state to restore to the user // A non-null bundle means there was a state that was saved previously // A bundle is just a Key-Value pairing - its a hash map :) if (savedInstanceState != null) { // There was a previous state - time to restore mFibSumValue = savedInstanceState.getInt(FIB_SUM_KEY); mUsername = savedInstanceState.getString(USERNAME_KEY); } updateMainText(); updateFibSumText(); } ``` --- ## example code for restoring state ```java private static final USERNAME_KEY = "USER_NAME_KEY"; private static final FIB_SUM_KEY = "FIB_SUM_KEY"; private int mFibSumValue = 0; @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // Check if there was a previously saved state to restore to the user if (savedInstanceState != null) { // There was a previous state - time to restore mFibSumValue = savedInstanceState.getInt(FIB_SUM_KEY); mUsername = savedInstanceState.getString(USERNAME_KEY); } updateMainText(); updateFibSumText(); } ``` --- --- ## Exercise: Parcelable and Activity State - Pair up! - Download the base code here: - This app lets a user enter a name, age, and favorite food - It stores this info in a `PersonModel` - When the app is killed, this information needs to remain edited - Implement the `Parcelable` interface for the PersonModel - Then use the Activity's relevant state change callbacks to `PersonModel` when the phone rotates --- ### Some Notes about `onSaveInstanceState()` - .red[Only use to save and restore session variables and the state of the UI] -- - Android won't always trigger the method -- - It is called when the `Activity` is closed and _expected_ to be restored soon -- - If your app crashes, or is closed, **the values in the bundle will disappear!** -- - If you use custom views, you can implement their version of `onSaveInstanceState`: Android will call `onSaveInstanceState()` every view in the layout --- --- layout: false .left-column[ #Input Event Goals ] .right-column[ Device Independence - Want / need device independence - Need a uniform and higher level abstraction for input Component Independence - Given a model for representing input, how do we get inputs delivered to the right component? ] ??? --- .left-column[ # Review: Logical Device Approach ] .right-column[ - Valuator → returns a scalar value - Button → returns integer value - Locator → returns position on a logical view surface - Keyboard → returns character string - Stroke → obtain sequence of points - Pick → select an object ] --- .left-column[ # Review: Dispatch ] .right-column[ Picking Bubbling Capture ]
layout: true