I know there is a way to set themes by defining in styles.xml and use it like that
setTheme(android.R.style.MyTheme);
However, I want to get themes from another app which I developed as well. I know the resources names and actually I am able to get theme id with this code block;
Resources res = getPackageManager().getResourcesForApplication("com.example.theme");
int resThemeId = res.getIdentifier("my_theme","style","com.example.theme");
When I debug, I see that resThemeId is not zero.
Then, I need the final command to set this theme. Before super.onCreate() function, I try to implement this method but it seems it is not working
setTheme(resThemeId);
But instead of this, if I write below statement, I works fine
setTheme(android.R.style.Theme_Holo_Light);
So, what should I do to use a theme from different package resource?
So, what should I do to use a theme from different package resource?
You shouldn't do this for many reasons. I wrote a simple project that shows that it is indeed possible as long as the package contains the resources your activity uses.
See: https://github.com/jaredrummler/SO-41872033
Basically, you would need to return the package's resources from the activity:
public class MainActivity extends AppCompatActivity {
Resources resources;
#Override protected void onCreate(Bundle savedInstanceState) {
int themeResId = getResources().getIdentifier("AppTheme", "style", "com.example.theme");
if (themeResId != 0) {
setTheme(themeResId);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override public Resources getResources() {
if (resources == null) {
try {
resources = getPackageManager().getResourcesForApplication("com.example.theme");
} catch (PackageManager.NameNotFoundException e) {
resources = super.getResources();
}
}
return resources;
}
}
This is just to show that it is possible. Again, I recommend that you avoid this.
As already mentioned in a comment you can access resources from other applications, but using another applications theme will not work.
Since having proof is always a good thing let's have a look at the source code (I used API 24 sources)
Calling setTheme() on an Activity will invoke initializeTheme() in the ContextThemeWrapper parent class, which will end up calling onApplyThemeResource(..) which in turn will try to load the actual theme data from resources by calling theme.applyStyle(resId, true)
Following the chain through the wrapper Resources.Theme we can see in ResourcesImpl.ThemeImpl the following, where AssetManager is called to load the style into the theme:
void applyStyle(int resId, boolean force) {
synchronized (mKey) {
AssetManager.applyThemeStyle(mTheme, resId, force);
mThemeResId = resId;
mKey.append(resId, force);
}
}
This is where you try and fail to load the foreign theme from your other app.
Since most of the methods you would need to use are static calls or package local methods it does not seem that there is any way to achieve what you want, (e.g. applying or creating a new Theme)
Even if you get a hold of the other application's AssetManager by using getAssets() on a context there is no accessible method to create or apply themes.
So the only way to use another app's resources would be to add the resources to yours.
Have you seen this demo: Multiple Theme Material Design
You can check this demo for runtime theme change.
Hope it will helps you.
Related
This is an effect that is difficult to describe.
Our Android app supports two languages, however we don't use the system language but let the user set this in the settings.
Then, before attaching the BaseContext of the Application we set the locale configuration.
// in Application class
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LocaleHelper.onAttach(base))
}
// the LocaleHelper
fun onAttach(context: Context): Context {
return setLocale(context, getPersistedLanguage(context), getPersistedCountry(context))
}
That way the attachBaseContext call gets a context that has the locale set to e.g. "de" instead of "en" - even if the device is in English.
This works great so far and depending on the settings all resources coming from the context are in that language. However, we now added another setting for the night-mode (i.e. giving the user the option to set the "theme" in either "normal" or "dark mode").
For that reason the idea was to set something like this
if (enableDarkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
in the onCreate() of the Application (we also tried in the Activity).
However, doing this, suddenly the resources (at least some) are loaded with the device locale. The menu entries are in the device language. However, checking the Locale.getLanguage() gives me the configured language and dynamically called Strings (e.g. context.getString(R.string.xyz)) also show in the correctly configured language.
This leads to the assumption that the menu resources are somewhat (re)loaded (again) but don't respect the set Locale from the JVM.
Does anyone have any idea how to find that bug? What are we missing here? Are the menu resources loaded differently?
I just discovered a hacky solution but in case there is anyone having the same problem this might help a bit:
I added to the activity in the manifest
android:configChanges="uiMode"
telling the Application to "handle ui mode changes myself".
In that case the resources stay "untouched" but I'm not sure what other implications this change might have.
So please let me know if you have any further hints on what's going wrong when letting the system / app handle the night mode changes itself.
Maybe late on this, but that can help other.
I managed to make sure that the dark theme doesn't change the locale language.
For that, I have a fragment which enable thank to a switch the dark theme.
SettingsFragment :
switchPreference.setOnPreferenceChangeListener((preference, isSwitchOn) -> {
if((boolean) isSwitchOn){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
return true;
});
Then in the parent activity, I just have to override attachBaseContext !
SettingActivity :
#Override
protected void attachBaseContext(Context newBase) {
// Get configuration and resources before onCreate method
Resources resources = newBase.getResources();
Configuration configuration = new Configuration(resources.getConfiguration());
configuration.uiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
Context context = newBase.createConfigurationContext(configuration);
// Set locale with configuration saved
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String langue = sharedPreferences.getString("langage_pref", "fr");
Locale locale = new Locale(langue);
Locale.setDefault(locale);
configuration.setLocale(locale);
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
super.attachBaseContext(newBase);
}
Hope that will help you ! :D
First I should explain what my ultimate goal is. I develop Android Apps, mostly using WebViews. They are great in various aspects, but one thing they don't do very well is "matching the native UI", especially fonts. Some phones (such as Samsung's) support using Flipfont to switch the default font for the entire system, but for some reasons, no browsers (as far as I know) adapt that setting and display webpages with the Flipfont settings. The same is true for WebViews, and thus creating inconsistent user experience.
But I think there should be a way to let WebViews using the Flipfont font. By studying the decompiled source code of iFont, I think I've figured out how to extract the .ttf file from the assets of a given Flipfont package, of which the package name always begins with com.monotype.android.font. And after that, I supposedly can let the WebView use that .ttf file as the default font. The problem is, I don't know which package I should extract, that is, I don't know which Flipfont package is currently in use, if any. It appears that iFont cannot determine that either; there's no place in that app that tells me explicitly "You're using font xxx".
But obviously there must be a way to determine that, because the Flipfont setting dialog shows me exactly that. However, I failed to decompile the setting dialog to study how it is done. From Logcat, it appears that the setting dialog has something to do with the package com.sec.android.easysettings and com.android.settings, but decompiling the corresponding apk's (which are under /system/app/easysettings and /system/priv-app/SecSettings, respectively) both result in no source code at all, only resources (can someone also explain why this happens?).
So does anyone know how to determine the current Flipfont package?
After more digging I finally found a solution that works.
For those system that uses Flipfont, the Typeface class has additional methods that allows one to obtain information regarding the Flipfont setting (I figured that out here). However since those methods are not defined on the standard Typeface class, one would need to use reflection to call those methods, with exception catching of course.
I came up with the following FontUtil class, of which getFlipFont method returns the File object of the current Flipfont .ttf file if there's one, or null if there's none.
import android.content.Context;
import android.graphics.Typeface;
import java.io.File;
import java.lang.reflect.Method;
public class FontUtil {
#SuppressWarnings("JavaReflectionMemberAccess")
public static File getFlipFont(Context context) {
try {
Method m = Typeface.class.getMethod("getFontPathFlipFont", Context.class, int.class);
String path = m.invoke(null, context, 1).toString();
if (!path.equals("default")) {
File file = new File(path + "/DroidSansFallback.ttf");
if (file.exists()) return file;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
After that, my goal was achieved by the following, should anyone cares. Add the following CSS on the webpages of my app:
#font-face {
font-family: Flipfont;
src: url(flipfont.ttf);
}
body {
font-family: Flipfont;
}
Next, use something like this in my activities:
private File flipFont;
private WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
flipFont = FontUtil.getFlipFont(this);
...
webView.setWebViewClient(new AppWebViewClients());
...
}
private class AppWebViewClients extends WebViewClient {
#Nullable
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.endsWith("flipfont.ttf") && flipFont != null) {
try {
InputStream stream = new FileInputStream(flipFont);
return new WebResourceResponse("application/x-font-ttf", "UTF-8", stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
return super.shouldInterceptRequest(view, request);
}
}
Now my WebView apps uses exactly the same font as any other native apps.
Update 2021.4.25:
Today I realize that on Samsung Note 9, the method name is semGetFontPathOfCurrentFontStyle() instead, so one should also check if this method is available. Also, in some methods of changing the font, the font filename might end up being DroidSans.ttf instead of DroidSansFallback.ttf, so it is also necessary that one should check the former as a "fallback" (in the opposite order to the filenames).
I'm a new mobile developer and i know how to put theme on an application i was able to make 2 mobile application as of now but i wanted to try to make a customized theme for phones. I'm wondering if anyone out there has idea on the following.
1.how to make a customized theme for phone.
2.what is the format of the theme.
3.is it possible to make it in eclipse.
4.what are the things needed to consider to make a theme for phone.
5.how to implement customized theme on phone.
6.available tutorial on making a customized phone theme.
If you want to change theme of a specific theme application like GO Launcher or aHome you can find a link on their site, see this question for more information Themes in Android?
but you can also write your launcher application, if you want to do this, you can see this links: Build An Application Launcher For Android
But if you want to change theme of your application, so you can read these documents and tutorials:
developer.android.com/guide/topics/ui/themes.html
Android Styles and Themes - Tutorial
Another way that i think you must know for building several themes is building an apk for every theme of your application, this solution make your app size smaller. like this function:
public static Resources getResource() {
resourcePackageName = "com.mine.MyApp";
PackageManager packageManager = mContext.getPackageManager();
Resources resources = null, resources2 = null;
try {
resources = mContext.getResources();
resources2 = packageManager
.getResourcesForApplication("com.mine.MyAppTheme");
resourcePackageName = "com.mine.MyAppTheme";
} catch (Exception e) {
Log.d("ss", e.toString());
}
if (resources2 != null)
mResource = resources2;
else
mResource = resources;
return mResource;
}
In this code, your app name is com.mine.MyApp and you have a app with all of resources in the com.mine.MyApp that is your theme, name of theme apk app is com.mine.MyAppTheme. you can use several apk as several theme. just put your resources on theme apk file. you can see this question:
Writing themed applications in Android
I realize I'm probably doing something fundamentally wrong with styles and themes but I'm still a bit of an Android newbie so please excuse my ignorance. I'm trying to change the style of my MediaRouteButton from the default dark to light since I have a light ActionBar. My MediaRouteButton is implemented in the ActionBar as follows:
<item
android:id="#+id/menu_item_cast"
android:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
android:actionViewClass="android.support.v7.app.MediaRouteButton"
android:showAsAction="always"
android:actionButtonStyle="#android:style/Theme.MediaRouter.Light"/>
However, this gives me:
android/res/menu/main.xml:24: error: Error: No resource found that matches the given name (at 'actionButtonStyle' with value '#android:style/Theme.MediaRouter.Light').
If you don't want to change the color of the icon, framework would choose the right one (dark or light) based on the theme of your actionbar, so for an actionbar with light background, it will choose a darker icon and vice versa; here is a sample app with two different themes, Theme.AppCompat.Light and Theme.AppCompat, respectively (everything else is identical):
As you can see, the appropriate one is selected automatically. If you want to use a different color based on your branding requirements, the easiest would be to add the following images to your project (with usual resolutions under mdpi, hdpi, ..):
mr_ic_media_route_disabled_holo_dark.png
mr_ic_media_route_off_holo_dark.png
mr_ic_media_route_on_0_holo_dark.png
mr_ic_media_route_on_1_holo_dark.png
mr_ic_media_route_on_2_holo_dark.png
(if you are using a light actionbar theme, replace "dark" with "light"). Take a look at the assets at Google Cast >
Sample Apps (section Cast Icons) to get a feel for what these images are and build your own ones based on those.
I ended up decompiling android-support-v7-mediarouter.jar to see what was going on. With the code available I was able to extend MediaRouteButton and set the private Drawable through reflection hacking. There has to be a better way right?
public class CustomMediaRouteButton extends MediaRouteButton {
private static final String TAG = "CustomMediaRouteButton";
public CustomMediaRouteButton(Context context){
this(context, null);
}
public CustomMediaRouteButton(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.mediaRouteButtonStyle);
}
public CustomMediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Drawable d = getResources().getDrawable(R.drawable.mr_ic_media_route_holo_light);
setRemoteIndicatorDrawable(d);
}
private void setRemoteIndicatorDrawable(Drawable d) {
try {
Field field = MediaRouteButton.class.getDeclaredField("mRemoteIndicator");
field.setAccessible(true);
Drawable remoteIndicator = (Drawable)field.get(this);
if (remoteIndicator != null) {
remoteIndicator.setCallback(null);
unscheduleDrawable(remoteIndicator);
}
field.set(this, d);
if (d != null) {
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == 0, false);
}
} catch (Exception e) {
Log.e(TAG, "problem changing drawable:" + e.getMessage());
}
refreshDrawableState();
}
}
You can change it easily now with your custom drawable.
Just call this method on your cast button.
mediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
mediaRouteButton.setRemoteIndicatorDrawable(yourDrawable);
I found a way to change your color of MediaRouteButton by code, and is easy to be done, no need to touch existing code.
The MediaRouteButton will style itself following the theme of context which you passed. You can create a ContextThemeWrapper to wrap the context and then pass it to MediaRouteActionProvider.
Following is an example:
MenuItem item = menu.add(Menu.NONE, R.id.menu_cast, Menu.NONE, "Cast");
MenuItemCompat.setActionProvider(item, new MediaRouteActionProvider(new ContextThemeWrapper(this, R.style.AppTheme)));
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
Here the R.style.AppTheme is a theme which extends from Theme.AppCompat, it is a dark theme so the cast button will always show in light version. You can also pass in light theme to make cast button behave in dark version. Also you can change it dynamically, just invalidate the option menu, it should recreate the action provider using the new theme.
I am using support library 23.1.1 and have not found any problem in this way.
If you want to change the icons used (not just the style), you need to name them the exact same way they are named here. For instance, for the light theme, you need to have a set of icons for every resolution with names: ic_cast_on_light.png, ic_cast_on_0_light.png, ic_cast_on_1_light.png, ic_cast_on_2_light.png, ic_cast_disabled_light.png, ic_cast_off_light.png.
You should be able to change the style by applying the style to your activity, e.g. in AndroidManifest.xml. If you want to change the drawable, I succeeded by adding mr_ic_media_route_holo_light drawable to my project. Just add it to drawables folder and customize it as you need it. Example: https://github.com/android/platform_frameworks_support/blob/master/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
I'm currently working on an app to display the battery status and I'd like to use Android-drawables instead of own images to reduce the app size.
I've found this page which lists available images and the availability for each SDK-version:http://www.fixedd.com/projects/android_drawables_display
My question: How can I access the "system"-drawables? If you click on the link and choose the tab "Status", there are some battery-drawables like "stat_sys_battery_0", but I can't access it, Eclipse doesn't offer intellisense for it and won't compile the app if I use one of those drawables.
As those drawables are part of all SDK-versions, I'd think I should be able to use them, or are those "special" drawables protected in a way so they can only be used by system-functions (and not apps)?
Any idea is appreciated.
Select0r
Hope this is what you were looking for:
private BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent intent) {
int level = intent.getIntExtra("level", 0);
int batteryIconId = intent.getIntExtra("icon-small", 0);
Button toolBarBattery = (Button) findViewById(R.id.toolBarButton);
LevelListDrawable batteryLevel = (LevelListDrawable) getResources().getDrawable(batteryIconId);
batteryLevel.setLevel(level);
toolBarBattery.setBackgroundDrawable(batteryLevel);
}
};
I've found another link with information that not all drawables are public. It doesn't say why some drawables would be private, but I guess I'll have to live with the fact and copy the needed images to my app.http://androiddrawableexplorer.appspot.com/
NOTE: Some of the images in the Android jar are not public and therefore cannot be directly used (you can copy them to you own application, but can't reference them via the "android" package namespace).
There actually seems to be a way to access the system icons, but it's not really working as stated in the documentation, but I'll add it in case somebody is interested:
intent.getIntExtra(BatteryManager.EXTRA_ICON_SMALL, -1)
Will get you the resource-ID of the icon that matches the current battery-status:http://developer.android.com/reference/android/os/BatteryManager.html#EXTRA_ICON_SMALL
Extra for ACTION_BATTERY_CHANGED:
integer containing the resource ID of
a small status bar icon indicating the
current battery state.
However, it always returns the same icon, no matter what the actual battery level is. Finding the icon by just trying random numbers may work, but I don't know if the IDs are consistent throughout the SKD-levels as well as different machines, so I'd rather not rely in that.