Set style? #16

Closed
Leland-Takamine opened this Issue Apr 9, 2015 · 13 comments

Comments

Projects
None yet
@Leland-Takamine

I can't seem to find a good way of setting a style for a view. For example if I wanted a large ProgressBar in XML I would write:

<ProgressBar
        style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Large"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

How do you do this in anko?

@yanex

This comment has been minimized.

Show comment
Hide comment
@yanex

yanex Apr 29, 2015

Contributor

Anko has a style() function, though traditional Android XML styles are unsupported now.
Android SDK does not provide a good way to set View styles in code out of the box, and we are trying to find a way to support it.

Contributor

yanex commented Apr 29, 2015

Anko has a style() function, though traditional Android XML styles are unsupported now.
Android SDK does not provide a good way to set View styles in code out of the box, and we are trying to find a way to support it.

@sfunke

This comment has been minimized.

Show comment
Hide comment
@sfunke

sfunke Jan 25, 2016

Inspired by the default Anko textView extension methods, and after doing some research on how to set a theme to a view by code, I came up with the following extension methods, which work very well so far (can be applied to any view class):

public inline fun ViewManager.textView(text: CharSequence?, styleRes: Int = 0, init: TextView.() -> Unit): TextView {
    return ankoView({ if (styleRes == 0) TextView(it) else TextView(ContextThemeWrapper(it, styleRes), null, 0) }) {
        init()
        setText(text)
    }
}

public fun ViewManager.textView(text: CharSequence?, styleRes: Int = 0): TextView = textView(text, styleRes) {}

Key is, you have to use ContextThemeWrapper, and the 3 argument constructor of the passed view, see this link: http://stackoverflow.com/a/28613069/1128600

That allows to pass an XML style, and have it fully applied to the newly created view (not comparable to the crappy setTextAppearance method)

@yanex Would it make sense to consider this for potential adoption into the standard library?

sfunke commented Jan 25, 2016

Inspired by the default Anko textView extension methods, and after doing some research on how to set a theme to a view by code, I came up with the following extension methods, which work very well so far (can be applied to any view class):

public inline fun ViewManager.textView(text: CharSequence?, styleRes: Int = 0, init: TextView.() -> Unit): TextView {
    return ankoView({ if (styleRes == 0) TextView(it) else TextView(ContextThemeWrapper(it, styleRes), null, 0) }) {
        init()
        setText(text)
    }
}

public fun ViewManager.textView(text: CharSequence?, styleRes: Int = 0): TextView = textView(text, styleRes) {}

Key is, you have to use ContextThemeWrapper, and the 3 argument constructor of the passed view, see this link: http://stackoverflow.com/a/28613069/1128600

That allows to pass an XML style, and have it fully applied to the newly created view (not comparable to the crappy setTextAppearance method)

@yanex Would it make sense to consider this for potential adoption into the standard library?

@dennislysenko

This comment has been minimized.

Show comment
Hide comment
@dennislysenko

dennislysenko Mar 10, 2016

@sfunke, does this only work for styling the text in a TextView, or is it generalized to applying any style resource to any view (including things like padding, backgrounds, borders, etc.?)

@sfunke, does this only work for styling the text in a TextView, or is it generalized to applying any style resource to any view (including things like padding, backgrounds, borders, etc.?)

@dennislysenko

This comment has been minimized.

Show comment
Hide comment
@dennislysenko

dennislysenko Mar 10, 2016

Nevermind. I created this shim:

public inline fun ViewManager.styledButton(text: CharSequence?, styleRes: Int = 0, init: Button.() -> Unit): Button {
    return ankoView({ if (styleRes == 0) Button(it) else Button(ContextThemeWrapper(it, styleRes), null, 0) }) {
        init()
        setText(text)
    }
}

public fun ViewManager.styledButton(text: CharSequence?, styleRes: Int = 0): Button = styledButton(text, styleRes) {}

Works perfectly, I just call styledButton("text", R.style.MyStyle). Thanks @sfunke!

Nevermind. I created this shim:

public inline fun ViewManager.styledButton(text: CharSequence?, styleRes: Int = 0, init: Button.() -> Unit): Button {
    return ankoView({ if (styleRes == 0) Button(it) else Button(ContextThemeWrapper(it, styleRes), null, 0) }) {
        init()
        setText(text)
    }
}

public fun ViewManager.styledButton(text: CharSequence?, styleRes: Int = 0): Button = styledButton(text, styleRes) {}

Works perfectly, I just call styledButton("text", R.style.MyStyle). Thanks @sfunke!

@BennyWang

This comment has been minimized.

Show comment
Hide comment
@BennyWang

BennyWang Mar 10, 2016

@sfunke I think

public inline fun ViewManager.AnyView(theme: Int = 0, init: TextView.() -> Unit): AnyView

is enough.

@sfunke I think

public inline fun ViewManager.AnyView(theme: Int = 0, init: TextView.() -> Unit): AnyView

is enough.

@fboldog

This comment has been minimized.

Show comment
Hide comment
@fboldog

fboldog Mar 10, 2016

Contributor

#143
;)

Contributor

fboldog commented Mar 10, 2016

#143
;)

@BennyWang

This comment has been minimized.

Show comment
Hide comment

@yanex yanex added this to the Anko 0.9 milestone Mar 25, 2016

yanex added a commit that referenced this issue Apr 19, 2016

yanex added a commit that referenced this issue Apr 21, 2016

@yanex yanex added testing duplicate and removed open labels Apr 29, 2016

@yanex yanex closed this Apr 29, 2016

@simophin

This comment has been minimized.

Show comment
Hide comment
@simophin

simophin Aug 2, 2016

Is there any way to set style? So now you can set theme for each widget, like AppTheme, AppTheme.Light, but what if you need to set individual styles, eg. Widget.Button, Widget.Button.Colored?

simophin commented Aug 2, 2016

Is there any way to set style? So now you can set theme for each widget, like AppTheme, AppTheme.Light, but what if you need to set individual styles, eg. Widget.Button, Widget.Button.Colored?

@gregschlom

This comment has been minimized.

Show comment
Hide comment
@gregschlom

gregschlom Aug 2, 2016

@simophin depends on what version you're targeting. on API 21+, Google added a 4th parameter to all Views constructors allowing you to set the style resource: https://developer.android.com/reference/android/view/View.html#View(android.content.Context,%20android.util.AttributeSet,%20int,%20int)

So if you're only targeting devices running 21+, it's easy to make custom Anko views calling that constructor. If you want to target devices running below 21, you're out of luck. There's no (easy) way to set the style programmatically because it's all private APIs used by the XML layout inflater.

What we ended up doing was to create very small XML layout files containing just <Button style="@style/WhiteButton" /> and then instantiating that layout with Anko's include<Button>(R.layout.white_button)

gregschlom commented Aug 2, 2016

@simophin depends on what version you're targeting. on API 21+, Google added a 4th parameter to all Views constructors allowing you to set the style resource: https://developer.android.com/reference/android/view/View.html#View(android.content.Context,%20android.util.AttributeSet,%20int,%20int)

So if you're only targeting devices running 21+, it's easy to make custom Anko views calling that constructor. If you want to target devices running below 21, you're out of luck. There's no (easy) way to set the style programmatically because it's all private APIs used by the XML layout inflater.

What we ended up doing was to create very small XML layout files containing just <Button style="@style/WhiteButton" /> and then instantiating that layout with Anko's include<Button>(R.layout.white_button)

@mvysny

This comment has been minimized.

Show comment
Hide comment
@mvysny

mvysny Aug 8, 2016

Works great, thanks! If you wish to resolve attr value from Activity theme to style resource, just use this code:

val View.contextThemeWrapper: ContextThemeWrapper
get() = context.contextThemeWrapper

val Context.contextThemeWrapper: ContextThemeWrapper
get() = when (this) {
    is ContextThemeWrapper -> this
    is ContextWrapper -> baseContext.contextThemeWrapper
    else -> throw IllegalStateException("Context is not an Activity, can't get theme: $this")
}

@StyleRes
fun View.attrStyle(@AttrRes attrColor: Int): Int = contextThemeWrapper.attrStyle(attrColor)
@StyleRes
private fun ContextThemeWrapper.attrStyle(@AttrRes attrRes: Int): Int =
        attr(attrRes) {
            it.getResourceId(0, 0)
        }

private fun <R> ContextThemeWrapper.attr(@AttrRes attrRes: Int, block: (TypedArray)->R): R {
    val typedValue = TypedValue();
    if (!theme.resolveAttribute(attrRes, typedValue, true)) throw IllegalArgumentException("$attrRes is not resolvable")
    val a = obtainStyledAttributes(typedValue.data, intArrayOf(attrRes));
    val result = block(a)
    a.recycle()
    return result
}

then,

textView(attrStyle(R.attr.my_title)) {
}

mvysny commented Aug 8, 2016

Works great, thanks! If you wish to resolve attr value from Activity theme to style resource, just use this code:

val View.contextThemeWrapper: ContextThemeWrapper
get() = context.contextThemeWrapper

val Context.contextThemeWrapper: ContextThemeWrapper
get() = when (this) {
    is ContextThemeWrapper -> this
    is ContextWrapper -> baseContext.contextThemeWrapper
    else -> throw IllegalStateException("Context is not an Activity, can't get theme: $this")
}

@StyleRes
fun View.attrStyle(@AttrRes attrColor: Int): Int = contextThemeWrapper.attrStyle(attrColor)
@StyleRes
private fun ContextThemeWrapper.attrStyle(@AttrRes attrRes: Int): Int =
        attr(attrRes) {
            it.getResourceId(0, 0)
        }

private fun <R> ContextThemeWrapper.attr(@AttrRes attrRes: Int, block: (TypedArray)->R): R {
    val typedValue = TypedValue();
    if (!theme.resolveAttribute(attrRes, typedValue, true)) throw IllegalArgumentException("$attrRes is not resolvable")
    val a = obtainStyledAttributes(typedValue.data, intArrayOf(attrRes));
    val result = block(a)
    a.recycle()
    return result
}

then,

textView(attrStyle(R.attr.my_title)) {
}
@hannesstruss

This comment has been minimized.

Show comment
Hide comment
@hannesstruss

hannesstruss Feb 3, 2017

Resolving the attr does not actually work in all cases. Example:

button("My Button", theme = attrStyle(R.attr.buttonBarButtonStyle))

will not apply the correct background, since the one-argument constructor of Button that Anko uses calls through to the three-argument constructor with a default value for int defStyleAttr which overrides the button background with the default.

While working in some cases, the theme support of Anko 0.9 is not a full replacement for the style XML attribute. Can we reopen this issue?

Resolving the attr does not actually work in all cases. Example:

button("My Button", theme = attrStyle(R.attr.buttonBarButtonStyle))

will not apply the correct background, since the one-argument constructor of Button that Anko uses calls through to the three-argument constructor with a default value for int defStyleAttr which overrides the button background with the default.

While working in some cases, the theme support of Anko 0.9 is not a full replacement for the style XML attribute. Can we reopen this issue?

@yanex

This comment has been minimized.

Show comment
Hide comment
@yanex

yanex May 3, 2017

Contributor

@hannesstruss I've created the new issue for this: #361.

I also mark this issue as "closed". If you have other problems with themed views, please feel free to create the new issues.

Contributor

yanex commented May 3, 2017

@hannesstruss I've created the new issue for this: #361.

I also mark this issue as "closed". If you have other problems with themed views, please feel free to create the new issues.

@yanex yanex closed this May 3, 2017

@zawadz88 zawadz88 referenced this issue in stepstone-tech/android-material-stepper May 9, 2017

Closed

Anko DSL error #112

@ayinmursalin

This comment has been minimized.

Show comment
Hide comment
@ayinmursalin

ayinmursalin Jan 26, 2018

now you can use horizontalProgressBar { }

so no need to set style horizontal to progressBar

now you can use horizontalProgressBar { }

so no need to set style horizontal to progressBar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment