-
Notifications
You must be signed in to change notification settings - Fork 91
Custom Views
Paris was designed with custom views in mind, and this is where it truly shines.
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.
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: TextView …
init {
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:
<MyHeader
…
app: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.
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.
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.