简体   繁体   中英

In Flex, what is a better way to build UI component whose height is based on width and content?

I need to build a new UI control based on UIComponent. The control's height should have a certain ratio to the width and also depends on what content is shown (amount of text inside). Could anybody recommend how I should do this?

I have tried googling around, but it seems all articles just try to avoid talking about the case. My personal feeling is that the way Flex renders, ie invalidate and update display list, is natively inefficient to deal with the situation.

Below are my codes. It works all fine, but it seems this is not the most efficient way because the height is calculated inside updateDisplayList(), which triggers another updateDisplayList().

The control is used inside a mobile project, so when the device is rotated, the parent's size will be changed and the control needs to be resized as well.

So, a few questions:

  1. What should I do in measure()? Since the height is based on actual width, I cannot get the final width until updateDisplayList(), because the parent of the control has width defined as percentage.
  2. Is it correct to calculate the height at the very beginning of updateDisplayList()? If not, how should I do it?

Below is an example what I mean.

import flash.text.TextField;
import flash.text.TextFieldAutoSize;

import mx.core.UIComponent;

public class MyControl extends UIComponent
{
public function MyControl()
{
    super();
}

public var textPadding:Number = 15;

public var heightRatio:Number = 1.61803398875;

private var _label:String;
private var labelChanged:Boolean = false;

public function get label():String
{
    return _label;
}

public function set label(value:String):void //can be long text with about 20-30 lines
{
    if (_label != value)
    {
        _label = value;
        labelChanged = true;

        invalidateProperties();
        invalidateSize();
        invalidateDisplayList();
    }
}

private var text:TextField;

override protected function createChildren():void
{
    super.createChildren();

    if (!text)
    {
        text = new TextField();
        text.autoSize = TextFieldAutoSize.LEFT;
        text.multiline = true;
        text.wordWrap = true;
        addChild(text);
    }
}

override protected function commitProperties():void
{
    super.commitProperties();

    if (labelChanged)
    {
        text.text = label;
        labelChanged = false;
    }
}

override protected function measure():void
{
    super.measure();

    //What should I do here?
}

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
    text.width = unscaledWidth;

    if (text.height < unscaledWidth / heightRatio)
    {
        height = unscaledWidth / heightRatio + textPadding * 2;
    }
    else
    {
        height = text.height + textPadding * 2;
    }

    super.updateDisplayList(unscaledWidth, unscaledHeight);

    text.x = textPadding;
    text.y = textPadding;
}
}

I need to build a new UI control based on UIComponent. The control's height should have a certain ratio to the width and also depends on what content is shown (amount of text inside). Could anybody recommend how I should do this?

Technically you can't.

Your whole approach / understanding of how Flex works is fundamentally flawed.

A Flex component will never size itself. It is always sized by its parent. The measure() method will set ideal values: measuredWidth, measuredHeight, measuredMinWidth, and measuredMinHeight. But, they are just suggestions to be provided to the parent container; which may or may not use them.

MXML masks this concept and your misunderstanding is very common.

In most cases, the default Flex layout containers will do their best to honor these values, but they have logic to handle situations where their is not enough space. For example; a VGroup with two children that are both set to 100% height won't size both children to 100% of the VGroup.

Without seeing your code that uses and sizes your component it is tough to say what is going on. Most likely the 'height' that you are setting is not getting overwritten by the parent container; and that is the only reason I can believe this approach works.

I'm making some assumptions here; but I think your measure method should look something like this:

override protected function measure():void
{
    super.measure();
    this.measuredwidth = text.getMeasuredOrExplicitWidth();
    this.measuredHeight = text.getMeasuredOrExplicitHeight()/heightRatio + textPadding * 2;
}

updateDisplayList(), should be something like this:

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
    text.width = unscaledWidth;
    text.height = unscaledWidth / heightRatio;

    super.updateDisplayList(unscaledWidth, unscaledHeight);

    text.x = textPadding;
    text.y = textPadding;
}

The updateDisplayList() in the component's parent container should do something like this:

myControlInstance.height = myControlInstance.measuredHeight;
myControlInstance.width = myControlInstance.measuredWidth;

The Spark framework uses all the same methods; but sometimes shuffles them around a bit. So, the updateDisplayList() and measure() are usually in a skin class; or a layout class.

According to Adobe Documentation http://livedocs.adobe.com/flex/3/html/help.html?content=ascomponents_advanced_2.html , measure() is used to measure default size and updateDisplayList() is used to size and place child components.

To answer your questions:

1) I'd say to do nothing, since you're not interested in default size.

2) I think, it doesn't matter since the value change doesn't have immediate effect (as you write updateDisplayList will be called again).

My suggestion is to have an listener for widthChanged event and use it calculate proper height. And to do height calculation when label is changed.

There is no way around the extra update cycle, that is just the nature of layout with text reflow. Usually how it's done (or at least how it's done in the flex components) is to measure based on it's preferred width first, which may be quite wide if measuring text with no explicit bounds. This measurement is important however as it notifies parent containers if and when it should try and expand out the width as much as possible, before the uper limit is set in layoutBoundsWidth or layoutBoundsHeight. Once your bounds are set in the first call to updateDisplayList, you should resize your text and invalidateSize() so you can measure the height on the second pass. Sorry I have no code examples, im on the mobile. If you need them, I can post some when I'm in the office.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM