Skinning Button in actionscript - Adding a border - apache-flex

I've just started playing about with skinning and am finding it harder than anything else I've come across in Flex 4 Mobile. It needs to be done in actionscript as I am trying to skin a flex mobile button. Here is what I have so far: (It does very little, only changes the background color)
package skins {
import flash.display.GradientType;
import flash.geom.Matrix;
import mx.utils.ColorUtil;
import spark.components.supportClasses.StyleableTextField;
import spark.skins.mobile.ButtonSkin;
import spark.skins.mobile.supportClasses.MobileSkin;
public class CustomButtonSkin extends ButtonSkin {
public function CustomButtonSkin() {
super();
layoutPaddingLeft = 10;
layoutPaddingRight = 10;
layoutPaddingTop = 2;
layoutPaddingBottom = 2;
layoutCornerEllipseSize = 1;
}
private static var colorMatrix:Matrix = new Matrix();
private static const CHROME_COLOR_ALPHAS:Array = [1, 1];
private static const CHROME_COLOR_RATIOS:Array = [0, 127.5];
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void {
super.drawBackground(unscaledWidth, unscaledHeight);
var chromeColor:uint = getStyle("chromeColor");
if (currentState == "up") {
graphics.beginFill(0x1C1C1C);
}
else{
var colors:Array = [];
colorMatrix.createGradientBox(unscaledWidth, unscaledHeight, Math.PI / 2, 0, 0);
colors[0] = 0xFFFFFF;//ColorUtil.adjustBrightness2(chromeColor, 70);
colors[1] = 0xEEEEEE;//chromeColor;
graphics.beginGradientFill(GradientType.RADIAL, colors, CHROME_COLOR_ALPHAS, CHROME_COLOR_RATIOS, colorMatrix);
graphics.endFill();
}
}
}
}
There doesn't seem to be much in the way of documentation for skinning Flex mobile components purely in actionscript. How do we add a border for example? If anyone could post their custom flex mobile skins it would be hugely appreciated!

Specifically to draw a border, I would draw one manually using the graphics API.
this.graphics.lineStyle(1, color, alpha, true);
this.graphics.drawRect(0, 0, width, height);
I have a bunch of blog posts/podcasts about building mobile skins / itemRenderers that may help you:
https://www.flextras.com/blog/index.cfm/2011/6/17/Mobile-itemRenderers--6172011---Episode-103--Flextras-Friday-Lunch
https://www.flextras.com/blog/index.cfm/2011/6/23/Understanding-Mobile-itemRenderers
https://www.flextras.com/blog/index.cfm/2011/6/24/Building-a-Mobile-ItemRenderer-in-Flex
https://www.flextras.com/blog/index.cfm/2011/7/8/Implementing-States-in-a-Mobile-Skin---782011---Episode-105--Flextras-Friday-Lunch
https://www.flextras.com/blog/index.cfm/2011/7/13/Implementing-States-in-a-Mobile-Skin-or-Mobile-ItemRenderer

The ButtonSkin has the properties upBorderSkin and downBorderSkin, which you would set equal to fxg graphic resources in the constructor:
public function CustomButtonSkin()
{
super();
// your padding settings here
upBorderSkin = path.to.fxg.resource.upBorder;
downBorderSkin = path.to.fxg.resource.downBorder;
}
And you would override drawBackground with an empty body, not even super.drawBackground():
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
}
This is so that your custom skin won't draw the default background.
You don't even have to detect a state change to draw something different. By specifying the border skin properties, you would be letting the ButtonSkin parent class to detect the state changes and draw the appropriate graphic.
See http://www.adobe.com/devnet/flex/articles/mobile-skinning-part1.html for more information, especially using fxg resources.

Related

Changing the scrolling amount of a spark DataGrid when using the mouse wheel

