原文链接:
AIR Native Extension实现iOS应用内付费(In-App Purchase)全教程(一)——概览

阅读本文的基础
ActionScript 3.0编程基础
Apple iOS开发者权限

前言
记得在写Flash开发iOS应用全攻略的时候,不少感兴趣的朋友询问Flash技术是否能实现App Store的应用内付费功能(In-App Purchase,以下简称IAP)。在那个时候我只能说,很抱歉,目前还不支持,但是在半年后的今天,在我提笔写这篇教程的时候,我刚刚完成了一个用Flash技术实现IAP的例子。确切的说,是用AIR 3.0的原生扩展功能(AIR Native Extension,以下简称ANE)实现的。ANE作为AIR 3.0的一项重要特性,为Flash平台往系统底层的功能延伸奠定了基础,为Flash开发者在移动设备上开发商业应用提供了技术保证。它的出现,使AIR更具开放性,随即推动的是使用AIR和原生技术共同开发工作流,AS开发者与Native开发者的关系也将从以往的竞争转为合作。

在这个系列的文章中,我会对ANE做一个详细的介绍,同样介绍的还有苹果IAP的相关知识,最后通过一个具体的例子来讲解从流程到代码,从思路到技巧等各方面的知识点。希望各位朋友在阅读之后可以全面掌握ANE和IAP,并在实际的应用开发中派上用场。

内容提要
本系列文章大概内容如下:

AIR面向iOS设备的原生扩展
AIR Native Extension介绍
ANE的组成部分
ActionScript 3.0扩展
Objective-C 扩展
使用ADT打包ANE
使用ADT打包IPA

iOS应用内付费的实现原理和准备流程
IAP简介
商品与交易
测试IAP的准备流程

ANE面向IAP的测试和开发
在Native扩展中使用StoreKit框架
ActionScript扩展
ANE-IAP开发实例分享

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

前段一大部分的时间是在研究Adobe AIR Native Extension实现iOS应用内付费(In-App Purchase),并且在 Adobe 的 James Li 的帮助下测试成功。
现在 Adobe 的 James Li 已经完成了关于 AIR Native Extension实现iOS应用内付费(In-App Purchase) 的全教程。
下面为从 Adobe 的 James Li 转载过来的原稿。

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

我是 Ticore,論壇上看有人問 Flash CS5.5 用 AS3 建立的 TextField 無法動態設置中文字體
實際測試之後,發現並不是 Flash IDE 的問題
而是 Flash Player 10.2, 10.3 且當 swf version 為 11, 12 時,TextField 用中文名稱設定字型失效
測試程式碼如下:

package {
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
 
	[SWF(width="300", height="200")]
	public class Main extends Sprite {
 
		public function Main() {
			var txt:TextField = new TextField();
			// txt.defaultTextFormat = new TextFormat("Microsoft JhengHei", 16);
			txt.defaultTextFormat = new TextFormat("微軟正黑體", 16);
			txt.text = "微軟正黑體";
			txt.x = 100;
			txt.y = 50;
			addChild(txt);
		}
	}
}

假如用 Flash CS5.5 發布為 Flash Player 10.2 版本
就會發現 “微軟正黑體” 設置失效
假如改成發布為 Flash Player 10, 10.1 版本
中文字體設置又正常了

最簡單的解決方式是統一使用中文字型的英文名稱
譬如 “微軟正黑體” 英文叫做 “Microsoft JhengHei”
這樣就能成功動態設置中文字型了

可是在 Flash Builder 開發上,SDK 都有最低 Player 版本的限制
無法隨意亂設 Player Version
另一方面,這個設定值也是需要對應 SDK 目錄下有 playerglobal.swc
假如真的確定程式碼內沒有需要用到 Player 10.2, 10.3 新 API 話
還可以用編譯參數 -swf-version=10 的方式,來改善這問題

SWF 版本與 Player 版本對照可以參考這裡
Versioning in Flash Runtime (-swf-version)
只要將 SWF 版本設為 10,就算使用 10.3 Player 播放
它也會自動關閉新的 API,以舊版的功能執行
間接避開 TextField 找不到中文字體問題

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

Flash 抽色效果濾鏡

20 2011 In: ActionScript3, Flash

