Skip to content

Custom Views

Nathanael Silverman edited this page Oct 24, 2018 · 3 revisions

Paris was designed with custom views in mind, and this is where it truly shines.

Supporting Custom Attributes

In addition to supporting the application of custom attributes, Paris helps get rid of the boilerplate associated with adding custom attributes to your views in the first place.

For more, see Custom View Attributes.

Styling Subviews

Sometimes your custom views may have subviews which you'd like to make individually styleable. For example, a custom view may contain both a title and subtitle, where each can be restyled separately. Paris has you covered.

First declare custom attributes for the substyles you'd like to support:

<declare-styleable name="MyHeader">
    <attr name="titleStyle" format="reference" />
    <attr name="subtitleStyle" format="reference" />
    …
</declare-styleable>

Then annotate your custom view's subviews with @StyleableChild and the corresponding attribute indexes:

@Styleable("MyHeader")
class MyHeader(…) : ViewGroup(…) {

    @StyleableChild(R.styleable.MyHeader_titleStyle)
    internal val title: TextView …
    
    @StyleableChild(R.styleable.MyHeader_subtitleStyle)
    internal val subtitle: TextViewinit {
        style(attrs)
    }
}
Click to see the example in Java.
@Styleable("MyHeader")
public class MyHeader extends ViewGroup {

    @StyleableChild(R.styleable.MyHeader_titleStyle)
    TextView title;
    
    @StyleableChild(R.styleable.MyHeader_subtitleStyle)
    TextView subtitle;
    
    …
    // Make sure to call Paris.style(this).apply(attrs) during initialization.
}

That's it!

You can now use these attributes in XML and Paris will automatically apply the specified styles to the subviews:

<MyHeaderapp:titleStyle="@style/Title2"
    app:subtitleStyle="@style/Regular" />

Or programmatically:

myHeader.style {
    // Defined in XML.
    titleStyle(R.style.Title2)
    // Defined programmatically.
    subtitleStyle {
        textColorRes(R.color.text_color_regular)
        textSizeRes(R.dimen.text_size_regular)
    }
}
Click to see the example in Java.
Paris.styleBuilder(myHeader)
        // Defined in XML.
        .titleStyle(R.style.Title2)
        // Defined programmatically.
        .subtitleStyle((builder) -> builder
                .textColorRes(R.color.text_color_regular)
                .textSizeRes(R.dimen.text_size_regular))
        .apply();

Attention: Extension functions like titleStyle and subtitleStyle are generated during compilation by the Paris annotation processor. When new @StyleableChild annotations are added, the project must be (re)compiled once for the related functions to become available.

Combining Substyles

Subview styles, or substyles, are always additive such that:

myHeader.style {
    subtitleStyle {
        textColorRes(R.color.text_color_regular)
    }
    subtitleStyle {
        textSizeRes(R.dimen.text_size_regular)
    }
}
Click to see the example in Java.
Paris.styleBuilder(myHeader)
        .subtitleStyle((builder) ->
                builder.textColorRes(R.color.text_color_regular))
        .subtitleStyle((builder) ->
                builder.textSizeRes(R.dimen.text_size_regular))
        .apply();

Is equivalent to:

myHeader.style {
    subtitleStyle {
        textColorRes(R.color.text_color_regular)
        textSizeRes(R.dimen.text_size_regular)
    }
}
Click to see the example in Java.
Paris.styleBuilder(myHeader)
        .subtitleStyle((builder) -> builder
                .textColorRes(R.color.text_color_regular)
                .textSizeRes(R.dimen.text_size_regular))
        .apply();

Which is particularly useful when combining with one or more XML-defined styles.

Linking Styles to Custom Views

XML resource files can get big and chaotic. For styles it can become hard to find which are available for any given view type. To remedy this, Paris lets your custom views explicitly declare their supported styles:

@Styleable
class MyView(…) : View(…) {

    companion object {
        // For styles defined in XML.
        @Style
        val RED_STYLE = R.style.MyView_Red

        // For styles defined programmatically.
        @Style
        val GREEN_STYLE = myViewStyle {
            background(R.color.green)
        }
    }
}
Click to see the example in Java.
@Styleable
public class MyView extends View {

    // For styles defined in XML.
    @Style
    static final int RED_STYLE = R.style.MyView_Red;

    // For styles defined programmatically.
    @Style
    static void greenStyle(MyViewStyleApplier.StyleBuilder builder) {
        builder.background(R.color.green);
    }
}

In Kotlin, the annotated properties must be public (default) or internal. In Java, the annotated fields and methods must be public or package-private.

Now when styling a view of type MyView you'll have access to helper functions for each of those styles:

// These are generated based on the name of the method.
myView.style { addRed() } // Equivalent to style(R.style.MyView_Red)
// And
myView.style {
    addRed() // Equivalent to add(R.style.MyView_Red)
    …
}

// These are generated based on the name of the method.
myView.style { addGreen() }
// And
myView.style {
    addGreen() // Equivalent to background(R.color.green)
    …
}
Click to see the example in Java.
// These are generated based on the name of the method.
Paris.style(myView).applyRed(); // Equivalent to apply(R.style.MyView_Red)
// And
Paris.styleBuilder(myView)
        .addRed() // Equivalent to add(R.style.MyView_Red)
        …
        .apply();

// These are generated based on the name of the method.
Paris.style(myView).applyGreen();
// And
Paris.styleBuilder(myView)
        .addGreen() // Equivalent to background(R.color.green)
        …
        .apply();

Attention: Extension functions like addRed and addGreen are generated during compilation by the Paris annotation processor. When new @Style annotations are added, the project must be (re)compiled once for the related functions to become available.

This doesn't prevent the application of other, non-linked styles.

Linking styles is particularly useful when recycling views across styles, see View Recycling.