Is there a way to make the containers and font sizes to adjust basing on the size of the device on which the app is installed? I have developed an application and on my phone (Samsung S8+), it appears the way I want it to appear. When I installed it on a phone with a smaller screen the layouts changed and the components look big on the small phone.
The code for the CSS of that page is:
Login-TextFields{
font-size: 3mm;
margin: 0.5mm 1mm 0mm 1mm;
padding: 1mm;
color: white;
background: transparent;
padding: 0mm 0mm 0mm 0mm;
}
Login-Field-Container{
border: none;
border-bottom: 0.25mm solid white;
padding-top: 5mm;
}
LoginFields-Container {
width: auto;
height: 2mm;
border-radius: 3mm;
padding-top: 8mm;
margin: 7mm 2mm 0mm 2mm;
background-color: transparent;
}
LoginForm-Background{
background-image: url(../assets/background.jpg);
cn1-background-type: cn1-image-scaled-fill;
padding: 5mm;
}
Logo-Container{
background: none;
}
Mask-Button{
color: white;
}
Login-Button {
background-color: #0052cc;
border-radius: 3mm;
border: none;
padding: 1mm 2mm 1mm 2mm;
color: white;
font-size: 3mm;
text-align: center;
margin: 2mm 3mm 2mm 3mm;
}
Forgot-Button{
text-align: right;
color: white;
font-style: italic;
font-size: 2.5mm;
margin: 0px;
}
SignUp-Button{
color: white;
margin: 1mm 2mm 1mm 0mm;
text-align: right;
}
Dont-Have-Account-Label{
color: #ccffff;
margin: 1mm 2mm 1mm 0mm;
text-align: center;
}
Logo-Area{
padding: 1mm;
margin: 2mm 3mm 2mm 3mm;
text-align: center;
}
Copyright{
color: white;
}
I would like to maintain the layout in the second image across all devices. I want the containers holding the fields to adjust according to the device while maintaining the layout and proportionality.
According to the requirements in your comment Adjust components basing on devices, first of all add to your project the following Java class. It contains more than you need, you can customize it as you wish. The most important thing is the line: int defaultScreenWidth = Display.getInstance().convertToPixels(70);
. It is self-explanatory: it indicates the reference dimension, in this case 70mm.
import com.codename1.io.Log;
import com.codename1.ui.CN;
import com.codename1.ui.Display;
import com.codename1.ui.Font;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
/**
*
*
*/
public class CSSUtilities {
// Note: we assume that the only target platforms are native iOS, native Android and Javascript
public static final boolean isAndroidTheme = UIManager.getInstance().isThemeConstant("textComponentOnTopBool", false);
public static final boolean isIOSTheme = !isAndroidTheme;
private static int percentage = calculateAdaptionPercentage();
/**
* Call this method if you need to recalculate the adapting percentage
*/
public static int recalculatePercentage() {
percentage = calculateAdaptionPercentage();
return percentage;
}
/**
* Load multiple CSSes, note that "iOS" css is loaded only if the native iOS
* theme is used, the "Android" css is loaded only if the native Android
* theme is used, the "WebApp" css is loaded only if the app is running on
* Javascript; for info about build.xml:
* https://stackoverflow.com/questions/53480459/multiple-codename-one-css
*
* @param css file names, WITHOUT SLASHES AND WITHOUT .css EXTENSION!!!
*/
public static void loadMultipleCSSes(String... css) {
try {
UIManager.initFirstTheme("/" + css[0]);
Resources resource = Resources.openLayered("/" + css[0]);
UIManager.getInstance().setThemeProps(adaptTheme(resource.getTheme("Theme")));
Resources.setGlobalResources(resource);
Log.p("Loaded " + css[0] + ".css", Log.DEBUG);
if (isIOSTheme) {
Log.p("The currently used native theme is iOS", Log.DEBUG);
}
if (isAndroidTheme) {
Log.p("The currently used native theme is Android", Log.DEBUG);
}
for (int i = 1; i < css.length; i++) {
if (css[i].equals("iOS")) {
if (!isIOSTheme) {
continue;
} else {
Log.p("Loading CSS for iOS native theme only", Log.DEBUG);
}
}
if (css[i].equals("Android")) {
if (!isAndroidTheme) {
continue;
} else {
Log.p("Loading CSS for Android native theme only", Log.DEBUG);
}
}
if (css[i].equals("WebApp")) {
if (!isJavascript()) {
continue;
} else {
Log.p("Loading CSS for web-app only", Log.DEBUG);
}
}
Resources res = Resources.openLayered("/" + css[i]);
if (!css[i].equals("MyController")) {
UIManager.getInstance().addThemeProps(adaptTheme(res.getTheme("Theme")));
} else {
UIManager.getInstance().addThemeProps(res.getTheme("Theme"));
}
Log.p("Loaded " + css[i] + ".css", Log.DEBUG);
}
// Log.p("CssUtilities.loadMultipleCSSes - success, loaded in the order: " + css.toString(), Log.INFO);
} catch (Exception ex) {
Log.p("CssUtilities.loadMultipleCSSes - ERROR", Log.ERROR);
Log.e(ex);
Log.sendLogAsync();
}
}
/**
* Calculate the percentage to adapt the font sizes to the screen width. The
* maximum decrease of the sizes is about 30%, increasing is disabled.
*
* @return percentage from -30 to 0
*/
private static int calculateAdaptionPercentage() {
int defaultScreenWidth = Display.getInstance().convertToPixels(70);
int currentScreenWidth = Display.getInstance().getDisplayWidth();
int currentInMM = currentScreenWidth / Display.getInstance().convertToPixels(1);
int percentage = currentScreenWidth * 100 / defaultScreenWidth - 100;
if (percentage < -30) {
percentage = -30;
} else if (percentage > 0) {
percentage = 0;
}
Log.p("Estimated screen width: " + currentInMM + " mm", Log.INFO);
Log.p("Font percentage: " + percentage + "%", Log.INFO);
return percentage;
}
/**
* Modify a theme changing the font sizes, margins and paddings
*
* @param themeProps
* @return the new theme
*/
private static Hashtable adaptTheme(Hashtable hashtable) {
Hashtable<String, Object> result = new Hashtable<>();
Set<String> keys = hashtable.keySet();
Iterator<String> itr = keys.iterator();
String key;
Object value;
while (itr.hasNext()) {
key = itr.next();
value = hashtable.get(key);
// Log.p("key: " + key + ", value is: " + value.getClass().getName() + ", " + value.toString());
if (value instanceof Font && ((Font) value).isTTFNativeFont() && percentage < 0) {
Font font = (Font) value;
float newSize = (int) (font.getPixelSize() * (100 + percentage) / 100);
result.put(key, font.derive(newSize, font.getStyle()));
} else if (key.endsWith("#margin") || key.endsWith(".margin")
|| key.endsWith("#padding") || key.endsWith(".padding")) {
if (value instanceof String) {
// Log.p("input: " + value);
// Log.p("output: " + resizeMarginPadding((String) value));
result.put(key, resizeMarginPadding((String) value));
}
} else {
result.put(key, value);
}
}
return result;
}
/**
* Returns a resized dimension (like a width or height)
*
* @param size, the unit of measurement (px, mm, pt, etc.) does not matter
* @return
*/
public static int getResized(int size) {
return size * (100 + percentage) / 100;
}
/**
* Returns a resized dimension (like a width or height)
*
* @param size, the unit of measurement (px, mm, pt, etc.) does not matter
* @return
*/
public static float getResized(double size) {
return (float) (size * (100 + percentage) / 100);
}
/**
* Returns a resized dimension (like a width or height)
*
* @param size, the unit of measurement (px, mm, pt, etc.) does not matter
* @param convertToPx if true, convert the given size from mm to pixels
* @return
*/
public static int getResized(int size, boolean convertToPx) {
if (!convertToPx) {
return getResized(size);
} else {
return getResized(Display.getInstance().convertToPixels(size));
}
}
/**
* Resizes the given margin or the padding
*
* @param input in a form like 0.0,1.0,0.9,15.0
* @return the given input if it's not a valid margin or padding, or a new
* String with the margins or paddings recalculated
*/
private static String resizeMarginPadding(String input) {
String result = "";
StringTokenizer st = new StringTokenizer(input, ",");
// Do we have 4 numbers?
if (st.countTokens() == 4) {
while (st.hasMoreTokens()) {
// Is this token a number like 1.5, 0.0, etc.?
String token = st.nextToken();
try {
Float number = Float.valueOf(token);
number = getResized(number);
number = ((int) (number * 10)) / 10.0f;
result += number;
if (st.countTokens() != 0) {
result += ",";
}
} catch (NumberFormatException e) {
return input;
}
}
} else {
return input;
}
return result;
}
/**
* Returns a resized dimension (like a width or height)
*
* @param size, the unit of measurement (px, mm, pt, etc.) does not matter
* @param convertToPx if true, convert the given size from mm to pixels
* @return
*/
public static double getResized(double size, boolean convertToPx) {
if (!convertToPx) {
return getResized(size);
} else {
return getResized(Display.getInstance().convertToPixels((float) size));
}
}
/**
* Returns true if the app is running in the CN1 Simulator
*
* @return
*/
public static boolean isSimulator() {
return Display.getInstance().isSimulator();
}
/**
* Returns true if the app is running as native Android app
*
* @return
*/
public static boolean isAndroidNative() {
return !isSimulator() && "and".equals(CN.getPlatformName());
}
/**
* Returns true if the app is running as native iOS app
*
* @return
*/
public static boolean isiOSNative() {
return !isSimulator() && "ios".equals(CN.getPlatformName());
}
/**
* Returns true if the app is running as Javascript port
*
* @return
*/
public static boolean isJavascript() {
return !isSimulator() && "HTML5".equals(CN.getPlatformName());
}
}
Then, in your main class, comment the theme = UIManager.initFirstTheme("/theme");
line, replace it with:
// theme = UIManager.initFirstTheme("/theme");
// We assume that CSS support is enabled
CSSUtilities.loadMultipleCSSes("theme");
That's all. This is an example of usage:
Form hi = new Form("Hi World", BoxLayout.y());
hi.add(new Label("(Recognized) screen width: " + (hi.getWidth() / CN.convertToPixels(1)) + " mm"));
hi.add(new SpanLabel("This text enters a line on a 70mm screen. Do tests."));
hi.add(FontImage.createMaterial(FontImage.MATERIAL_LOCAL_RESTAURANT, "Label", CSSUtilities.getResized(10.0f)));
hi.show();
Note that the recognized width is NOT the actual width, that can be different: however, my code ensures that even in cases where Codename One cannot detect the width correctly, the text adapts as you requested.
A few considerations:
this code has limits, it requires that the orientation is locked vertically, it is only suitable for smartphone screens (no tablet);
remember that the native fonts of Android and iOS are different;
this code automatically resizes the dimensions that you have specified in your css, regarding text, margin and padding (use mm or pt, 1pt is about 0.35mm);
for everything else the resizing is not automatic, in the example code I showed you how to automatically adapt an image;
the code is limited to reduce the text up to a maximum of 30%, in my tests this is fine, provided that the default size is 70mm;
the code never increases the size of the text: again, I decided this behavior based on my tests, you can do as you like.
even more precisely, this code helps me especially on devices that don't report their screen density correctly and therefore "fool" Codename One.
I hope this is useful to you. It is much more useful on real devices than in the simulator.