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).
Related
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.
I have an app fully developped with Android Studio. I need to create a second app and this one I would like to do it with phonegapp. These two apps are related one to each other... so ideally I would like to combine them both in the same app... By combining I do not mean they need to work together. I would like them to be downloadad together (as the same app) and be able to go from one to the other withinn the app... so they can be completely two separate apps, but where the user would think it is only one app. I hope I am making myself well understood, The hybrid app uses the microphone with the Cordova plugin. At one time I thorugh about placing it with an iframe, but we would lose the microphone which invokes speech recognition Google native in Android, to convert it to text and pass it to edit text.
So my questions is:
Can I combine these two apps (native and hybrid) into one app?
Would the microphone with speech recognition to convert to text work well in both cases?
Yes, it was a bit of a pain to set up, but I have it working. I am not using PhoneGap, but I am using Construct along with the Cordova plugin, so it should be a similar setup.
First I took all of the web files generated by Cordova and placed them in the assets directory (if you do not have an assets directory you can just create one in the main directory). Then I used a WebView to display the content:
public class CordovaActivity extends AppCompatActivity {
private WebView mWebView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cordova);
mWebView = (WebView) findViewById(R.id.content);
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
settings.setMediaPlaybackRequiresUserGesture(false);
}
mWebView.loadUrl("file:///android_asset/www/index.html");
}
#Override
public void onResume() {
super.onResume();
if(mWebView != null) {
mWebView.resumeTimers();
mWebView.onResume();
}
}
#Override
public void onPause() {
super.onPause();
if(mWebView != null) {
mWebView.pauseTimers();
mWebView.onPause();
}
}
}
The setJavaScriptEnabled(), setDomStorageEnabled() and setAllowUniversalAccessFromFileURLs() settings all had to be set to true or the content would not load. The last setting, setMediaPlaybackRequiresUserGesture(), was required to allow audio to play.
It will be a little more work to get the Cordova content to interact with the rest of the app though; you will have to use a JavaScriptInterface in order to do so.
1) Nothing is stopping you from having two apps that act as one (aside from the downloading part). They can "speak" to one another using dedicated Intents and share data using ContentProviders. You will have to take care of the case where one app is installed and the other isn't.
2) I don't see any particular issue with the the microphone in this scenario.
However, if I were you, I'd opt for using a single app if that is possible. I am not sure what are the phonegapp limitations for this kind of implementation though.
I am a beginner in iOS and swift.
I used to write Android and I think the R.java is a good idea to manage ids, drawables, strings and other resources.
So I'm surprised that iOS does't provide a good function to access resources.
If I want to get a Image from assets, I call UIImage(named: "img_name"), but I don't think this is the best way to access img_name, I may use a wrong name and I can't get the image.
I found some open source project like Shark and SwiftGen, but Shark only support images and SwiftGen it seems need to run a command not automatically.
Do you have any better solution? Thank you!
I have a open source project R.swift-plugin it provides features as you mentioned. You just need to install the Xcode Plugin via Alcatraz and search R.swift
The plugin will automatically generate a resource file to manage your images, localizable strings and colors, and you can access them like using R.java
Usage:
Sincerely recommend you to try it. And feel free to give me any suggestion to improve it :)
If you are using InterfaceBuilder (also called Storyboard, xib), there is no need to define id for each view. You can bind outlets in code.
If you want to retrieve views using their ids (like R.java as you asked), you can set tag to each view and manipulate them in code.
Unlike AndroidStudio, Xcode will not generate any file.
func viewDidLoad() {
let labelView = self.view.viewWithTag(0) as? UILabel
}
There is no such function in Xcode itself, but there is an open source project that does just this: R.swift
It automatically updates the generated file and supports a lot of different resource types such as images, fonts, segues and more.
You can have similar functionality by using extensions and enums.
Using enums allows you to avoid typos and benefit from Xcode's autosuggest/autocomplete.
Example for UIImage:
extension UIImage {
enum ImageId: String {
// These are your images NAMES,
// as in "SpriteMonster.jpg"
case SpriteMonster, SpriteHero, BaseLandscape
}
convenience init!(id: ImageId) {
self.init(named: id.rawValue)
}
}
Usage:
let monster = UIImage(id: .SpriteMonster) // the "SpriteMonster.jpg" image
For this example I'm force-unwrapping the convenience init, so be careful to actually have the image with the correct name in your bundle.
For String:
extension String {
enum StringId: String {
case Welcome = "Welcome to the game!"
case GameOver = "You loose! Game over!"
}
init(id: StringId) {
self = id.rawValue
}
}
Usage:
let label = String(id: .Welcome) // "Welcome to the game!"
For fonts:
extension UIFont {
enum FontId {
case HelveticaNeueLarge
case HelveticaNeueMedium
case HelveticaNeueSmall
func font() -> UIFont {
switch self {
case .HelveticaNeueLarge:
return UIFont(name: "HelveticaNeue", size: 18)!
case .HelveticaNeueSmall:
return UIFont(name: "HelveticaNeue", size: 12)!
default:
return UIFont(name: "HelveticaNeue", size: 14)!
}
}
}
class func get(id: FontId) -> UIFont {
return id.font()
}
}
Usage:
let font = UIFont.get(.HelveticaNeueLarge) // <UICTFont: 0x7ffd38f09180> font-family: "Helvetica Neue"; font-weight: normal; font-style: normal; font-size: 18.00pt
These are only examples to demonstrate the concept, you can go much further with this.
If you use Tuist is has SwiftGen inside. It allows to use it out of the box without adding third-party solutions. The disadvantage is that you have to run it every time when you change resource
I am trying to render a checkbox in a Xamarin Forms app. There is nothing rendered at runtime, as far as I can tell the renderer is not even getting called.
Does anyone understand what I am missing or doing incorrectly?
Here is my class in Forms:
public class LegalCheckbox : View
{
public LegalCheckbox ()
{
}
}
And my custom renderer class in Droid:
public class CheckBoxRenderer : ViewRenderer<LegalCheckbox, CheckBox>
{
protected override void OnElementChanged (ElementChangedEventArgs<LegalCheckbox> e)
{
base.OnElementChanged (e);
CheckBox control = new Android.Widget.CheckBox(this.Context);
control.Checked = false;
control.Text = "I agree to terms";
control.SetTextColor (Android.Graphics.Color.Rgb (60, 60, 60));
this.SetNativeControl(control);
}
}
Along with the Assembly Directive:
[assembly: ExportRenderer(typeof(demo.LegalCheckbox), typeof(demo.Droid.CheckBoxRenderer))]
Took your code and fired up a new project with it. The code appears to function fine.
Only thin I can think that might be causing you an issue is the location of you assembly attribute. I typically place them just above the namespace declaration in the same file as my renderer.
I threw what I created up on my github maybe you can spot the difference.
https://github.com/DavidStrickland0/Xamarin-Forms-Samples/tree/master/RendererDemo
#Thibault D.
Xlabs isn't a bad project but its basically just all the code the opensource community came up with during the first year or so of Xamarin.Forms life. Its not really "Their Labs projects" and considering how much of it is marked up with Alpha Beta and the number of bugs in their issues page it's probably best not to imply that the Xamarin company has anything to do with it.
I am not sure if that is the issue but it would make more sense to me if your LegalCheckbox would inherit from a InputView rather than View.
Also, even if Xamarin.Forms does not have a Checkbox control you can still have a look at their "Labs" project here:
https://github.com/XLabs/Xamarin-Forms-Labs/wiki/Checkbox-Control
(And I can actually see that they inherit from View...)
I've developed Cordova App with Cordova Version 3.6.3 and JQuery. The only one problem that i still can't get the best solutions is when i test my app on Android 4.4+ and there are users who love to change font size in setting > display > font-size of their device to be larger than normal. It causes my app layout displays ugly (the best display is when the font-size setting is normal only). But there is no effect of font-size setting for Android older than 4.4 (4.3,4.2...) So the app displays perfectly on older version.
The solutions that I've applied to my app is creating the custom plugin to detect configuration changed and it will detects if user uses Android 4.4+ and if they set font-size setting to anything that is not normal, I'll use JQuery to force font-size to the specified size.
Example is....
if (font_scale == huge)
{
$("div").css("font-size","20px !important");
}
This works fine but sometimes after the page loaded, the css doesn't changes as I want. And suppose if there are 30 divs+ on that page so i must insert the statement like above 30 times and it takes too much time needlessly.
I just want to know, are there another ways to solve this problem that is easier than using this plugin? Maybe using some XML configuration or CSS3 properties that can makes my app displays properly without side effect from font-size setting of Android 4.4?
Another ways I also tried and it doesn't works are
inserting fontScale on Androidmanifest.xml > activity tag
inserting webkit-text-size-adjust:none; in css
I'd love to hear any ideas that help to solve this.
So you just want to ignore the system font preferences.
Here is the solution,I use MobileAccessibilty Plugin to ignore the system font preferences.
You just have to write following code in your onDeviceReady function in index.js
if(window.MobileAccessibility){
window.MobileAccessibility.usePreferredTextZoom(false);
}
usePreferredTextZoom(false) will just ignore the system font preferences. :) :) :)
I hope this helps!
In Cordova 8.0:
Edit MainActivity.java
Add references in the file header:
import android.webkit.WebView;
import android.webkit.WebSettings;
Add the code after loadUrl method:
loadUrl(launchUrl);
WebView webView = (WebView)appView.getEngine().getView();
WebSettings settings = webView.getSettings();
settings.setTextSize(WebSettings.TextSize.NORMAL);
Only this way worked for me.
Try to remove this one from your index.html:
target-densitydpi=device-dpi
Good luck.
Here is an new solution,hope it can help you. Solution
//in base activity add this code.
public void adjustFontScale( Configuration configuration) {
configuration.fontScale = (float) 1.0;
DisplayMetrics metrics = getResources().getDisplayMetrics();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity = configuration.fontScale * metrics.density;
getBaseContext().getResources().updateConfiguration(configuration, metrics);
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adjustFontScale( getResources().getConfiguration());
}
If your plugin allows you to know the font scale, change all your font sizes to em and use
$(document.body).css("font-size","70%");
just once.
Do you mind sharing your plugin?
This is quite old but since I still needed it in 2019 someone else might need it as well.
In case for some reason you do not want (or can't) integrate a whole plugin for a single functionality, here is the usePreferredTextZoom(false) functionality extracted (it's from a MobileFirst 7.1 app, based on Cordova). All credit goes to the guys developing the plugin, of course.
Within the main Java file of your Cordova Android app change the onInitWebFrameworkComplete method by adding the preventDeviceZoomChange() method call:
public void onInitWebFrameworkComplete(WLInitWebFrameworkResult result){
if (result.getStatusCode() == WLInitWebFrameworkResult.SUCCESS) {
super.loadUrl(WL.getInstance().getMainHtmlFilePath());
preventDeviceZoomChange();
} else {
handleWebFrameworkInitFailure(result);
}
}
In the same class, define the preventDeviceZoomChange() method:
private void preventDeviceZoomChange() {
try {
Method getSettings = (this.appView).getClass().getMethod("getSettings");
Object wSettings = getSettings.invoke(this.appView);
Method setTextSize = wSettings.getClass().getMethod("setTextSize", WebSettings.TextSize.class);
setTextSize.invoke(wSettings, WebSettings.TextSize.NORMAL);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}