سؤال

I am trying to do a circular progress bar, but I am stuck.

I don't have any problem at drawing the colored circle, but I don't know how to remove the color of the center and how to remove the little part which represent the missing percent.

The actual solution is to use an image in background which I replace each 5%. It work, but it's heavy and jerky, and if I need to change the color of the circle, I have to redraw all the 20 pictures.

It there any solution which allow me to draw this "progress circle" dynamically ?

هل كانت مفيدة؟

المحلول 2

Thanks you guys.

Finally, I have both Josh's code and script from http://flassari.is/2009/11/pie-mask-in-as3/

I have used the code from flassari to draw and update the circle (edited to do it in revert mode, from 100% to 0%), and the way to do of Josh to make a hole at the center of the circle.

Here is the main code I used:

private function init():void
{
    // Store sprites
    var container:UIComponent = new UIComponent();
    gr.addElementAt(container, 0);

    // Draw the full circle with colors
    circleToMask.graphics.beginGradientFill(GradientType.RADIAL, [0x000000, 0x000000], [0, 1], [80, 130]);
    circleToMask.graphics.drawCircle(0, 0, 50);
    circleToMask.graphics.endFill();

    // Add the circle
    container.addChildAt(circleToMask, 0);

    // Add the mask
    container.addChild(circleMask);

    Set the position of the circle
    circleToMask.x = (circleMask.x = 14);
    circleToMask.y = (circleMask.y = 14);
    circleToMask.mask = circleMask;

    // Draw the circle at 100%
    renderChart(1);
}
private function renderChart(percentage:Number):void
{
    circleMask.graphics.clear();
    circleMask.graphics.beginFill(0);

    // Draw the circle at the given percentage            /------ set the begin at the middle top
    drawPieMask(circleMask.graphics, percentage, 100, -1.57, 8);
    circleMask.graphics.endFill();
}

// Function from flassari (a little simplified)
private function drawPieMask(graphics:Graphics, percentage:Number, radius:Number = 50, rotation:Number = 0, sides:int = 6):void {
    radius /= Math.cos(1/sides * Math.PI);
    var lineToRadians:Function = function(rads:Number):void {
        graphics.lineTo(Math.cos(rads) * radius + x, Math.sin(rads) * radius + y);
    };
    var sidesToDraw:int = Math.floor(percentage * sides);
    for (var i:int = 0; i <= sidesToDraw; i++)
        lineToRadians((i / sides) * (Math.PI * 2) + rotation);
    if (percentage * sides != sidesToDraw)
        lineToRadians(percentage * (Math.PI * 2) + rotation);
}

And here, the mxml:

<s:Group verticalCenter="0" horizontalCenter="0">
    <s:Rect width="100%" height="100%">
        <s:fill>
            <s:SolidColor color="0xcccccc"/>
        </s:fill>
    </s:Rect>
    <s:Label id="lab" verticalCenter="0" horizontalCenter="0" text="test"/>
    <s:Group id="gr" cacheAsBitmap="true" blendMode="layer" verticalCenter="0" horizontalCenter="0">
        <s:Group verticalCenter="0" horizontalCenter="0" blendMode="erase" cacheAsBitmap="true">
            <s:Ellipse width="30" height="30">
                <s:fill>
                    <s:SolidColor color="0"/>
                </s:fill>
            </s:Ellipse>
        </s:Group>
    </s:Group>
</s:Group>

Thanks you guys for your help

نصائح أخرى

This is a lot more complicated than I think you realize.

You'll need to use an alpha mask to punch a hole in the circle. This is a pain to figure out since there is very little documentation on how to do it properly, but relatively easy once you figure it out. You need to create a Group with a blendMode of "layer" and set cacheAsBitmap to true. You then create your shape using Ellipse and create a Group (that must be below it) with a blendMode of "erase" and cacheAsBitmap set to true. In that Group, you draw the shape you wish to use to punch a hole in the parent shape.

Example:

<s:Group cacheAsBitmap="true" blendMode="layer">
    <s:Ellipse id="background" width="84" height="84" >
        <s:fill>
            <s:SolidColor color="0"/>
        </s:fill>
    </s:Ellipse>

    <s:Group id="backgroundMask" horizontalCenter="0" verticalCenter="0" blendMode="erase" cacheAsBitmap="true">
        <s:Ellipse width="64" height="64" >
            <s:fill>
                <s:SolidColor color="0"/>
            </s:fill>
        </s:Ellipse>
    </s:Group>
</s:Group>

That is taken directly from an app I recently built so I can verify it works. That will create a circle of diameter 84px with a 64px hole in the center.

EDIT: As RIAStar pointed out in the comments, you can actually do this with a stroke rather than my convoluted way.

From his comment:

<s:Ellipse width="74" height="74">
    <s:stroke>
    <s:SolidColorStroke weight="10"/>
    </s:stroke>
</s:Ellipse>

To do the actual progress, you need to do a bit of trig. There is no way (that I know of) to do this without bezier paths, unfortunately. So you basically create a Path object and on a progress update, redraw its data. This will be the mask of the actual circle.

You need to draw it based on quadrant, too, so it is even more complicated. Basically, you start at the top-middle, draw down to the center of the circle, draw to the current point in the progress, and then draw to that quadrant's corner and then to each previous corner and finally back to the top-middle.

I can't give the code for drawing the actual path (it's not generic code that can be found anywhere and my NDA prevents me from doing so), but I can show you how to get the point on the circle for the current progress. You may have to pad the point depending on your circle's attributes, but it should be a good starting point to work with, at any rate.

var halfHeight:Number = this.background.height / 2;
var halfWidth:Number = this.background.width / 2;
var angle:Number = -this.progress * 2 * Math.PI - Math.PI / 2; (uses negative progress to indicate counter-clockwise direction)
var pointOnCircle:Point = new Point(halfWidth * Math.cos( angle ) + halfWidth, halfWidth * Math.sin( angle ) + halfWidth);

The pointOnCircle should lay right on the outer edge of the circle drawn above. this.progress is a number between 0 and 1. Make sure you limit it to that range. If I remember correctly, this breaks if it goes beyond those bounds.

Hopefully that helps. Again, I can't really show you anything more than that, but it should be more than enough to get you started

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top