So here is the situation:
I have DogActivity and FavoritesActivity. DogActivity is just a ListView. When you click on a Dog in the list, it takes you to FavoritesActivity.
I want to have a number of themes ready to go. They don’t need to be dynamically generated. They can already exist in XML form.
Depending on which dog the user selects from the list, I want to have the FavoritesActivity shown in one of my pre-existing themes.
I hear talks about ContextWrapper, but I am not sure how to apply it. Any thoughts on how I may accomplish this?
Details:
Here is the usual single theme:
for v21/styles.xml
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="android:colorControlHighlight">#color/colorAccentLight</item>
<item name="android:colorControlNormal">#color/colorAccent</item>
<item name="android:itemTextAppearance">#style/AppTheme.itemTextStyle</item>
<item name="popupMenuStyle">#style/PopupMenu.MyAppTheme</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">#android:color/transparent</item>
<item name="android:colorControlHighlight">#color/colorAccentLight</item>
</style>
<style name="AppTheme.itemTextStyle" parent="#android:style/TextAppearance.Widget.IconMenu.Item">
<item name="android:textColor">#color/colorPrimary</item>
</style>
</resources>
for styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
Want I want to do:
Essentially I just want to change the colorPrimary, colorPrimaryDark and colorAccent on the fly and have all the styles and themes and XML layouts that use them to change. So if I can change those colors before I launch FavoritesActivity then that would solve my problems.
You can just send the dog type as an Intent extra, and then use the setTheme() method to set the appropriate Theme.
For this example, suppose you just have two Themes:
<style name="AppThemeOne" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
<style name="AppThemeTwo" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimaryCustom</item>
<item name="colorPrimaryDark">#color/colorPrimaryDarkCustom</item>
<item name="colorAccent">#color/colorAccentCustom</item>
</style>
Then, in DogActivity, set an Intent Extra to the Dog type the user selected from the ListView:
Intent intent = new Intent(DogActivity.this, FavoritesActivity.class);
intent.putExtra("dog_type", "terrier");
startActivity(intent);
Then, in FavoritesActivity, load the correct Theme:
#Override
protected void onCreate(Bundle savedInstanceState) {
String dogType = getIntent().getStringExtra("dog_type");
if (dogType.equals("terrier")) {
setTheme(R.style.AppThemeOne);
} else {
setTheme(R.style.AppThemeTwo);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.favorites_layout);
//.....
}
I've accomplish it quite simply on my latest project, you just have to set the values on the theme via Java. Like the following code:
public class FavoritesActivity extends AppCompatActivity { // it can be Activity too
#Override
public void onCreate(Bundle savedInstanceState) {
if( ... check condition to change theme ) {
// this will replace every value from FavoritesActivity theme by the
// the values on `other_style` theme.
getTheme().applyStyle(R.style.other_style, true);
}
// call super AFTER applying the theme
super.onCreate(savedInstanceState);
.. carry on your normal stuff
}
that's very useful because you can very easily replace just a few values and keep the rest as the original, or change everything from the original. It all depends what argument you pass to the applyTheme method.
Also is great you don't have to mock with ContextThemeWrapper. The values are there on the theme and that's it.
https://developer.android.com/reference/android/content/res/Resources.Theme.html#applyStyle(int, boolean)
You can use this.recreate() method for this.
Based on https://stackoverflow.com/a/29722976/7547681 answer.
Related
I am trying to implement a theme change in Android since I need to change the look and feel of the whole application dynamically as a result of some asynchronous action.
I have several themes like this:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="preferenceTheme">#style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="YellowTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">#color/colorYellowLight</item>
<item name="colorPrimaryDark">#color/colorYellowDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="preferenceTheme">#style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="GreenTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">#color/colorGreenLight</item>
<item name="colorPrimaryDark">#color/colorGreenDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="preferenceTheme">#style/PreferenceThemeOverlay.v14.Material</item>
</style>
and I am using MutableLiveData to store some object which is used as a basis for the theme change:
val dayTypeDTO = MutableLiveData<DayTypeDTO>()
I am then observing this object and when it's value changes, I need to change the app theme:
cache.dayTypeDTO.observe(this, Observer { dto ->
if (dto != null) setTheme(dto.theme)
})
The problem is that the theme change should happen after the view is inflated. Otherwise, it is not applied. But since I am registering the observers after the view is created and due to the dynamic change which cannot be predicted, I cannot really do it this way.
I know I can call activity.recreate() but this just results in an infinite loop.
Can someone suggest how to achieve this theme change so all components using colorPrimary are updated as a result of this observable action? Preferably other then changing them manually one by one.
Add an onGlobalLayoutListener to your view to ensure that the layout is inflated before setting the theme on it:
https://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener
First off, I'm brand new to Xamarin Forms so be gentle. I'm needing to get rid of the bottom line that shows for Entry input in Android. It displays fine in iOS. I've done some research and found that this:
<style name="NoBaseline" parent="android:style/Widget.EditText">
<item name="android:background">#D3D3D3</item>
</style>
should do the trick by simply just making the underline the same color as the background color of the Entry box. I've placed this code in my styles.xml file, but I feel like I need to apply this style somewhere, but I'm just not sure where. Any help for a newb would be greatly appreciated.
Here's the whole file:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<style name="MyTheme" parent="MyTheme.Base">
</style>
<style name="MyTheme.Base"
parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimary">#2196F3</item>
<item name="colorPrimaryDark">#1976D2</item>
<item name="colorAccent">#FF4081</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:datePickerDialogTheme">#style/AppCompatDialogStyle</item>
</style>
<style name="NoBaseline" parent="android:style/Widget.EditText">
<item name="android:background">#D3D3D3</item>
</style>
<style name="AppCompatDialogStyle"
parent="Theme.AppCompat.Light.Dialog">
<item name="colorAccent">#FF4081</item>
</style>
<style name="Splash" parent ="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowDisablePreview">true</item>
</style>
<style name="MyTheme.Splash"
parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">#drawable/splash_screen</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
</resources>
In case you didn't see this while you were researching, here is a solution posted to Github by dkudelko which is probably a little simpler if you are just trying to remove the underline.
To do this simply create a class in you Android project called NoUnderlineEntry then add this code.
using <YourApp>.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Entry), typeof(NoUnderlineEntry))]
namespace <YourApp>.Droid
{
public class NoUnderlineEntry : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
Control?.SetBackgroundColor(Android.Graphics.Color.Transparent);
}
}
}
After you replace with the name of your app, you will have created a custom renderer that overrides the default Entry on Android to set the control background color to transparent.
Here is documentation for creating custom renderers for Entry.
**Note: I haven't personally tested it, but multiple people commented that it works.
I have my activity theme as below set.
<style name="MyTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorPrimary</item>
<item name="android:colorButtonNormal">#color/colorPrimary</item>
</style>
The activity have a simple FragmentDialog that is started using (Kotlin code)
MyDialogFragment().show(supportFragmentManager, MyDialogFragment.TAG)
The MyDialogFragment does have a Button. Hence I expect the color of the Button is colorPrimary as per the theme. However the color of the Button (on v21) is only grey)
This works on Marshmallow (i.e. v23) and not Lollipop (v21). I haven't tried v22. ... So I guess the v21 doesn't automatically inherit the theme from the activity.
For KitKat and below, this doesn't apply, as it doesn't use 'android:colorButtonNormal'
How should I get my FragmentDialog get the theme that I set on my activity?
I found a way to do it, which is to explicitly define my FragmentDialog theme as below, on top of defining my activity theme.
<style name="MyTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorPrimary</item>
<item name="android:colorButtonNormal">#color/colorPrimary</item>
</style>
<style name="MyDialogTheme" parent="Theme.AppCompat.Light.Dialog">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorPrimary</item>
<item name="android:colorButtonNormal">#color/colorPrimary</item>
</style>
Then I need to explicitly set it from my FragmentDialog onCreate().
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.MyDialogTheme)
}
Note: It has to be in onCreate as mentioned in https://stackoverflow.com/a/26582301/3286489
I'm still open to more elegant answer if there's any out there.
Once I switched from ActionBarActivity to AppCompatActivity one of the only changes I did was add this line:
<item name="windowNoTitle">true</item>
Here is my entire styles.xml file:
<style name="gptheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">#color/primary</item>
<item name="colorPrimaryDark">#color/primaryDark</item>
<item name="colorAccent">#color/accent</item>
<item name="android:windowContentOverlay">#null</item>
</style>
<style name="ThemeNoActionBar" parent="gptheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
Now, all the new AppCompatDialogs in my code, (which were formerly Dialog d = new Dialog(mContext), all have no titles even though I use setTitle().
Obviously the change I made to the titles specified windowNoTitle but that should only affect the parent activity. I would think, anyway.
How exactly does this new feature work?
// style.xml
<style name="DialogActivity" parent="Theme.AppCompat.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="colorAccent">#fff</item>
</style>
// manifest.xml
<activity
android:name=".MyPopupActivity"
android:theme="#style/DialogActivity"/>
// activity
import android.app.Activity;
public class MyPopupActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_popup);
}
}
My main preference activity is set to "#android:style/Theme.Light". One of my preferences is a DialogPreference who's Dialog contains a ListView. The ListView's dialog is dark grey (because DialogPreference uses AlertBuilder which creates dark grey dialogs) and the text in the list is black (because Theme.Light causes listViews to have black text). Is there an easy way to either get the ListView to behave with the same style as the dark dialog? Or to get the dark dialog to behave with the same style as the light activity?
EDIT:
Based on Merlin's comments, it seems like what I should try to do is create a LightDialog Theme. In order to do this I tried: 1. extending android's Theme.Light and adding the dialogy properties from Theme.Dialog
<style name="Theme.LightDialog" parent="#android:style/Theme.Light">
<item name="android:windowFrame">#null</item>
<item name="android:windowTitleStyle">#android:style/DialogWindowTitle</item>
<item name="android:windowBackground">#android:drawable/panel_background</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">#null</item>
<item name="android:windowAnimationStyle">#android:style/Animation.Dialog</item>
</style>
... and 2. extending android's Theme.Dialog and adding the lighty properties from Theme.Light.
<style name="Theme.LightDialog" parent="#android:style/Theme.Dialog">
<item name="android:windowBackground">#drawable/screen_background_light</item>
<item name="android:colorBackground">#android:color/background_light</item>
<item name="android:colorForeground">#androidcolor/bright_foreground_light</item>
<item name="android:colorForegroundInverse">#android:color/bright_foreground_light_inverse</item>
<item name="android:textColorPrimary">#android:color/primary_text_light</item>
<item name="android:textColorSecondary">#android:color/secondary_text_light</item>
<item name="android:textColorTertiary">#android:color/tertiary_text_light</item>
<item name="android:textColorPrimaryInverse">#android:color/primary_text_dark</item>
<item name="android:textColorSecondaryInverse">#android:color/secondary_text_dark</item>
<item name="android:textColorTertiaryInverse">#android:color/tertiary_text_dark</item>
<item name="android:textColorPrimaryDisableOnly">#android:color/primary_text_light_disable_only</item>
<item name="android:textColorPrimaryInverseDisableOnly">#android:color/primary_text_dark_disable_only</item>
<item name="android:textColorPrimaryNoDisable">#android:color/primary_text_light_nodisable</item>
<item name="android:textColorSecondaryNoDisable">#android:color/secondary_text_light_nodisable</item>
<item name="android:textColorPrimaryInverseNoDisable">#android:color/primary_text_dark_nodisable</item>
<item name="android:textColorSecondaryInverseNoDisable">#android:color/secondary_text_dark_nodisable</item>
<item name="android:textColorHint">#android:color/hint_foreground_light</item>
<item name="android:textColorHintInverse">#android:color/hint_foreground_dark</item>
</style>
Both of these attempts failed because they use non-public attributes. Any suggestions on how to create a LightDialog theme?
You can inherit defult themes when creating styles in Android.
Please read the documentation on Applying Styles and Themes specifically the section on inheritance
You should be able to take Theme.light and correct any issues that you have with it.
You may find that different vendors alter the themes on their devices so if you are targeting a broad range of hardware then you may be better creating a full theme to ensure that your app is consistent on all platforms.
UPDATE
As stated in this answer Consistent UI color in all Android devices there are public and non-public attributes. The answer provides a link to a list of public attributes however kernel.org is still down so you will need to dig through the source for core/res/res/values/public.xml
You can specify style of DialogPreference's dialog using android:alertDialogTheme (supported starting from API 11) them property of preferences activity:
<style name="PreferencesActivityTheme" parent="#style/Theme.AppCompat.Light.DarkActionBar">
<item name="android:alertDialogTheme">#style/Theme.MyDialog</item>
</style>
<style name="Theme.MyDialog" parent="#style/Theme.AppCompat.Light.Dialog">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
I have a solution how you can do it programmatically (I think its the easier way):
public class CustomDialogPreference extends DialogPreference {
...
#Override
protected void showDialog(Bundle state) {
super.showDialog(state);
//changing color of divider
int divierId = getDialog().getContext().getResources()
.getIdentifier("android:id/titleDivider", null, null);
View divider = getDialog().findViewById(divierId);
divider.setBackgroundColor(getContext().getResources().getColor(R.color.light_orange));
//changing color of title textview
int titleId = getDialog().getContext().getResources()
.getIdentifier("android:id/alertTitle", null, null);
TextView title = (TextView) getDialog().findViewById(titleId);
title.setTextColor(getContext().getResources().getColor(R.color.light_orange));
}
...
}