anvil-ui/anvil

Setting styles

Closed this issue · 11 comments

corbt commented

Really happy with how Anvil is working, and I'm writing new functionality for my app using it.

I'm having trouble assigning styles to elements created by anvil. For example, I want to add a shadow to the text on a button. Using standard layouts I can do something like the following:

<style name="ButtonText">
        <item name="android:shadowColor">#000000</item>
        <item name="android:shadowDx">1</item>
        <item name="android:shadowDy">1</item>
        <item name="android:shadowRadius">2</item>
    </style>

and then set the style from XML. However, it doesn't look like there is a way to set the style on an element programmatically, and thus no way to do it from anvil. Am I missing something?

Glad to hear you're making progress with an app using Anvil!

As for the styles - I'm open to any suggestions. Meanwhile I'm using good old functions to style views. In your case (using java) shadows can be set in java using setShadowLayer(r, dx, dy, color) view method, or with Anvil:

public AttrNode buttonTextStyle() {
  return attrs(
    shadowLayer(2, 1, 1, Color.BLACK)
    // maybe some other style attributes go here as well
  );
}

v(Button.class,
  buttonTextStyle());

Does it make sense? In my experience every XML attributes corresponds to some java method of a certain view class. We're just too much used to the (misleading) names of the XML attributes, that it may be really hard to find a matching view method.

Oops. I see what you mean. There is no generated shadowLayer() method because it takes 4 arguments and generated methods use only one (since it's a typical case to set just one attribute value). Here I see three ways:

  1. Write your own AttrNode generator in your app (see examples in the Attrs.java, you may put 4 parameters into a List, so that equals() will be true iff all 4 parameters are equal). I did similar for admob views and my own custom views.
  2. This may be a common case, so your AttrNode generator can be added to trikita.anvil.v15.V15Attrs, which is hand-written parent of the trikita.anvil.v15.Attrs class
  3. If there is a lot of setters with more than one parameter - it would make sense to fix gen.gradle to generate methods in Attrs class for those setters as well.

One more option would be to add some style() generator to Anvil that will add some style ID to the view properties without applying it. Then fix Anvil renderer to look for that style ID and use ContextThemeWrapper if ID is not zero. This will bring the support of styles. I personally find styles XMLs very ugly and of course prefer pure java styling, but Anvil users might think the opposite. Also this solution requires hacking renderer to look into AttrNodes, which are treated as black box right now. So it would be another leaking abstraction forced by the broken android API design.

corbt commented

Good suggestions. I agree that the Android API (including the divide between styles vs themes vs attributes... wat) is very painful to work with -- that's why I'm using Anvil despite its early state. :) If I can do everything I need to through the Anvil API without having to use styles that would be preferable from my perspective.

I ended up going with option (1) because it seemed the easiest to implement. I can add it to V15Attrs or BaseAttrs in a PR if you think it would be generally useful. And if we run into this more frequently (3) might make sense -- I don't have a good idea of how common these multi-method setters are yet, since I've mainly used the xml layouts for styling and layout up to now.

    public static AttrNode shadowLayer(final float radius, final float dx, final float dy, final int color) {
        final List<Object> params = new ArrayList<Object>() {{
            add(radius); add(dx); add(dy); add(color);
        }};

        return new SimpleAttrNode(params) {
            public void apply(View v) {
                if(v instanceof TextView)
                    ((TextView) v).setShadowLayer(radius, dx, dy, color);
            }
        };
    }

I scanned the android.jar in API levels 10 and 15 - it looks like there is only about 40 setters with more than one parameter. In API level 21 there's ~50 of them. However, most of them are the ones I never heard of:

API level 15 (in API level 10 there's even less):

setAppWidget
setChildIndicatorBounds
setColorFilter
setColumnCollapsed
setColumnShrinkable
setColumnStretchable
setCompoundDrawables
setCompoundDrawablesWithIntrinsicBounds
setDate
setEGLConfigChooser
setError
setHttpAuthUsernamePassword
setImageState
setImeActionLabel
setInAnimation
setIndicatorBounds
setInterpolator
setItemChecked
setLayerType
setLineSpacing
setOutAnimation
setPadding
setParentTitle
setPopupOffset
setPrevNextListeners
setQuery
setScrollIndicators
setSelectedChild
setSelection
setSelectionFromTop
setShadowLayer
setSwitchTextAppearance
setSwitchTypeface
setTag
setText
setTextAppearance
setTextKeepState
setTextSize
setTitle
setTypeface

Most of them are just advanced versions of single parameter methods like setTag or setTypeface.
Probably they should be added manually on-demand, like setShadowLayer since most of them will never be used anyway.

corbt commented

Yeah adding them on demand is probably fine, there aren't many of those that I know what do that don't have single-parameter equivalents.

I've added the suggested shadowLayer to Anvil's BaseAttrs, so I'm closing this.

I've added the suggested shadowLayer to Anvil's BaseAttrs, so I'm closing this.

I think the bigger issue is that I currently have an app with a defined theme and style structure. I don't want to be able to just add a single attribute such as match parent but instead add a style for example "MyTextViewStyle"

Here's some thoughts about how one can organize view styles in Anvil: http://zserge.com/blog/anvil-howto-style-views.html

Is there a way to easily translate the android predefined themes to anvil? For example Widget.AppCompat.Button.Borderless and other Material elements. It's a pain having to hunt on all the sub definitions of the theme to recreate them in anvil.