Esempio di TextField: formattazione del testo nello stile di un quotidiano

Flash Player 9 e versioni successive, Adobe AIR 1.0 e versioni successive

L'esempio News Layout formatta il testo in modo che abbia l'aspetto di un articolo di quotidiano. Il testo immesso può contenere un titolo, un sottotitolo e il corpo dell'articolo. Se sono date la larghezza e l'altezza dello schermo, l'esempio News Layout formatta il titolo e il sottotitolo in modo che occupino l'intera larghezza dell'area di visualizzazione. Il testo dell'articolo viene distribuito su due o più colonne.

Questo esempio illustra le seguenti tecniche di programmazione ActionScript:

  • Estensione della classe TextField

  • Caricamento e applicazione di un file CSS esterno

  • Conversione degli stili CSS in oggetti TextFormat

  • Uso della classe TextLineMetrics per ottenere informazioni sulle dimensioni di visualizzazione del testo

Per ottenere i file dell'applicazione per questo esempio, visitate la pagina www.adobe.com/go/learn_programmingAS3samples_flash_it. I file dell'applicazione News Layout sono disponibili nella cartella Samples/NewsLayout. L'applicazione è composta dai seguenti file:

File

Descrizione

NewsLayout.mxml

o

NewsLayout.fla

Interfaccia utente dell'applicazione per Flex (MXML) o Flash (FLA).

com/example/programmingas3/newslayout/StoryLayoutComponent.as

Una classe UIComponent di Flex che colloca l'istanza StoryLayout.

com/example/programmingas3/newslayout/StoryLayout.as

La classe ActionScript principale che organizza tutti i componenti di un articolo per la visualizzazione.

com/example/programmingas3/newslayout/FormattedTextField.as

Una sottoclasse della classe TextField che gestisce il proprio oggetto TextFormat.

com/example/programmingas3/newslayout/HeadlineTextField.as

Una sottoclasse della classe FormattedTextField che regola la dimensione dei caratteri per adattarsi a una larghezza desiderata.

com/example/programmingas3/newslayout/MultiColumnTextField.as

Una classe ActionScript che distribuisce il testo su due o più colonne.

story.css

Un file CSS che definisce gli stili di testo per il layout.

Lettura di un file CSS esterno

L'applicazione News Layout comincia leggendo il testo dell'articolo da un file XML locale. Quindi, legge un file CSS esterno che fornisce le informazioni di formattazione per il titolo, il sottotitolo e il testo principale.

Il file CSS definisce tre stili: uno stile di paragrafo standard per l'articolo e gli stili h1 e h2, rispettivamente per il titolo e il sottotitolo.

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; 
}

La tecnica utilizzata per leggere il file CSS esterno è identica alla tecnica descritta nella sezione Caricamento di un file CSS esterno. Quando il file CSS è stato caricato, l'applicazione esegue il metodo onCSSFileLoaded(), mostrato qui sotto.

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(); 
}

Il metodo onCSSFileLoaded() crea un nuovo oggetto StyleSheet e fa in modo che quest'ultimo analizzi i dati CSS di input. Il testo principale della storia è visualizzato in un oggetto MultiColumnTextField, che può utilizzare un oggetto StyleSheet direttamente. Tuttavia, i campi del titolo utilizzano la classe HeadlineTextField, che usa un oggetto TextFormat per la formattazione.

Il metodo onCSSFileLoaded() chiama il metodo getTextStyle() due volte per convertire una dichiarazione di stile CSS in un oggetto TextFormat da utilizzare con ognuno dei due oggetti 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; 
}

I nomi di proprietà e il significato dei valori di proprietà nelle dichiarazioni di stile CSS sono diversi rispetto agli oggetti TextFormat. Il metodo getTextStyle() converte i valori delle proprietà CSS nei valori previsti dall'oggetto TextFormat.

Disposizione degli elementi dell'articolo sulla pagina

La classe StoryLayout formatta e dispone i campi del titolo, del sottotitolo e del testo principale con un layout in stile quotidiano. Inizialmente, il metodo displayText() crea e posiziona i vari campi.

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

