# What went wrong? .left-column50[![:img File browser with toolbar, 90%, width](img/layout-in-practice/goodLayout.png)] .right-column50[![:img File browser with only half of same toolbar, 80%, width](img/layout-in-practice/badLayout.png)] ??? - Changing available space e.g., window resized by user - Changing sizes, fonts, etc. - Adding and removing components - Layout mechanism has to deal with all cases --- # What went wrong? ![:img File browser with only half of same toolbar, 80%, width](img/layout-in-practice/worseLayout.png) ??? - No scroll bar for text boxes that are too narrow - No way to redistribute space between directory & file list - Important controls (e.g., Open) get hidden - Min size is much too small - No way to send the dialog away (buttons gone) --- name: inverse layout: true class: center, middle, inverse --- # Layout II: Layout in Practice Lauren Bricker CSE 340 Spring 2021 --- name: normal layout: true class: --- layout: false [//]: # (Outline Slide) # Goals for today - Announcements - Remember `git add`, `git commit`, `git push`, then **Turn in** through [GitGrade](https://gitgrade.cs.washington.edu/student/assignment/147/turnin)! - Code and video "lock" Saturday 10pm - Reminder: Peer review emails will go out Saturday night/Sunday morning (or sooner if they are all turned in before then) - Practice Quiz 2 is on Canvas. - Finish [Layout](/courses/cse340/21sp/slides/wk02/layout.html#39) - Android Layout Algorithm - **Constraints** - Creating Layout Programatically --- # Reminder: Layout Types in Android - [FrameLayout](https://developer.android.com/reference/android/component/FrameLayout.html) - good for position views on top of each other, or encapsulating a bunch of views. Used in [Doodle](/courses/cse340/21sp/assignments/doodle). - [LinearLayout](https://developer.android.com/reference/android/component/LinearLayout.html) - places views one after the other in order according to the orientation (Horizontal or Vertical). Used in [Layout](/courses/cse340/21sp/assignments/layout). - [RelativeLayout](https://developer.android.com/reference/android/widget/RelativeLayout) - Positions of the children are desribed in relation to one another - [TableLayout](https://developer.android.com/reference/android/component/TableLayout.html) - Rows and columns style way of declaring a layout - [GridLayout](https://developer.android.com/reference/android/component/GridLayout.html) - Uses an [*adapter*](https://developer.android.com/reference/android/interactor/Adapter.html) that provides i tems to display in a grid - [ConstraintLayout](https://developer.android.com/reference/android/component/ConstraintLayout.html) Let's you use constraints to specify how things should lay out. Used in [Layout](/courses/cse340/21sp/assignments/layout). More on [declaring layout](https://developer.android.com/guide/topics/ui/declaring-layout.html) ??? Talk about different Toolkits may have different layouts. --- name: constraint-template # Constraints: A Powerful Option .left-column[
![:img A simple layout on an android watch with a textview and two buttons (save and discard) and the constraints highlighted,100%, width](img/layout/watch3.png)] --- template: constraint-template .right-column[ Can do everything we can do with springs, struts, and linear layouts Declare relationships (**what** should be true) System automatically maintains relationships under change (**how** this should happen) ] --- template: constraint-template .right-column[ Can do everything we can do with springs, struts, and linear layouts Declare relationships (what should be true) - This should be centered in that - This should be 12 pixels to the right of that - Parent should be 5 pixels larger than child System automatically maintains relationships under change (how) ] --- # Constraint Layout in Android .row[ You can see little lines connecting the `textView` to its container and its sibling (the `linearLayout`). ] .row[ .left-column30[ ![:img A simple layout on an android watch with a textview and two buttons (save and discard) and the constraints highlighted,125%, width](img/layout/constraint-editor.png) ] .right-column60[ - This specifies how it's attached (can change type by clicking on right) - If you were to change the interface (e.g. a different sized screen), it would stay attached and keep filling the space - All ends up in XML you can explore too ] ] --- # Constraint Layout in Android .left-column50[ ![:img A simple layout on an android phone instead of a watch with a textview and two buttons (save and discard),100%, width](img/layout/explore-devices.png) ] .right-column50[ You can test this in the emulator: modify the aspect ratio of your display, and flip it to horizontal to test things ] ??? Demonstration: consider demoing this interface live PRINT THIS LIST - Show dragging things to create layout - Show different size and orientation phones - Show editing attributes - Show clicking on constraints in box to change type - Show simple interface hierarchy - Show simplifying xml - Show changing button names/ids --- template: constraint-template .right-column[ - What do you think this does? `app:layout_constraintBottom_toTopOf="@+id/linearLayout"` ] --- template: constraint-template count: false .right-column[ - What do you think this does? `app:layout_constraintBottom_toTopOf="@+id/linearLayout"` - And this? `app:layout_constraintEnd_toEndOf="parent"` ] --- template: constraint-template count:false .right-column[ - What do you think this does? `app:layout_constraintBottom_toTopOf="@+id/linearLayout"` - And this? `app:layout_constraintEnd_toEndOf="parent"` - We also use `Start_tStartOf` and `Top_toTopOf` to create this layout ] --- # All of this can be specified in XML ```xml <TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="0dp" android:text="@string/sample_text" app:layout_constraintBottom_toTopOf="@+id/linearLayout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp"/> ``` --- .left-column[ ## Android's Limits ![:img picture of attributes window showing controls for constraint-based layout including five types of elements, 100%, width](img/layout/constraints.png)] .right-column[ Android provides a limited set of constraints ([Docs](https://developer.android.com/training/constraint-layout/)): - 1 Size ratio - 2 Delete constraint (not a constraint, just removes them) - 3 Height/width mode (3 main types): - Wrap constraint ![:img wrap symbol >>>, 5%, width](img/layout/wrap.png) - Fixed size ![:img fixed symbol I--I, 5%, width](img/layout/fixed.png) - Match Constraint ![:img match symbol IvvvI, 5%, width](img/layout/match.png) - 4 Margins - 5 Constraint bias (essentially weights on competing constraints) Range of attachment options (e.g. button sides, corners) Worth getting to know additional abstractions (groups; guidelines; barriers; chains) ] ??? -bias lets us create something that is 2-3 of the way over rather than centered go back to android to demo again --- exclude: true # Note that these are one-way constraints You can change the right side, and it will update the left side (not the reverse) Can be very inefficient .jax[$$O(2^n)$$] But highly efficient incremental update algorithms exist ??? Only have to update things that might change Hudson's work on this was seminal can have a Directed Acyclic Graph (DAG) but not a cycle (thus, a tree) hard to guard against cycles --- # Constraints: Very General General mechanism for establishing and maintaining relationships between things - Layout is one use - Several other uses in UI - Connection of application to UI, e.g. deriving appearance from data - Multiple views of same data - Automated semantic feedback - Automatic arrangement of lines (Snapping in drawing --- # Layouts in Play .left-column50[ Which layouts are used in these apps? ] .right-column50[ ![:img Pinterest layout, 36%, width](img/layout-in-practice/pinterest-android.jpg) ![:img Pinterest layout, 35%, width](img/layout-in-practice/android-linear.png) ] -- .left-column50[ Probably: - **LinearLayout** - **ConstraintLayout** Thinking about what layouts are in an app is part of your Part 4 of the Layout assignment. ] --- layout: false [//]: # (Outline Slide) # Goals for today - Constraints - **Layout in practice** --- # Time to talk about position again .left-column60[ .quote[You have been asked to create a new `SquareView` object that can draw a square. The user wants to add a square with its top left corner at (150,50) and a width of (20). In `SquareView#onDraw(Canvas)` you will need to call `canvas.drawRect(left, top, right, bottom, paint)` What values should you use for `left` and `top`? ] ] ??? (0,0) -- count: false .right-column40[ `(0,0)` ] --- # Time to talk about position again .left-column50[ .quote[Consider the same square (at `150,50` with a size of `20` in its parent). How should we set up the bounding box for the `SquareView` so that its `Canvas` will be correctly clipped? What are `x`, `y`, `width` and `height` for the bounding box (in parent coordinates?) ] ] ??? (150,50,20,20) -- count: false .right-column50[ `(150,50,20,20)` ] --- # So what have we learned (from Doodle)? -- count: false When an interactor draws itself, its drawing area always starts at (0,0) The `View` of that interactor is inside of (that interactor's *parent* `View`) determines the location the iteractor's bounding box. The **parent** is what correctly positions the interactor! --- # How does layout come into this? In Doodle you hard-coded the position of everything (or used animation to set it) But in most interfaces, we use *layout containers* to accomplish this. - Layout containers make decisions like "stack these views vertically" or "make them fit into this size area" and set their position and bounding box on this basis - The *toolkit architecture* then helps to enforce this --- # Key Issues Containers have to Consider - Where do components get placed? - How much space should they occupy? - How should components to react to changes in layout (such as screen rotation) - number of components - size of window - How to enforce this during drawing? --- # Example: Spot the Heron .left-column50[ | Prototype | Wireframe | |--|--| | ![:img Spot the heron low fidelity prototype, 80%, width](img/layout-in-practice/spottheheron2-prototype.png) | ![:img Spot the heron layout wireframe, 70%, width](img/layout-in-practice/spottheheron2-wireframe.png) | ] -- .right-column50[ Interactor Hierarchy
graph TD W(ViewGroup 1) --> V[ViewGroup 2] W --> V1[View 1.1] W --> V2[View 1.2] V --> V3[View 2.1] V --> V4[View 2.2] V --> V5[View 2.3] classDef blue font-size:14pt,text-align:center classDef darkblue font-size:14pt,text-align:center class W,V darkblue class V1,V2,V3,V4,V5 blue
] --- name: measure-layout # Measure, then Layout, then Draw .left-column40[
graph TD W(ViewGroup 1) --> V[ViewGroup 2] W --> V1[View 1.1] W --> V2[View 1.2] V --> V3[View 2.1] V --> V4[View 2.2] V --> V5[View 2.3] classDef blue font-size:14pt,text-align:center classDef darkblue font-size:14pt,text-align:center class W,V darkblue class V1,V2,V3,V4,V5 blue
] ??? let's talk about how the size and position of each view is determined --- template: measure-layout .right-column60[ ## Measure - Parent calculates some constraints (based on its measured size) - Passes them to child and calls `onMeasure()` - Child calculates and stores its measured width and height ] -- .bottom[ In Doodle (`DrawView`) we ignore the specs and return a fixed width and height ```java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getMaxWidth(), getMaxHeight()); } ``` ] ??? Note that we ignore the specs here and always return a fixed width and height --- template: measure-layout .right-column60[ ## Measure - Typically this is recursive, [depth-first post-order](https://www.tutorialspoint.com/data_structures_algorithms/tree_traversal.htm) (i.e. based on its childrens' responses to `onMeasure()`) - Why does a container need to know about its kids' sizes to measure itself? ] ??? Example: it might need to be big enough to fit them all (LinearLayout) -- .right-column60[ What if the child needs to `WRAP_CONTENT`? What if the child needs to `MATCH_PARENT`? ] --- # Example: Spot the Heron .left-column50[ | Prototype | Wireframe | |--|--| | ![:img Spot the heron low fidelity prototype, 80%, width](img/layout-in-practice/spottheheron2-prototype.png) | ![:img Spot the heron layout wireframe, 70%, width](img/layout-in-practice/spottheheron2-wireframe.png) | ] .right-column50[ - Title text: - `width` is `MATCH_PARENT` and the `height` is `WRAP_CONTENT` and - constrained to the top of the screen. - Button Bar: - `width` is `MATCH_PARENT` and the `height` is `WRAP_CONTENT` and - constrained to the bottom of the screen. - Image: `width` and `height` are `MATCH_PARENT` ] -- .bottom[ The outer ConstraintLayout needs to "ask" the Title Text and Button Bar
"How big are you?" ] --- # Parent-child communication Child can define `layoutParams` in XML or java (`setLayoutParams()`) - These are type-specific, examples: - `LinearLayout` defines [`LinearLayout.LayoutParams`](https://developer.android.com/reference/android/widget/LinearLayout.LayoutParams)) - `RelativeLayout` defines [`RelativeLayout.LayoutParams`](https://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams)) - `LayoutParams` give the parent hints (such as "center me" or "give me priority") and can also specific preferred width and height: ```java ViewGroup.LayoutParams params = view.getLayoutParams(); param.width = width; // the width the view wants param.height = height; // the height the view wants view.setLayoutParams(param); ``` --- # Parent-child communication In [Layout](/courses/cse340/21sp/assignments/layout) we see this in a few places: In `MainActivity` we define: ```java public static final RelativeLayout.LayoutParams PARAMS = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); ``` Then these params are set on the each child view as it is added to the `contents` -- In Part 3 & 4 you will have to define the appropriate `LayoutParams` for your layout, then attach them to each view in that Layout. --- # Parent-child communication - Just before the child is actually place on the parent, it gets an `onMeasure` "call" - The parent passes a "spec" for width and height ('AT_MOST' or 'EXACTLY' or 'UNSPECIFIED') - The child **must** respond with `setMeasuredDimension()` which sets the child's desired width and height - Lots of helpers for this in `View` class (such as `resolveSize(size, measureSpec)`, `getMaxHeight()`, `getMaxWidth()`, `getMeasuredHeight()`, `getMeasuredWidth()`...) - Handle things you would otherwise deal with manually such as padding ```java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // compute desired_width and desired_height setMeasuredDimension(desired_width, desired_height); } ``` --- # A few notes You may not need to override `onMeasure` - this only happens if your `View` needs to do something specific about its sizing. Example - [Spot the Heron](https://gitlab.cs.washington.edu/cse340/exercises/cse340-spot-the-heron-v-1.0) case study code - [Layout](/courses/cse340/21sp/assignments/layout) `Part2View` Other times you do: - [Layout](/courses/cse340/21sp/assignments/layout) `Part3View` and probably `Part4View` --- # End of deck