I'm developing an app with React Native for both iOS and Android, and I am trying to prevent device-specific scaling of the display in the app.
For text/font size scaling, putting the following code in the root-level App.js file solves the issue for both iOS and Android:
if (Text.defaultProps == null) {
Text.defaultProps = {};
}
Text.defaultProps.allowFontScaling = false;
However, Android devices have the following Display size setting that is still being applied:
I've tried (unsuccessfully) to piece together a variety of "solutions" to this issue that I've found in answers to the following questions:
Change the system display size programatically Android N
Disabling an app or activity zoom if Setting -> Display -> Display size changed to Large or small
how to prevent system font-size changing effects to android application?
I've often found references to a BaseActivity class that extends the Activity class. My understanding is that it is inside of that class where I would be writing a method (let's call it adjustDisplayScale) to make changes to the Configuration of the Context that I get from Resources, and that then I would be calling adjustDisplayScale within the onCreate() method after super.onCreate() in the MainApplication.java file.
As of now, in this directory I just have two files - MainApplication.java and MainActivity.java.
I've attempted creating a new Module and associated Package file to implement adjustDisplayScale following these instructions and it did not work:
https://facebook.github.io/react-native/docs/text.html
I've attempted placing implementing the functionality of adjustDisplayScale within the onCreate() like this and it did not work:
#Override
public void onCreate() {
super.onCreate();
Context context = getApplicationContext();
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
configuration.fontScale = 1f;
DisplayMetrics metrics = res.getDisplayMetrics();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity = 1f;
configuration.densityDpi = (int) res.getDisplayMetrics().xdpi;
context = context.createConfigurationContext(configuration);
SoLoader.init(this, /* native exopackage */ false);
}
A potentially promising answer included the following:
protected override void AttachBaseContext(Context #base) {
var configuration = new Configuration(#base.Resources.Configuration);
configuration.FontScale = 1f;
var config = Application.Context.CreateConfigurationContext(configuration);
base.AttachBaseContext(config);
}
But when I tried to utilize this, I got errors about not recognizing the symbol #base.
Some background... I've done 99% of my work on this project in JavaScript / React Native and I have almost no understanding about things like Resources, Context, Configuration, and DisplayMetrics associated with Android development AND the last time I wrote code in Java was 10 years ago. I've spent a number of agonizing hours trying to figure this out and any help would be greatly appreciated.
ps. I am well-aware that accessibility settings exist for a good reason so please spare me the diatribe I've seen in so many "answers" on why I need to fix my UI to work with accessibility settings rather than disable them.
NOTE
I strongly discourage applying such a solution. In a certain way, Screen Zoom just "emulates" different screen sizes and densities in the same device. So, if your app can't handle well a specific screen zoom level, it means that your app may not be displayed correctly on a real screen out there. If your app can't support screen changes, tell the user about it...
There are some docs about screen sizes in the Android Developer and that's how you should handle different screen sizes.
On Android 12, it seems this the context created via context.createConfigurationContext(configuration) is imuttable. So, you may have problems on Android 12 when rotating the device, for example. context.getResources().getxxxxx() may return portrait resources (because the context was created in portrait) instead of landscape resources (the new orientation)
Support Different Screen Sizes
supports-screens-element
The answer below is just a "hack" where I tried to circumvent the screen zoom feature. I don't use that on my apps and I strongly recommend dealing with the screen zoom in a more conventional way.
Answer
My first answer does not work if you change the screen resolution. On Samsung devices, you can change the screen zoom but you can also change the screen resolution on some models (Settings->Display->Screen Resolution-> HD, FHD, WQHD etc).
So, I came up with a different code which seems to work with that feature as well. Just, please, note I can't fully test this code since I don't have too many devices to test. On those devices I tested, it seems to work.
One additional note. Ideally, you don't need to use such kind of code to circumvent the screen zoom. In a certain way, the screen zoom is just "simulating" bigger or smaller screens. So, if your app properly supports different screen sizes, you don't need to completely "disable" the screen zoom.
public class BaseActivity extends AppCompatActivity {
#TargetApi(Build.VERSION_CODES.N)
private static final int[] ORDERED_DENSITY_DP_N = {
DisplayMetrics.DENSITY_LOW,
DisplayMetrics.DENSITY_MEDIUM,
DisplayMetrics.DENSITY_TV,
DisplayMetrics.DENSITY_HIGH,
DisplayMetrics.DENSITY_280,
DisplayMetrics.DENSITY_XHIGH,
DisplayMetrics.DENSITY_360,
DisplayMetrics.DENSITY_400,
DisplayMetrics.DENSITY_420,
DisplayMetrics.DENSITY_XXHIGH,
DisplayMetrics.DENSITY_560,
DisplayMetrics.DENSITY_XXXHIGH
};
#TargetApi(Build.VERSION_CODES.N_MR1)
private static final int[] ORDERED_DENSITY_DP_N_MR1 = {
DisplayMetrics.DENSITY_LOW,
DisplayMetrics.DENSITY_MEDIUM,
DisplayMetrics.DENSITY_TV,
DisplayMetrics.DENSITY_HIGH,
DisplayMetrics.DENSITY_260,
DisplayMetrics.DENSITY_280,
DisplayMetrics.DENSITY_XHIGH,
DisplayMetrics.DENSITY_340,
DisplayMetrics.DENSITY_360,
DisplayMetrics.DENSITY_400,
DisplayMetrics.DENSITY_420,
DisplayMetrics.DENSITY_XXHIGH,
DisplayMetrics.DENSITY_560,
DisplayMetrics.DENSITY_XXXHIGH
};
#TargetApi(Build.VERSION_CODES.P)
private static final int[] ORDERED_DENSITY_DP_P = {
DisplayMetrics.DENSITY_LOW,
DisplayMetrics.DENSITY_MEDIUM,
DisplayMetrics.DENSITY_TV,
DisplayMetrics.DENSITY_HIGH,
DisplayMetrics.DENSITY_260,
DisplayMetrics.DENSITY_280,
DisplayMetrics.DENSITY_XHIGH,
DisplayMetrics.DENSITY_340,
DisplayMetrics.DENSITY_360,
DisplayMetrics.DENSITY_400,
DisplayMetrics.DENSITY_420,
DisplayMetrics.DENSITY_440,
DisplayMetrics.DENSITY_XXHIGH,
DisplayMetrics.DENSITY_560,
DisplayMetrics.DENSITY_XXXHIGH
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("TESTS", "Dimension: " + getResources().getDimension(R.dimen.test_dimension));
}
#Override
protected void attachBaseContext(final Context baseContext) {
Context newContext = baseContext;
// Screen zoom is supported from API 24+
if(Build.VERSION.SDK_INT >= VERSION_CODES.N) {
Resources resources = baseContext.getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
Configuration configuration = resources.getConfiguration();
Log.v("TESTS", "attachBaseContext: currentDensityDp: " + configuration.densityDpi
+ " widthPixels: " + displayMetrics.widthPixels + " deviceDefault: " + DisplayMetrics.DENSITY_DEVICE_STABLE);
if (displayMetrics.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE) {
// display_size_forced exists for Samsung Devices that allow user to change screen resolution
// (screen resolution != screen zoom.. HD, FHD, WQDH etc)
// This check can be omitted.. It seems this code works even if the device supports screen zoom only
if(Settings.Global.getString(baseContext.getContentResolver(), "display_size_forced") != null) {
Log.v("TESTS", "attachBaseContext: This device supports screen resolution changes");
// density is densityDp / 160
float defaultDensity = (DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT);
float defaultScreenWidthDp = displayMetrics.widthPixels / defaultDensity;
Log.v("TESTS", "attachBaseContext: defaultDensity: " + defaultDensity + " defaultScreenWidthDp: " + defaultScreenWidthDp);
configuration.densityDpi = findDensityDpCanFitScreen((int) defaultScreenWidthDp);
} else {
// If the device does not allow the user to change the screen resolution, we can
// just set the default density
configuration.densityDpi = DisplayMetrics.DENSITY_DEVICE_STABLE;
}
Log.v("TESTS", "attachBaseContext: result: " + configuration.densityDpi);
newContext = baseContext.createConfigurationContext(configuration);
}
}
super.attachBaseContext(newContext);
}
#TargetApi(Build.VERSION_CODES.N)
private static int findDensityDpCanFitScreen(final int densityDp) {
int[] orderedDensityDp;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
orderedDensityDp = ORDERED_DENSITY_DP_P;
} else if(Build.VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
orderedDensityDp = ORDERED_DENSITY_DP_N_MR1;
} else {
orderedDensityDp = ORDERED_DENSITY_DP_N;
}
int index = 0;
while (densityDp >= orderedDensityDp[index]) {
index++;
}
return orderedDensityDp[index];
}
}
ORIGINAL ANSWER
You can try following code (overriding attachBaseContext). This will "disable" the screen zoom in on your app. This is the way to re-scale the whole screen at once.
#Override
protected void attachBaseContext(final Context baseContext) {
Context newContext;
if(Build.VERSION.SDK_INT >= VERSION_CODES.N) {
DisplayMetrics displayMetrics = baseContext.getResources().getDisplayMetrics();
Configuration configuration = baseContext.getResources().getConfiguration();
if (displayMetrics.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE) {
// Current density is different from Default Density. Override it
configuration.densityDpi = DisplayMetrics.DENSITY_DEVICE_STABLE;
newContext = baseContext.createConfigurationContext(configuration);
} else {
// Same density. Just use same context
newContext = baseContext;
}
} else {
// Old API. Screen zoom not supported
newContext = baseContext;
}
super.attachBaseContext(newContext);
}
On that code, I check if the current density is different from the Device's default density. If they are different,
I create a new context using default density (and not the current one). Then, I attach this modified context.
You must do that on every Activity. So, you can create a BaseActivity and add that code there. Then, you just need to update your activities in order to extend BaseActivity
public class BaseActivity extends AppCompatActivity {
#Override
protected void attachBaseContext(final Context baseContext) {
....
}
}
Then, in your activities:
public class MainActivity extends BaseActivity {
// Since I'm extending BaseActivity, I don't need to add the code
// on attachBaseContext again
// If you don't want to create a base activity, you must copy/paste that
// attachBaseContext code into all activities
}
I tested this code with:
Log.v("Test", "Dimension: " + getResources().getDimension(R.dimen.test_dimension));
Different Screen Zoom (using that code):
2019-06-26 16:38:17.193 16312-16312/com.test.testapplication V/Test: Dimension: 105.0
2019-06-26 16:38:35.545 16312-16312/com.test.testapplication V/Test: Dimension: 105.0
2019-06-26 16:38:43.021 16579-16579/com.test.testapplication V/Test: Dimension: 105.0
Different Screen Zoom (without that code):
2019-06-26 16:42:53.807 17090-17090/com.test.testapplication V/Test: Dimension: 135.0
2019-06-26 16:43:19.381 17090-17090/com.test.testapplication V/Test: Dimension: 120.0
2019-06-26 16:44:00.125 17090-17090/com.test.testapplication V/Test: Dimension: 105.0
So, using that code, I can get the same dimension in pixels regardless of the zoom level.
Edit
According to this bug: bugzilla.xamarin.com/show_bug.cgi?id=52597 the PopToRoot of the Navigation object still leak GRefs (in fact JNI Global References)
In one application we use Listview really intensively and to avoid crash I had to implement a workaround using two methods :
/// <summary>
/// Workaround to minimize the quantity of GREFs leaked by Xamarin.Forms JMA 17.02.2017
/// </summary>
/// <param name="animate">if set to <c>true</c> [animate].</param>
public async Task PopToRoot(bool animate)
{
var nav = Application.Current.MainPage.Navigation;
if(animate == false) //simulate animate = false
for (int i = 1; i < nav.NavigationStack.Count; i++)
if(nav.NavigationStack[i] is BaseView)
{
//hide the animation
(nav.NavigationStack[i] as BaseView).IsVisible = false;
//hide the toolbar title change
(nav.NavigationStack[i] as BaseView).Title = (nav.NavigationStack[nav.NavigationStack.Count - 1] as BaseView).Title;
}
await _PopToRootInner(true);
}
/// <summary>
/// Workaround to minimize the quantity of GREFs leaked by Xamarin.Forms JMA 17.02.2017
/// </summary>
/// <param name="animate">if set to <c>true</c> [animate].</param>
private async Task _PopToRootInner(bool animate)
{
var nav = Application.Current.MainPage.Navigation;
await PopAsync(animate);
if (nav.NavigationStack.Count > 1 && nav.NavigationStack[nav.NavigationStack.Count - 1] is BaseView)
await (nav.NavigationStack[nav.NavigationStack.Count - 1] as BaseView)._PopToRootInner(animate);
}
My question is : Do you know any better method to do a PopToRoot without animation and without leaking GRefs ?
And Also, if you know how to correct the bug, I am of course interested !
I am trying to use a LookToWalk script in my Unity VR app that should run on my Daydream View. In the "Game" Mode to preview the changes everything works as expected (I configured the script to run forward once the user camera faces 30.0 degrees downwards or more.
However when I try to build the daydream app and install it on my Google Pixel the CharacterController.SimpleMove doesn't seem to work any more.
The logs were showing that the 30.0 degree stuff was triggered as expected but no movement was seen on the daydream.
Do you know why this could be happening? Seems really strange that it runs on the "emulator" but not the 'real' device.
using UnityEngine;
using System.Collections;
public class GVRLookWalk : MonoBehaviour {
public Transform vrCamera;
public float toggleAngle = 30.0f;
public float speed = 3.0f;
private bool shouldWalk;
private CharacterController cc;
// Use this for initialization
void Start () {
cc = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update () {
if (vrCamera.eulerAngles.x >= toggleAngle && vrCamera.eulerAngles.x < 90.0f){
shouldWalk = true;
} else {
shouldWalk = false;
}
if (shouldWalk) {
Vector3 forward = vrCamera.TransformDirection (Vector3.forward);
cc.SimpleMove (forward * speed);
}
}
Is the Camera a child of another transform? You cannot move the camera directly. "you cannot move the camera directly in Unity. Instead, the camera must be a child of another GameObject, and changes to the position and rotation must be applied to the parent’s Transform."
https://unity3d.com/learn/tutorials/topics/virtual-reality/movement-vr
I have two problems, about autofocus on Vuforia's AR-camera and iBeacon receiver. These are not work.
However, I see these problem just on an Android tablet(me173x).
I tried with 3 Android phones and iPhone5, 5s and 6+. No problem on them.
Development Environments are:
MacBookPro-Retina15-Early2012 (FullSpec)
OSX 10.10.4 (Latest)
Unity 5.1.1p2 (Latest), Professional (but no iOS-Pro and no Android-Pro)
Vuforia 4.2.3 (Latest)
I captured screenshot of terminal running 'pidcat --min-level w' (levels above warning), because so many logs.
No other logs above warning.
Error logs about linking of class for iBeacon found.
The tablet supports bluetooth 4.0 and BLE, and some camera apps support autofocus.
I guess... these problems due to the device architecture.
Please help me!
P.S.
thank you for your response, jacob.
my code to enable autofocus is:
using UnityEngine;
using Vuforia;
using System;
public class VuforiaARCameraAutoFocusMultiSelect : MonoBehaviour {
[SerializableAttribute]
public struct FocusModes {
public CameraDevice.FocusMode secondary;
public CameraDevice.FocusMode primary;
}
public FocusModes iOSFocusModes;
public FocusModes androidFocusModes;
public FocusModes defaultFocusModes;
void Start() {
var qcar = FindObjectOfType<QCARAbstractBehaviour>();
if (qcar != null) {
qcar.RegisterQCARStartedCallback(OnQCARStarted);
qcar.RegisterOnPauseCallback(OnQCARPaused);
Debug.Log("QCARBehaviour Found in current scene");
} else
Debug.LogError("Failed to find QCARBehaviour in current scene..");
}
/// <summary>
/// Raises the QCAR started event.
/// </summary>
private void OnQCARStarted() {
Debug.Log("OnQCARStarted");
SetFocusMode();
}
/// <summary>
/// Raises the QCAR paused event.
/// </summary>
/// <param name="paused">true: paused, false: resumed</param>
private void OnQCARPaused(bool paused) {
if (paused) {
Debug.Log("OnQCARPaused");
} else {
Debug.Log("OnQCARResumed");
SetFocusMode();
}
}
private void SetFocusMode() {
FocusModes modes;
#if UNITY_IOS
modes = iOSFocusModes;
#elif UNITY_ANDROID
modes = androidFocusModes;
#else
modes = defaultFocusModes;
#endif
if (CameraDevice.Instance.SetFocusMode(modes.primary))
Debug.LogFormat("Successfully enabled autofocus mode: {0}", modes.primary);
else if (CameraDevice.Instance.SetFocusMode(modes.secondary))
Debug.LogFormat("Successfully enabled autofocus mode: {0}", modes.secondary);
else
Debug.LogError("Couldn't enabled autofocus!!");
}
}
This code attached to the 'AR Camera' gameobject in the scene.
I'd like to display my website only in landscape mode when viewed from modern smartphones. Is there a way to force the major mobile browsers- Chrome/Firefox/Safari/IE10 to do so? Basically I'd like them to start the browser in landscape mode, irrespective of screen orientation and rotation lock.
Is it too much to ask? I think not because certainly there are games that only play in landscape mode, and the Windows Phone UI home screen uses only portrait mode.
Well, you can't force browsers orientation to landscape, but you sure can try to detect device orientation and suggest the user to rotate his/her device (through a full screen <div> and hide it when the user rotated the display)!
if (window.DeviceOrientationEvent) {
console.log("DeviceOrientation is supported");
}
Then add the appropriate listeners once you know device orientation is supported:
if (window.DeviceOrientationEvent) {
// Listen for the event and handle DeviceOrientationEvent object
window.addEventListener('deviceorientation', devOrientHandler, false);
}
Then, handle the event:
if (window.DeviceOrientationEvent) {
document.getElementById("doEvent").innerHTML = "DeviceOrientation";
// Listen for the deviceorientation event and handle the raw data
window.addEventListener('deviceorientation', function(eventData) {
// gamma is the left-to-right tilt in degrees, where right is positive
var tiltLR = eventData.gamma;
// beta is the front-to-back tilt in degrees, where front is positive
var tiltFB = eventData.beta;
// alpha is the compass direction the device is facing in degrees
var dir = eventData.alpha
// call our orientation event handler
deviceOrientationHandler(tiltLR, tiltFB, dir);
}, false);
} else {
document.getElementById("doEvent").innerHTML = "Not supported."
}
...and display the result (i.e hide the full screen suggestion <div>)!
document.getElementById("doTiltLR").innerHTML = Math.round(tiltLR);
document.getElementById("doTiltFB").innerHTML = Math.round(tiltFB);
document.getElementById("doDirection").innerHTML = Math.round(dir);
// Apply the transform to the image
var logo = document.getElementById("imgLogo");
logo.style.webkitTransform =
"rotate("+ tiltLR +"deg) rotate3d(1,0,0, "+ (tiltFB*-1)+"deg)";
logo.style.MozTransform = "rotate("+ tiltLR +"deg)";
logo.style.transform =
"rotate("+ tiltLR +"deg) rotate3d(1,0,0, "+ (tiltFB*-1)+"deg)";
Reference: http://www.html5rocks.com/en/tutorials/device/orientation/
Hope that helped :)