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:

  • Rozszerzanie klasy TextField.

  • Ładowanie i stosowanie zewnętrznego pliku CSS.

  • Przekształcanie stylów CSS w obiekty TextFormat.

  • Wykorzystanie klasy TextLineMetrics do pobrania informacji o wielkości obszaru wyświetlania.

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:

File

Opis

NewsLayout.mxml

lub

NewsLayout.fla

Interfejs użytkownika dla aplikacji Flex (MXML) lub Flash (FLA).

com/example/programmingas3/newslayout/StoryLayoutComponent.as

Klasa Flex UIComponent, która umieszcza instancję klasy StoryLayout.

com/example/programmingas3/newslayout/StoryLayout.as

Główna klasa języka ActionScript, która rozmieszcza wszystkie składniki artykułu przeznaczone do wyświetlenia.

com/example/programmingas3/newslayout/FormattedTextField.as

Podklasa klasy TextField, która zarządza własnym obiektem TextFormat.

com/example/programmingas3/newslayout/HeadlineTextField.as

Podklasa klasy FormattedTextField, która dopasowuje wielkości czcionek tak, aby tekst zmieścił się na żądanej szerokości.

com/example/programmingas3/newslayout/MultiColumnTextField.as

Klasa języka ActionScript, która dzieli tekst między dwie lub większą liczbę kolumn.

story.css

Plik CSS, który definiuje style tekstu dla układu.

Odczytywanie zewnętrznego pliku CSS

Dział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 stronie

Klasa 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 pola

Na 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 kolumn

Klasa 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.