Efficient Android Layouts Dan Lew Give me the place to stand, and I - - PowerPoint PPT Presentation

efficient android layouts
SMART_READER_LITE
LIVE PREVIEW

Efficient Android Layouts Dan Lew Give me the place to stand, and I - - PowerPoint PPT Presentation

Efficient Android Layouts Dan Lew Give me the place to stand, and I shall move the earth. ~Archimedes Give me a standing desk, and I shall write an Android app. ~Me ViewGroups Complex ConstraintLayout RelativeLayout


slide-1
SLIDE 1

Efficient Android Layouts

Dan Lew

slide-2
SLIDE 2
slide-3
SLIDE 3

“Give me the place to stand, and I shall move the earth.”

~Archimedes

slide-4
SLIDE 4

“Give me a standing desk, and I shall write an Android app.”

~Me

slide-5
SLIDE 5

ViewGroups

slide-6
SLIDE 6

RelativeLayout ConstraintLayout LinearLayout FrameLayout Complex Simple

slide-7
SLIDE 7

RelativeLayout / ConstraintLayout

  • Position views relative to each other
  • RelativeLayout: Slow
  • ConstraintLayout: Alpha
slide-8
SLIDE 8

LinearLayout

  • Stack views vertically/horizontally
  • Weight distribution
slide-9
SLIDE 9

But I <3 RelativeLayout

  • LinearLayout == sometimes slow
  • RelativeLayout == always slow
  • ConstraintLayout == savior
  • Profile!
slide-10
SLIDE 10

FrameLayout

  • Positioning based on parent bounds
  • Overlapping Views
  • Clickable item backgrounds
  • Toggle container
slide-11
SLIDE 11
slide-12
SLIDE 12
slide-13
SLIDE 13

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="horizontal"
 >
 
 <include
 layout="@layout/avatar_view"
 android:layout_width="48dp"
 android:layout_height="48dp"
 />
 
 <!-- Rest of layout here... -->
 </LinearLayout>

slide-14
SLIDE 14

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="horizontal"
 >
 
 <include
 layout="@layout/avatar_view"
 android:layout_width="48dp"
 android:layout_height="48dp"
 />
 
 <!-- Rest of layout here... -->
 </LinearLayout>

slide-15
SLIDE 15

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="horizontal"
 >
 
 <com.trello.view.AvatarView
 android:id="@+id/avatar_view"
 android:layout_width="48dp"
 android:layout_height="48dp"
 />
 
 <!-- Rest of layout here... -->
 </LinearLayout>

slide-16
SLIDE 16

public class AvatarView extends FrameLayout {
 
 ImageView icon;
 TextView initials;
 
 public AvatarView(Context context, AttributeSet attrs) {
 super(context, attrs);
 
 LayoutInflater.from(context).inflate(R.layout.view_avatar, this);
 
 icon = (ImageView) findViewById(R.id.icon);
 initials = (TextView) findViewById(R.id.initials);
 }
 
 public void bind(Member member) {
 // ...Load icon into ImageView...
 // OR
 // ...Setup initials in TextView...
 }
 }

slide-17
SLIDE 17

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 >
 
 <TextView
 android:id="@+id/initials"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 />
 
 <ImageView
 android:id="@+id/icon"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 />
 
 </FrameLayout>

slide-18
SLIDE 18

AvatarView (FrameLayout) FrameLayout ImageView TextView

slide-19
SLIDE 19

public class AvatarView extends FrameLayout {
 
 ImageView icon;
 TextView initials;
 
 public AvatarView(Context context, AttributeSet attrs) {
 super(context, attrs);
 
 LayoutInflater.from(context).inflate(R.layout.view_avatar, this);
 
 icon = (TextView) findViewById(R.id.icon);
 initials = (TextView) findViewById(R.id.initials);
 }
 
 public void bind(Member member) {
 // ...Load icon into ImageView...
 // OR
 // ...Setup initials in TextView...
 }
 }

slide-20
SLIDE 20

<merge xmlns:android="http://schemas.android.com/apk/res/android"
 >
 
 <TextView
 android:id="@+id/initials"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 />
 
 <ImageView
 android:id="@+id/icon"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 />
 
 </merge>

slide-21
SLIDE 21

AvatarView (Framelayout) ImageView TextView

slide-22
SLIDE 22

Custom Drawing

slide-23
SLIDE 23

Step #1: onMeasure()

(…sometimes you can skip this…)

