Building Layouts for Android 🤖

Hero image for Building Layouts for Android 🤖

Basic rules

View binding

View binding

Read more about the pros and cons of Android view access strategies here

Kotlin Android Extensions (Synthetics)

Synthetics allow recovering views from Activities, Fragments, and Views in a seamless way. The plugin will generate some extra code that will allow you to access views in the layout XML, just as if they were properties with the name of the id you used in the layout definition. It also builds a local view cache. Learn more

import kotlinx.android.synthetic.main.activity_main.*

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main) 
    
    // tvWelcomeMessage is the id of the UI component
    tvWelcomeMessage.text = "Hello Kotlin!"
}

View layouts

There are a few commonly used layouts which are the LinearLayout, RelativeLayout, and FrameLayout. Most of what can be done in LinearLayout and RelativeLayout can now, and should, be done with a new layout system called ConstraintLayout.

It is important to understand the class hierarchy of these View Layouts. Each of them subclass ViewGroup, which itself subclasses View. ViewGroup also contains the nested static class LayoutParams which is used for creating or editing layouts in code. Keep in mind that each subclass of ViewGroup, such as LinearLayout, has its own nested static class LayoutParams which is a subclass of ViewGroup.LayoutParams. Learn more

Margin / padding

Margin and padding values for views allow us to position and space elements in a layout. Learn more

  • Margin defines the amount of space around the outside of a view
  • Padding defines the amount of space around the contents or children of a view.
<LinearLayout>
   <TextView android:layout_margin="5dp" android:padding="5dp">
   <Button layout_marginBottom="5dp">
</LinearLayout>

Checking the layout bounds? Let us go to the Android Developer Options in your phone’s settings to enable Show layout bounds.

View binding

Resources naming

Basic principle

Basic principle

  • <WHAT> indicates what the resource actually represents; often a standard Android view class. Limited options per resource type.
  • <WHERE> describes where it logically belongs in the app. Resources used in multiple screens use general, all others use the custom part of the Android view subclass they are in.
  • <DESCRIPTION> differentiates multiple elements in one screen.
  • <SIZE> either a precise size or size bucket; optionally used for drawables and dimensions.

Layouts

Layouts are relatively simple, as there are usually only a few layouts per screen.

Where <WHAT> is one of the following:

  • activity_main: content view of the MainActivity
  • fragment_article_detail: view for the ArticleDetailFragment
  • view_menu: layout inflated by custom view class MenuView
  • item_article: list item in ArticleRecyclerView
  • layout_actionbar_backbutton: layout for an action bar with a back button. That kind of layouts is usually used for <include> and <merge> attributes, and hence are reusable.

Strings

The <WHAT> part for Strings is irrelevant. So we use <WHERE> to indicate where the string will be used:

  • article_detail_title: title of ArticleDetailFragment
  • feedback_explanation: feedback explanation in FeedbackFragment
  • feedback_name_hint: hint of name field in FeedbackFragment
  • general_done: generic done string

<WHERE> obviously is the same for all resources in the same view.

Drawables

The <WHAT> part for Drawables is the type such as

  • ic for icon
  • bg for background
  • fg for foreground
  • placeholder for placeholder

<WHERE> is used to indicate where the drawable will be used:

Optionally you can add a <SIZE> argument, which can be an actual size 24dp or a size qualifier small.

  • placeholder_article_detail: placeholder in ArticleDetailFragment
  • bg_article_detail_banner: background for the banner in article detail
  • ic_general_info: generic info icon
  • ic_general_info_large: large version of generic info icon
  • ic_general_info_24dp: 24dp version of generic info icon

IDs

For IDs, <WHAT> is the class name of the xml element it belongs to. Next is the screen the ID is in, followed by an optional description to distinguish similar elements in one screen. However, because IDs are often referenced in the code, they follow the CamelCase naming convention.

  • tlMain -> TabLayout in MainActivity
  • ivMenuProfile -> profile image in custom MenuView
  • tvArticleDetailTitle -> title TextView in ArticleDetailFragment

Dimensions

Apps should only define a limited set of dimensions, which are constantly reused. This makes most dimensions general by default.

Note that this list only contains the most used <WHAT>s. Other dimensions qualifiers like: rotation, scale, etc. are usually only used in drawables and as such less reused.

  • toolbar_height: height of all toolbars
  • listitem_keyline_text: listitem text is aligned at this keyline
  • text_medium: medium size of all text
  • menu_icon_size: size of icons in menu
  • menu_profile_image_height: height of profile image in menu

XML Attribute Ordering

Where appropriate, XML attributes should appear in the following order:

* `style` attribute (if there is)
* `id` attribute
* `layout_*` attributes
* style attributes such as `gravity` or `textColor`
* value attributes such as `text` or `src`

Within each of these groups, the attributes should be ordered alphabetically.

Best practices

Drawable or Bitmap?

  • Bitmap actually refers to the images like webp, png, jpg, etc.

  • Drawable is something that can be drawn like a layout, vector, image, etc.

    To ensure bitmap images are fully compressed from the start, we should convert every image to a .webp file. We can achieve this by right-clicking our image file, to then execute “Convert to WebP..”. Make sure you set the encoding quality to 100%. By doing this we can reduce our image size to the fullest, while still maintaining the image quality.

    If you plan on reading an image as a bit stream in order to convert it to a bitmap, put your images in the res/raw/ folder instead, where they will not be optimized by the aapt tool during compile time.

  • Use shapes and selectors instead of images as much as possible.

    Make your project light weight by drawing basic shapes with XML instead of using images. Almost all basic shapes can be drawn with Shape Drawables. The best part about vectors is that they render neatly in various display densities. This further reduces APK size.

    Basic shapes and gradients can easily be drawn using the <shape /> tag without the use of images. The resulting shapes that are drawn are always sharp and do not need to be created for multiple densities.

    The <selector /> tag can be used to add different visual states (like pressed, disabled, checked) to Views.

    For small icons that need to be shown in different colors in your project screens, there is no need to duplicate images in all quality, you can use the same image and can apply the property android:tint=”@color/primary_color” This will change the color of image in a similar way to the ColorOverlay property in Photoshop.

  • Apply Nine-Patch Image for Stretchable backgrounds

    Sometimes you might need to use an image as background for content where the length of the content is dynamic (e.g. Chat Bubble, Custom Pop Up), you need to create a Nine-Patch image so that you can define the stretchable region inside that image. The Nine-Patch can be created with any photo editing tool or the SDK.

Layouts

  • Layout XMLs are code, organise them well

  • Reusable XML

    Write XML Layouts as reusable layouts, which will avoid duplicating the same layout with different names. Use the <include>, <fragment> and <merge> tags for achieving this.

    Sometimes, there are cases where you have a complex view that is only rarely used. For example, it could be a view that is only visible depending on a specific condition. In such a case, you can use a ViewStub element. It is a lightweight view with no dimensions and is cheap to inflate and cheap to leave in a view hierarchy.

    <ViewStub
        android:id="@+id/stub_import"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/progress_overlay"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />
    
  • Avoid Weight

    It is pretty easy to play around with LinearLayout and weight, but it makes your layout expensive performance wise. Sometimes you might need weight to achieve something, but dividing two components equally in a screen can achieve without the use of weight.

    Place the divider first in the center by applying the proper constraints on a ConstraintLayout as shown in the figure, and then insert another two buttons on the left and right respectively by applying the proper constraints.

  • Avoid Nested Layouts

    Nesting layouts to deeper level decreases your layout performance. Avoid nested layouts and nested weights.

    Many developers use nested structure with LinearLayout for setting the visibility dynamically. In LinearLayout, the components are arranged in a linear way, so hiding one component will not affect alignment of the rest of the components.

  • Improving and designing high-performance layouts

    Your choice of ViewGroup in your layout is important when it comes to performance; if the layout hierarchy is too complex, it can cause performance problems when running an Android app. Learn more

    Whenever possible, always favour the ConstraintLayout. As opposed to the other container layouts, the ConstraintLayout offers much-improved performance while still offering similar layout control. Prefer layouts with shallow and wide hierarchy, rather than narrow and deep hierarchy.

    Optimize your layouts by using the powerful tool that comes with SDK. Hierarchy Viewer allows you to inspect your layout component that helps you to debug the layout problems and can increase your layout performance by calculating the inflating time for your layout.

  • Support for screens with different form factors

    Since there are a wide range of devices running different OS versions and different screen sizes and densities, you need to consider all types of devices.

    Learn more about how to build a Responsive UI in Android here

Styling

  • Separate layout and styling elements

    Separate the layout and its styling elements, such as dimens, strings, colors. In this way, you can have the flexibility in your layout to work seamlessly on different screen sizes. You can then specify a new set of dimens for tablets by applying qualifiers.

  • Use styles to avoid duplicate attributes in layout XMLs

  • Use multiple style files to avoid a single huge one

  • Keep your colors.xml short and DRY, just define the palette

  • Also keep dimens.xml DRY, define generic constants

    When the only thing that changes within a color’s definition is the alpha value, the naming convention is then to append that value at the end of the color’s name, e.g.

        <color name="black_26">#42000000</color>
        <color name="black_40">#66000000</color>
        <color name="black_73">#BA000000</color>
    

    Cheat sheet: https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4

    Whenever we add a new color resource, we want to check inside our existing colors if it’s available already or not. This way we can improve reusability and reduce duplication. So, whenever two different color codes look similar to each other, there might be a chance that they’re actually one and the same. To confirm this, we make use of: https://www.htmlcsscolor.com/, e.g.

        <color name="black_nero">#1A1A1A</color>
        <color name="green_lime">#3AC538</color>
    

Others

  • Accessing Resources: Load app resources in code (via generated R class) and in XML (via @color, @drawable, @string, etc). Do NOT use global extensions or variables to repeat defining app resources for re-using.

    val Context.mediumPadding: Int
        get() = resources.getDimensionPixelSize(R.dimen.medium_padding)
    
    class SomeClass {
    
      private fun sampleMethod() {
        view.setPadding(context.mediumPadding, 0, 0, 0)
      }
    }
    
    class SomeClass {
    
      private val mediumPadding by lazy { resources.getDimensionPixelSize(R.dimen.medium_padding) }
    
      private fun sampleMethodA() {
        val largePadding = context.resources.getDimensionPixelSize(R.dimen.large_padding)
        viewA.setPadding(mediumPadding, largePadding, 0, 0)
      }
    
      private fun sampleMethodB() {
        viewB.setPadding(mediumPadding, 0, 0, 0)
      }
    }
    
  • Run the lint tool on layout files to search for possible view hierarchy optimisations.

  • Reuse code in different projects by generic naming

    Defining styles.xml and dimens.xml as shown in this figure will allow you to reuse the code in different projects. The colors and text sizes here are defined in a generic manner.

  • Third-party libraries

    There are many third-party libraries for Android to build elegant layouts. Several of them are must have libraries that are extremely popular and are often used in almost any Android project. Each has different purposes but all of them make life as a developer much more pleasant. The major libraries are listed below in a few categories. Learn more

    Paris lets you define and apply styles programmatically to Android views, including custom attributes.