I want to give the user the choice between a few different themes, and was wondering if this is an alright way of doing things. I did a little test with this method and it worked, but I think there may be better ways and think it may cause some problems later on, so wanted to ask.
I was thinking of creating a different layout for each theme, and in onCreate just having a switch for the setContentView() method. I'd load a saved SharedPreference value (integer) first and depending on what that value was display the corresponding layout. Obviously the user could change the SharedPreference value with a button or something.
As these layouts would be basically the same but with different colours, I'd want to use the same IDs for my TextViews and other Views in each layout file. My main question is would this cause problems?
Sorry for the wall of text with no code. I'd just like to get a general idea of good practice for this situation. Thanks in advance.
I actually have this feature in my application and additionally, I allow users to change theme at runtime. As reading a value from preferences takes some time, I'm getting a theme id via globally accessible function which holds cached value.
As already pointed out - create some Android themes, using this guide. You will have at least two <style> items in your styles.xml file. For example:
<style name="Theme.App.Light" parent="#style/Theme.Light">...</style>
<style name="Theme.App.Dark" parent="#style/Theme">...</style>
Now, you have to apply one of these styles to your activities. I'm doing this in activitie's onCreate method, before any other call:
setTheme(MyApplication.getThemeId());
getThemeId is a method which returns cached theme ID:
public static int getThemeId()
{
return themeId;
}
This field is being updated by another method:
public static void reloadTheme()
{
themeSetting = PreferenceManager.getDefaultSharedPreferences(context).getString("defaultTheme", "0");
if(themeSetting.equals("0"))
themeId = R.style.Theme_Light;
else
themeId = R.style.Theme_Dark;
}
Which is being called whenever preferences are changed (and, on startup of course). These two methods reside in MyApplication class, which extends Application. The preference change listener is described at the end of this post and resides in main activity class.
The last and pretty important thing - theme is applied, when an activity starts. Assuming, you can change a theme only in preference screen and that there's only one way of getting there, i.e. from only one (main) activity, this activity won't be restarted when you will exit preference screen - the old theme still will be used. Here's the fix for that (restarts your main activity):
#Override
protected void onResume() {
super.onResume();
if(schduledRestart)
{
schduledRestart = false;
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage( getBaseContext().getPackageName() );
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}
scheduledRestart is a boolean variable, initially set to false. It's set to true when theme is changed by this listener, which also updates cached theme ID mentioned before:
private class ThemeListener implements OnSharedPreferenceChangeListener{
#Override
public void onSharedPreferenceChanged(SharedPreferences spref, String key) {
if(key.equals("defaultTheme") && !spref.getString(key, "0").equals(MyApplication.getThemeSetting()))
{
MyApplication.reloadTheme();
schduledRestart = true;
}
}
sp = PreferenceManager.getDefaultSharedPreferences(this);
listener = new ThemeListener();
sp.registerOnSharedPreferenceChangeListener(listener);
Remember to hold a reference to the listener object, otherwise it will be garbage colleted (and will cease to work).
If you are using Material Components themes and followed Light and Dark theme guidelines then you can do it from AppCompatDelegate. These themes can be changed/applied at run time without restarting your application.
private fun handleThemeChange(theme: String) {
when (newTheme) {
getString(R.string.light) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
getString(R.string.dark) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
getString(R.string.system) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
You can also change dynamically theme using:
ContextThemeWrapper w = new ContextThemeWrapper(this, <newTHEMEId>);
getTheme().setTo(w.getTheme());
Before onCreate for each activity.
It does work if you do it this way, and I don't think it would cause any problem, but it seems like a lot of hassle (you have to multiply all your layouts by all the themes you want to add. If later you want to modify a resource in a layout, you'll have to modify it in all the themes. You're definitely bound to forget one)
Why not using the Styles and Themes feature of Android?
They can be applied to the whole activity easily:
<activity android:theme="#style/my_theme">
So that when you detect a change in the SharedPreferences value you use (button on a preference activity, or something) you can just switch the style. Or better, you can set the style to read your preference value at runtime (when creating the activity) and apply the correct style/theme accordingly.
Related
I have an Activity that I have already implemented sometime ago.
It involves around making a in app purchase, so all the logic is relatively self contained. it doesn't need to care about anything else.
Now, i wish to make that Activity to optionally show up in a dialog in some other activity. Is there a quick way to do that? I still need to keep the old behavior however, where the activity show up as a regular screen.
So is there someway that I could launch the activity with that make it show up as a dialog?
Thanks
You cant show activity as dialog.
Your options are:
1: Open the other activity with some boolean extra like "showDialog", true
Intent intent = new Intent(this, OtherActivity.class);
intent.putExtra("showDialog", true);
and in the other activity in (for example) onCreate:
Boolean showDialog = getIntent().getExtras().getBoolean("showDialog");
if (showDialog) {
// Code to show dialog
}
2: Create a DialogFragment and show it in your original activity. This custom DialogFragment you can use on both activities
https://guides.codepath.com/android/Using-DialogFragment
Probably your cleanest option depending on how complex your Activity is, is to create a new DialogFragment based on your current activity.
A DialogFragment is basically a Fragment, so has a relatively similar set of lifecycle callbacks to your Activity so it shouldn't be too difficult to re-work as a DialogFragment.
If the in-app purchase framework has specific callback requirements with an Activity then you will need to take that into account.
Another separate option would be to mock the appearance of a Dialog, by creating an Activity that may be transparent around the border of the main content.
Just Inflate the layout one button click on onCreate Method.
WhAT I WILL SUGGEST IS try alert box and in place of normal layout inflate you activity layout .
these might help
The easiest way to do that is to apply a dialog theme to the activity:
<activity android:theme="#style/Theme.AppCompat.Dialog" />
Or in the code:
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.Theme_AppCompat_Dialog);
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
}
You can customize parameters of the theme in styles.xml, e.g. dim enabled/disabled, click outside behavior.
The crucial point is to perform setTheme() before super.onCreate(), because Theme is immutable, once set through super.onCreate() it cannot be mutated later.
I want to develop an application that, at the beginning, checks a variable's value and, basing on this value, starts the activity A or the activity B, something like this:
protectec void onCreate(...){
boolean b = checkVariable();
if(b){
startActivityA();
} else {
startActivityb();
}
}
What I'm doing
This is the method I have currently implemented:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_limitation);
varChecker = new VarChecker(this);
if(varChecker.getRemainingUses() <= 0){
limitReached();
} else if(varChecker.isFirstTime()){
firstTime();
} else {
startMainActivity();
}
}
This is an activity that must be shown the first time the application is executed. Else, depending on the getRemainingUses() result, it must start the activity A or the activity B automatically.
The question
Is there any way to do what I want, without the need of create a new Activity, specially to avoid the super.onCreate(savedInstanceState) and the setContentView(R.layout.activity_limitation)?
Is there any way to do what I want, without the need of creating a new
Activity, specially to avoid the super.onCreate(savedInstanceState)
and the setContentView(R.layout.activity_limitation)?
I had a similar problem some time ago. As far as I know, It's not possible to avoid calling super.onCreate(savedInstance) and setContentView(R.layout.activity_limitation). It's design of the Android activity.
I see the following possible solutions:
if you want to choose an appropriate activity by e.g. clicking a
button, then you just need to create a new Intent basing on a
variable and there's no problem.
if you want to choose an activity in
a different moment of the flow - e.g. during the application start you can:
create a single activity for two options, use fragments and switch between them
create "dummy" activity with an empty layout, create an intent and switch to an appropriate activity basing on a variable and finish "dummy" activity. Please note that this solution is a kind of workaround and it's worth to spend some time to figure out something better, but I'm not sure if there is a recommended solution for such use cases.
In any application the add/edit will be comparatively having lesser inputs. I have seen that the application, esp., calendar, are using clever strategy to show these as simple dialog, so that the user may not notice that there is empty space in the designed form
As shown below
My question is, how to make it happen?
What I'm doing is I extend DialogFragment:
public class AboutFragment extends DialogFragment { ... }
I also have an activity that contains that fragment. And when the dialog/activity needs to be called, this method decides how to display it:
public static void showAbout(Activity parent) {
if (isTablet) {
AboutFragment aboutFragment = AboutFragment.newInstance();
aboutFragment.setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogTheme);
DialogUtils.showDialog(parent, aboutFragment);
} else {
Intent aboutIntent = new Intent(parent, AboutActivity.class);
parent.startActivity(aboutIntent);
}
}
How to decide whether it is a tablet, is totally up to you.
This technique is explained in the documentation.
In my opinion the best approach here is to use
<!-- Theme for a window without an action bar that will be displayed either full-screen
on smaller screens (small, normal) or as a dialog on larger screens
(large, xlarge). -->
"android:Theme.Holo.Light.DialogWhenLarge.NoActionBar"
The best/easiest solution I've found is to always use an Activity, and based on screensize (and version), change your Theme parent.
in res/values/themes.xml
<style name="Detail.Theme" parent="#android:style/Theme.Holo.Light" >
...
</style>
and in res/values-large/themes.xml
<style name="Detail.Theme" parent="#android:style/Theme.Holo.Light.DialogWhenLarge" >
...
</style>
use Context.setTheme method to set them programmetically. As the doc says
this should be called before any views are instantiated in the Context
(for example before calling.
So, to switch between themes need to call setTheme before onCreate
public void onCreate(Bundle savedInstanceState) {
// check screen size
setTheme(dialogTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
As #StinePike answered, setting a dialog theme programatically doesn't do any use (to me), as it shows a wierd black screen behind the dialog, rather than a dimmed background (as shown in the question). This is obviously a bug.
Instead of trying to set it programatically, or in style.xml, and pretty much everywhere except for AndroidManifest.xml, I did the reverse, which has worked for me.
(the solution which I took from the marvelous solution of the above issue)
The simplest solution (that works) as follows:
1. Make the activity a dialog by default through AndroidManifest.xml:
e.g., in the AndroidManifest.xml:
<activity
android:name="com.example.MyActivity"
android:label="#string/title_activity_mine"
android:theme="#android:style/Theme.DeviceDefault.Dialog">
...
</activity>
2. On starting the activity, set the theme to default if device is not a tablet.
if (!(isTablet(this)) {
setTheme(defaultTheme);
}
super.onCreate(savedInstanceState);
...
Note:
solution will work with custom styles defined in style.xml.
Ref:
How to detect device is Android phone or Android tablet?
Dialog with transparent background in Android
Issue 4394 - android - setTheme() does not work as expected
PS: final app on tablet and phone is as follows:
Use a DailogFragment and then control how its shown with setShowsDialog()
Could someone tell me how i can switch the the theme from holo to holo light in my application on runtime ?
I would like to have two buttons in settings to choose light or black theme.
How can it be set applicationwide and not only for the activity ?
I already tried a few things with setTheme() but i wasn't able to change the theme when i click a button.
This is my Settings activity where i would like to set the theme:
public class SettingsActivity extends Activity {
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(android.R.style.Theme_Holo_Light);
super.onCreate(savedInstanceState);
setContentView(R.layout.settings);
}
well this works and my Theme is set but as i am saying i would like to change it systemwide by pressing a button.
Thanks !
As you can see theme is setting on the onCreate() and before setContentView(). So you should call the oncreate() method again when you want to change the theme. But the onCreate() function will be called only once in the life cycle of an Activity.
There is a simple way to do this. I am not sure that it is the best way.
Suppose you want to apply new theme to Activity 1 on a button click.
inside the onClick event
Save the theme to be applied such that it should be retained even after the application restart (preferences or static volatile variables can be used).
Finish the current activity (Activity 1) and call a new activity (Activity 2).
Now in Activity 2
Call Activity 1 and finish current activity (Activity 2).
In Activity 1
Apply the saved theme inside onCreate.
Hope it is not confusing.. :)
You cannot change the theme of other applications (thank goodness).
The only way to somewhat accomplish this would be to create your own build of the operating system with your own theme as the device default theme. However, applications that do not use the device default theme (i.e. they explicitly set the theme to Holo, Holo light, etc) will not get the device default theme.
Edit- To accomplish this application-wide using a base Activity, create an Activity that looks like this:
public class BaseActivity extends Activity {
private static final int DEFAULT_THEME_ID = R.id.my_default_theme;
#Override
public void onCreate(Bundle savedInstanceState) {
int themeId = PreferenceManager.getDefaultSharedPreferences(context)
.getInt("themeId", DEFAULT_THEME_ID);
setTheme(themeId);
super.onCreate(savedInstanceState);
}
}
Then all of your Activities should extend this BaseActivity. When you want to change the theme, be sure to save it to your SharedPreferences.
I'm currently trying to learn to work with Views and states. I'm normally able to set its color in functions like in the following:
View mColorRegion = findViewById(R.id.color_region);
mColorRegion.setBackgroundColor(Color.CYAN);
However, I can't seem to be able to set the color in an onRestoreInstanceState(), as
mColorRegion.setBackgroundColor(savedInstanceState.getInt("color"));
However, working with the same View as a TextView, I'm able to restore text as in the following:
TextView mText = (TextView)findViewById(R.id.color_region);
mText.setText(savedInstanceState.getString("text");
What's the difference, and how I can set the background color in onRestoreInstanceState()?
EDIT: Since the original post, I've noticed two things:
1) mColorRegion.setBackgroundColor(Color.CYAN) doesn't seem to work in onCreate() either.
2) Even though the following function correctly changes the View color when a button is pressed, it doesn't work if I call it directly from onRestoreInstanceState():
public void centerButton1(View clickedButton) {
mColorRegion.setBackgroundColor(Color.CYAN);
}
Hmm...
So I found a "half-solution". If you add the following line to AndroidManifest.xml, it will preserve the color during orientation changes:
android:configChanges="orientation|screenSize"
However, this still doesn't answer why I can set the text but not the color in onRetoreInstanceState() or onCreate()...
Many people frown upon using android:configChanges="orientation|screenSize", like Google's Dianne Hackborn. For one, It will make the process of switching between multiple layouts for your app very difficult (for example, if you want one layout for landscape, and one for portrait) since you'll have to do all of the work that Android normally does automatically for you, in onConfigurationChanged().
Anyway, I also had this sort of problem. I was creating a DialogPreference, and upon rotation I couldn't change the progress of a SeekBar in onRestoreInstanceState(savedInstanceBundle)...so this is what I suggest if you cannot use onCreate(savedInstanceBundle) or onActivityCreated(savedInstanceBundle) (for fragments) to restore the state of your view objects:
1) Make a private class member called "mRestoredBGColor",
private int mRestoredBGColor = -1;
2) In onRestoreInstanceState():
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
mRestoredBGColor = savedInstanceState.getInt("key_you_used_in_onSaveInstanceState");
}
3) Then in onResume(), because onRestoreInstanceState() is called after onStart(), but before onResume()
#Override
public void onResume(){
super.onResume();
if(mColorRegion != null && mRestoredBGColor != -1){
mColorRegion.setBackgroundColor(mRestoredBGColor);
mRestoredBGColor = -1; //to make sure it only sets this once per rotation.
}
}
Hope this helps somebody. I believe there is always another way (except when you want quickly rotating WebViews...) than using android:configChanges="orientation|screenSize".