简体   繁体   中英

How is an mx:Canvas measured when inside a renderer in Flex 3?

I'm having a sizing issue with a canvases located inside an HBox. It seems "_graphic", "_border" and "_fill" canvases (in com.example.ThingRenderer.mxml) do not get measured at the same time as all the other measurements inside the renderer. However, this problem is only observed on the first pass-through. Refer to the images for a visual... 1st image shows the state of the app after it finished loading. 2nd image represents what the screen looks like after the Populate button is clicked. 3rd image shows what happens when the stepper is incremented. The question is how come the drawing in the 3rd image doesn't get rendered once the data is populated into the table?

RendererTest.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    creationComplete="handleCreationComplete(event)"
>
    <mx:Script>
        <![CDATA[
            import com.example.Thing;

            import mx.collections.ArrayCollection;
            import mx.events.FlexEvent;
            import mx.events.NumericStepperEvent;

            private const _thingProvider:ArrayCollection = new ArrayCollection();
            private var _thing1:Thing;

            protected function handleCreationComplete(event:FlexEvent):void {
                _thing1 = new Thing("thingy", 0xff0000, 0.3);
                _stepper.value = _thing1.ratio;
            }

            protected function handlePopulateClick(event:MouseEvent):void {
                _thingProvider.addItem(_thing1);
            }

            protected function handleStepperChange(event:NumericStepperEvent):void {
                _thing1.ratio = event.value;
            }

        ]]>
    </mx:Script>
    <mx:VBox>
        <mx:Button label="Populate" click="handlePopulateClick(event)" />
        <mx:NumericStepper id="_stepper" minimum="0" maximum="1" stepSize="0.01" change="handleStepperChange(event)" />
        <mx:AdvancedDataGrid dataProvider="{_thingProvider}" variableRowHeight="true" width="100%" height="100%">
            <mx:columns>
                <mx:AdvancedDataGridColumn headerText="Name" dataField="name" />
                <mx:AdvancedDataGridColumn headerText="Display"
                    width="150" sortable="false"
                    itemRenderer="com.example.ThingRenderer"
                />
            </mx:columns>
        </mx:AdvancedDataGrid>
    </mx:VBox>
</mx:Application>

com.exampleThingRenderer.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas
    xmlns:mx="http://www.adobe.com/2006/mxml"

    width="100%"
    horizontalScrollPolicy="off" verticalScrollPolicy="off"
>
    <mx:Script>
        <![CDATA[
            import mx.binding.utils.ChangeWatcher;

            private var _thing:Thing;
            private var _ratioWatcher:ChangeWatcher;

            private var _doClearContent:Boolean;
            private var _doDrawBorder:Boolean;
            private var _doUpdateFill:Boolean;

            override public function set data(value:Object):void {
                if(value && value is Thing) {
                    _thing = Thing(value);
                    if(_ratioWatcher) {
                        _ratioWatcher.unwatch();
                    }
                    _ratioWatcher = ChangeWatcher.watch(_thing, "ratio", handleRatioChanged);

                    _doClearContent = false;
                    _doDrawBorder = true;
                    _doUpdateFill = true;
                    _graphic.invalidateSize();
                    _border.invalidateSize();
                }
                else {
                    _doClearContent = true;
                    _doDrawBorder = false;
                    _doUpdateFill = false;
                }
                super.data = value;
            }

            private function handleRatioChanged(event:Event):void {
                _doUpdateFill = true;
                invalidateDisplayList();
            }

            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
                if(_doClearContent) {
                    _container.visible = false;
                    _container.includeInLayout = false;
                    _doClearContent = false;
                }

                super.updateDisplayList(unscaledWidth, unscaledHeight);

                if(_doDrawBorder) {
                    trace("_thingContainer.width="+_container.width, "_thingGraphic.width="+_graphic.width, "_thingBorder.width="+_border.width);
                    _border.graphics.clear();
                    _border.graphics.moveTo(0, 0);
                    _border.graphics.lineStyle(1, _thing.color);
                    _border.graphics.lineTo(_border.width, 0);
                    _border.graphics.lineTo(_border.width, _border.height);
                    _border.graphics.lineTo(0, _border.height);
                    _border.graphics.lineTo(0, 0);

                    _doDrawBorder = false;
                }

                if(_doUpdateFill) {
                    _percentage.text = Math.round(_thing.ratio * 100.0) + "%";
                    _fill.graphics.clear();
                    _fill.graphics.beginFill(_thing.color);
                    _fill.graphics.drawRect(0, 0, _fill.width * _thing.ratio, _fill.height);

                    _doUpdateFill = false;
                }
            }
        ]]>
    </mx:Script>

    <mx:HBox id="_container" width="100%" paddingLeft="5" paddingTop="5" paddingRight="5" paddingBottom="5">
        <mx:Label id="_percentage" width="45" />
        <mx:Canvas id="_graphic" width="100%" height="15">
            <mx:Canvas id="_border" x="0" y="0" width="100%" height="100%" />
            <mx:Canvas id="_fill" x="0" y="0" width="100%" height="100%" />
        </mx:Canvas>
    </mx:HBox>
</mx:Canvas>

com.example.Thing.as

package com.example {
    public class Thing {
        [Bindable] public var name:String;
        [Bindable] public var color:uint;
        [Bindable] public var ratio:Number;

        public function Thing(name:String, color:uint, ratio:Number) {
            this.name = name;
            this.color = color;
            this.ratio = ratio;
        }
    }
}

应用程序加载完成后的初始外观

单击“填充”按钮后

步进器从0.30增加到0.40之后

All this happens because you can't use width and height properties in updateDisplayList, they are not updated yet. Make separate component (eg ThingProgressBar) and put drawing logick inside it, that will solve everything:

package
{
import mx.core.UIComponent;

public class ThingProgressBar extends UIComponent
{
    private var _ratio:Number;
    public function get ratio():Number
    {
        return _ratio;
    }
    public function set ratio(value:Number):void
    {
        _ratio = value;
        invalidateDisplayList();
    }

    override protected function updateDisplayList(
                     unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
        graphics.clear();
        if (unscaledWidth > 0 && unscaledHeight > 0)
        {
            graphics.lineStyle(1, 0xFF0000);
            graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);

            graphics.beginFill(0xFF0000);
            graphics.drawRect(0, 0, unscaledWidth * ratio, unscaledHeight);
            graphics.endFill();
        }
    }
}
}

So your renderer might look like this:

<mx:HBox
xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:mx="library://ns.adobe.com/flex/mx"
horizontalScrollPolicy="off" verticalScrollPolicy="off" xmlns:local="*"
>
<fx:Script>
    <![CDATA[
        [Bindable] private var _thing:Thing;

        override public function set data(value:Object):void
        {
            _thing = value as Thing;
            super.data = value;
        }
    ]]>
</fx:Script>

<mx:HBox width="100%"
             paddingLeft="5" paddingTop="5"
             paddingRight="5" paddingBottom="5">
    <mx:Label text="{_thing.name}" width="45" />
    <local:ThingProgressBar width="100%" height="15"
                                    ratio="{_thing.ratio}"/>
</mx:HBox>
</mx:HBox>
  1. I removed watcher. Binding by watcher is considered a bad practice, use mxml binding or events instead.
  2. I removed two Canvases with separated border and fill - they can be cobined together.
  3. I used UIComponent instead of Canvas. Don't use containers unless you need layout, they are heavy.
  4. I used HBox instead of Canvas in renderer because I like boxes more :) But you can't avoid using second container in renderer if you need custom styles since List overwrites renderer's stylesheet.

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