Basically, I'd like to have a single layout that I can skin differently on the theme. Many examples and entries on this site seem dance around the issue a little so I'm not entirely certain it can be done. Or I just don't get it.
Here's the concept.
Let's say my app is sports-related.. the app has a default them of 'SportTheme'
I'd like users also to say they want the 'Football' or 'Baseball' theme, and on designated <TextView> elements, I'd like the text (defaults to 'Sport') to change to 'Football' or 'Baseball' given the overall theme applied to the activity?
in strings.xml
<string name="label_sport">Sport</string>
<string name="label_football">Football</string>
<string name="label_baseball">Baseball</string>
in activityA.java - The important thing here is that the theme is set for the activity (or application is fine, too).
#Override
protected void onCreate(Bundle savedInstanceState)
{
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
this.setContentView(R.layout.layout_a);
switch (user.ThemePreference)
{
case FOOTBALL_THEME:
this.setTheme(R.style.FootballTheme);
break;
case BASEBALL_THEME:
this.setTheme(R.style.BaseballTheme);
break;
default:
this.setTheme(R.style.SportTheme);
break;
}
}
in layout_a.xml
...
<TextView
android:id="#+id/tvSport"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="#string/label_sport"
android:style="#style/SportLabel"></TextView>
What do I do in themes/styles? Something like this? The important thing here is the text in the TextView. I'll be using the same textView in several different activities throughout the application.
<theme name="SportTheme" parent="android:Theme" />
<theme name="FootballTheme" parent="SportTheme">
<item name="android:background">#color/brown</item>
</theme>
<theme name="BaseballTheme" parent="SportTheme">
<item name="android:background">#color/green</item>
</theme>
<theme name="SportTheme.SportLabel">
<item name="android:text">#string/label_sport</item>
</theme>
<theme name="FootballTheme.SportLabel">
<item name="android:text">#string/label_football</item>
<item name="android:textColor">#color/black</item>
</theme>
<theme name="BaseBallTheme.SportLabel">
<item name="android:text">#string/label_baseball</item>
<item name="android:textColor">#color/white</item>
</theme>
Thanks for any insight you can provide
To customize your UI with themes you need to define attributes you want to customize inside your themes and use references to these attributes in layouts (e.g. attr/backgroundColor).
There're three files in Android sources which are used for this purpose: attrs.xml, styles.xml and themes.xml. If you need some custom attributes for customization then you should declare them in attrs.xml. If you're going to use only predefined Android attributes then you don't need to create this file.
<declare-styleable name="SportTheme">
<attr name="customAttribute" format="color" />
<attr name="sportLabelStyle" format="reference" />
</declare-styleable>
The styles.xml file is used for defining sets of attribute values. For example you can define different style sets for each widget.
<style name="Widget.TextView.SportLabel" parent="#android:style/Widget.TextView">
<item name="android:textColor">#android:color/white</item>
<item name="android:textSize">20sp</item>
</style>
The themes.xml is the main file used for customizing. All themes are usually defined in this file. You can customize something in several ways. For example you can define a default value in the theme and reference it from a layout. Also you can define a reference to a style.
<style name="Theme.FootballTheme" parent="#android:style/Theme">
<!-- define value for predefined Android attribute -->
<item name="android:colorBackground">#android:color/white</item>
<!-- define value for custom attribute -->
<item name="customAttribute">#android:color/black</item>
<!-- define reference to a style -->
<item name="sportLabelStyle">#style/Widget.TextView.SportLabel</item>
</style>
layout.xml
<TextView
android:background="?android:attr/colorBackground"
android:textColor="?attr/customAttribute"
style="?attr/sportLabelStyle" />
Notice that style is used without the android namespace. That's not a typo.
So if you want to customize your layout using themes you can create several themes and define default values for attributes and attribute sets (styles) and reference these values using
?[android:]attr/attributeName
Sounds difficult but it's not really. You can use Android resources as an example of styling.
Please ask your question if something is not clear.
I have written a blog post about Themes which may help you. There is also a series of article on Custom Controls which explains how to create a custom control which is themeable, and this provides additional information about how Android themes work.
Related
I'm struggling with custom views defStyleAttr. (Short note I'm using a Preference as example cause it's the same way Google uses it)
So for almost every View or Preference that is provided by Android you'll have a constructor like this:
public SeekBarPreference(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.seekBarPreferenceStyle);
}
This defines the default style attribute to be R.attr.seekBarPreferenceStyle.
If you now look into the definition you'll find this:
<attr name="seekBarPreferenceStyle" format="reference" />
Until now everything is clear. But this attribute is somehow linked to a theme:
<resources>
<style name="PreferenceThemeOverlay">
<!-- ... -->
<item name="seekBarPreferenceStyle">#style/Preference.SeekBarPreference.Material</item>
<!-- ... -->
</style>
<!-- ... -->
</resources>
Which then finally links a style with the needed layout resource id that will be handed over to the super class to be inflated:
<style name="Preference.SeekBarPreference.Material">
<item name="android:layout">#layout/preference_widget_seekbar_material</item>
<!-- ... -->
</style>
Unfortunately I wasn't able to find a hint on how the theme PreferenceThemeOverlay is linked to the attribute seekBarPreferenceStyle.
So how are these two linked?
I finally found an answer that explained the basics you need to know.
Custom View
For the examples I use SeekBarPreference as a custom object (Preference and View are very similar)
So in short there are two ways to set your default style.
Either set a custom theme to your activity (or the like) that links something to your custom style (seekBarPreferenceStyle) or set the style directly by the style XML attribute.
Theme
styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="seekBarPreferenceStyle">#style/LINK_TO_DEFINING_STYLE</item>
</style>
AndroidManifest.xml
<activity android:name=".SomeActivity" android:theme="#style/AppTheme">
Style attribute
some.xml
<SeekBarPreference
[...]
style="#style/LINK_TO_DEFINING_STYLE"
[...] />
Androids Way
But I wanted to know exactly how all of it is connected to then work without any style attributes in XML files for SeekBarPreference or other Preference and View objects provided by Android.
So (via Android Studio) I followed Theme.AppCompat.Light.DarkActionBar down to its parent Theme.Holo.Light and look what I found there:
<!-- ... -->
<item name="seekBarPreferenceStyle">#style/Preference.Holo.SeekBarPreference</item>
<!-- ... -->
Linking to this style which links the layout resource:
<style name="Preference.Holo.SeekBarPreference">
<item name="layout">#layout/preference_widget_seekbar</item>
</style>
And to finally bring some more confusion to you again, Android seems to link a the Material style from the question instead of the Holo theme from the answer to the default theme (be it DeviceDefault or something else).
So if you got any clue on this please feel free to add to the comments :)
I want to offer my user two themes: light and dark. I want the background color of a certain layout to change based on whichever theme has been chosen.
Essentially, I want to have two themes like this:
<style name="Theme.Light">
<item name="myBackgroundColor">#ffffff</item>
</style>
<style name="Theme.Dark">
<item name="myBackgroundColor">#000000</item>
</style>
And then from my Activity, I would reference the variable like this:
<RelativeLayout
...
android:background="?myBackgroundColor"/>
How do I do that?
Ok I've figured out an answer of sorts. If anyone has a better answer, please post it.
You need to declare the attribute "myBackgroundColor" as styleable. E.G:
<declare-styleable name="TestStyleable">
<attr name="myBackgroundColor" format="color|reference" />
</declare-styleable>
I have no idea what the name refers to, it seems you can set it to anything. Once you have declared this block, you can use "myBackgroundColor" as I have done above.
A create a simple Theme as
<style name='one'>
<item name='android:textColor'>#eea</item>
<item name='android:textSize'>20sp</item>
</style>
However on viewing in the emulator the screen goes black.when i do not apply theme the screen has a white background .
what really happens here.i am just starting with android.
In addition ,if a apply a theme to my activity then the attributes of the theme applies to all components of my activity say button,textfields and edittexts .
why would i then write
android:textSize=?android:textSize
to reference value from the theme for any button in my layout when the same value would already be applying.
is the syntax above the correct way to reference an attribute from my theme to assign to attribute for any view in my layout.
thanks
tejinder
Yeah, so you need to do a little more reading.
Let's start with the basics,
You need to understand the differente betweent an Attribute, a Style, and a Theme.
An Attribute is something that can be styled. For instance: android:textSize is an attribute that can have any value.
A Style is a set of specific attributes that will be applied to a Widget. They are defined
in your /values/styles.xml
For instance:
<style name="normalTextThin" parent="android:Widget.Holo.Light.TextView">
<item name="android:gravity">left|center_vertical</item>
<item name="android:padding">8dp</item>
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">16sp</item>
</style>
The styles can be applied either as part of a theme or directly as theme-independent.
Theme-indepentent styling of a widget is like this:
<TextView
android:id="#+id/text"
style="#style/normalTextThin"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
You are then theming only that one TextView.
A Theme is a collection of Styles that can be applied to a part of your UI, such a a whole Activity, or your whole Application.
For instance:
<style name="AppTheme" parent="android:Theme.Light">
<item name="android:editTextStyle">#style/EditTextAppTheme</item>
<item name="android:buttonStyle">#style/ButtonAppTheme</item>
<item name="android:imageButtonStyle">#style/ImageButtonAppTheme</item>
</style>
Here, we are declaring that all EditText in your application will use the style named EditTextAppTheme, and so forth and on. When done like this, in order to actually have the theme be active, you declare it in the manifest:
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
That means that you are not required to declare the style on each widget you create.
<EditText
android:id="#+id/input"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="#string/hint_search">
<requestFocus />
</EditText>
That widget right there would already be styled using EditTextAppTheme without the need of you explicitely declaring so.
I recommend you try to read on what attributes can be styled, how to style them, and so forth and on.
If you don't want to though, it's fine, you can still get a lot done with the following tools for styling:
ActionBarStyleGenerator to help you create styles for the ActionBar.
Android Holo Colors to help you style standard widgets.
Hope that helps.
Additional Info
Let me clarify on the whole ?attr/attributeName
The ? means that the system will choose the specific attributeName value for the current Configuration (not specific to different themes). This should be used only when you want the value to be different on different configurations. For example:
?android:attr/actionBarSize
This line is a dimension, and it will be different not based on the current theme, but on the current device screen size and orientation (values, values-land, values-sw600dp).
It's important to know that specifying ?android: means you are accessing preset Android values, not yours. If you have or want to create and use your own attribute values for specific configurations, you must do the following:
Create a file named attrs.xml on your /values/ folder.
Declare the desired custom attribute:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<attr name="my_custom_attr" format="reference" />
</resources>
Declare a value for the custom attribute, let's say on your own theme.
<style name="AppTheme" parent="android:Theme.Light">
<item name="my_custom_attr">#resource_type/resource_name</item>
<item name="android:editTextStyle">#style/EditTextAppTheme</item>
<item name="android:buttonStyle">#style/ButtonAppTheme</item>
<item name="android:imageButtonStyle">#style/ImageButtonAppTheme</item>
</style>
And then you can use it on the Widget you'd like:
Hope that clears things out.
EDIT 2
For a better answer to your question, please update your question. And like I said, read more on how to properly create styles.
The Theme named 'one', what do you want to apply it to? An activity, a Widget, the whole Application?
How are you applying the theme? Show the lines of code where you specify the usage of theme 'one'.
Your theme as you specified is simply not a properly constructed theme/style.
<style name='one'>
<item name='android:textColor'>#eea</item>
<item name='android:textSize'>20sp</item>
</style>
This says absolutely nothing, and it is definitely not suitable for an Activity-level theme. The reason you specify a parent is so your theme can inherit all of the attributes from the parent, and then you specifiy which ones to change.
For instance, if you want to use your theme and have a light background, do this:
<style name='one' parent="android:Theme.Holo.Light>
<item name='android:textColor'>#eea</item>
<item name='android:textSize'>20sp</item>
</style>
But even here, despite the fact that it will apply, you don't want to have the same text color and size for the whole application do you? That'd be nonsense, different text color and sizes account for a big part of the user experience, so rather than setting those values from what we can refer to as the main style, we can create substyles and apply them to certain widgets.
I can't really go any more detailed that what I already have, the above explains how to accomplish Widget-specific styling, and activity/application level theming.
For a complete start-up guide, read the Android Developer Site, try the test styles declared there, see how they work, and until then try to create your own, don't try to create something out of nowhere if no reading has been made.
I've got an app with two themes (dark and light) that can be selected at runtime. This works. I also have a ListView with rows that can have one of three different layouts, each of which has a style (say, different colors). This also works. But I can't get these two features to work together. I really need six different styles, three for one theme (dark) and three for the other (light), but I can't figure out how to choose a style for a list item based on the current theme, or get that effect any other way by using XML files. My three layouts each point to a custom theme that sets the color, but that overrides whatever theme I've got set. Themes can only contain items that are "styleable", so I can't put my own custom items in there. There may be a way to do this programmatically, but I was hoping to do it declaratively. Any ideas?
Thanks to wingman for the hint. My situation involved colors, which are a bit more complicated, so I'll write up my solution here.
I have two themes (light and dark) which the user can choose from in the Settings screen. I have a ListView which can have two types of rows (plain and note), each with its own styling. Firstly each layout needs to point to a style:
<TextView style="#style/PlainItemText" ... />
(or NoteItemText) and we need to define the styles:
<style name="PlainItemText">
<item name="android:textSize">#dimen/list_item_font_size</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">?plainTextColor</item>
</style>
The text color can't be fixed because it depends on the selected theme. We must create a custom attribute and refer to it with a question mark, as above. We define the attribute in res/values/attrs.xml:
<!-- Attributes we use to set the text color of the various list items. -->
<attr name="plainTextColor" format="reference|color"/>
<attr name="noteTextColor" format="reference|color"/>
We can then define the various colors. Here we have two styles and two themes, so we need four color state lists, each in its own file under res/color. For example, here's res/color/plain_text_color_dark.xml:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:color="#android:color/white"/>
<item android:state_selected="true" android:color="#android:color/black"/>
<item android:state_focused="true" android:color="#android:color/black"/>
<item android:state_pressed="true" android:color="#android:color/black"/>
<item android:color="#android:color/white"/>
</selector>
The selected/focused/pressed colors are the same in all these files because they're over the highlight color. Be careful with the state_window_focused version. It didn't behave as advertised, and I had to set it to the default color (the last line above) in all cases. Now we need to create our themes and bind the attributes to one of the colors. These lines go into res/values/themes.xml:
<style name="Theme.Dark" parent="android:Theme">
<item name="plainTextColor">#color/plain_text_color_dark</item>
<item name="noteTextColor">#color/note_text_color_dark</item>
</style>
<style name="Theme.Light" parent="android:Theme.Light">
<item name="plainTextColor">#color/plain_text_color_light</item>
<item name="noteTextColor">#color/note_text_color_light</item>
</style>
Finally we pick a theme at run-time, in an Activity's onCreate() method, before calling super.onCreate():
if (isDarkTheme) {
activity.setTheme(R.style.Theme_Dark);
} else {
activity.setTheme(R.style.Theme_Light);
}
Note that I don't take into account newer themes like Holo, so my app looks old on Honeycomb and later. I'll fix that at some point, but it wasn't a regression here.
A twist in my case is that some Activities have a larger title bar in order to fit some buttons. In principle I should have created four themes, a light and dark for a narrow title and a light and dark for a fat title. But instead I created a mix-in style:
<!-- Mix-in style for activities. -->
<style name="ButtonTitleBar">
<item name="android:windowTitleSize">44dp</item>
</style>
and procedurally add it to whatever theme I'm using. This code goes right after the above setTheme() calls:
if (buttonTitleBar) {
// Mix in this other style.
Resources.Theme theme = activity.getTheme();
theme.applyStyle(R.style.ButtonTitleBar, true);
}
I didn't see this documented anywhere, and I don't know if it's legit, but the code of Activity.getTheme() implies that it should work fine, and it has worked in all my testing. This can help avoid the combinatorial explosion of themes that you can find in the standard Android theme list.
It's a long time ago that Lawrence Kesteloot published his solution in 2012. Now it is six years later, a am new in Android and try to solve the similar problem:
How can I exchange the whole style of the application by just exchanging one theme?
This is a generalisation of Lawrences issue how to organise two exchangeable themes.
I figured out a solution based on Lawrence's and going a step further.
(Not claiming it is the perfect solution, yet an improvement.)
Lawrence figured out the power of user defined attributes to reach this goal. He uses them to address colours depending on the the currently selected theme.
While this is working it still requires to define attributes for each and every property. It doesn't scale well. So why not bundling the properties into styles and themes and using the same mechanism?
This results in a master theme, that is defining child themes and styles.
res/values/attrs.xml
<resources>
...
<attr name="mainViewTheme" format="string"/>
<attr name="asideViewTheme" format="string"/>
...
</resources>
When defining the attribute to set a theme, there is no special format for it. The format string does it.
res/values/styles.xml
<style name="MasterTheme">
...
<item name="mainViewTheme">#style/MainViewTheme</item>
<item name="asideViewTheme">#style/AsideViewTheme</item>
...
</style>
<style name="MainTextTheme">
...
</style>
<style name="MainViewTheme">
...
</style>
res/layouts/main.xml
<TextView
android:theme="?mainViewTheme"
...
By exchanging the master theme all styles are adjusted. It still requires the definition of a handful of theme attributes and then does a powerful job. Setting up attributes for every property is not required any more.
I'm making out a few menus for my Android App and throughout there is a standard textview that is repeated 5 times, only changing the android:text tag each time, everything else is the same.
There are a good number of properties on this and it feels very inefficient to be copy/pasting all these for each of the textviews.
Is there a way I define the common properties just once and add them to each TextView element?
Yes you can define a style. Create a file in your values res folder names styles.xml and add something like this:
<resources>
<style name="my_header_text">
<item name="android:textStyle">bold</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">#android:color/white</item>
</style>
</resources>
This defines the style. In your layout you might have a field like this:
<TextView
android:id="#+id/my_title"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="#style/my_header_text"
android:layout_centerVertical="true"
android:layout_marginLeft="5dip"/>
Notice the style statement refers to the style defined above. Styles can contain almost any property. Read up on them here.