2

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.

Small phone (Tecno) Samsung S8+

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.

Richard Matovu
  • 501
  • 4
  • 15
  • This is pretty seamless if you use millimeter sizes etc. I'll need a sample of specific CSS you used to give you more information on this. – Shai Almog Jun 12 '20 at 03:24
  • In one of my projects I have a code that allows what you asked for, but before I publish an answer I would like to make sure I understand the question correctly. For example: if on a 70mm wide phone a certain amount of text enters a line, do you want the same amount of text to enter a line on a 50mm wide phone? So the size of the text should vary according to the size of the screen? Does it? If so, do you assume that the orientation is blocked? – Francesco Galgani Jun 12 '20 at 21:48
  • Yes @Francesco. I want the same amount of text to enter a line on a 50mm wide screen as it does on a 70mm screen. I want the size of the text, containers and fields to vary according to the size of the screen. With regard to orientation, I'm asking with regard to a portrait orientation. – Richard Matovu Jun 12 '20 at 22:09
  • @RichardMatovu Ok, see my answer – Francesco Galgani Jun 12 '20 at 23:30

2 Answers2

2

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.

Francesco Galgani
  • 6,137
  • 3
  • 20
  • 23
  • I have implemented your class but I'm failing to read the images I loaded in my css file. It displays on the console that it successfully loads the Android.css but it throws a null exception at all the points where I get an image from the css. Kindly advise. – Richard Matovu Jun 30 '20 at 16:18
  • Also, I request for clarification, so do i create multiple css files and name them Android.css then iOS.css and so on and then in my main class I add them all in place of theme (as seen in your code) Or do i maintain the word theme in my main class but I name my css files accordingly? – Richard Matovu Jun 30 '20 at 16:31
  • My Java class is an example to customize, as it is based on my needs. You don't need to use more than one CSS like I did, you can delete the part about loading more CSS. What interests you is mostly how I used the `private static Hashtable adaptTheme(Hashtable hashtable)` method. Loading images from CSS works for me in the way explained here: https://github.com/shannah/cn1-css/wiki/Images – Francesco Galgani Jul 01 '20 at 07:09
  • About "theme.css", my code assumes that you shouldn't use the word "theme" and that you modified the build.xml to support your single css or multiple csses. The issue with the word "theme" is that it's used by Codename One to support the restyling of the UI on the fly without recompiling the project, but that feature "could" be incompatible with my code: I'm not sure about that, I didn't check if they are compatible toghether. – Francesco Galgani Jul 01 '20 at 08:24
  • 1
    I made some changes to the code to adapt it to my project. I changed the `loadMultipleCSSes(String... css)` method from void to Resources in order to return the loaded theme. Then in my main class, in place of `theme = UIManager.initFirstTheme("/theme");` i replaced it with `theme=CSSUtilities.loadMultipleCSSes("Android","iOS");` and it worked for me. – Richard Matovu Jul 01 '20 at 09:19
1

My approach is a little easier to implement, yet efficient. I do the following to adjust my apps to varying screen sizes:

Catch screen dimension with :

int height = Display.getInstance().getDisplayHeight();

Then I use (a portions of) the resulting value to define the margins and padding according to the components I have in the containers. It needs a little refinement, but works great for all devices:

label.getAllStyles().setMargin(height/20,5,30,30);
label.getAllStyles().setPadding(height/30,height/30,25,25);

Catching the height and dividing it by a fixed integer makes the margins/ padding large for large devices and small for smaller screens.

You can do the same with the fonts:

int fontSize = Display.getInstance().convertToPixels(height/30);

You can even combine CSS settings with this approach, but the setMargin() or setPadding() method has to come after setUIID() in order to override the CSS settings.

A snippet could look like this:

int height = Display.getInstance().getDisplayHeight();
int fontSize = Display.getInstance().convertToPixels(height/30);
Font redHatFont = Font.createTrueTypeFont("Red Hat Text Medium", "RedHatText-Medium.ttf").derive(fontSize, Font.STYLE_PLAIN);
label.setUIID("MyLabel");
label.getAllStyles().setMargin(height/20,5,30,30);
label.getAllStyles().setPadding(height/30,displayHeight/30,25,25);
label.getUnselectedStyle().setFont(redHatFont);
rainer
  • 3,295
  • 5
  • 34
  • 50
  • Interesting :) We had ideas to solve the same problem, but with different approaches. – Francesco Galgani Jul 01 '20 at 08:26
  • Thanks for upvoting @FrancescoGalgani .... I always hunt for the easiest available approach. I have also studied yours with great interest :-) – rainer Jul 02 '20 at 15:34