Datafields: Best practices for using layouts? Should I use layouts at all?

Former Member
Former Member
I'm new to Connect IQ and doing my first Datafields. Here is what I want to have:


My first try was to do all layouting (positioning/size of the arrows, font sizes etc) programatically which worked fine but wasn't that readable afterwards. The type of code with position-calculations you find very intuitive while writing it but don't understand it after a hour or two :)

So I tried to do things with different layouts. But I don't think Layouts are made for the different datafield sizes of the Edge series?!
  • There seems to be no way to automatically detect the size of the datafield and choose the right layout? For example I want to display the arrows on top or below the speed for big datafields and on the right for small datafields.
  • Also aligning the indicator arrows to the right seems to be impossible? You have to position it with a absolute X position, or am I missing something. I found no way to provide a percent value or (like in css) something like providing positioning relative to the right or bottom.
  • There seems to be no way to position shapes you defined in a drawable? For example I want to define the position of my arrows dynamically but drawable.setLocation() or drawable.locX=123; won't work.

So I ended up in a terrible mixture of defining some things in a layout and doing some things in the code. In my opinion this will increase complexity and coupling :(

So my main question is: Am I using the layouting via xml completely wrong? I've about 8 years of experience in web development (HTML, CSS, Javascript, Java) but Monkey C and the layouting concept seems to be quite different.
I was very frustrated when I started the simulator with the Edge 520 instead of the 1000 => Everything was overlapping and misaligned. This was much better wich my "code only" approach.
  • So I tried to do things with different layouts. But I don't think Layouts are made for the different datafield sizes of the Edge series?!

    Yeah, it can be a real pain if you want to do a data field that looks slightly different based on the field size. It gets worse when you have to deal with round and semi-round watch faces.

    There seems to be no way to automatically detect the size of the datafield and choose the right layout? For example I want to display the arrows on top or below the speed for big datafields and on the right for small datafields.

    There is nothing to do it automatically, but you can do it pretty easily with code. You can determine the size of the data field by asking the dc parameter for its width and height, and then you can use that information to get the appropriate layout.

    // I'd put this in LayoutSelector_Edge1000.mc and then only include that file in the build
    // when building for the edge_1000.
    class LayoutSelector
    {
    function getSelection(dc) {
    var width = dc.getWidth();
    var height = dc.getHeight();

    // get the symbol for the best fit layout, then get that layout
    // from the layouts module
    var method = new Lang.Method(Rez.Layouts, getLayout(width, height));

    // invoke that method, returning a layout
    return method.invoke(dc);
    }

    hidden function getLayout(width, height) {
    if (width > 240) {
    return getLayout_400xYYY(height);
    }
    else if (width > 199) {
    return getLayout_240xYYY(height);
    }
    else if (width > 132) {
    return getLayout_199xYYY(height);
    }
    else if (width > 119) {
    return getLayout_132xYYY(height);
    }
    else {
    return getLayout_119xYYY(height);
    }
    }

    // portrait

    hidden function getLayout_240xYYY(height) {
    if (height > 199) {
    return :Layout240x400;
    }
    else if (height > 132) {
    return :Layout240x199;
    }
    else if (height > 99) {
    return :Layout240x132;
    }
    else if (height > 79) {
    return :Layout240x99;
    }
    else {
    return :Layout240x79;
    }
    }

    hidden function getLayout_119xYYY(height) {
    return :Layout119x79;
    }

    // landscape

    hidden function getLayout_400xYYY(height) {
    if (height > 119) {
    return :Layout400x240;
    }
    else {
    return :Layout400x119;
    }
    }

    hidden function getLayout_199xYYY(height) {
    return :Layout199x119;
    }

    hidden function getLayout_132xYYY(height) {
    if (height > 79) {
    return :Layout132x119;
    }
    else {
    return :Layout132x79;
    }
    }
    }

    class LayoutTestView extends Ui.DataField {

    hidden var mBackgnd;
    hidden var mValue;
    hidden var mArrowUp;
    hidden var mArrowDn;

    hidden var mSelector;

    function initialize() {
    DataField.initialize();

    // allocate this once and re-use it. we can have device-specific
    // implementations and compile them in/out as necessary.
    mSelector = new LayoutSelector();
    }

    function onLayout(dc) {
    setLayout(mSelector.getSelection(dc));

    mArrowUp = self.findDrawableById("Up");
    mArrowDn = self.findDrawableById("Down");
    mBackgnd = self.findDrawableById("Background");
    mValue = self.findDrawableById("Value");

    return true;
    }

    // more view stuff
    }


    I don't think that code is super complicated. The only tricky part is calling the layout function given the symbol, but once you understand that it is quite simple. I have working code for the edge_1000 that I wrote to prove this will work to myself. It does exactly what you're trying to do if you want it.

    Also aligning the indicator arrows to the right seems to be impossible? You have to position it with a absolute X position, or am I missing something. I found no way to provide a percent value or (like in css) something like providing positioning relative to the right or bottom.

    If you want to place something at 60% of the width of the draw area, you can do the math and encode the value directly, or you can encode the arithmetic directly in the layout...

    <layout id="Layout240x400">
    <drawable class="Background" />
    <label id="Value" x="center" y="center" color="Gfx.COLOR_WHITE" justification="Gfx.TEXT_JUSTIFY_CENTER" font="Gfx.FONT_NUMBER_THAI_HOT" />
    <drawable class="Triangle">
    <param name="locX">dc.getWidth() * .5</param>
    <param name="locY">dc.getHeight() * .25</param>
    <param name="width">75</param>
    <param name="height">75</param>
    <param name="mode">1</param>
    </drawable>
    <drawable class="Triangle">
    <param name="locX">dc.getWidth() * .5</param>
    <param name="locY">dc.getHeight() * .75</param>
    <param name="width">75</param>
    <param name="height">75</param>
    <param name="mode">0</param>
    </drawable>
    </layout>


    There seems to be no way to position shapes you defined in a drawable? For example I want to define the position of my arrows dynamically but drawable.setLocation() or drawable.locX=123; won't work.

    You can position the drawables as shown above.

    So my main question is: Am I using the layouting via xml completely wrong?

    No, I don't think you're using it wrong, I just don't think you have enough experience with it to do things as easily as you would with web stuff.

    I was very frustrated when I started the simulator with the Edge 520 instead of the 1000 => Everything was overlapping and misaligned. This was much better wich my "code only" approach.

    Unfortunately, given that the dimensions of the data fields between devices differ, it isn't always easy. The edge_1000 fields have different dimensions than those on the edge_520, the fonts are different sizes, and sometimes the font ascent/descent values are different. Even if you define your layouts completely based on percentages, and come up with some 'fuzzy logic' to pick a layout based on the dimensions, things will often look wrong.

    If you can come up with concrete examples of how you'd like it to work, I think you should post them. There is a lot of room for this stuff to be made easier.

    Travis
  • Former Member
    Former Member over 9 years ago
    Thanks for your comprehensive answer, Travis.
    Unfortunately I couldn't try things out by now, but I hope I'll have time soon.

    I don't know if I got you wrong or maybe my question was pointing in the wrong direction: I want to avoid having a specific layout for each datafield size, but I want to have as few layouts as possible.
    I didn't see any example with dynamic positions by now, so I'm very happy about your example (<param name="locX">dc.getWidth() * .5</param>) and hope it will help me to build my datafield with two or at max three different layouts. Hope I can spend some time on it today or tomorrow.
  • I understand not wanting to define layouts for every field size. It gets old. That may work fine for something like the edge with only a few layouts and helper functions, but you'll likely have to get more creative if you want to support some of the round and semi-round devices. Is there some reason you're trying to avoid creating the layouts aside from the initial cost of doing the work?

    One other thing that you might find useful.. You can call arbitrary code from the layout. For instance, if you want to pick a font based on some dimensions and a sample text string...

    function select_font_for_text_in_area(dc, width, height, sample_text)
    {
    // pick a font that will allow sample_text to fit into the provided area
    // using dc.getTextDimensions()
    }


    Then you can use it like this..

    <drawable class="Text">
    <param name="locX">dc.getWidth() * .33</param>
    <param name="locY">dc.getHeight() * .5</param>
    <param name="color">Gfx.COLOR_WHITE</param>
    <param name="font">select_font_for_text_in_area(dc, dc.getWidth() * .33, dc.getHeight() * .5, "-88.88")</param>
    <param name="justification">Gfx.TEXT_JUSTIFY_CENTER</param>
    </drawable>


    Travis
  • Former Member
    Former Member over 9 years ago
    Thanks again Travis.

    The example for choosing a font is something I also will need.

    Regarding your question why I don't want to create layouts
    I don't know how to express this correct in englisch but I'll try: I don't wont to create a separate layout for each possible datafield size just for the purpose of aligning things with absolute values. It is perfectly fine for me to have different layouts for datafields that really look different (arrows on top/bottom of the text vs. arrows on the right). But I want to avoid having 5 very similar layouts with just a bit bigger or smaller arrows or for defining a smaller font for a smaller datafield.

    I think your approach with some calculations inside the layout-template will be just fine for me. I hope I'll find some time to try this all out soon.
  • This is another question regarding layouts.

    Could someone provide a working example of how to position a label from layouts file.

    I can set locX in code with wattLabel.locY = dc.getHeight() * 0.5; But I can't set it in resource file as <label/> doesn't accept <param>:s.
  • If you use the custom drawable style for variables it should work.

    <drawable id="value" class="Ui.Text">
    <param name="locX">dc.getWidth() * .33</param>
    <param name="locY">dc.getHeight() * .5</param>
    <param name="color">Gfx.COLOR_WHITE</param>
    <param name="font">Gfx.FONT_SMALL</param>
    <param name="justification">Gfx.TEXT_JUSTIFY_CENTER</param>
    </drawable>


    You might also be able to do that with the x attribute of the label. I switched to using the custom drawable syntax so I get consistency.
  • If you use the custom drawable style for variables it should work.

    <drawable id="value" class="Ui.Text">
    <param name="locX">dc.getWidth() * .33</param>
    <param name="locY">dc.getHeight() * .5</param>
    <param name="color">Gfx.COLOR_WHITE</param>
    <param name="font">Gfx.FONT_SMALL</param>
    <param name="justification">Gfx.TEXT_JUSTIFY_CENTER</param>
    </drawable>


    You might also be able to do that with the x attribute of the label. I switched to using the custom drawable syntax so I get consistency.


    Bravo. This should be in the documentation. :-)

    Can you do relative locations with params? View.getDrawableById() or getDrawableById() didn't work inside param tag.
    This did not work:
    <param name="locY">View.getDrawableById("other").locX - 20</param>
  • No, you can not currently do that given what is provided with ConnectIQ. At the time that the layout is created there is no view in scope for you to refer to. You'd need to do this adjustment after you load the layout. If you look at the documentation for LAYOUT_VALIGN_TOP (and friends), it looks like there is some built-in support for this type of thing, but I haven't seen any documentation for it outside of using the Ui.Picker.
  • No, you can not currently do that given what is provided with ConnectIQ. At the time that the layout is created there is no view in scope for you to refer to. You'd need to do this adjustment after you load the layout. If you look at the documentation for LAYOUT_VALIGN_TOP (and friends), it looks like there is some built-in support for this type of thing, but I haven't seen any documentation for it outside of using the Ui.Picker.


    Well. You can do that in the program, but not in the resource file. Relative is easy to implement, but looks like it is a disaster to manage with different layouts. There may be some right way to do it, but I just haven't figured it out yet.

    Right now I'm stuck with for example this problem:
    I have one label isn't visible in smaller layout. Yet I have to set all text colors in case the black/white scheme has changed. I haven't figured out any way to not have to double check the layout size later in the code.
  • Right now I'm stuck with for example this problem:

    Personally, what you are seeing is one of the reasons I don't use layouts for much. Also, on devices with a 16k limit on df's, they seem to take more memory than they are worth.

    I use width and height (and maybe obscurity flags on round/semi-round) and then use my own x/y for displaying things, and even pick the font sizes that works. I set that in onLayout(), and things go from there...