我是 Ticore,這次要分享的是用 Pixel Bender 做出來的抽色濾鏡效果
可以依據指定的色彩,調整點陣圖的色彩飽和度
色彩相差越遠的飽和度越低
效果看起來就會像是把特定色彩抽出來了一樣

直接從 RGB 數值去思考會覺得非常難做
其實只要把 RGB 轉為 HSV 色彩模式便簡單許多
拿到 Hue 值之後,便能拿來與設定主色色彩做比較
差越遠的,就把飽和度調得越低
以下便是我自製的 Partial Color Shader 原始碼:

<languageVersion : 1.0;>
kernel PartialColor
<
    namespace : "PartialColor";
    vendor : "Ticore Shih";
    version : 1;
    description : "only keep saturation of assigned color";
>
{
 
    parameter float hue
    <
        defaultValue: 0.0;
        minValue: 0.0;
        maxValue: 360.0;
    >;
 
    parameter float range
    <
        defaultValue : 40.0;
        minValue : 0.0;
        maxValue : 180.0;
    >;
    input image4 src;
    output pixel4 dst;
 
    void evaluatePixel() {
        pixel4 inputColor = sampleNearest(src, outCoord());
        float minColor, maxColor, delta;
        float h, s, v;
        float r, g, b;
        float f, p, q, t;
        int i;
 
        r = inputColor.r; g = inputColor.g; b = inputColor.b;
 
        minColor = min(min(r , g) , b);
        maxColor = max(max(r , g) , b);
        delta = maxColor - minColor;
 
        // value
        v = maxColor;
        // saturation
        if (maxColor !=0.0 ) {
            s = delta / maxColor;
        }
 
        // hue
        if (maxColor == r) {
            h = (g - b) / delta;
        } else if (maxColor == g) {
            h = 2.0 + (b - r) / delta;
        } else {
            h = 4.0 + (r - g) / delta;
        }
        h *= 60.0;
        h = mod(h + 360.0, 360.0);
 
 
        float diff = min(mod(abs(hue - h - 360.0), 360.0), mod(abs(hue - h + 360.0), 360.0));
 
        // sine
 
        s *= sin(clamp(1.0 - diff / range, 0.0, 1.0) * 90.0 * 3.14159 / 180.0);
 
        s = clamp(s, 0.0, 1.0);
 
        if (s == 0.0) {
            r = g = b = v;
        } else {
            h /= 60.0;
            int i = int(floor(h));
            f = h - float(i);
 
            p = v * ( 1.0 - s );
            q = v * ( 1.0 - s * f );
            t = v * ( 1.0 - s * ( 1.0 - f ) );
 
            if (i == 0) {
                r = v; g = t; b = p;
            } else if (i == 1) {
                r = q; g = v; b = p;
            } else if (i == 2) {
                r = p; g = v; b = t;
            } else if (i == 3) {
                r = p; g = q; b = v;
            } else if (i == 4) {
                r = t; g = p; b = v;
            } else if (i == 5) {
                r = v; g = p; b = q;
            }
        }
 
        dst = pixel4(r, g, b, inputColor.a);
    }
}

實際拿一張色彩豐富的照片來試試看
(照片為印度孟買某學校外)

照片下方特別加上了色條,比較容易看出不同 Hue 值與抽色濾鏡的效果關係

Partial Color Shader 線上範例:

This movie requires Flash Player 9

Partial Color Shader 原始檔案下載

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

Spark 多重欄位排序的 DataGrid

15 2011 In: ActionScript3, Flex

Flex 4.5 Spark 雖然提供了 DataGrid
可是它沒有像 mx:AdvancedDataGrid 那樣支援多 Column 排序功能
看到 Adobe Cookbooks – How do I perform a multicolumn sort in a Spark DataGrid?
有人提供,可是那幾乎是寫死在組件外部
不能讓 User 自己選擇排序欄位
所以我想辦法繼承 Spark DataGrid,複寫 columnHeaderGroup_clickHandler 函式
當 User 在 Column Header 同時按下 Ctrl + Mouse Left Click 時
提供多重 Column 排序的功能

MultiColSortableDataGrid.as