È possibile posizionare ogni campo sotto il campo precedente impostando la proprietà y del campo su un valore pari alla proprietà y del campo precedente più l'altezza dello stesso. Questo calcolo di posizionamento dinamico è necessario poiché gli oggetti HeadlineTextField e gli oggetti MultiColumnTextField possono modificare la propria altezza per adattarsi ai contenuti.

Modifica della dimensione dei caratteri per adattarli alle dimensioni del campo

Se sono dati una larghezza in pixel e un numero massimo di righe da visualizzare, l'oggetto HeadlineTextField modifica la dimensione dei caratteri per adattare il testo al campo. Se il testo è breve, la dimensione del carattere è grande, creando un titolo in stile tabloid. Se il testo è lungo, la dimensione del carattere è inferiore.

Il metodo HeadlineTextField.fitText() mostrato di seguito attiva il funzionamento del ridimensionamento dei caratteri:

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; 
    } 
}

Il metodo HeadlineTextField.fitText() utilizza una semplice tecnica ricorsiva per ridimensionare il carattere. Innanzi tutto, determina per supposizione un numero medio di pixel per carattere nel testo e da tale valore calcola una dimensione iniziale in punti. Quindi, modifica la dimensione del carattere di testo e verifica se il testo utilizza il ritorno a capo automatico per creare più del numero massimo di righe di testo. Se sono presenti troppe righe, chiama il metodo shrinkText() per ridurre la dimensione del carattere e riprovare. Se non sono presenti troppe righe, chiama il metodo growText() per aumentare la dimensione del carattere e riprovare. Il processo termina quando l'incremento della dimensione del carattere di un altro punto creerebbe troppe righe di testo.

Distribuzione del testo su più colonne

La classe MultiColumnTextField distribuisce il testo su più oggetti TextField, che vengono successivamente riorganizzati come colonne di quotidiano.

La funzione di costruzione MultiColumnTextField() crea innanzi tutto un array di oggetti TextField, uno per ciascuna colonna, come mostrato di seguito:

    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); 
    }

Ogni oggetto TextField viene aggiunto all'array e all'elenco di visualizzazione con il metodo addChild().

Ogni volta che la proprietà text o styleSheet dell'oggetto StoryLayout cambia, chiama il metodo layoutColumns() per rivisualizzare il testo. Il metodo layoutColumns() chiama il metodo getOptimalHeight() per determinare l'altezza in pixel necessaria per adattare tutto il testo all'interno della larghezza di layout data.

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; 
    } 
}

Innanzi tutto, il metodo getOptimalHeight() calcola la larghezza di ogni colonna. Quindi, imposta la larghezza e la proprietà htmlText del primo oggetto TextField dell'array. Il metodo getOptimalHeight() utilizza il primo oggetto TextField per rilevare il numero totale di righe di testo con ritorno a capo automatico e, sulla base di questo valore, individua il numero di righe che deve essere presente in ogni colonna. Infine, chiama il metodo TextField.getLineMetrics() per recuperare un oggetto TextLineMetrics che contiene i dettagli relativi alle dimensioni del testo nella prima riga. La proprietà TextLineMetrics.height rappresenta l'altezza completa di una riga di testo, espressa in pixel, comprese le misure ascent, descent e leading. L'altezza ottimale per l'oggetto MultiColumnTextField è pertanto l'altezza della riga moltiplicata per il numero di righe per colonna, più 4 per il bordo di 2 pixel nella parte superiore e inferiore di un oggetto TextField.

Qui di seguito è riportato il codice per il metodo layoutColumns() completo:

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); 
        } 
    } 
    } 
}

Dopo che la proprietà preferredHeight è stata impostata chiamando il metodo getOptimalHeight(), il metodo layoutColumns() esegue le iterazioni sugli oggetti TextField, impostando l'altezza di ciascuno sul valore preferredHeight. Il metodo layoutColumns() distribuisce quindi a ogni campo il numero di righe di testo sufficiente per non far verificare lo scorrimento in un singolo campo, e il testo in ogni campo successivo ha inizio dove termina il testo nel campo precedente. Se lo stile di allineamento del testo è stato impostato su “justify”, viene chiamato il metodo justifyLastLine() per giustificare l'ultima riga di testo di un campo. In caso contrario, l'ultima riga verrebbe trattata come riga di fine paragrafo e pertanto non sarebbe giustificata.