name: inverse layout: true class: center, middle, inverse --- # Layout III: Layout Algorithms Lauren Bricker CSE 340 Spring 23 --- name: normal layout: true class: --- layout: false [//]: # (Outline Slide) # Goals for today - Do this now - Fill out the [survey on Ed](https://edstem.org/us/courses/38124/lessons/59406/slides/331427) - Announcements - Post privately on Ed if you did not receive Doodle Peer Reviews - Doodle grades will be released this week - Information on Resubmissions will be released by Wednesday - Layout part 1-2 due Wednesday - NO EXTENSIONS! - **Finish a few things in [Layout In practice](../wk02/layout-in-practice.html#23)** - **Quick Layout demo with Spot the Heron (branch: `layout-ii`)** - Layout algorithm - Rethinking Layout - How layout is implemented in the toolkit --- # Demo of Programatic Layout One thing that we didn't cover well is how to set up layout params/constraints programatically. So let's demo it now! To follow along: 1. Clone [Spot the Heron](https://gitlab.cs.washington.edu/cse340/exercises/cse340-spot-the-heron) if you have not already 2. Switch to branch `layout-ii` using `git checkout layout-ii` * If you made changes and it complains about conflicts you may need to * commit your changes to your local repo (`git add`/`git commit`) but don't `git push` (you won't be able to) * do a `git stash push` to temporarily store your changes, then `git stash pop` later to get them back* * re-clone STH in a different directory Here are resources on [git branches](https://www.atlassian.com/git/tutorials/using-branches) and [git conflict resolution](https://rawgit.com/mernst/git-conflict-tutorial/master/git-conflict-resolution.html) --- # Recap - We created a toolbar in a separate `.xml` file. - We used the inflater to turn the `.xml` button bar into a "live" `View` object. - We created `LayoutParams` to lay the button bar out correctly in the parent - We found the main parent view by it's id. - We attached the button bar `View` to the parent. --- 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
] ??? Reminder: we were talking about the android algorithm for layout --- template: measure-layout .right-column60[ High level overview: - The Interactor Hierarchy in just about everything - Draw and redraw - Layout - Toolkit architecture invokes layout - Containers do the work - Manage size and position of children - Enforce component abstraction recursive tree traversal through child components - Translate child to correct location - Clip child after drawing is complete - Know how to handle resizing (reactive) ] ??? - Parent knows how to setup for drawing of children, invoke their drawing code, and add additional output (before and/or after) based on what the parent is and its internal state - Whorfian effects (things not in the library are hard) - Nesting not well defined --- template: measure-layout .right-column60[ Key Issues: - Where do components get placed? - How much space should they occupy? - How should components to react to changes? - number of components - size of window - Who should get to decide children's sizes? parents or children? - Should it be a top down vs bottom up approach - Depth first tree traversal ] --- # Simplest layout implementation - Parent `View` responds to an `onLayout` call - No consideration of child size or position in doing layout - Simplified from FrameLayout [source code](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/FrameLayout.java), [View](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java) and [ViewGroup](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java) ```java protected void onLayout(boolean changed, int l, int t, int r, int b) { foreach child of this view { if (child.isVisible()) { LayoutParams lp = child.getLayoutParams(); childLeft = padding + lp.x; childTop = padding + lp.y; child.layout(childLeft, childTop, lp.width, lp.height); } } } ``` ??? - we're ignoring the implementation of gravity here - Protected means it can be used by child classes. --- # Generalized implementation of layout Details of most else, and two-pass process, handled in [View](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java) and [ViewGroup](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java) ```java public void layout(Rectangle newBounds) { onMeasure() // measure myself; this is recursive onLayout() // again; recursive. Makes use of calculated measurements } ``` --- # Measuring .footnote[We'll be talking more about callbacks when we talk about events] [Review](../wk02/layout-in-practice.html#39): `onMeasure()` is a callback* invoked by `View.measure(int widthMeasureSpec, int heightMeasureSpec)` -- count: false `View` is given guidance about the size is given from parent view in the form of `MeasureSpec` parameters -- count: false `onMeasure` ensures every view has a width and height value set prior to a layout pass -- count: false Note : You must manually take padding into account (subtract padding from the width/height dimensions) --- # Best toolkits let child specify Size (based on nature of component (think TextBox vs Button vs LinearLayout) - preferred size - minimum size - maximum size Parent gets to ignore all of that, but usually tries not to. Someone has to be in charge; it's the parents (my kids hate that! :) - Usually tries to give each child as much as possible & divide up the remainder proportionally - However, child component has to deal if not enough (i.e. truncate text) --- # Reference Implementation of onMeasure ```java // First: Measure everything protected void onMeasure(int widthspec, int heightspec) { // loop through children in order they were added foreach child { // get child's preferred layout size onMeasure() // store relevant information & do calculations about // the container size } // based on all that, update our dimensions setMeasuredDimension(...) } ``` - You can call `super.onMeasure()` in a subclass to ensure the recursion is handled - Until you call `onMeasure` your Views will not have a size. - Is this part of the _Toolkit Architecture_ or _Toolkit Library_? ??? It's part of the toolkit architecture. Even though the library refers to all of the different kinds of View and Layout classes, you have the implementation of those classes can reference parts of the toolkit architecture. It's the architecture that decides when to call on measure, for example. --- ## Frame layout -- actual (simplified) implementation ```java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { foreach child { // recursively calculate size of children // ... calculate max child width and max child height maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + margins); maxHeight = ... } // ... add in padding setMeasuredDimension(... based on what was found) // now recursively set dimensions for all children foreach child { final View child = getChildAt(i); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // recursion then happens through callback } } ``` ??? How would we do this for linearLayout? --- # Generalized implementation of layout Details of most else, and two-pass process, handled in [View](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java) and [ViewGroup](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java) ```java public void layout(Rectangle newBounds) { onMeasure() // measure myself; this is recursive onLayout() // again; recursive. Makes use of calculated measurements } ``` --- # Layout `onLayout()` is a callback triggered when a view's position is changed -- count: false Gives the new position __relative__ to the parent -- count: false Causes a layout call on all children (!) --- template: measure-layout .right-column60[ ## Layout - Parent *decides* size allocated to children - Can ignore child preferences (children will be clipped) - Parent *also decides* position of children - This is typically done recursively, [depth first post-order](https://www.tutorialspoint.com/data_structures_algorithms/tree_traversal.htm) (i.e. kids layout their kids, then parents layout them) - These are stored in `getWidth()`, `getHeight()`, `getX()` and `getY()` (i.e. they are *relative to the parent*) ] --- # Reference implementation of onLayout ```java // a layout class should override onLayout. protected void onLayout(boolean changed, int l, int t, int r, int b) { // recursively calls layout on each child foreach child { LayoutParams lp = child.getLayoutParams(); // calculate the width and height of the kids int width = calculated width int height = calculated height // calculate the position of kids int childLeft = calculated position int childTop = calculated position child.layout(childLeft, childTop, childLeft + width, childTop + height); } } ``` ??? What might we use for left/top/width/height - in FrameLayout? - in LinearLayout? --- # How would LinearLayout calculate ... - child *n*'s position? - child *n*'s width? - child *n*'s height? (This is just setting the bounding box!) However, - we need to know if we are vertical or horizontal - we can decide to set our width (height) entirely based on kids - we can decide the child's position based on order and height of preceding kids --- template: measure-layout .right-column60[ ## Draw - Happens when `child.draw()` is called - `draw` is also a recursive method which triggers `onDraw`. Why wasn't our `onDraw` recursive in Doodle? ] -- .right-column60[ Because the things you implemented had no children!! ] --- template: measure-layout .right-column60[ ## Draw - Happens when `child.draw()` is called - `draw` is also a recursive method which triggers `onDraw`. - The tree determines *z-order* - This is a [depth-first **pre-order**](https://www.tutorialspoint.com/data_structures_algorithms/tree_traversal.htm) traversal (parents draw first) | order | id | z-height | |-------|----------------|-------| | 6,7 | 1.1, 1.2 | 3 | | 3,4,5 | 2.1, 2.2, 2.3 | 2 | | 2 | 2 | 1 | | 1 | 1 | 0 | ] --- # Note: Parents enforce position and size .left-column30[ ![:img Animation of the draw algorithm moving and clipping a child, 100%, width](img/layout-algorithm/view-draw.gif) ] .right-column70[ During drawing - Translate child's `Canvas` to correct location before calling `onDraw(canvas)` - Clip child to width and height after drawing is complete - This is childs last change to *obey* parent (it can draw bigger but will be clipped. Or it can try to fit in what was allocated to it) - Translate `Canvas` back - **Why?** ] --- count: false # Note: Parents enforce position and size .left-column30[ ![:img Animation of the draw algorithm moving and clipping a child, 100%, width](img/layout-algorithm/view-draw.gif) ] .right-column70[ During drawing - Translate child's `Canvas` to correct location before calling `onDraw(canvas)` - Clip child to width and height after drawing is complete - This is childs last change to *obey* parent (it can draw bigger but will be clipped. Or it can try to fit in what was allocated to it) - Translate `Canvas` back - **Why?** - Ensures `(0,0)` is what you would expect ] --- # Reference Implementation of draw ```java protected void draw() { onDraw(); // Call onDraw for the parent view for each child { // Go through all the children if (child.isVisible()) { // Only draw visible children child.draw(); // Draw child (and all its kids) } } } ``` --- # Reference Implementation of draw ```java protected void draw() { onDraw(); // Call onDraw for the parent view for each child { // Go through all the children if (child.isVisible()) { // Only draw visible children * LayoutParams lp = child.getLayoutParams(); // Used for child coordinates * canvas.save(); // Save the current state of canvas * canvas.translate(lp.x, lp.y); // Move origin to child's top-left corner * canvas.clip(0,0,lp.width, lp.height); // Clip to child child.draw(); // Draw child (and all its kids) * canvas.restore(); // Restore } } } ``` ??? Draw out on paper again Note that this has absolute layout! --- class: center, middle, inverse # When does all of this happen? --- # When to (re)Draw? Drawing is costly- (e.g. frame rates dropping for scrolling interactions) -- Often the screen is static -- We should *draw when we need to* -- Re-drawing "damaged" areas: call `invalidate()` on those areas. The rest is asynchronous --- # When is Layout used ??? - Whenever the interface is *damaged* (call `invalidate(true)` to force this) - Before the recursive `draw()` function we talked about last week. **Why?** -- - Whenever the interface is *damaged* (call `invalidate(true)` to force this) - Before any recursive `draw()` call **Why?** ??? Because `draw()` needs to know where interactors are to properly *translate* and *clip* before calling `onDraw()` on each of them -- count: false - Because `draw()` needs to know where interactors are to properly *translate* and *clip* before calling `onDraw()` on each of them --- # Summary of the Drawing Process 1) Android calls measure on root node (view) ```java // View specifies size of self protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec); ``` --- # Summary of the Drawing Process 1) Android calls measure on root node (view) ```java // View specifies size of self protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec); ``` 2) Android calls layout ```java // View specifies size and position of children protected void onLayout(boolean changed, int left, int top, int right, int bottom); ``` --- # Summary of the Drawing Process 1) Android calls measure on root node (view) ```java // View specifies size of self protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec); ``` 2) Android calls layout ```java // View specifies size and position of children protected void onLayout(boolean changed, int left, int top, int right, int bottom); ``` 3) Android draws everything ```java // Add the drawing happens protected void onDraw(Canvas canvas); ``` --- class: center, middle, inverse # So why did you tell me this? --- # You can build anything you want!!! If you build your own container you could - Change where things are placed - Change the z-order (traverse the tree differently) - Decorate your kids (after they draw) - Anything else you can imagine! --- # What of this do *you* need to do - If you are creating an interface ("Interface Developer"), none of it - Just use the layouts - If you are creating a new type of layout class ("Component Developer") - It depends - If you can manage layout entirely by specifying an interactor hierarchy with layout params that do what you want, you are golden (our assignment part 2) - If you want to do something entirely new (lay out images in a spiral for example) you have to override `onLayout()` or `onMeasure()` or both (Layout assignment part 3 and possibly part 4) --- # End of deck --- exclude: true #Conceptual form of UI constraints ``` java // this is 5 pixels to the right of that this.x = that.x + that.w + 5 // this is centered this.x = that.x + that.w/2 - this.w/2 // this is 10 larger than children this.w = 10 + max_child().x + max_child().w ``` --- exclude: true # Frame layout -- actual implementation (slightly simplified) Simplified from FrameLayout [source code](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/FrameLayout.java), [View](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java) and [ViewGroup](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java) ```java // this is implemented in view public void layout(int l, int t, int r, int b) { // first make sure everything has a size (recursively) onMeasure() // then do layout onLayout(changed, left, top, right, bottom) } // a layout class should override onLayout. In FrameLayout protected void onLayout(boolean changed, int l, int t, int r, int b) { // recursively calls layout on each child foreach child { LayoutParams lp = child.getLayoutParams(); int width = child.getMeasuredWidth(); int height = child.getMeasuredHeight(); int childLeft = parentLeft + lp.leftPadding; // add padding int childTop = parentTop + lp.topPadding; // add padding child.layout(childLeft, childTop, childLeft + width, childTop + height); // recursion through callback } } ``` ??? Traverses the hierarchy many times over as implemented Can try to be more efficient or give more control... tradeoffs