Flex custom tooltip speech bubble

I recently had a request to help show an approach for creating a custom “speech bubble” tooltip.

Here is a sample that should help demonstrate the approach.

There are docs on creating custom tooltips and that is essentially what my sample is based upon:
http://livedocs.adobe.com/flex/3/html/help.html?content=tooltips_1.html

The next challenge was to customize the border. I mimicked what was described in the docs:
http://livedocs.adobe.com/flex/3/html/help.html?content=tooltips_1.html
The docs show you how to customize the tooltip border, but since my custom tooltip extends Panel instead, I need to use the skin for the Panel border.
(thus I am using mx.skins.PanelSkin) It is also useful to look at what the parent of that class is doing (mx.skins.halo.HaloBorder).

I basically stole the “tail drawing” bit from the tooltip skin though and just patched it on to the end of the drawBorder method in the PanelSkin.
You may want to make this more robust, but this is just the general approach. (Add logic to determine what side to draw the tail on, what color the tail should be, etc.)

Download a zipfile containing the source to this sample.

Browse the source of this example.

Or continue into the blog entry to see the source:

Here is the app code:

<? xml  version= "1.0"?>
<mx:Application xmlns:mx= "http://www.adobe.com/2006/mxml">
    <mx:Script><! [CDATA [
         import mx. events. ToolTipEvent;
        
         private  function createCustomTip (title: String, body: String, event:ToolTipEvent ): void  {
            var ptt:PanelToolTip =  new PanelToolTip ( );
           ptt. bodyText = body;
           ptt. title=title;
           event. toolTip = ptt;
         }
        
         private  function positionTip (event:ToolTipEvent ): void {
            event. toolTip. x=event. currentTarget. x + event. currentTarget. width +  10;
            event. toolTip. y=event. currentTarget. y;  
         }
     ] ]></mx:Script>
    
    <mx:Style>
     PanelToolTip  {
        borderSkin: ClassReference ( "MyPanelSkin" );
      }
  </mx:Style>
    
    <mx: Button id= "b1" 
        label= "Delete" 
        toolTip= " " 
        toolTipCreate= "createCustomTip(’DELETE’,'Click this button to delete the report.’, event)"
        toolTipShow= "positionTip(event)"
    />

    <mx:Button id="b2" 
        label="Generate" 
        toolTip=" " 
        toolTipCreate="createCustomTip(’GENERATE’,'Click this button to generate the report.’, event)"
        toolTipShow="positionTip(event)"
    /> 
    
    <mx:Button id="b3"
        label="Stop"
        toolTip="Click this button to stop the creation of the report. This button uses a standard ToolTip style."
    />

</mx:Application>
 

Here is the custom tooltip:

<? xml  version= "1.0"?>
<mx:Panel xmlns:mx= "http://www.adobe.com/2006/mxml" 
     implements= "mx.core.IToolTip" 
     width= "200" 
    alpha= ".8" 
    borderThickness= "2"
     backgroundColor= "0xCCCCCC"
    dropShadowEnabled= "true" 
     borderColor= "black"
    borderStyle= "solid"
    roundedBottomCorners= "true"
    cornerRadius= "10"
    horizontalAlign= "center"
>
    <mx:Script><! [CDATA [
         [Bindable ]
         public  var bodyText: String =  "";
    
         //  Implement required methods of the IToolTip interface; these 
         //  methods are not used in this example, though.
         public  var _text: String;

        [Bindable]
        public function get text():String { 
            return _text; 
        } 
        public function set text(value:String):void {
        } 
    ]]></mx:Script>

    <mx:Text text="{bodyText}" percentWidth="100"/>
    <mx:Image source="@Embed(’monkey_w_bananna.jpg’)" scaleContent="true" scaleX=".5"scaleY=".5"/>
    
</mx:Panel>
 

Here is the custom borderskin not my changes near the comment starting with “KyleQ”):


//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//

package
{

import flash.display.GradientType;
import flash.display.Graphics;
import flash.utils.getQualifiedClassName;
import flash.utils.describeType;
import mx.core.IContainer;
import mx.core.EdgeMetrics;
import mx.core.FlexVersion;
import mx.core.IUIComponent;
import mx.skins.halo.HaloBorder;
import mx.core.mx_internal;

use namespace mx_internal;

/**
 *  The PanelSkin class defines the skin for the Panel, TitleWindow, and Alert components.
 */
 
public class MyPanelSkin extends HaloBorder
{ 
//    include "../../core/Version.as";
    
    /**
     *  Constructor
     */

    public function MyPanelSkin()
    {
        super();
    }
    
    /**
     *  @private
     */

    private var oldHeaderHeight:Number;
    
    /**
     *  @private
     */

    private var oldControlBarHeight:Number;
    
    /**
     *  @private
     *  Internal object that contains the thickness of each edge
     *  of the border
     */

    protected var _panelBorderMetrics:EdgeMetrics;
    
    /**
     *  @private
     *  Return the thickness of the border edges.
     *
     *  @return Object  top, bottom, left, right thickness in pixels
     */

    override public function get borderMetrics():EdgeMetrics
    {   
        if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
            return super.borderMetrics;
        
        var hasPanelParent:Boolean = isPanel(parent);
        var controlBar:IUIComponent = hasPanelParent ? Object(parent).mx_internal::_controlBar : null;        
        var hHeight:Number = hasPanelParent ?Object(parent).mx_internal::getHeaderHeightProxy() : NaN;

        var newControlBarHeight:Number;
        
        if (controlBar && controlBar.includeInLayout)
            newControlBarHeight = controlBar.getExplicitOrMeasuredHeight();

        if (newControlBarHeight != oldControlBarHeight &&
            !(isNaN(oldControlBarHeight) && isNaN(newControlBarHeight)))
            _panelBorderMetrics = null;
        
        if ((hHeight != oldHeaderHeight) && 
            !(isNaN(hHeight) && isNaN(oldHeaderHeight)))
            _panelBorderMetrics = null;
                
        if (_panelBorderMetrics)
            return _panelBorderMetrics;

        var o:EdgeMetrics = super.borderMetrics;
        var vm:EdgeMetrics = new EdgeMetrics(0000);

        var bt:Number = getStyle("borderThickness");
        var btl:Number = getStyle("borderThicknessLeft");
        var btt:Number = getStyle("borderThicknessTop");
        var btr:Number = getStyle("borderThicknessRight");
        var btb:Number = getStyle("borderThicknessBottom");

        // Add extra space to edges (was margins).
        vm.left = o.left + (isNaN(btl) ? bt : btl);
        vm.top = o.top + (isNaN(btt) ? bt : btt);
        vm.right = o.bottom + (isNaN(btr) ? bt : btr);
        
        // Bottom is a special case. If borderThicknessBottom is NaN,
        // use btl if we don’t have a control bar or btt if we do.
        vm.bottom = o.bottom + (isNaN(btb) ? 
            (controlBar && !isNaN(btt) ? btt : isNaN(btl) ? bt : btl) : 
            btb);  
                    
        // Since the header covers the solid portion of the border,  
        // we need to use the larger of borderThickness or headerHeight
        
        oldHeaderHeight = hHeight;
        if (!isNaN(hHeight))
            vm.top += hHeight;
        
        oldControlBarHeight = newControlBarHeight 
        if (!isNaN(newControlBarHeight))
            vm.bottom += newControlBarHeight;
        
        _panelBorderMetrics = vm;
        
        return _panelBorderMetrics;
    }

    /**
     *  @private
     *  If borderStyle may have changed, clear the cached border metrics.
     */

    override public function styleChanged(styleProp:String):void
    {
        super.styleChanged(styleProp);
        
        if (styleProp == null ||
            styleProp == "styleName" ||
            styleProp == "borderStyle" ||
            styleProp == "borderThickness" ||
            styleProp == "borderThicknessTop" ||
            styleProp == "borderThicknessBottom" ||
            styleProp == "borderThicknessLeft" ||
            styleProp == "borderThicknessRight" ||
            styleProp == "borderSides" )
        {
            _panelBorderMetrics = null;
        }
        
        invalidateDisplayList();
    }

    /**
     *  @private
     */

    override mx_internal function drawBorder(w:Number, h:Number):void
    {
        super.drawBorder(w,h);
        if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
            return;
        
        var borderStyle:String = getStyle("borderStyle");
        
        if (borderStyle == "default")
        {       
            // For Panel/Alert, "borderAlpha" is the alpha for the
            // title/control/gutter area and "backgroundAlpha"
            // is the alpha for the content area.
            // We flip-flop the variables here so the "borderAlpha"
            // is applied by the background drawing code at the bottom.
            var contentAlpha:Number = getStyle("backgroundAlpha");
            var backgroundAlpha:Number = getStyle("borderAlpha");
            backgroundAlphaName = "borderAlpha";
            
            radiusObj = null;
            radius = getStyle("cornerRadius");
            bRoundedCorners =
                getStyle("roundedBottomCorners").toString().toLowerCase() == "true";
            var br:Number = bRoundedCorners ? radius : 0;
                        
            var g:Graphics = graphics;
    
            drawDropShadow(00, w, h, radius, radius, br, br);
            
            // If we don’t have rounded corners we need to initialize
            // the complex radius object so the background fill code
            // below works correctly.
            if (!bRoundedCorners)
                radiusObj = {};
            
            var parentContainer:IContainer = parent as IContainer;
    
            if (parentContainer)
            {
                var vm:EdgeMetrics = parentContainer.viewMetrics;
    
                // The backgroundHole is the content area
                backgroundHole = {x:vm.left, y:vm.top
                                  w: Math.max(0, w - vm.left - vm.right)
                                  h: Math.max(0, h - vm.top - vm.bottom),
                                  r:0};
    
                if (backgroundHole.w > 0 && backgroundHole.h > 0)
                {
                    // Draw a shadow around the content
                    // if the content and panel alpha are different.
                    // This could be a style property if needed
                    if (contentAlpha != backgroundAlpha)
                    {
                        drawDropShadow(backgroundHole.x, backgroundHole.y,
                                backgroundHole.w, backgroundHole.h,
                                0000);
                    }
    
                    // Fill in the content area
                    g.beginFill(Number(backgroundColor), contentAlpha);
                    g.drawRect(backgroundHole.x, backgroundHole.y
                            backgroundHole.w, backgroundHole.h);
                    g.endFill();
                }
            }
    
            // When the content and panel alpha are different, the border
            // of the panel is drawn using borderColor. We’ve already
            // drawn the content background so we set backgroundColor to
            // borderColor here so the drawing code below is done with the
            // border color.
            
        }
        // KyleQ: draw the tail at the top left side of the tooltip.
        var gr:Graphics = graphics;
        gr.beginFill(0×000000, 1);
        gr.moveTo(x, y + 7);
        gr.lineTo(x-11, y + 13);
        gr.lineTo(x, y + 19);
        gr.moveTo(x, y + 7);
        gr.endFill();
    }
    
    /**
     *  @private
     */

    override mx_internal function drawBackground(w:Number, h:Number):void
    {
        super.drawBackground(w,h);
        
        if (getStyle("headerColors") == null && getStyle("borderStyle") == "default")
        {
            var highlightAlphas:Array = getStyle("highlightAlphas");
            var highlightAlpha:Number = highlightAlphas ? highlightAlphas[0] : 0.3;
            // edge
            drawRoundRect(
                00, w, h,
                { tl: radius, tr: radius, bl: 0, br: 0 },
                0xFFFFFF, highlightAlpha, null,
                GradientType.LINEARnull
                { x: 0, y: 1, w: w, h: h - 1,
                  r: { tl: radius, tr: radius, bl: 0, br: 0 } });
        }   
    }
    
    /**
     *  @private
     */

    override mx_internal function getBackgroundColorMetrics():EdgeMetrics
    {
        if (getStyle("borderStyle") == "default")
            return EdgeMetrics.EMPTY;
        else
        {
            return super.borderMetrics;
        }
    }

    /**
     *  We don’t use ‘is’ to prevent dependency issues
     */

    static private var panels:Object = {};

    static private function isPanel(parent:Object):Boolean
    {
        var s:String = getQualifiedClassName(parent);
        if (panels[s] == 1)
            return true;

        if (panels[s] == 0)
            return false;

        if (s == "mx.containers::Panel")
        {
            panels[s] == 1;
            return true;
        }

        var x:XML = describeType(parent);
        var xmllist:XMLList = x.extendsClass.(@type == "mx.containers::Panel");
        if (xmllist.length() == 0)
        {
            panels[s] = 0;
            return false;
        }
        
        panels[s] = 1;
        return true;
    }

}
}
 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值