I am trying to create a custom compound view (subclassed out of relativelayout) on android and am trying to specify default styles. I can accomplish this, but I want to know what the defStyleAttr parameter is used for, and how to use it.
I have run into this method context.obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes).
I understand that:
AttributeSet set I will get from the view's constructor if
inflating from XML,
int[] attrs will be the key values for any
custom view fields I may have created,
int defStyleRes will be a
default style that I can provide, which is defined as a style
resource.
However, I can't figure out how to use int defStyleAttr parameter. I've read the documentation and have provided an attr resource like this:
<declare-styleable name="BMTheme">
<attr name="BMButtonStyle" format="reference" />
</declare-styleable>
and then in my themes resource file I have this:
<style name="Theme.Custom" parent="#android:style/Theme">
<item name="BMButtonStyle">#style/BMButton</item>
</style>
which in turn references this style:
<style name="BMButton">
<item name="text">some string</item>
<item name="android:paddingLeft">10dp</item>
<item name="android:paddingRight">10dp</item>
</style>
In code, when I pass in 0 for defStyleAttr and the style resource id for defStyleRes, I successfully get the default values that I defined in my BMButton style:
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RightArrowButton, 0, R.style.BMButton);
However when I pass in the attr resource id for BMButtonStyle(which in turn references the BMButton style) for defStyleAttr, and 0 for defStyleRes then I do not get the values I specified in the style.
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RightArrowButton, R.attr.BMButtonStyle, 0);
Please let me know if I am using the defStyleAttr parameter the wrong way, and if anyone knows, why is there a need for both a defStyleAttr and a defStyleRes parameter when it seems like just a defStyleRes will suffice?
I figured out where I was going wrong after reading this post:
Creating default style with custom attributes
Basically, in order to use the defStyleAttr parameter, my project needed to set my custom theme defined theme in the manifest.
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.Custom" >
Related
I want to implement custom style for view. Let's take an example of button. Some common values will be shared across the app for that I am using style like below:
<style name="Widget.Demo.Button.Primary" parent="#style/Widget.MaterialComponents.Button">
<item name="fontFamily">#font/roboto</item>
<item name="android:fontFamily">#font/roboto</item>
<item name="android:minHeight">64dp</item>
<item name="android:theme">#style/ThemeOverlay.Demo.GrayPrimary</item>
</style>
<style name="ThemeOverlay.Demo.GrayPrimary" parent="">
<item name="colorPrimary">#color/gray</item>
</style>
Now I want to add some of the custom attribute in the button like below (This is just an example not the real attribute):
<com.android.CustomButton
android:id="#+id/btn_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/first"
app:abc="primary"
style="#style/Widget.Demo.Button.Primary"/>
attrs.xml:
<declare-styleable name="CustomButtonAttr" >
<attr name="abc" format="enum" >
<enum name="primary" value="1"/>
<enum name="secondary" value="2"/>
</declare-styleable>
I don't want to define style and custom attribute in xml everytime. Is there any way to get custom attribute directly in my customview and Widget.Demo.Button.Primary set by default in my below class?
class CustomButton : MaterialButton {
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(
ThemeEnforcement.createThemedContext(context, attributeSet, 0, 0),
attributeSet,
0
)
}
I don't want to define style in xml for every button or view which I create. So is there a way to define in CustomButton class along with custom attributes ? If yes could you please give me some references.
Thanks in advance.
Yep, you can use the default style attribute in the constructor.
Define a new attribute:
<attr name="customButtonStyle" format="reference" />
Then use the appropriate view constructor:
private val defStyleAttr = R.attr.customButtonStyle
class CustomButton(context: Context, attrs: AttributeSet) : MaterialButton(
ThemeEnforcement.createThemedContext(context, attributeSet, defStyleAttr, 0),
attributeSet,
defStyleAttr
) {
init {
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.CustomButton,
defStyleAttr,
0
)
...
}
In your theme, point customButtonStyle to the style resource that you want used by default:
<style name="Theme.Demo" parent="Base.Theme.Demo">
<item name="customButtonStyle">#style/Widget.Demo.Button.Primary</item>
</style>
Note that android:theme should be changed to materialThemeOverlay in that style resource, as it won't be applied when read from a default style. As you're already wrapping the context with the ThemeEnforcement function (newer versions of Material Design Components change this to MaterialThemeOverlay), this custom view supports materialThemeOverlay 👍
You can add your custom attributes to the style too:
<style name="Widget.Demo.Button.Primary" parent="#style/Widget.MaterialComponents.Button">
<item name="abc">primary</item>
<item name="fontFamily">#font/roboto</item>
<item name="android:fontFamily">#font/roboto</item>
<item name="android:minHeight">64dp</item>
<item name="materialThemeOverlay">#style/ThemeOverlay.Demo.GrayPrimary</item>
</style>
Reference (blog + video)
I am looking to create a custom ViewGroup to be used in a library; which contains a few ImageButton objects. I would like to be able to apply a style each ImageButton; but I cannot figure out how to apply a style programmatically other than by applying a attribute resource to the defStyleAttr parameter; like so:
mImageButton = new ImageButton(
getContext(), // context
null, // attrs
R.attr.customImageButtonStyle); // defStyleAttr
The issue with this is that the only way to change the style of each ImageButton would be by applying a style to this attribute in a parent theme. But I would like to be able to set a default style, without having to manually set this attribute for each project that uses this library.
There is a parameter that does exactly what I am looking for; defStyleRes, which can be used like so:
mImageButton = new ImageButton(
getContext(), // context
null, // attrs
R.attr.customImageButtonStyle, // defStyleAttr
R.style.customImageButtonStyle); // defStyleRes
This parameter is only available at API Level 21 and above, but my projects target API Level 16 and above. So how can I set the defStyleRes, or apply a default style, without access to this parameter?
I applied my style using a ContextThemeWrapper, as suggested by #EugenPechanec, which seems to work well, but each ImageButton now has the default ImageButton background, even though my style applies <item name="android:background">#null</item>.
Here is the style I am using:
<style name="Widget.Custom.Icon" parent="android:Widget">
<item name="android:background">#null</item>
<item name="android:minWidth">56dp</item>
<item name="android:minHeight">48dp</item>
<item name="android:tint">#color/selector_light</item>
</style>
And this is how I am applying it:
ContextThemeWrapper wrapper = new ContextThemeWrapper(getContext(), R.style.Widget_Custom_Icon);
mImageButton = new AppCompatImageButton(wrapper);
On the left is what I am getting, and on the right is what I would like it to look like:
defStyleAttr is for resolving default widget style from theme attribute.
Example: AppCompatCheckBox asks for R.attr.checkBoxStyle. Your theme defines <item name="checkBoxStyle">#style/Widget.AppCompat.CheckBox</item>.
If that attribute is not defined in your theme the widget would pickup its defStyleRes e.g. R.style.Widget_AppCompat_CheckBox.
Note that these are not actual values used by the widget.
I have not seen defStyleRes constructor parameter used outside of the framework. All of these parameters (plus defaults) are however used when asking TypedArray for resources.
How to actually solve your problem
So the four parameter constructor is not available on all platforms. You need to find a way to feed in your default style. Consider a style you'd like to apply:
<style name="MyImageButtonStyle" parent=""> ... </style>
You need a way to convert it to a defStyleAttr parameter. Define the default style on a theme overlay:
<style name="MyImageButtonThemeOverlay" parent="">
<!-- AppCompat widgets don't use the android: prefix. -->
<item name="imageButtonStyle">#style/MyImageButtonStyle</item>
</style>
Now you can create your ImageButton using this theme overlay:
// When creating manually you have to include the AppCompat prefix.
mImageButton = new AppCompatImageButton(
new ContextThemeWrapper(getContext(), R.style.MyImageButtonThemeOverlay)
);
You don't need to specify any other parameters as AppCompatImageButton will pickup R.attr.imageButtonStyle by default.
If that looks hacky you can always inflate your custom view hierarchy or individual widgets from XML where you specified the style="#style/MyImageButtonStyle" attribute.
I've made a custom View in a library. I want my view's background to be a reference of a theme attribute. For example like this :
<style name="MyView">
<item name="android:background">?attr/my_view_background</item>
</style>
This pattern is used by Android's framework in several places, for example for list dividers.
But being in a library, i don't want to make a custom theme, because this would force someone using the lib to extend this custom theme which a find prohibitive.
I would rather like that the user be able to set the attribute whatever theme he uses, but this means somewhere a default value has to be defined.
So the question is how to define a default value, without making a custom theme ?
Here's how to make it : using third parameter of Context.obtainStyledAttributes(int, int[], int, int) inside MyView constructor :
public MyView(AttributeSet attrs, int defStyle) {
TypedArray backgroundArray = getContext().obtainStyledAttributes(
attrs, // The attrs passed to the view
new int[] { android.R.attr.background }, // I'm only interrested in getting the background
R.attr.myViewBackground, // The user can pass a custom background using this theme attribute
theme R.style.MyView // And that's the default style
);
setBackgroundDrawable(backgroundArray.getDrawable(0)); // At 0, there's the background attribute
backgroundArray.recycle();
}
I've written a custom widget for a control that we use widely throughout our application. The widget class derives from ImageButton and extends it in a couple of simple ways. I've defined a style which I can apply to the widget as it's used, but I'd prefer to set this up through a theme. In R.styleable I see widget style attributes like imageButtonStyle and textViewStyle. Is there any way to create something like that for the custom widget I wrote?
Yes, there's one way:
Suppose you have a declaration of attributes for your widget (in attrs.xml):
<declare-styleable name="CustomImageButton">
<attr name="customAttr" format="string"/>
</declare-styleable>
Declare an attribute you will use for a style reference (in attrs.xml):
<declare-styleable name="CustomTheme">
<attr name="customImageButtonStyle" format="reference"/>
</declare-styleable>
Declare a set of default attribute values for the widget (in styles.xml):
<style name="Widget.ImageButton.Custom" parent="android:style/Widget.ImageButton">
<item name="customAttr">some value</item>
</style>
Declare a custom theme (in themes.xml):
<style name="Theme.Custom" parent="#android:style/Theme">
<item name="customImageButtonStyle">#style/Widget.ImageButton.Custom</item>
</style>
Use this attribute as the third argument in your widget's constructor (in CustomImageButton.java):
public class CustomImageButton extends ImageButton {
private String customAttr;
public CustomImageButton( Context context ) {
this( context, null );
}
public CustomImageButton( Context context, AttributeSet attrs ) {
this( context, attrs, R.attr.customImageButtonStyle );
}
public CustomImageButton( Context context, AttributeSet attrs,
int defStyle ) {
super( context, attrs, defStyle );
final TypedArray array = context.obtainStyledAttributes( attrs,
R.styleable.CustomImageButton, defStyle,
R.style.Widget_ImageButton_Custom ); // see below
this.customAttr =
array.getString( R.styleable.CustomImageButton_customAttr, "" );
array.recycle();
}
}
Now you have to apply Theme.Custom to all activities that use CustomImageButton (in AndroidManifest.xml):
<activity android:name=".MyActivity" android:theme="#style/Theme.Custom"/>
That's all. Now CustomImageButton tries to load default attribute values from customImageButtonStyle attribute of current theme. If no such attribute is found in the theme or attribute's value is #null then the final argument to obtainStyledAttributes will be used: Widget.ImageButton.Custom in this case.
You can change names of all instances and all files (except AndroidManifest.xml) but it would be better to use Android naming convention.
Another aspect in addition to michael's excellent answer is overriding custom attributes in themes.
Suppose you have a number of custom views that all refer to the custom attribute "custom_background".
<declare-styleable name="MyCustomStylables">
<attr name="custom_background" format="color"/>
</declare-styleable>
In a theme you define what the value is
<style name="MyColorfulTheme" parent="AppTheme">
<item name="custom_background">#ff0000</item>
</style>
or
<style name="MyBoringTheme" parent="AppTheme">
<item name="custom_background">#ffffff</item>
</style>
You can refer to the attribute in a style
<style name="MyDefaultLabelStyle" parent="AppTheme">
<item name="android:background">?background_label</item>
</style>
Notice the question mark, as also used for reference android attribute as in
?android:attr/colorBackground
As most of you have noticed, you can -and probably should- use #color references instead of hard coded colors.
So why not just do
<item name="android:background">#color/my_background_color</item>
You can not change the definition of "my_background_color" at runtime, whereas you can easily switch themes.
I'm trying to use the TextView constructor with style like this:
TextView myText = new TextView(MyActivity.this, null, R.style.my_style);
However, when I do this, the text view does not appear to take the style (I verified the style by setting it on a static object).
I've also tried using myText.setTextAppearance(MyActivity.this, R.style.my_style) but it also doesn't work.
I do not believe you can set the style programatically. To get around this you can create a template layout xml file with the style assigned, for example in res/layout create tvtemplate.xml as with the following content:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is a template"
style="#style/my_style" />
then inflate this to instantiate your new TextView:
TextView myText = (TextView)getLayoutInflater().inflate(R.layout.tvtemplate, null);
You can create a generic style and re-use it on multiple textviews like the one below:
textView.setTextAppearance(this, R.style.MyTextStyle);
Edit: this refers to the Context object.
You can pass a ContextThemeWrapper to the constructor like this:
TextView myText = new TextView(new ContextThemeWrapper(MyActivity.this, R.style.my_style));
You can set the style in the constructor (but styles can not be dynamically changed/set).
View(Context, AttributeSet, int) (the int is an attribute in the current theme that contains a reference to a style)
Answer from Romain Guy
reference
Parameter int defStyleAttr does not specifies the style. From the Android documentation:
defStyleAttr - An attribute in the current theme that contains a
reference to a style resource that supplies default values for the
view. Can be 0 to not look for defaults.
To setup the style in View constructor we have 2 possible solutions:
With use of ContextThemeWrapper:
ContextThemeWrapper wrappedContext = new ContextThemeWrapper(yourContext, R.style.your_style);
TextView textView = new TextView(wrappedContext, null, 0);
With four-argument constructor (available starting from LOLLIPOP):
TextView textView = new TextView(yourContext, null, 0, R.style.your_style);
Key thing for both solutions - defStyleAttr parameter should be 0 to apply our style to the view.
Dynamically changing styles is not supported (yet). You have to set the style before the view gets created, via XML.
When using custom views that may use style inheritance (or event styleable attributes), you have to modify the second constructor in order not to lose the style. This worked for me, without needing to use setTextAppearence():
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, attrs.getStyleAttribute());
}
The accepted answer was great solution for me. The only thing to add is about inflate() method.
In accepted answer all android:layout_* parameters will not be applied.
The reason is no way to adjust it, cause null was passed as ViewGroup parent.
You can use it like this:
View view = inflater.inflate(R.layout.view, parent, false);
and the parent is the ViewGroup, from where you like to adjust android:layout_*.
In this case, all relative properties will be set.
Hope it'll be useful for someone.
I met the problem too, and I found the way to set style programatically. Maybe you all need it, So I update there.
The third param of View constructor accepts a type of attr in your theme as the source code below:
public TextView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
So you must pass a type of R.attr.** rather than R.style.**
In my codes, I did following steps:
First, customize a customized attr to be used by themes in attr.xml.
<attr name="radio_button_style" format="reference" />
Second, specific your style in your used theme in style.xml.
<style name="AppTheme" parent="android:Theme.Translucent">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
<item name="radio_button_style">#style/radioButtonStyle</item>
</style>
<style name="radioButtonStyle" parent="#android:style/Widget.CompoundButton.RadioButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">64dp</item>
<item name="android:background">#000</item>
<item name="android:button">#null</item>
<item name="android:gravity">center</item>
<item name="android:saveEnabled">false</item>
<item name="android:textColor">#drawable/option_text_color</item>
<item name="android:textSize">9sp</item>
</style>
At the end, use it!
RadioButton radioButton = new RadioButton(mContext, null, R.attr.radio_button_style);
the view created programatically will use the specified style in your theme.
You can have a try, and hope it can work for you perfectly.
We can use TextViewCompact.setTextAppearance(textView, R.style.xyz).
Android doc for reference.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
textView.setTextAppearance(R.style.yourStyle)
you can use Extension Functions kotlin
fun TextView.setStyle(#StyleRes resId: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setTextAppearance(resId)
} else {
setTextAppearance(context, resId)
}
}
I have only tested with EditText but you can use the method
public void setBackgroundResource (int resid)
to apply a style defined in an XML file.
Sine this method belongs to View I believe it will work with any UI element.
regards.