Skip to content

Grouping Models

Eli Hart edited this page Sep 9, 2020 · 10 revisions

EpoxyModelGroup is an EpoxyModel that groups other models. It allows you to arbitrarily combine other models into whatever layout configurations you like. This is useful if you want to arrange models in certain ways, or add functionality to an existing model via composition. In general you should only use EpoxyModelGroup when you need to position models relative to each other in special ways - do not use it unless adding models directly to an EpoxyController does not allow the layout you need.

This is NOT a replacement for creating a custom view. Models in a group should be independent and not rely on each other's state.

Usage

The model group is defined by two things:

  1. A list of models to use in the group. Each submodel should have a unique, stable id assigned to it for diffing to work correctly.
  2. A layout resource that defines the ViewGroup to add the models to, and how to arrange the models.

EpoxyModelGroup can be subclassed, or you can create a new instance of EpoxyModelGroup directly and pass the models and layout resource to its constructor.

The layout must have a viewgroup as its top level view; it determines how the view of each model is laid out. There are two ways to specify this:

  1. Leave the viewgroup empty. The view for each model will be inflated and added in order. This works fine if you don't need to include any other views, your model views don't need their layout params changed, and your views don't need ids (eg for saving state).

For example, if the layout looked like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"/>

Then any models in the list would have their views created and added in order. The result would be a horizontal linear layout containing all the models' views.

Alternatively you can have nested view groups, with the innermost viewgroup given the id "epoxy_model_group_child_container" to mark it as the viewgroup that should have the model views added to it. The viewgroup marked with this id should be empty. This allows you to nest viewgroups, such as a LinearLayout inside of a CardView.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/epoxy_model_group_child_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

</android.support.v7.widget.CardView>
  1. Include a ViewStub for each of the models in the list. There should be at least as many view stubs as models. Extra stubs will be ignored. Each model will be inflated into a view stub in order of the view stub's position in the view group. That is, the view group's children will be iterated through in order. The first view stub found will be used for the first model in the models list, the second view stub will be used for the second model, and so on. A depth first recursive search through nested viewgroups is done to find these viewstubs.

For example we could use a more complicated layout like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:animateLayoutChanges="true">

       // The first model's view will be inserted here
        <ViewStub
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top|left" />
       // The layout parameters set on this view stub 
       // will be transferred to the model's view by default

        // The second model's view will be inserted here
        <ViewStub
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top|right" />


    </FrameLayout>

    // The third model's view will be inserted here
    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:inflatedId="@+id/recycler_view" />
   // We set an inflatedId here so the model's view will 
   // have this id. This allows the model's view to have its 
   // state saved (eg scroll position for this nested RecyclerView) 

</LinearLayout>

The layout can be of any ViewGroup subclass, and can have arbitrary other child views besides the view stubs. It can arrange the views and view stubs however is needed.

Any layout param options set on the view stubs will be transferred to the corresponding model view by default. If you want a model to keep the layout params from it's own view you can override useViewStubLayoutParams(EpoxyModel, int)

Kotlin DSL

Available since version 4.0.0

A Kotlin function group is provided as an extension to the interface ModelCollector, which allows you to easily define model groups in your EpoxyController buildModels code.

For example:

group {
    id("my model  group")
    layout(R.layout.vertical_linear_group) // You must provide your own layout file here

    coloredSquareView {
        id("red square")
        color(Color.RED)
    }

    coloredSquareView {
        id("blue square")
        color(Color.BLUE)
    }
}

This function creates a GroupModel_, provides it as a receiver to a lambda where you can add models to it via the normal Epoxy model building Kotlin DSL syntax, and then adds the group to the controller.

View IDs

If you want to override the id used for a model's view you can set ViewStub#setInflatedId(int) via xml. That id will be transferred over to the view taking that stub's place. This is necessary if you want your model to save view state, since without this the model's view won't have an id to associate the saved state with.

By default an instance of EpoxyModelGroup inherits the same id as the first model in the list. You can manually override the group's id if needed.

Accessing The Root View

You can access the parent root view that holds all of the model views via the Holder param in bind/unbind. Use Holder#getRootView() to access it.

Visibility Events

Note that if you use visibility callbacks, models within an EpoxyModelGroup do not currently support registering visibility listeners on them - they will not get any visibility callbacks. Only the top level group model will receive visibility callbacks