slide-24
SLIDE 24
  • nMeasure()
  • onMeasure() signature

void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

  • measureSpec - packed integer

int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);

slide-25
SLIDE 25

MeasureSpec

  • EXACTLY - Must be that size
  • AT_MOST - Maximum width
  • UNDEFINED - Ideal width
slide-26
SLIDE 26
  • nMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int width; if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { int desiredWidth = 500; // Whatever calculation you want if (widthMode == MeasureSpec.AT_MOST) { width = Math.min(desiredWidth, widthSize); } else { width = desiredWidth; } } // ...to be continued... }

slide-27
SLIDE 27
  • nMeasure()

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width; int height; // ...Calculate width and height... setMeasuredDimension(width, height); }

slide-28
SLIDE 28

Step #2: onDraw()

(this is up to you)

slide-29
SLIDE 29

Custom Drawables

  • onMeasure() -> getIntrinsicHeight() / getIntrinsicWidth()
  • onDraw() -> draw()
  • https://speakerdeck.com/cyrilmottier/mastering-android-drawables
slide-30
SLIDE 30

Styles

slide-31
SLIDE 31

Styles

  • No style

<View android:background=“#FF0000” />

  • Style

<!--- some_layout.xml --> <View style="@style/MyStyle" /> <!--- styles.xml -->
 <style name="MyStyle">
 <item name="android:background">#FF0000</item>
 </style>

slide-32
SLIDE 32

Efficient

  • Semantically identical Views
  • All styled Views should change at
  • nce
slide-33
SLIDE 33

Not Efficient

  • Single-use styles
  • Coincidentally using the same attributes

<TextView
 android:id="@+id/title"
 android:textColor="@color/blue_200"
 android:textColorHint=“@color/grey_500" />
 
 <TextView
 android:id="@+id/body"
 android:textColor="@color/blue_200"
 android:textColorHint=“@color/grey_500" />

slide-34
SLIDE 34

Not Efficient

  • Single-use styles
  • Coincidentally using the same attributes

<TextView
 android:id="@+id/title"
 android:textColor="@color/blue_200"
 android:textColorHint=“@color/grey_500" />
 
 <TextView
 android:id="@+id/body"
 android:textColor="@color/blue_200"
 android:textColorHint=“@color/grey_500" />

slide-35
SLIDE 35

Not Efficient

  • Single-use styles
  • Coincidentally using the same attributes

<TextView
 android:id="@+id/title"
 android:textColor="@color/blue_200"
 android:textColorHint=“@color/grey_500" />
 
 <TextView
 android:id="@+id/body"
 android:textColor="@color/blue_200"
 android:textColorHint=“@color/grey_500" />

slide-36
SLIDE 36

static final int NUM_COLUMNS = 3;
 
 static final int NUM_RETRIES = 3; static final int NUM_THREE = 3;

slide-37
SLIDE 37

// static final int NUM_COLUMNS = 3;
 
 // static final int NUM_RETRIES = 3;
 static final int NUM_THREE = 3;

slide-38
SLIDE 38

Themes

slide-39
SLIDE 39

Themes

  • Affect multiple Views at once
  • Default styles
  • Configure system-created Views
slide-40
SLIDE 40
  • Application

<application
 android:theme="@style/Theme.AppCompat">

  • Activity

<activity
 android:theme=“@style/Theme.AppCompat.Light”>

  • View

<Toolbar
 android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
 app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
 />

slide-41
SLIDE 41

AppCompat

  • Material on all devices
  • Baseline themes/styles
  • Enables View theming pre-Lollipop
slide-42
SLIDE 42

<style name="ColorTheme" parent="Theme.AppCompat">
 <item name="colorPrimary">#F00</item>
 <item name="colorPrimaryDark">#0F0</item>
 <item name="colorControlNormal">#00F</item>
 </style>

slide-43
SLIDE 43

<style name="AppTheme" parent="Theme.AppCompat">
 <item name="buttonStyle">@style/MyButton</item>
 <item name="android:spinnerItemStyle">@style/MySpinnerItem</item>
 
 <item name="android:textAppearance">@style/MyText</item>
 <item name="android:textAppearanceInverse">@style/MyTextInverse</item>
 </style>

slide-44
SLIDE 44

<style name="AttrTheme" parent="Theme.AppCompat">
 <item name="selectableItemBackground">@drawable/bg</item>
 </style> <!-- some_layout.xml -->
 <Button android:background="?attr/selectableItemBackground" />

slide-45
SLIDE 45

Resources

slide-46
SLIDE 46

v24 port xxxhdpi w411dp h731dp en_US

slide-47
SLIDE 47
slide-48
SLIDE 48
slide-49
SLIDE 49
slide-50
SLIDE 50
slide-51
SLIDE 51

Resource Qualifier System

  • Define alternative resources for device configurations
  • Android automatically picks correct resource
slide-52
SLIDE 52
slide-53
SLIDE 53

values-sw600dp-port values-port values-sw600dp values not sw600dp or portrait not sw600dp not portrait

slide-54
SLIDE 54

Resources as code

  • Resource == parameter
  • Parameter <-- device configuration
slide-55
SLIDE 55

int square() {
 return 8 * 8;
 }
 int squareLarge() {
 return 16 * 16;
 } int square(int num) {
 return num * num;
 }

VS

slide-56
SLIDE 56

getResources().getBoolean(R.bool.is_portrait) <bool name="is_portrait">false</bool> <bool name="is_portrait">true</bool>

slide-57
SLIDE 57

setContentView(R.layout.activity_main)

slide-58
SLIDE 58

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 >
 
 <!-- ... -->
 
 <include layout="@layout/some_include" />
 
 <!-- ... -->
 
 </LinearLayout>

slide-59
SLIDE 59

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 >
 
 <!-- ... -->
 
 <include layout="@layout/some_include" />
 
 <!-- ... -->
 
 </LinearLayout>

slide-60
SLIDE 60

<TextView
 android:layout_width="wrap_content"
 android:layout_height="match_parent"
 android:textSize="24sp"
 android:textColor="#FF00FF"
 /> <TextView
 android:layout_width="wrap_content"
 android:layout_height="match_parent"
 android:textSize="16sp"
 android:textColor="#FF00FF"
 />

slide-61
SLIDE 61

<TextView
 android:layout_width="wrap_content"
 android:layout_height="match_parent"
 android:textSize="@dimen/welcome_text_size"
 android:textColor="#FF00FF"
 /> <dimen name="welcome_text_size">24sp</dimen> <dimen name="welcome_text_size">16sp</dimen>

slide-62
SLIDE 62

<style name="WelcomeText" parent="TextAppearance.AppCompat">
 <item name="android:textSize">24sp</item> <item name="android:textColor">#FF00FF</item>
 </style> <style name="WelcomeText" parent="TextAppearance.AppCompat">
 <item name="android:textSize">16sp</item> <item name="android:textColor">#FF00FF</item>
 </style>

slide-63
SLIDE 63

<style name="WelcomeText" parent="TextAppearance.AppCompat">
 <item name="android:textSize">@dimen/welcome_text_size</item>
 <item name="android:textColor">#FF00FF</item>
 </style>

<dimen name="welcome_text_size">24sp</dimen> <dimen name="welcome_text_size">16sp</dimen>

slide-64
SLIDE 64

activity_main.xml TextView style=@style/WelcomeText some_include.xml some_include.xml portrait default textSize=16sp textSize=24sp sw600dp default

slide-65
SLIDE 65

Drawables

slide-66
SLIDE 66

Image: https://www.flickr.com/photos/ufv/8042499199

Design “I need this”

slide-67
SLIDE 67

Design “Not enough” “I tweaked the color,
 here’s those assets again.”

slide-68
SLIDE 68

Assets as code

slide-69
SLIDE 69

Drawable XML

  • Built into Android
  • Simple shapes
  • State selectors
  • Layer lists
slide-70
SLIDE 70
slide-71
SLIDE 71

Button Outline

<?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle"
 >
 
 <solid android:color="@color/transparent" />
 
 <stroke
 android:width="1dp"
 android:color="@color/white"
 />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 
 </shape>

slide-72
SLIDE 72

Button Outline

<?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle"
 >
 
 <solid android:color="@color/transparent" />
 
 <stroke
 android:width="1dp"
 android:color="@color/white"
 />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 
 </shape>

slide-73
SLIDE 73

Button Outline

<?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle"
 >
 
 <solid android:color="@color/transparent" />
 
 <stroke
 android:width="1dp"
 android:color="@color/white"
 />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 
 </shape>

slide-74
SLIDE 74

Button Outline

<?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle"
 >
 
 <solid android:color="@color/transparent" />
 
 <stroke
 android:width="1dp"
 android:color="@color/white"
 />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 
 </shape>

slide-75
SLIDE 75

Button Outline

<?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle"
 >
 
 <solid android:color="@color/transparent" />
 
 <stroke
 android:width="1dp"
 android:color="@color/white"
 />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 
 </shape>

slide-76
SLIDE 76

Button Selector

<?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
 <item>
 <selector>
 <item android:state_pressed="true">
 <shape android:shape="rectangle">
 <solid android:color="@color/blue_200" />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 <item android:drawable="@color/transparent" />
 </selector>
 </item>
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 </layer-list>

slide-77
SLIDE 77

Button Selector

<?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
 <item>
 <selector>
 <item android:state_pressed="true">
 <shape android:shape="rectangle">
 <solid android:color="@color/blue_200" />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 <item android:drawable="@color/transparent" />
 </selector>
 </item>
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 </layer-list>

slide-78
SLIDE 78

Button Selector

<?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
 <item>
 <selector>
 <item android:state_pressed="true">
 <shape android:shape="rectangle">
 <solid android:color="@color/blue_200" />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 <item android:drawable="@color/transparent" />
 </selector>
 </item>
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 </layer-list>

slide-79
SLIDE 79

Button Selector

<?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
 <item>
 <selector>
 <item android:state_pressed="true">
 <shape android:shape="rectangle">
 <solid android:color="@color/blue_200" />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 <item android:drawable="@color/transparent" />
 </selector>
 </item>
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 </layer-list>

slide-80
SLIDE 80

Button Selector

<?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
 <item>
 <selector>
 <item android:state_pressed="true">
 <shape android:shape="rectangle">
 <solid android:color="@color/blue_200" />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 <item android:drawable="@color/transparent" />
 </selector>
 </item>
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 </layer-list>

slide-81
SLIDE 81

Button Selector

<?xml version="1.0" encoding="utf-8"?>
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
 
 <item>
 <selector>
 <item android:state_pressed="true">
 <shape android:shape="rectangle">
 <solid android:color="@color/blue_200" />
 
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 <item android:drawable="@color/transparent" />
 </selector>
 </item>
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 </layer-list>

slide-82
SLIDE 82

<?xml version="1.0" encoding="utf-8"?>
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
 android:color="@color/blue_200"
 >
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 <item android:id="@android:id/mask">
 <shape android:shape="rectangle">
 <solid android:color="@color/white" />
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 </ripple>

Button Selector (v21)

slide-83
SLIDE 83

<?xml version="1.0" encoding="utf-8"?>
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
 android:color="@color/blue_200"
 >
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 <item android:id="@android:id/mask">
 <shape android:shape="rectangle">
 <solid android:color="@color/white" />
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 </ripple>

Button Selector (v21)

slide-84
SLIDE 84

<?xml version="1.0" encoding="utf-8"?>
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
 android:color="@color/blue_200"
 >
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 <item android:id="@android:id/mask">
 <shape android:shape="rectangle">
 <solid android:color="@color/white" />
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 </ripple>

Button Selector (v21)

slide-85
SLIDE 85

<?xml version="1.0" encoding="utf-8"?>
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
 android:color="@color/blue_200"
 >
 
 <item android:drawable="@drawable/btn_welcome_outline" />
 
 <item android:id="@android:id/mask">
 <shape android:shape="rectangle">
 <solid android:color="@color/white" />
 <corners android:radius="@dimen/corner_radius_tiny" />
 </shape>
 </item>
 
 </ripple>

Button Selector (v21)

slide-86
SLIDE 86

Button Versions

button_welcome_outline.xml button_welcome.xml button_welcome.xml (with ripple)

slide-87
SLIDE 87

Vector drawables

slide-88
SLIDE 88

VectorDrawable != SVG

Design

:(

slide-89
SLIDE 89

SVG -> VectorDrawable

  • Android Studio: New Vector Asset
  • Victor: github.com/trello/victor

android {
 sourceSets {
 main {
 svg.srcDir 'src/main/svg'
 }
 }
 }

slide-90
SLIDE 90

vs

slide-91
SLIDE 91
slide-92
SLIDE 92

Tinting Images

  • XML
  • Simple

drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);

  • Comprehensive

Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
 DrawableCompat.setTint(wrappedDrawable, color);

N

  • t

b a c k w a r d s c

  • m

p a t i b l e

slide-93
SLIDE 93
slide-94
SLIDE 94

Thank You!

  • @danlew42
  • danlew.net
  • speakerdeck.com/dlew