Przykład użycia klasy TextField: formatowanie tekstu w stylu „gazetowym”Flash Player 9 i nowsze wersje, Adobe AIR 1.0 i nowsze wersje Przykład News Layout formatuje tekst do postaci przypominającej artykuł w drukowanej gazecie. Tekst wejściowy może zawierać nagłówek, podtytuł i treść artykułu. Biorąc pod uwagę szerokość i wysokość ekranu, przykładowa aplikacja News Layout formatuje nagłówek i podtytuł tak, aby zajmowały całą szerokość obszaru wyświetlania. Treść artykułu jest dzielona na dwie lub większą liczbę kolumn. Ten przykład ilustruje następujące techniki programowania w języku ActionScript:
Aby pobrać pliki tej przykładowej aplikacji, należy przejść na stronę www.adobe.com/go/learn_programmingAS3samples_flash_pl. Pliki aplikacji News Layout znajdują się w folderze Samples/NewsLayout. Aplikacja składa się z następujących plików:
Odczytywanie zewnętrznego pliku CSSDziałanie aplikacji News Layout rozpoczyna się od odczytania tekstu artykułu z lokalnego pliku XML. Następnie odczytywany jest zewnętrzny plik CSS zawierający informacje o formatowaniu nagłówka, podtytułu i tekstu głównego. W pliku CSS zdefiniowane są trzy style: standardowy styl akapitu artykułu oraz style h1 i h2, odpowiednio dla nagłówka i podtytułu. p {
font-family: Georgia, "Times New Roman", Times, _serif;
font-size: 12;
leading: 2;
text-align: justify;
indent: 24;
}
h1 {
font-family: Verdana, Arial, Helvetica, _sans;
font-size: 20;
font-weight: bold;
color: #000099;
text-align: left;
}
h2 {
font-family: Verdana, Arial, Helvetica, _sans;
font-size: 16;
font-weight: normal;
text-align: left;
}
Technika używana do odczytu zewnętrznego pliku CSS jest taka sama, jak technika opisana w sekcji Ładowanie zewnętrznego pliku CSS. Po załadowaniu pliku CSS aplikacja wykonuje metodę onCSSFileLoaded() przedstawioną poniżej. public function onCSSFileLoaded(event:Event):void
{
this.sheet = new StyleSheet();
this.sheet.parseCSS(loader.data);
h1Format = getTextStyle("h1", this.sheet);
if (h1Format == null)
{
h1Format = getDefaultHeadFormat();
}
h2Format = getTextStyle("h2", this.sheet);
if (h2Format == null)
{
h2Format = getDefaultHeadFormat();
h2Format.size = 16;
}
pFormat = getTextStyle("p", this.sheet);
if (pFormat == null)
{
pFormat = getDefaultTextFormat();
pFormat.size = 12;
}
displayText();
}
Metoda onCSSFileLoaded() tworzy obiekt StyleSheet i powoduje, że obiekt ten analizuje wejściowe dane CSS. Główny tekst artykułu jest wyświetlany w obiekcie MultiColumnTextField, który może bezpośrednio korzystać z obiektu StyleSheet. Jednak pola nagłówków są obiektami klasy HeadlineTextField, która do formatowania używa obiektu TextFormat. Metoda onCSSFileLoaded() dwukrotnie wywołuje metodę getTextStyle() w celu przekształcenia deklaracji stylów CSS w obiekt TextFormat przeznaczony do użycia z oboma obiektami HeadlineTextField. public function getTextStyle(styleName:String, ss:StyleSheet):TextFormat
{
var format:TextFormat = null;
var style:Object = ss.getStyle(styleName);
if (style != null)
{
var colorStr:String = style.color;
if (colorStr != null && colorStr.indexOf("#") == 0)
{
style.color = colorStr.substr(1);
}
format = new TextFormat(style.fontFamily,
style.fontSize,
style.color,
(style.fontWeight == "bold"),
(style.fontStyle == "italic"),
(style.textDecoration == "underline"),
style.url,
style.target,
style.textAlign,
style.marginLeft,
style.marginRight,
style.indent,
style.leading);
if (style.hasOwnProperty("letterSpacing"))
{
format.letterSpacing = style.letterSpacing;
}
}
return format;
}
W deklaracjach stylów CSS i obiektach TextFormat stosowane są różne nazwy właściwości i znaczenia wartości właściwości. Metoda getTextStyle() tłumaczy wartości właściwości arkusza stylów CSS na wartości oczekiwane przez obiekt TextFormat. Układanie elementów artykułu na stronieKlasa StoryLayout formatuje i rozmieszcza pola tekstowe nagłówka, podtytułu i głównego tekstu w układzie „gazetowym”. Metoda displayText() tworzy i wstępnie rozmieszcza różne pola. public function displayText():void
{
headlineTxt = new HeadlineTextField(h1Format);
headlineTxt.wordWrap = true;
headlineTxt.x = this.paddingLeft;
headlineTxt.y = this.paddingTop;
headlineTxt.width = this.preferredWidth;
this.addChild(headlineTxt);
headlineTxt.fitText(this.headline, 1, true);
subtitleTxt = new HeadlineTextField(h2Format);
subtitleTxt.wordWrap = true;
subtitleTxt.x = this.paddingLeft;
subtitleTxt.y = headlineTxt.y + headlineTxt.height;
subtitleTxt.width = this.preferredWidth;
this.addChild(subtitleTxt);
subtitleTxt.fitText(this.subtitle, 2, false);
storyTxt = new MultiColumnText(this.numColumns, 20,
this.preferredWidth, 400, true, this.pFormat);
storyTxt.x = this.paddingLeft;
storyTxt.y = subtitleTxt.y + subtitleTxt.height + 10;
this.addChild(storyTxt);
storyTxt.text = this.content;
...
Każde pole jest umieszczane pod poprzednim polem poprzez ustawienie właściwości y na wartość właściwości y poprzedniego pola powiększoną o jego wysokość. Dynamiczne obliczanie położenia jest wymagane, ponieważ wysokość obiektów HeadlineTextField i MultiColumnTextField może ulegać zmianie w wyniku dopasowania do treści. Zmiana wielkości czcionki w celu dopasowania do wielkości polaNa podstawie znanej szerokości w pikselach i maksymalnej liczby wierszy do wyświetlania obiekt HeadlineTextField dostosowuje wielkość czcionki w taki sposób, aby tekst zmieścił się w polu. Jeśli tekst jest krótki, czcionka zostanie powiększona, przez co nagłówek stanie się „krzykliwy”, niczym w tabloidach. Jeśli tekst jest długi, czcionka będzie mniejsza. Operację zmiany wielkości czcionki realizuje metoda HeadlineTextField.fitText() przedstawiona poniżej: public function fitText(msg:String, maxLines:uint = 1, toUpper:Boolean = false, targetWidth:Number = -1):uint
{
this.text = toUpper ? msg.toUpperCase() : msg;
if (targetWidth == -1)
{
targetWidth = this.width;
}
var pixelsPerChar:Number = targetWidth / msg.length;
var pointSize:Number = Math.min(MAX_POINT_SIZE, Math.round(pixelsPerChar * 1.8 * maxLines));
if (pointSize < 6)
{
// the point size is too small
return pointSize;
}
this.changeSize(pointSize);
if (this.numLines > maxLines)
{
return shrinkText(--pointSize, maxLines);
}
else
{
return growText(pointSize, maxLines);
}
}
public function growText(pointSize:Number, maxLines:uint = 1):Number
{
if (pointSize >= MAX_POINT_SIZE)
{
return pointSize;
}
this.changeSize(pointSize + 1);
if (this.numLines > maxLines)
{
// set it back to the last size
this.changeSize(pointSize);
return pointSize;
}
else
{
return growText(pointSize + 1, maxLines);
}
}
public function shrinkText(pointSize:Number, maxLines:uint=1):Number
{
if (pointSize <= MIN_POINT_SIZE)
{
return pointSize;
}
this.changeSize(pointSize);
if (this.numLines > maxLines)
{
return shrinkText(pointSize - 1, maxLines);
}
else
{
return pointSize;
}
}
Metoda HeadlineTextField.fitText() wykorzystuje prostą technikę rekurencyjną do określenia wielkości czcionki. Najpierw szacunkowo określa średnią liczbę pikseli przypadającą na znak tekstu i na tej podstawie oblicza wstępnie wielkość czcionki w punktach. Następnie zmienia wielkość czcionki i sprawdza, czy w wyniku zawijania wierszy powstała liczba wierszy większa od możliwej do wyświetlenia. Jeśli liczba wierszy jest za duża, wywoływana jest metoda shrinkText() w celu zmniejszenia czcionki i podejmowana jest kolejna próba. Jeśli liczba wierszy NIE jest za duża, wywoływana jest metoda growText() w celu zwiększenia czcionki i podejmowana jest kolejna próba. Proces kończy się, gdy zwiększenie czcionki o jeden punkt spowodowałoby powstanie zbyt dużej liczby wierszy. Podział tekstu między wiele kolumnKlasa MultiColumnTextField rozkłada tekst między wiele obiektów TextField, które są następnie rozmieszczane podobnie, jak kolumny w gazecie. Konstruktor MultiColumnTextField() tworzy najpierw tablicę obiektów TextField, po jednym dla każdej kolumny, co przedstawiono poniżej: for (var i:int = 0; i < cols; i++)
{
var field:TextField = new TextField();
field.multiline = true;
field.autoSize = TextFieldAutoSize.NONE;
field.wordWrap = true;
field.width = this.colWidth;
field.setTextFormat(this.format);
this.fieldArray.push(field);
this.addChild(field);
}
Każdy obiekt TextField jest dodawany do tablicy, a następnie dodawany do listy wyświetlania wewnątrz metody addChild(). Każda zmiana właściwości text lub styleSheet obiektu StoryLayout powoduje wywołanie metody layoutColumns() w celu ponownego wyświetlenia tekstu. Metoda layoutColumns() wywołuje metodę getOptimalHeight() w celu ustalenia prawidłowej liczby pikseli, która pozwoli zmieścić cały tekst w układzie o danej szerokości. public function getOptimalHeight(str:String):int
{
if (field.text == "" || field.text == null)
{
return this.preferredHeight;
}
else
{
this.linesPerCol = Math.ceil(field.numLines / this.numColumns);
var metrics:TextLineMetrics = field.getLineMetrics(0);
this.lineHeight = metrics.height;
var prefHeight:int = linesPerCol * this.lineHeight;
return prefHeight + 4;
}
}
Najpierw metoda getOptimalHeight() oblicza szerokość każdej kolumny. Następnie ustawia szerokość i właściwość htmlText pierwszego obiektu TextField w tablicy. Metoda getOptimalHeight() wykorzystuje ten pierwszy obiekt TextField do ustalenia łącznej liczby wierszy powstałych w wyniku zawijania tekstu i na tej podstawie określa wymaganą liczbę wierszy w każdej kolumnie. Następnie wywołuje metodę TextField.getLineMetrics() w celu pobrania obiektu TextLineMetrics zawierającego szczegółowe informacje o wielkości tekstu w pierwszym wierszu. Właściwość TextLineMetrics.height określa pełną wysokość wiersza tekstu w pikselach, włączając w to elementy pod linią bazową, nad górną krawędzią i interlinię. Optymalną wysokością obiektu MultiColumnTextField jest wówczas wysokość wiersza pomnożona przez liczbę wierszy w każdej kolumnie plus 4 (w celu uwzględnienia górnej i dolnej krawędzi obiektu TextField, która ma grubość dwóch pikseli). Oto cały kod metody layoutColumns(): public function layoutColumns():void
{
if (this._text == "" || this._text == null)
{
return;
}
var field:TextField = fieldArray[0] as TextField;
field.text = this._text;
field.setTextFormat(this.format);
this.preferredHeight = this.getOptimalHeight(field);
var remainder:String = this._text;
var fieldText:String = "";
var lastLineEndedPara:Boolean = true;
var indent:Number = this.format.indent as Number;
for (var i:int = 0; i < fieldArray.length; i++)
{
field = this.fieldArray[i] as TextField;
field.height = this.preferredHeight;
field.text = remainder;
field.setTextFormat(this.format);
var lineLen:int;
if (indent > 0 && !lastLineEndedPara && field.numLines > 0)
{
lineLen = field.getLineLength(0);
if (lineLen > 0)
{
field.setTextFormat(this.firstLineFormat, 0, lineLen);
}
}
field.x = i * (colWidth + gutter);
field.y = 0;
remainder = "";
fieldText = "";
var linesRemaining:int = field.numLines;
var linesVisible:int = Math.min(this.linesPerCol, linesRemaining);
for (var j:int = 0; j < linesRemaining; j++)
{
if (j < linesVisible)
{
fieldText += field.getLineText(j);
}
else
{
remainder +=field.getLineText(j);
}
}
field.text = fieldText;
field.setTextFormat(this.format);
if (indent > 0 && !lastLineEndedPara)
{
lineLen = field.getLineLength(0);
if (lineLen > 0)
{
field.setTextFormat(this.firstLineFormat, 0, lineLen);
}
}
var lastLine:String = field.getLineText(field.numLines - 1);
var lastCharCode:Number = lastLine.charCodeAt(lastLine.length - 1);
if (lastCharCode == 10 || lastCharCode == 13)
{
lastLineEndedPara = true;
}
else
{
lastLineEndedPara = false;
}
if ((this.format.align == TextFormatAlign.JUSTIFY) &&
(i < fieldArray.length - 1))
{
if (!lastLineEndedPara)
{
justifyLastLine(field, lastLine);
}
}
}
}
Po nadaniu wartości właściwości preferredHeight poprzez wywołanie metody getOptimalHeight() metoda layoutColumns() iteracyjnie przechodzi przez obiekty TextField, nadając każdemu z nich wysokość równą preferredHeight. Teraz metoda layoutColumns() rozmieszcza tyle wierszy tekstu w każdym polu, by poszczególne pola nie wymagały przewijania, a tekst w każdym następnym polu zaczynał się tam, gdzie skończył się tekst w polu poprzednim. Jeśli wybranym stylem wyrównanie tekstu jest „justify”, wywoływana jest metoda justifyLastLine() w celu wyjustowania ostatniego wiersza tekstu w polu. W przeciwnym razie ostatnia wersja zostałaby potraktowana jako ostatni wiersz akapitu i nie byłaby justowana. |
|