package {
	import mx.collections.ICollectionView;
	import mx.collections.IList;
	import mx.collections.ISort;
	import mx.collections.ISortField;
	import mx.collections.Sort;
	import mx.styles.AdvancedStyleClient;
 
	import spark.components.DataGrid;
	import spark.components.Grid;
	import spark.components.gridClasses.GridColumn;
	import spark.components.gridClasses.GridSortField;
	import spark.events.GridEvent;
	import spark.events.GridSortEvent;
 
	/**
	 *  Multi-column sortable data grid class.
	 *  Extends spark data grid and override columnHeaderGroup_clickHandler function.
	 *  Provide multi-column sort function when ctrl + left click on column header.
	 */
	public class MultiColSortableDataGrid extends DataGrid {
 
		/**
		 *  @private
		 */
		override protected function columnHeaderGroup_clickHandler(event:GridEvent):void {
			const column:GridColumn = event.column;
			var columnIndices:Vector.<int>;
 
			if (!enabled || !sortableColumns || !column || !column.sortable)
				return;
 
			columnIndices = Vector.<int>([column.columnIndex]);
 
			// If the sort isn't cancelled, will also update the columnHeaderGroup
			// visibleSortIndiciatorIndices.
 
			sortByMultiColumns(columnIndices, true, event.ctrlKey);
		}
 
 
 
	    /**
		 *  Copy and modify from DataGrid::sortByColumns function.
	     *  Add one more argument multiColSort, decide if continue to use old sort fields.
		 *  Achieve multi-column sort function.
	     */
		public function sortByMultiColumns(columnIndices:Vector.<int>,
							isInteractive:Boolean = false , multiColSort:Boolean = false):Boolean {
 
			const dataProvider:ICollectionView = this.dataProvider as ICollectionView;
			if (!dataProvider)
				return false;
 
			var sort:ISort = dataProvider.sort;
			if (sort) {
				sort.compareFunction = null;
			} else {
				sort = new Sort();
			}
			var sortFields:Array = createSortFields(columnIndices, sort.fields, isInteractive);
			if (!sortFields)
				return false;
 
			var oldSortFields:Array = (dataProvider.sort) ? dataProvider.sort.fields : null;
 
 
			// implements multi-column sort function.
			if (multiColSort && oldSortFields) {
				if (columnHeaderGroup) {
					var colSortIndices:Vector.<int> = columnHeaderGroup.visibleSortIndicatorIndices;
					for (var j:int = colSortIndices.length - 1 ; j >= 0 ; --j) {
						var idx:int = colSortIndices[j];
						if (columnIndices.indexOf(idx) < 0) {
							columnIndices.unshift(idx);
						}
					}
				}
 
				outer: for (var i:int = oldSortFields.length - 1 ; i >= 0 ; --i) {
					var oldField:ISortField = oldSortFields[i] as ISortField;
 
					for (var k:int = 0 ; k < sortFields.length ; ++k) {
						var newField:ISortField = sortFields[k] as ISortField;
 
						if (newField.name == oldField.name) {
							continue outer;
						}
					}
					sortFields.unshift(oldField);
				}
			}
 
 
			// Dispatch the "changing" event. If preventDefault() is called
			// on this event, the sort operation will be cancelled.  If columnIndices or
			// sortFields are changed, the new values will be used.
			if (isInteractive) {
				// This is a shallow copy which means only the pointers to the ISortField objects
				// are copied to the new Array, not the ISortField objects themselves.
				if (oldSortFields)
					oldSortFields = oldSortFields.concat();
 
				if (hasEventListener(GridSortEvent.SORT_CHANGING)) {
					const changingEvent:GridSortEvent =
						new GridSortEvent(GridSortEvent.SORT_CHANGING,
						false, true,
						columnIndices,
						oldSortFields, /* intended to be read-only but no way to enforce this */
						sortFields);
 
					// The event was cancelled so don't sort.
					if (!dispatchEvent(changingEvent))
						return false;
 
					// Update the sort columns since they might have changed.
					columnIndices = changingEvent.columnIndices;
					if (!columnIndices)
						return false;
 
					// Update the new sort fields since they might have changed.
					sortFields = changingEvent.newSortFields;
					if (!sortFields)
						return false;
				}
			}
 
			// Remove each old SortField that's not a member of the new sortFields Array
			// as a "styleClient" of this DataGrid.
 
			if (oldSortFields) {
				for each (var oldSortField:ISortField in oldSortFields) {
					var oldASC:AdvancedStyleClient = oldSortField as AdvancedStyleClient;
					if (!oldASC || (oldASC.styleParent != this) || (sortFields.indexOf(oldASC) != -1))
						continue;
					removeStyleClient(oldASC);
				}
			}
 
			// Add new SortFields as "styleClients" of this DataGrid so that they
			// inherit this DataGrid's locale style. 
 
			for each (var newSortField:ISortField in sortFields) {
				var newASC:AdvancedStyleClient = newSortField as AdvancedStyleClient;
				if (!newASC || (newASC.styleParent == this))
					continue;
				addStyleClient(newASC);
			}
 
			sort.fields = sortFields;
 
			dataProvider.sort = sort;
			dataProvider.refresh();
 
			if (isInteractive) {
				// Dispatch the "change" event.
				if (hasEventListener(GridSortEvent.SORT_CHANGE)) {
					const changeEvent:GridSortEvent =
						new GridSortEvent(GridSortEvent.SORT_CHANGE,
						false, true,
						columnIndices,
						oldSortFields, sortFields);
					dispatchEvent(changeEvent);
				}
 
				// Update the visible sort indicators.
				if (columnHeaderGroup)
					columnHeaderGroup.visibleSortIndicatorIndices = columnIndices;
			}
 
			return true;
		}
 
 
	    /**
	     *  @private
	     *  Copy from DataGrid::createSortFields function.
	     */
		private function createSortFields(columnIndices:Vector.<int>,
									previousFields:Array, preservePrevious:Boolean):Array {
			if (columnIndices.length == 0)
				return null;
 
			var fields:Array = new Array();
 
			for each (var columnIndex:int in columnIndices) {
				var col:GridColumn = this.getColumnAt(columnIndex);
				if (!col)
					return null;
 
				var dataField:String = col.dataField;
 
				// columns all must have a dataField or a labelFunction or a sortCompareFunction
				if (dataField == null && col.labelFunction == null && col.sortCompareFunction == null)
					return null;
 
				const isComplexDataField:Boolean = (dataField && (dataField.indexOf(".") != -1));
				var sortField:ISortField = null;
				var sortDescending:Boolean = col.sortDescending;
 
				// Check if we just sorted this column.
				sortField = findSortField(dataField, fields, isComplexDataField);
 
				// If we haven't sorted by this column yet, check if
				// we've sorted by this column in the previous sort.
				if (!sortField && previousFields)
					sortField = findSortField(dataField, previousFields, isComplexDataField);
				else
					preservePrevious = false;
 
				// Previously sorted column, so flip sortDescending.
				if (sortField)
					sortDescending = !sortField.descending;
 
				// Create a SortField from the column.  If the sortField was found in the
				// previousFields and we need to preserve them, create a new sort field for the column.
				if (!sortField || preservePrevious)
					sortField = col.sortField;
 
				col.sortDescending = sortDescending;
				sortField.descending = sortDescending;
				fields.push(sortField);
			}
 
			return fields;
		}
 
 
	    /**
	     *  @private
	     *  Copy from DataGrid::getColumnAt function.
	     */
		private function getColumnAt(columnIndex:int):GridColumn {
			const grid:Grid = grid;
			if (!grid || !grid.columns)
				return null;
			const columns:IList = grid.columns;
			return ((columnIndex >= 0) && (columnIndex < columns.length)) ?
											columns.getItemAt(columnIndex) as GridColumn : null;
		}
 
 
		/**
		 *  @private
		 *  Copy from DataGrid::findSortField function.
		 * 
		 *  Finds a SortField using the provided dataField and returns it.
		 *  If the dataField is complex, it tries to find a GridSortField
		 *  with a matching dataFieldPath.
		 *
		 *  @param dataField The dataField of the column.
		 *  @param fields The array of SortFields to search through.
		 *  @param isComplexDataField true if the dataField is a path.
		 */
		private static function findSortField(dataField:String, fields:Array, isComplexDataField:Boolean):ISortField {
			if (dataField == null)
				return null;
			for each (var field:ISortField in fields) {
				var name:String = field.name;
				if (isComplexDataField && (field is GridSortField)) {
					name = GridSortField(field).dataFieldPath;
				}
				if (name == dataField)
					return field;
			}
			return null;
		}
	}
}

以上組件就能提供多重欄位排序功能
假如還想要顯示排序優先順序號碼
就需要再提供自訂的 Skin
參考原始檔案

Spark 多重欄位排序線上範例:

This movie requires Flash Player 9

原始檔案下載

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter