# Hall of Fame or Hall of Shame? .left-column-half[ Think about those Visual Design Tips! ![:img Picture of two layouts with very small areas for interaction or images that are too small to see, 60%](img/layout-algorithm/bad-layout.jpg) ] .right-column-half[ - #1: Don't rely on blue for small objects - #2: Don't rely on blue for older users - #3: Make sure that contrast is high enough - #4: Minimize saturated colors - #5: Use redundant cues - #6: Make things distinct - #7: Use small multiples - #8: Manage expectations if you can't change response time - #9: Replace subtle changes with obvious ones - #10: Use well-tested visual grouping strategies - #11: Minimize the number of options - #12: Rely on recognition rather than recall ] --- # What went wrong? .left-column50[![:img File browser with toolbar, 90%](img/layout-algorithm/goodLayout.png)] .right-column50[![:img File browser with only half of same toolbar, 80%](img/layout-algorithm/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%](img/layout-algorithm/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 Algorithms Lauren Bricker CSE 340 Spring 2020 --- name: normal layout: true class: --- layout: false [//]: # (Outline Slide) # Goals for today - __Review__ - How layout is implemented in the toolkit --- # Review: XML Inflation Inflation in `MainActivity#onCreate(Bundle)`: ```java // contents is the ScrollView contents.addView(getLayoutInflater().inflate(R.layout.part1solution, null), PARAMS); ``` To break this down, recall that `MainActivity` inherits (indirectly) from `Context` ```java LayoutInflater inflater = getLayoutInflater(); // ask the Context for the inflater View newView = inflater.inflate(R.layout.part1, null); // newView is at the root of the inflated tree contents.addView(newView, PARAMS); // attach the newView to the parent ``` In a View (in say `Part3View.java`) get the inflater this way: ```java LayoutInflater inflater = LayoutInflater.from(context); // Use a static method to get the inflater // The rest is the similar ``` ??? Remind that you can inflate once but use many times --- # Review: Adding Programatically Adding a view programatically in `MainActivity` broken down in steps 1. The LayoutParams are created 2. The new view is created 3. Use [ViewGroup#addView(View, ViewGroup.LayoutParams)](https://developer.android.com/reference/android/view/ViewGroup#addView%28android.view.View,%20android.view.ViewGroup.LayoutParams%29) method on the parent to add it to the Interactor Hierarchy (with the params) ```java public static final RelativeLayout.LayoutParams PARAMS = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); .... Part2View p2v = new Part2View(this, getImageStrings(), vMargin); contents.addView(p2v, PARAMS); ``` --- # Review: Adding Programatically A slightly different way: 1. Create the new view 2. Add it to the parent using [ViewGroup#addView(View)](https://developer.android.com/reference/android/view/ViewGroup#addView%28android.view.View%29) 3. Set up the LayoutParams 4. Add the LayoutParams to the view ```java public static final RelativeLayout.LayoutParams PARAMS = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); ... Part2View p2v = new new Part2View(this, getImageStrings(), vMargin); contents.addView(p2v); // Add more setup for the params if needed. contents.setLayoutParams(PARAMS); ``` --- # Solutions to [LayoutLab](https://github.com/harshitha-akkaraju/layoutlab) Don't peek until you've tried it :) - [Inflation](https://github.com/harshitha-akkaraju/layoutlab/blob/complete/app/src/main/res/layout/activity_main.xml) - [Programatically](https://github.com/harshitha-akkaraju/layoutlab/blob/complete/app/src/main/java/com/harshiakkaraju/layoutlab/ProgrammaticConstraints.java) --- # Aside: changing properties dynamically What if you canted to change background color of all of the buttons to red? ```java for (int i = 0; i < layout.getChildCount(); i++) { view = layout.getChildAt(i); view.setBackgroundColor(Color.RED); } ``` --- # 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](/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](/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 items 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](/assignments/layout). - More on https://developer.android.com/guide/topics/ui/declaring-layout.html ??? Talk about different Toolkits may have different layouts. --- # Layouts in Play .left-column-half[ Which layouts are used in these apps? ] .right-column-half[ ![:img Pinterest layout, 36%](img/layout-algorithm/pinterest-android.jpg) ![:img Pinterest layout, 35%](img/layout-algorithm/android-linear.png) ] -- .left-column-half[ Probably: - **LinearLayout** - **ConstraintLayout** Thinking about what layouts are in an app is part of your Part 4 of the Layout assignment. ] --- # General & Powerful Approach: Constraints 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 (powerpoint snapping!) --- layout: false ## ConstraintLayout in Android .left-column[ ![:img A simple layout on an android watch with a textview and two buttons (save and discard) and the constraints highlighted,100%](img/layout-algorithm/watch3.png) ] .right-column[ - [ConstraintLayout](https://developer.android.com/reference/android/support/constraint/ConstraintLayout) - Allows you to position widgets in a flexible way - Useful for building **responsive** interfaces in Android. - You will be using ConstraintLayout for the Layout exercise - Review Monday's slides for more details ] --- layout: false [//]: # (Outline Slide) # Goals for today - Review - **How layout is implemented in the toolkit** --- # Time to talk about position again .left-column-half[ .quote[You have been asked to create a new `CircleView` object that can draw a circle in our `drawingView` object in Doodle. The user wants to add a circle so that the center point is at (100,100) on the drawingView and has a radius of (10). In `CircleView#onDraw(Canvas)` you will need to call `canvas.drawCircle(cx, cy, radius, paint)` (note that drawCircle takes the (x, y) location of the center of the circle as input). What values should you use for `cx` and `cy` in this your onDraw code?] ] .right-column-half[ ![:img Quiz results showing that many people correctly selected 10 10 although some incorrectly selelected 100 100, 100%](img/layout-algorithm/quiz1.png) ] --- # Time to talk about position again .left-column-half[ .quote[Consider the same CircleView object that contains a circle of radius 10 that we want to display in the drawingView so the center appears at `100, 100` in the parent view's coordinate system. How should we set up the bounding box for the `CircleView` so that its canvas will be correctly clipped? What are `x`, `y`, `width` and `height` for the bounding box (in parent coordinates?)] ] .right-column-half[ ![:img Quiz results showing that many people correctly selected 90 90 20 20, 100%](img/layout-algorithm/quiz2.png) ] --- # Time to talk about position again .left-column-half[ .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()` you will need to call `canvas.drawRect(left, top, right, bottom, paint)` What values should you use for `left` and `top`? ] ] ??? (0,0) -- .right-column-half[ `(0,0)` ] --- # Time to talk about position again .left-column-half[ .quote[Consider the same square (at `150,50` with a width 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) -- .right-column-half[ `(150,50,20,20)` ] --- # So: what have we learned? -- When an interactor draws itself, its drawing area always starts at (0,0) The *View* that interactor is in (it's *parent*) determines the location of its bounding box. This is what correctly positions it! --- # 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 --- # How is position calculated by the toolkit architecture? .left-column-half[
graph TD W(ViewGroup) --> V[ViewGroup] W --> V1[View] W --> V2[View] V --> V3[View] V --> V4[View] V --> V5[View] 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
] .right-column-half[ - The Component (Interactors + ViewGroups) 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: Simple loop through child components --> Recursive tree traversal - 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 --- # How do Container Components actually do layout? .left-column-half[
graph TD W(ViewGroup) --> V[ViewGroup] W --> V1[View] W --> V2[View] V --> V3[View] V --> V4[View] V --> V5[View] 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
] .right-column-half[ 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 possible layout implementation (similar to what is in Frame layout) Depth first tree traversal 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 c { if (child.isVisible()) { Rectangle r = child.getLayoutParams(); childLeft = padding + r.x; childTop = padding + r.y; child.layout(childLeft, childTop, r.w, r.h); } } } ``` ??? we're ignoring the implementation of gravity here remind them what protected means? --- # 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 // Second: do the actual updating public void layout(Rectangle newBounds) { onMeasure() // measure myself; this is recursive onLayout() // again; recursive. Makes use of calculated measurements } ``` --- .left-column[ # Measuring ] .right-column[ `onMeasure()` is a callback* invoked by `View.measure()` ] .footnote[We'll be talking more about callbacks when we talk about events] -- .right-column[ Object is given guidance about the size is given from parent view in the form of `MeasureSpec` parameters ] -- .right-column[ `onMeasure` ensures every view has a width and height value set prior to a layout pass ] -- .right-column[ Note: You must manually take padding into account (subtract padding from the width/height dimensions) ] --- .left-column[ # Best toolkits let child specify ] .right-column[ 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() // story relevant information & do calculations about // the container size } // based on all that, update our dimensions setMeasuredDimension() } ``` 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) --- # Aside `ImageView` height: - What is it's height before layout is done? Unknown! - But you need it in Part3 to decide where to put it - You haven't added it to the interactor hierarchy yet - How do you get height? -- Call `image.measure()` (you'll have to think about what the right parameters are) Then call `image.getMeasuredHeight()` (why not `image.getHeight()`?) -- Because it's 0! Since this image isn't laid out. --- # Frame layout -- actual implementation (slightly simplified) ```java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec { // recursively calculate size of children foreach child { // ... 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 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 // Second: do the actual updating public void layout(Rectangle newBounds) { onMeasure() // measure myself; this is recursive onLayout() // again; recursive. Makes use of calculated measurements } ``` --- .left-column[ # Layout ] .right-column[ `onLayout()` is a callback triggered when a view's position is changed ] -- .right-column[ Gives the new position __relative__ to the parent ] -- .right-column[ Causes a layout call on all children (!) ] --- # 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 these? This is just setting the bounding box! What is child *n*'s position? Width? Height? - need to know we are vertical - can decide to set our width entirely based on kids - we get to decide position --- # Generalized implementation of onLayout When is this called? ??? - Whenever the interface is *damaged* (call `invalidate(true)` to force this) - Before the recursive `drawAll()` function we talked about last week. **Why?** -- - Whenever the interface is *damaged* (call `invalidate(true)` to force this) - Before the recursive `drawAll()` function we talked about last week. **Why?** ??? Because `drawAll()` needs to know where interactors are to properly *translate* and *clip* before calling `onDraw()` on each of them -- - Because `drawAll()` needs to know where interactors are to properly *translate* and *clip* before calling `onDraw()` on each of them --- # How does this relate to drawing? .left-column-half[
graph TD W(ViewGroup) --> V[ViewGroup] W --> V1[View] W --> V2[View] V --> V3[View] V --> V4[View] V --> V5[View] 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
] .right-column-half[ *After* measuring & layout: __Tree traversal__ may be done on the layout hierarchy. Order depends on the specific layout algorithm (constraints don't require any!) ] -- .right-column-half[ Each view is responsible for drawing itself ] -- .right-column-half[ Siblings are drawn in the order that they appear ] --- .left-column[ # When to (re)Draw? ] .right-column[ Drawing is costly- (e.g. frame rates dropping for scrolling interactions) ] -- .right-column[ Often the screen is static ] -- .right-column[ We should *draw when we need to* ] -- .right-column[ Re-drawing "damaged" areas: call `invalidate()` ] --- .left-column[ # Summary of the Drawing Process ] .right-column[ 1) Android calls measure on root node (view) ```java // View specifies size of self protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec); ``` ] --- .left-column[ # Summary of the Drawing Process ] .right-column[ 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 on view ```java // View specifies size and position of children protected void onLayout(boolean changed, int left, int top, int right, int bottom); ``` ] --- .left-column[ # Summary of the Drawing Process ] .right-column[ 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 on view ```java // View specifies size and position of children protected void onLayout(boolean changed, int left, int top, int right, int bottom); ``` 3) Android calls draw on view ```java // Add the drawing happens protected void onDraw(Canvas canvas); ``` ] --- # What of this do *you* need to do - If you are creating an interface, none of it - Just use the layouts - If you are creating a new type of layout class - 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) - 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 (not assigned) --- # End of deck --- #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 ``` --- # 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