Basically what the title says. I checked DataGridSkin, and it has a Scroller skin part, but I can't find any attribute for the scrolling amount. I might have missed it.
Can anybody please help me on this issue? Thanks.
I suggest the following. You can find part of scrolling logic in VScrollBar class in mouseWheelHandler method.
var nSteps:uint = Math.abs(delta);
...
for (var vStep:int = 0; vStep < nSteps; vStep++)
{
var vspDelta:Number = vp.getVerticalScrollPositionDelta(navigationUnit);
...
In my case delta equals 3. And grid is scrolled for 3 renderers heights. You can extend VScrollBar class. Then create custom ScrollerSkin (copy from original one) and set you vertical bar there instead of default one. Then create custom DataGrid skin (again copy from original one) and set your custom scroller skin for Scroller there.
In custom VScrollBar class override mouseWheelHandler method. Example:
package {
import flash.events.Event;
import flash.events.MouseEvent;
import mx.core.IInvalidating;
import mx.core.mx_internal;
import mx.events.FlexMouseEvent;
import spark.components.VScrollBar;
import spark.core.IViewport;
import spark.core.NavigationUnit;
import spark.utils.MouseEventUtil;
use namespace mx_internal;
public class MyVScrollBar extends VScrollBar {
public function MyVScrollBar() {
}
override mx_internal function mouseWheelHandler(event:MouseEvent):void {
const vp:IViewport = viewport;
if (event.isDefaultPrevented() || !vp || !vp.visible || !visible)
return;
var changingEvent:FlexMouseEvent = MouseEventUtil.createMouseWheelChangingEvent(event);
if (!dispatchEvent(changingEvent))
{
event.preventDefault();
return;
}
const delta:int = changingEvent.delta;
var nSteps:uint = 1; //number of renderers scrolled!
var navigationUnit:uint;
var scrollPositionChanged:Boolean;
navigationUnit = (delta < 0) ? NavigationUnit.DOWN : NavigationUnit.UP;
for (var vStep:int = 0; vStep < nSteps; vStep++)
{
var vspDelta:Number = vp.getVerticalScrollPositionDelta(navigationUnit);
if (!isNaN(vspDelta))
{
vp.verticalScrollPosition += vspDelta;
scrollPositionChanged = true;
if (vp is IInvalidating)
IInvalidating(vp).validateNow();
}
}
if (scrollPositionChanged)
dispatchEvent(new Event(Event.CHANGE));
event.preventDefault();
}
}
}
Set nSteps variable to number of renderers you want to be scrolled. So in this example grid is scrolled by only one item on wheel.
I found a solution: I used the idea described above, but instead of creating a class that extends VScrollBar, I added a MouseWheelChanging event listener to the scroll bar in the Scroller skin:
<fx:Component id="verticalScrollBarFactory">
<s:VScrollBar visible="false" mouseWheelChanging="outerDocument.vScrollBarWheelChanging(event)"/>
</fx:Component>
internal function vScrollBarWheelChanging(event:MouseEvent):void
{ event.delta/=Math.abs(event.delta); }
You can set event.delta to the desired scroll amount.

flex tree gets chopped even after using scroll bar

when i use the following tree renderer class the the informtions in the tree gets chopped. Is there any solution to fix this bug. please help me.
The PLTree class is as follows:
import flash.events.Event;
import mx.events.ScrollEvent;
import mx.controls.Tree;
import mx.core.ScrollPolicy;
import mx.core.mx_internal;
import mx.events.TreeEvent;
public class PLTree extends Tree
{
private var _lastWidth:Number = 0;
private var _lastHeight:Number = 0;
public function PLTree() {
super();
horizontalScrollPolicy = ScrollPolicy.AUTO;
}
override public function get maxHorizontalScrollPosition():Number
{
return mx_internal::_maxHorizontalScrollPosition;
}
override public function set maxHorizontalScrollPosition(value:Number):void
{
mx_internal::_maxHorizontalScrollPosition = value;
dispatchEvent(new Event("maxHorizontalScrollPositionChanged"));
scrollAreaChanged = true;
invalidateDisplayList();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
var diffWidth:Number = measureWidthOfItems(0,0) - (unscaledWidth - viewMetrics.left - viewMetrics.right);
if (diffWidth <= 0) {
maxHorizontalScrollPosition = 0;
horizontalScrollPolicy = ScrollPolicy.OFF;
} else {
maxHorizontalScrollPosition = diffWidth;
horizontalScrollPolicy = ScrollPolicy.ON;
}
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
override protected function scrollHandler(event:Event):void
{
if (mx_internal::isOpening)
return;
// TextField.scroll bubbles so you might see it here
if (event is ScrollEvent){
super.scrollHandler(event);
invalidateDisplayList();
}
}
}
i am attaching the image file of how it looks when executed.
When surfing using google i found a suggesion to fix this bug is it the right way ?
(
Issue: Text getting chopped of at end.
Fix: change
maxHorizontalScrollPosition = diffWidth;
to
maxHorizontalScrollPosition = diffWidth + 10;
or what ever correction factor you need.
)
Kindly help me .Thanks a lot in advance.
Looking at your picture, I'd suspect the issue has nothing to do with the specific tree, and is only slightly related to the renderer. Instead, I think that when the container holding the Tree is created, it doesn't have a size, and when the Tree sizes its renderers, it gives them the wrong size. Since List-based controls don't set the actual width on renderers, choosing to set explicitWidth instead, the renderers aren't triggered into changing their size.
Check out http://www.developria.com/2009/12/handling-delayed-instantiation-1.html for more explicit details and fixes.
similar to the scroll handler in the above mentioned program. Use a mouse wheel scroll handler to handle that event as follows:
override protected function mouseWheelHandler(eventMouse:MouseEvent):void
{ if (mx_internal::isOpening)
return;
if (eventMouse is MouseEvent){
super.mouseWheelHandler(eventMouse);
invalidateDisplayList();
}
}

How do I get list item selection working when using a png mask in an item renderer in a Flex 4.5 mobile app?

I'm creating a mobile app in which I need to show a calendar with months at the top. The months are part of a component that extends from SkinnableDataContainer (and has some custom scrolling/behaviour - which is why I did'nt use a spark list). I need the months to be shown as a 'trapezium' shaped tab and so I'm using a png image as a mask in the item renderer for the component.
When the mask is not applied, it all works well - the months render, the list/data container selection works when I click on a month and so on.
When the mask is applied, it renders well, scrolling and everything else seems to work well - but when I click on a month, nothing happens visually. And from the trace statements in my code, it appears list item selection is not changing. Looks like mouse clicks are not working.
Any ideas on how to fix this?
I've looked for similar sounding questions (but ask the opposite thing) here on SO. (http://stackoverflow.com/questions/1741172/restrict-mouseevents-to-mask-in-flex-skin)
Regards,
Krishna
Code:
public class TopCalendarMonthRenderer extends LabelItemRenderer {
[Embed(source="/assets/trapezium_alpha.png")]
private static var TrapeziumMask:Class;
private static var trapeziumMaskInstance:BitmapAsset;
override protected function createChildren():void {
super.createChildren();
setLabelProperties();
createMask();
}
private function createMask():void {
if (!this.maskShape){
if (!trapeziumMaskInstance){
trapeziumMaskInstance = (new TrapeziumMask()) as BitmapAsset;
}
maskShape = new Sprite();
//maskShape.visible = false;
//maskShape.mouseEnabled = false;
maskShape.cacheAsBitmap = true;
this.cacheAsBitmap = true;
this.addChild(maskShape);
//this.hitArea = maskShape;
}
}
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void {
//don't call the parent's draw: because we draw our own background
var bgColor:uint = 0x555555;
if (this.selected)
bgColor = backgroundColor;
var g:Graphics = this.graphics;
g.beginFill(bgColor);
g.drawRoundRectComplex(0, 0, unscaledWidth, unscaledHeight, 3, 3, 0, 0);
g.endFill();
//TODO: make the mask a hitArea - so the user can interact with it - HOW?
drawMask();
}
private function drawMask():void {
var g:Graphics = maskShape.graphics;
var img:BitmapData = trapeziumMaskInstance.bitmapData;
g.beginBitmapFill(img, null, false, true);
//g.beginGradientFill(GradientType.RADIAL, [0xffffff, 0xff0000], [1, 0], [0, 255]);
//g.beginFill(0xff0000);
g.drawRect(0, 0, img.width, img.height);
g.endFill();
this.mask = maskShape;
//this.hitArea = maskShape;
}
}
I finally found this:
http://aaronhardy.com/flex/displayobject-quirks-and-tips/
This explains how setting a PNG image as an alpha mask on a DisplayObject breaks mouse events.
A work around is also provided in that article - which worked for me :)

