Custom item in theme - android

I want to create style that I will use in android:textAppearance of my TextView. But this style differs on different API levels, so I want to have:
one common style (for all API levels),
some styles that inherits after that common style in values-v16, values-v21 dirs,
one directive (style / attr ?) I will put in TextView's android:textAppearance parameter (layout files are common for all API levels).
I tried multiple combinations from Google and Stack and finally ended with something like this (which is of course not working):
values/attrs.xml
<attr name="myTextAppearance" format="reference" />
values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="myTextAppearance">#style/CommonTextAppearance</item>
</style>
<style name="CommonTextAppearance">
<item name="android:textColor">#00f</item>
</style>
values-v16/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="myTextAppearance">#style/V16TextAppearance</item>
</style>
<style name="V16TextAppearance" parent="CommonTextAppearance">
<item name="android:textSize">20sp</item>
</style>
layout/activity_main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Lorem"
android:textAppearance="?attr/myTextAppearance"/>
</RelativeLayout>
And currently I'm getting an error:
Couldn't find theme resource ?attr/myTextAppearance for the current theme
I spent a lot of time trying to do this on my own, but I failed. Can you help me please?

This is because the ?attr/[name] value is a reference to the owner's style parameter. If there's no paraneter with [name], the code falls back to the default parameter set which is defined in a theme.
Basically, the TextView class checks the inline style defined in layout xml, then the default theme and then the textViewStyle reference. None of them contains your myTextAppearance attribute.
You have to override the view and in constructor use obtainStyledAttributes. Pass your theme attribute as the default one. Then parse the value and use it as the new text appearance.
http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet,int[],int,int)
http://developer.android.com/reference/android/view/View.html#View(android.content.Context,android.util.AttributeSet,int)

Related

Create style that extends from default style set in application theme