How to prevent a component from being dragged out of the stage in Flex 3

I think there is a simple solution to this question, just not simple enough for me to find it.
Question:
How do you constrain a TitleWindow in Flex 3 from being dragged off the screen/stage? Is there a way to restrict the TitleWindow to the viewing area?
Example: Let's say I have an application that take 100% of the screen. Next, I create a TitleWindow via the PopUpManager. I can then proceed to click and hold (drag) that window off the screen, then release the mouse button. That window is now lost off-screen somewhere. Is there a way to keep the window from being dragged beyond the viewing area?
Thanks for the help in advance.
this is a very old post, but here's another way of doing it:
Whether you are extending the component or not, in the TitleWindow definition add the following line: move:"doMove(event)"
Import the Application library (import mx.core.Application;)
and add the doMove function:
private function doMove(event:Event):void
{//keeps TW inside layout
var appW:Number=Application.application.width;
var appH:Number=Application.application.height;
if(this.x+this.width>appW)
{
this.x=appW-this.width;
}
if(this.x<0)
{
this.x=0;
}
if(this.y+this.height>appH)
{
this.y=appH-this.height;
}
if(this.y<0)
{
this.y=0;
}
}
For flex 4 the answer is here: http://blog.flexexamples.com/2010/01/20/constraining-the-movement-on-a-spark-titlewindow-container-in-flex-4/
You can set its isPopUp property to false to prevent it from being dragged in the first place.
var popupWin:TitleWindow = PopUpManager.createPopUp(this, TitleWindow);
PopUpManager.centerPopUp(popupWin);
popupWin.isPopUp = false;
I don't know if the DragManager class in flex supports bounds checking, but if you really want to allow dragging but limit its bounds, you can still set isPopUp to false and implement the dragging code yourself so that the component never goes outside the limits specified by you. Check startDrag() method for an example. Bounds rectangle is the key.
Flex 4
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
windowMoving="windowMovingHandler(event)">
.
.
.
protected function windowMovingHandler(event:TitleWindowBoundsEvent):void
{
var appBounds:Rectangle = parentApplication.getBounds(DisplayObject(parentApplication));
if(!appBounds.containsRect(event.afterBounds)){
event.preventDefault();
}
}
// for better precision, corect appBounds manualy, or, instead of "parentApplication.getBounds..." create new rectangle of your application size
Subclass the TitleWindow and add a canvas over the title bar as a drag proxy. Then you can explicity call startDrag with a boundary rectangle.
This is pretty skeletal, but should put you on the path...
The reason for the proxy is you may get some weird behavior when you click the titleBar label if you don't have the canvas over it.
public class MyTitleWindow extends TitleWindow
{
public var titleBarOverlay:Canvas;
override protected function createChildren():void
{
super.createChildren();
if(!titleBarOverlay)
{
titleBarOverlay = new Canvas();
titleBarOverlay.width = this.width;
titleBarOverlay.height = this.titleBar.height;
titleBarOverlay.alpha = 0;
titleBarOverlay.setStyle("backgroundColor", 0x000000);
rawChildren.addChild(titleBarOverlay);
}
addListeners();
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
titleBarOverlay.width = this.width;
titleBarOverlay.height = this.titleBar.height;
}
private function addListeners():void
{
titleBarOverlay.addEventListener(MouseEvent.MOUSE_DOWN, onTitleBarPress, false, 0, true);
titleBarOverlay.addEventListener(MouseEvent.MOUSE_UP, onTitleBarRelease, false, 0, true);
}
private function onTitleBarPress(event:MouseEvent):void
{
// Here you can set the boundary using owner, parent, parentApplication, etc.
this.startDrag(false, new Rectangle(0, 0, parent.width - this.width, parent.height - this.height));
}
private function onTitleBarRelease(event:Event):void
{
this.stopDrag();
}
}
You could simply override the move function and prevent "illegal" movement (it is called internally by the Panel drag management).
I think that you also should listen on stage resize, because reducing it (e.g. if the user resize the browser window) could send your popup out of stage even without actually moving it.
public class MyTitleWindow extends TitleWindow {
public function MyTitleWindow() {
// use a weak listener, or remember to remove it
stage.addEventListener(Event.RESIZE, onStageResize,
false, EventPriority.DEFAULT, true);
}
private function onStageResize(event:Event):void {
restoreOutOfStage();
}
override public function move(x:Number, y:Number):void {
super.move(x, y);
restoreOutOfStage();
}
private function restoreOutOfStage():void {
// avoid the popup from being positioned completely out of stage
// (use the actual width/height of the popup instead of 50 if you
// want to keep your *entire* popup on stage)
var topContainer:DisplayObjectContainer =
Application.application.parentDocument;
var minX:int = 50 - width;
var maxX:int = topContainer.width - 50;
var minY:int = 0;
var maxY:int = topContainer.height - 50;
if (x > maxX)
x = maxX
else if (x < minX)
x = minX;
if (y > maxY)
y = maxY
else if (y < minY)
y = minY;
}
}
In your TitleWindow's creationComplete handler add the following:
this.moveArea.visible=false;
This will do the job.
On the other hand, if you have a custom skin, you can remove the "moveArea" part. This should work, too.

Drawing an overlay in custom flex component

How would one create a custom MXML component in flex which is based on an existing component but draws an overlay over this existing component in certain situations.
Ideally, the new component should be based on (derive from) the exiting component so that occurrences of the existing component could just be swapped out with the new one.
I tried to override updateDisplayList() in the new component and to paint the overlay using this.graphics. This resulted in the overlay being drawn underneath the children of the existing component. I also tried to do the drawing upon receiving a render-event which lead to similar results.
When the external condition which should trigger the display of the overlay changes, I call invalidateDisplayList() on my new component. That works to trigger the drawing for both cases described above. The remaining problem seems to be to figure out how to draw on top of all the other components once they are added.
The following example should illustrate what I tried to do; when overlayEnabled was set and the component's invalidateDisplayList() method was called, the red rectangle would get painted in the background....
// NewComponent.mxml
<ExistingComponent ...>
<mx:Script>
...
public var overlayEnabled:Boolean;
override protected updateDisplayList(...) {
super.updateDisplayList(...)
if (overlayEnabled) {
var g:Graphics = this.graphics;
g.beginFill(0xFF0000, 0.5);
g.drawRect(0, 0, width, height);
g.endFill();
}
}
...
</mx:Script>
</ExistingComponent>
Also, feel free to suggest different approaches.
You will have to add a DisplayObject for you overlay and insure when you call updateDisplayList that it is place on the top of the other.
public var overlayEnabled:Boolean;
public overlayHolder:(whatever display object you want to use)
override protected updateDisplayList(...) {
super.updateDisplayList(...)
if (overlayEnabled) {
if (overlayHolder.parent != this){
addChild(overlayHolder);
} else {
if (numChildren > 0)
setChildIndex(overlayHolder, numChildren-1);
}
var g:Graphics = overlayHolder.graphics;
g.beginFill(0xFF0000, 0.5);
g.drawRect(0, 0, width, height);
g.endFill();
} else if (overlayHolder.parent == this) {
removeChild(overlayHolder);
}
}
Edit:
One property you can use to add your overlay to the display list can be the rawchildren:
package {
import flash.display.Graphics;
import flash.display.Sprite;
import mx.containers.VBox;
public class MyVBox extends VBox {
public var overlayEnabled : Boolean = true;
public var overlay : Sprite = new Sprite();
public function MyVBox() {
super();
}
protected override function updateDisplayList(unscaledWidth : Number, unscaledHeight : Number) : void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (overlayEnabled) {
if (overlay.parent != this) {
rawChildren.addChild(overlay);
} else {
if (rawChildren.numChildren > 0)
rawChildren.setChildIndex(overlay, rawChildren.numChildren - 1);
}
var g : Graphics = overlay.graphics;
g.beginFill(0xFF0000, 0.5);
g.drawRect(0, 0, width, height);
g.endFill();
} else if (overlay.parent == this) {
rawChildren.removeChild(overlay);
}
}
}
}

Resources