I have set the default button style in my application theme, so that it is applied to every button in the app.
There are few buttons that need to have their text capitalized in addition to default style. So what I'm trying to do is something like this
(the following code snippet doesn't work as I cannot set parent="?attr/materialButtonStyle"):
<style name="capitalizedButton" parent="?attr/materialButtonStyle">
<item name="android:textAllCaps">true</item>
</style>
Is it possible to extend a style defined in theme.xml? I don't want to refer to the local style that I'm setting in theme as that can change later, instead I intend to access it using theme attribute.
If the capitalization happens in addition to your custom default button style, there is no need to make the capitalization style a child of ?attr/materialButtonStyle (This cannot be done anyhow, as ?attr references an attribute in the currently applied theme). You can read more about it here.
styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="buttonStyle">#style/DefaultButton</item>
</style>
<style name="DefaultButton" parent="Widget.AppCompat.Button">
<item name="android:textColor">#color/white</item>
</style>
<style name="AllCapsButton">
<item name="android:textAllCaps">true</item>
</style>
layout.xml
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:theme="#style/AllCapsButton"/>
In the code above, button gets the DefaultButton style (white text) applied by the theme, and the AllCapsButton style (capitalized text) applied in the layout file.
You can directly pass style to Button widget in you XML like:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="capitalizedButton"/>
Hope you get the answer. If not please explain your question.

Overriding editTextStyle doesn't work with latest Material Components base style

In an app of mine, I'm using the Theme.MaterialComponents.Light.NoActionBar as a base style. In this style, which I call AppTheme, I'm trying to override editTextStyle to provide a custom style for com.google.android.material.textfield.TextInputEditText (according to the source code, it uses R.attr.editTextStyle as a default style).
This is my current theme, related to the TIEditText and TILayout:
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
[ primary and secondary colors, OnColors, etc.]
<item name="editTextStyle">#style/AppTheme.TextInputEditText</item>
<item name="textInputStyle">#style/AppTheme.TextInputLayout</item>
[ Custom attribute for testing, defined in attrs.xml ]
<item name="textInputEditTextStyle">#style/AppTheme.TextInputEditText</item>
</style>
For some reason, even though I set editTextStyle, if I use it in code, it does not get applied:
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/tilFirstName"
style="?attr/textInputStyle"
android:hint="#string/label_firstname"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/firstName"
style="?attr/editTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:text="#={viewModel.firstName}" />
</com.google.android.material.textfield.TextInputLayout>
However if I replace the style of firstName with ?attr/textInputEditTextStyle, it works.
Why can't I override editTextStyle in the default theme? What the hell is going on?
Target SDK is 28, minSDK is 21, Material library version is 1.1.0-alpha06
Let's just move past the part where we all recognize that Android themes and styles are singularly the most absurd wasteland of hackery and guesswork ever devised by human beings.
This is an expansion on the previous answer. Same silly 'hack'. I was able to style the TextInputEditText by setting editTextStyle, but not where it intuitively belongs, but rather inside a custom materialThemeOverlay nested within the style defined for textInputStyle. Witness:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- works fine -->
<item name="textInputStyle">#style/AppTheme.TextInputLayoutStyle</item>
<!-- should work fine, doesn't work, happily ignored -->
<!-- <item name="editTextStyle">#style/AppTheme.TextInputEditTextStyle</item> -->
</style>
<style name="AppTheme.TextInputLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<!-- other props (boxBackgroundMode, boxBackgroundColor, boxStrokeColor, etc) -->
<!-- can we set editTextStyle from here? Of course not! We should magically know we need a material theme overlay-->
<item name="materialThemeOverlay">#style/AppTheme.MaterialThemeOverlay</item>
</style>
<!-- style inception! a style, child of another style, whose only purpose is to refer to yet another style -->
<style name="AppTheme.MaterialThemeOverlay">
<item name="editTextStyle">#style/AppTheme.TextInputEditTextStyle</item>
</style>
<!-- finally, the style we SHOULD have been able to set from the theme -->
<style name="AppTheme.TextInputEditTextStyle" parent="#style/Widget.MaterialComponents.TextInputEditText.OutlinedBox">
<item name="android:textColor">#color/white</item>
</style>
All of the above ridiculousness and ANOTHER day of my life thrown in the trash, just to change the color of text. Thaaaaanks Aaaaaandroid.
For some reason, even though I set editTextStyle, if I use it in code, it does not get applied
It happens because the default styles of the TextInputLayout override the editTextStyle using the materialThemeOverlay attribute.
For example the Widget.MaterialComponents.TextInputLayout.FilledBox has this default style:
<style name="Widget.MaterialComponents.TextInputLayout.FilledBox" parent="Base.Widget.MaterialComponents.TextInputLayout">
<item name="materialThemeOverlay">
#style/ThemeOverlay.MaterialComponents.TextInputEditText.FilledBox
</item>
....
</style>
<style name="ThemeOverlay.MaterialComponents.TextInputEditText.FilledBox">
<item name="editTextStyle">#style/Widget.MaterialComponents.TextInputEditText.FilledBox</item>
</style>

Is it possible to reference attributes from styles.xml file?

I want to give the user the possibility to switch the colors skin of my entire application. I mean to switch the style of some custom views of the app dynamically when the user presses a button of the screen. I know that if you call Activity.setTheme() before onCreate() method, you can change the theme of the app dynamically, but normal Views (for example, NavigationView) with custom styles applied on their xml layout, do not have setTheme or setStyle methods, so it is does not appear possible to change their style dynamically.
I think that my objective would be possible referencing colors declared in an AppTheme declared inside styles.xml file. I mean, i can have two AppThemes declared, each one with one set of colors, and then, in the custom styles declared for the custom views, reference the colors. Something like this:
<resources>
<style name="AppTheme" parent="Theme.AppCompat">
<item name="customColor">#111111</item>
</style>
<style name="AppTheme.AnotherColor" parent="Theme.AppCompat">
<item name="customColor">#222222</item>
</style>
<style name="CustomActionBar">
<!-- title text color -->
<item name="android:textColorPrimary">#styles/customColor</item>
</style>
</resources>
So, by default, the custom Color declared on my "AppTheme" will be applied by default, using color 111111. But when I change the theme of my app using setTheme(R.styles.AppTheme_AnotherColor) the color applied will be 222222. If this would be possible it would be perfect! but it is not possible or I don't know how to access to a color declared inside a style directly from another style of the same styles.xml file. I mean that #styles/customColor is not correct and I don't know how to access that color.
How can this be achieved?
Yes, it is definitely possible to add custom attributes and colors to the themes. For this you need to:
Define your custom attribute in your res/values/attrs.xml file:
<resources>
<attr name="customColor" format="color" />
</resources>
Define the attribute's value in your themes:
<style name="AppTheme" parent="Theme.AppCompat">
<item name="customColor">#111111</item>
</style>
<style name="AppTheme.AnotherColor" parent="Theme.AppCompat">
<item name="customColor">#222222</item>
</style>
Use your custom attribute in your styles:
<style name="CustomActionBar">
<!-- title text color -->
<item name="android:textColorPrimary">?attr/customColor</item>
</style>

Set a consistent theme for all the editTexts in Android

I have finished making my app. Now, I want to reset all my editTexts to have the layout width as fill parent instead of wrap content.
android:layout_width="fill_parent"
while currently all my editTexts are
android:layout_width="wrap_content"
Is there any way i can do this in a style xml file, instead of individually in each layout?
I currently have this as my styles.xml
<style name="AppTheme" parent="android:Theme.Light">
<item name="android:fontFamily">Verdana</item>
<item name="android:editTextStyle">#style/EditTextStyle</item>
</style>
<style name="EditTextStyle" parent="#android:style/Widget.EditText">
<item name="android:layout_width">fill_parent</item>
<item name="android:textColor">#808080</item>
</style>
But i'm getting an exception saying that layout_width must be specified.
This is my exception:
07-15 11:13:34.872: E/AndroidRuntime(1195): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.passwordkeeper.ui/com.passwordkeeper.ui.ActivityLogin}: java.lang.RuntimeException: Binary XML file line #29: You must supply a layout_width attribute.
Any easy way out or do i have to change the attribute in all my editText's individually?
You can try this one.
Here is the part of the manifest file you need to change to call your custom theme (the custom theme called here is AppTheme:
<application android:name="YourApplication"
android:theme="#style/AppTheme" >
Then in your file styles.xml, create and customize this custom theme:
<style name="AppTheme" parent="#android:style/Theme.Holo.Light">
<item name="android:typeface">YourTypeFace</item>
</style>
You can add the parameters you need inside the style. This will apply the style to all your textviews.
One solution to your problem is to apply a custom theme to all of your activities. In order to do that, you can inherit properties from an existing theme and override the properties that you want to change.
In AndroidManifest.xml, locate the <application> element.
Add the attribute to it:
android:theme="#style/"
Locate styles.xml file in the values folder.
Use the following template:
<style name="ApplicationStyle" parent="android:Theme.Light">
<item name="#android:editTextStyle">#style/customEditText</item>"
</style>
Names used in the above are just examples, you may use your own. As to the parent theme, that is also up to you.
All that is left is the definition of editTextStyle (or whatever name you have chosen for the style). You should inherit properties from Widget.EditText and override the properties that you want to change, like the following:
<style name="customEditText" parent="#android:style/Widget.EditText">
<item name="android:textColor" >#ffffff</item>
</style>
To quote the official android guide:
The parent attribute in the element is optional and specifies
the resource ID of another style from which this style should inherit
properties. You can then override the inherited style properties if
you want to.
I tried to make it easy to understand and follow. I'm a junior dev so while the above solution works for me, it may not be the best one out there. As I said though, it solves the problem rather efficiently.
Unfortunately it is not possible to set layout attributes (layout_*) from a theme (see Layout Parameters documentation and this answer from an Android framework engineer). You must set them on each element or set the layout attributes in a style and let each element reference the style like this:
<Button style="#style/ButtonBig" android:text="my text" />
where ButtonBig is defined like this:
<style name="ButtonBig" parent="android:Widget.Holo.Light.Button">
<item name="android:textSize">20sp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">100dp</item>
</style>
I was having this exact issue. For some reason it helped me to drop the android: part in the AppTheme definition, and leave it only as editTextStyle (as mentioned in this answer):
<style name="AppTheme" parent="android:Theme.Light">
<item name="android:fontFamily">Verdana</item>
<item name="editTextStyle">#style/EditTextStyle</item>
</style>
You'll have to set the property style="#styles/EditTextStyle" to all of your EditText components in your application.
define the style attribute for all EditText like below:
<EditText
android:id="#+id/editText1"
android:layout_height="wrap_content"
style="#style/EditTextStyle">
You have to specify layout_width individually to each and every View. There is no way To escape. You can create a LayoutParams object and set its width and height in it and set in it every EditText like
textView1.setLayoutParams(lParams);
textView2.setLayoutParams(lParams);
...

How to add multiple theme attributes to the same activity on Android Manifest?

I have an android manifest with an activity that I want to apply to styles to:
<activity android:label="#string/app_name" android:name="Language" android:theme="#android:style/Theme.NoTitleBar>
Is how it looks right now, but while keeping the NoTitleBar attribute, I would like to add this attribute as well:
android:style/Theme.Light"
But I'm just so new to Android that I can't figure it out.
Please help!
You cannot have more than one theme applied at once in your manifest.
I believe there is a theme Theme.Light.NoTitleBar that will do what you want - but I will show you below how you can easily do this yourself and customize more.
What you need to do is create a theme which has either Theme.NoTitleBar or Theme.Light as it's parent and customizes the bits you want -- in this case the easiest way is to create a theme with Theme.Light as it's parent and just hide the title bar (rather than have the Theme.NoTitleBar as the parent and then have to make everything light which is much harder!).
You can do this with the following code in your themes.xml file in the values folder:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- use the Android Light theme as the base for our own theme -->
<style name="MySuperTheme" parent="#android:style/Theme.Light">
<!-- hide the Window Title -->
<item name="android:windowNoTitle">true</item>
<!-- You could change the scrollbar, checkbox style, anything! -->
</style>
</resources>
Then use android:theme="#style/MySuperTheme" for your activity (or you could even apply it to your whole application by placing it on the application element -- if you apply a style to an individual activity and have one set for the whole application as well then the style of the individual activity will be the one shown).
Take a look at the Android themes.xml for a list of all the things you can customize in your own theme.
You can also look at all of the Android styles to see how they are done.
You'll need at least 2 styles, best inheriting from base styles, e.g. Theme.Material variants, or if you use appcompat then Theme.AppCompat variants. In each style override values such as colours, drawables etc with theme-specific values.
values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- original theme attributes -->
...
<item name="android:textColorPrimary">#FFFFFF</item>
</style>
<style name="AppTheme.Dark" parent="Theme.AppCompat">
<!-- alternative theme attributes -->
...
<item name="android:textColorPrimary">#000000</item>
</style>
This will be sufficient if you only use framework or appcompat attributes (e.g. colorAccent, android:textColorPrimary etc) in your layouts. But if you need your own attributes (e.g. a drawable with color that is different per theme), then you will need to define custom attributes.
values/attrs.xml
<attr name="themedMenuStoryDrawable" format="reference" />
<attr name="themedMenuCommentDrawable" format="reference" />
...
Specify theme-specific values for your custom attributes:
values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- original theme attributes -->
...
<item name="themedMenuStoryDrawable">#drawable/ic_subject_white_24dp</item>
<item name="themedMenuCommentDrawable">#drawable/ic_mode_comment_white_24dp</item>
</style>
<style name="AppTheme.Dark" parent="Theme.AppCompat">
<!-- alternative theme attributes -->
...
<item name="themedMenuStoryDrawable">#drawable/ic_subject_black_24dp</item>
<item name="themedMenuCommentDrawable">#drawable/ic_mode_comment_black_24dp</item>
</style>
Then refer to your custom attributes with ?attr/ prefix in layouts, menus etc:
menu/my_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#id/menu_comment"
android:icon="?attr/themedMenuCommentDrawable" />
<item android:id="#id/menu_story"
android:icon="?attr/themedMenuStoryDrawable" />
</menu>
Check out my blog post for the complete guide.

Categories

Resources