1. 程式人生 > >QT Demo 之 text

QT Demo 之 text

學習了MouseArea,我們繼續選擇一個基本的元件進行學習,這次我們學習text的Demo。

text的Demo位於F:\Qt\Qt5.3.2\Examples\Qt-5.3\quick\text目錄。通過text.qmlproject檔案我們瞭解,該Demo的mainFile是text.qml。

Item {

    height: 480
    width: 320
    LauncherList {
        id: ll
        anchors.fill: parent
        Component.onCompleted: {
            addExample("Hello", "An Animated Hello World", Qt.resolvedUrl("fonts/hello.qml"));
            addExample("Fonts", "Using various fonts with a Text element", Qt.resolvedUrl("fonts/fonts.qml"));
            addExample("Available Fonts", "A list of your available fonts",  Qt.resolvedUrl("fonts/availableFonts.qml"));
            addExample("Banner", "Large, scrolling text", Qt.resolvedUrl("fonts/banner.qml"));
            addExample("Img tag", "Embedding images into text", Qt.resolvedUrl("imgtag/imgtag.qml"));
            addExample("Text Layout", "Flowing text around items", Qt.resolvedUrl("styledtext-layout.qml"));
        }
    }
}
從程式碼來看,該Example主介面是一個LauncherList,其中包含6個子元素,分別從6個方面演示text的操作。

先看一下程式執行的效果圖:

LauncherList簡述

LauncherList是一個自定義的容器,具體實現是在qrc:/shared/LauncherList.qml檔案中,其自身的註釋說明如下:
    //model is a list of {"name":"somename", "url":"file:///some/url/mainfile.qml"}
    //function used to add to model A) to enforce scheme B) to allow Qt.resolveUrl in url assignments


此處我們知道LaunchList是一個可以新增name,description和url的可點選欄即可,後面有機會再詳細分析LaunchList。

在使用LauncherList時,新增List元素是在Component.onCompleted:{}響應函式中新增的,針對Component,官方說明如下:
Components are reusable, encapsulated QML types with well-defined interfaces.
Components are often defined by component files - that is, .qml files.


而completed()訊號的以及onCompleted響應函式的說明如下:
completed()
Emitted after the object has been instantiated. This can be used to execute script code at startup, once the full QML environment has been established.The corresponding handler is onCompleted.


因此,我們瞭解到在這裡LauncherList.qml整體是作為一個Component的,當LauncherList例項化完成之後,就會觸發onCompleted響應函式,來向LauncherList中addExample。
下面我們就開始分析每一個Example。

fonts/hello.qml檔案

hello.qml原始碼結構比較簡單,只有一個Item:

Rectangle {
    id: screen

    width: 320; height: 480
    color: "black"

    Item {....}
}
Item中只有一個Text欄位,座標在父元素的居中位置:
    Item {
        id: container
        x: screen.width / 2; y: screen.height / 2

        Text {....}
    }
Text中描述了顏色(白色)、文字內容(Hello world!)、字型大小(32),而且還定義了兩個SequentialAnimation分別表示字間距和透明度上的動畫效果。
        Text {
            id: text
            anchors.centerIn: parent
            color: "white"
            text: "Hello world!"
            font.pixelSize: 32

//! [letterspacing]
            SequentialAnimation on font.letterSpacing {....}
//! [letterspacing]
            SequentialAnimation on opacity {....}
        }

字間距動畫效果

            SequentialAnimation on font.letterSpacing {
                loops: Animation.Infinite;
                NumberAnimation { from: 0; to: 50; easing.type: Easing.InQuad; duration: 3000 }
                ScriptAction {
                    script: {
                        container.y = (screen.height / 4) + (Math.random() * screen.height / 2)
                        container.x = (screen.width / 4) + (Math.random() * screen.width / 2)
                    }
                }
            }
從上面這個動畫效果,我們可以看到以下幾個組成部分:
  1. 動畫迴圈次數:這裡是無線迴圈的顯示動畫。(如果要停掉動畫,可以設定running屬性為false,或者呼叫stop()函式)
  2. 定義一個具體的動畫:這裡使用的是一個可以根據數字變化的動畫效果:從0增加到50(數值的改變作用在font.letterSpacing),設定擦除曲線為InQuad(具體效果未知),動畫時長為3s
  3. 定義一個指令碼動作:具體的效果是隨機改變container的x,y座標值

整體的動畫效果就是:

  • 在3s內不斷增大文字的字間距從0到50,然後隨機改變一下文字的x,y座標;
  • 迴圈進行上述操作。

透明度動畫效果

            SequentialAnimation on opacity {
                loops: Animation.Infinite;
                NumberAnimation { from: 1; to: 0; duration: 2600 }
                PauseAnimation { duration: 400 }
            }
通過上面對字間距動畫的效果分析,我們可以知道本例中的透明度動畫效果是:
  • 在2.6s內不斷改變文字的透明度從1到0,然後暫停0.4s(以便和上面的3s動畫保持步調一致);
  • 迴圈進行上述操作。

fonts/fonts.qml檔案

fonts.qml檔案中定義了一個成員變數myText,3個FontLoader和一個Column,Column中有6個Text使用不同的字型顯示myText內容。

Rectangle {
    property string myText: "The quick brown fox jumps over the lazy dog."

    width: 320; height: 480
    color: "steelblue"

//! [fontloader]
    FontLoader { id: fixedFont; name: "Courier" }
//! [fontloader]
//! [fontloaderlocal]
    FontLoader { id: localFont; source: "content/fonts/tarzeau_ocr_a.ttf" }
//! [fontloaderlocal]
//! [fontloaderremote]
    FontLoader { id: webFont; source: "http://www.princexml.com/fonts/steffmann/Starburst.ttf" }
//! [fontloaderremote]

    Column {....}
}

FontLoader簡述

The FontLoader type is used to load fonts by name or URL.

一個FontLoader共有3個屬性,分別是:
name : string               This property holds the name of the font family.
source : url                The url of the font to load
status : enumeration        This property holds the status of font loading. It can be one of:FontLoader.Null,FontLoader.Ready,FontLoader.Loading,FontLoader.Error

本例中使用了上面的兩種方式載入字型,分別載入了"Courier"和"content/fonts/tarzeau_ocr_a.ttf"兩種本地字型,以及"http://www.princexml.com/fonts/steffmann/Starburst.ttf"網路字型,針對Starburst.ttf網路字型,因為涉及到網路載入,使用了第三個屬性status,如最後一個Text是在字型載入的不同狀態下顯示不同的文字:

        Text {
            text: {
                if (webFont.status == FontLoader.Ready) myText
                else if (webFont.status == FontLoader.Loading) "Loading..."
                else if (webFont.status == FontLoader.Error) "Error loading font"
            }
        }

字型顯示

以下6個Text的字型顯示分別如下(省略了部分程式碼):

字型是Times,大小是20

        Text {
            font.family: "Times"
            font.pixelSize: 20
        }
字型是Times,對齊方式居中,大小是20,文字全部大寫
        Text {
            horizontalAlignment: Text.AlignHCenter
            font { family: "Times"; pixelSize: 20; capitalization: Font.AllUppercase }
        }
字型是上面fixedFont指定的Courier字型,對齊方式是右對齊,大小是20,加粗,文字全部小寫
        Text {
            horizontalAlignment: Text.AlignRight
            font { family: fixedFont.name; pixelSize: 20; weight: Font.Bold; capitalization: Font.AllLowercase }
        }
字型是上面fixedFont指定的Courier字型,大小是20,斜體,文字小型大寫(大小跟小寫字母一樣,樣式是大寫)
        Text {
            font { family: fixedFont.name; pixelSize: 20; italic: true; capitalization: Font.SmallCaps }
        }
字型是上面localFont指定的tarzeau_ocr_a.ttf字型,大小是20,文字每個單詞的首字母大寫
        Text {
            font { family: localFont.name; pixelSize: 20; capitalization: Font.Capitalize }
        }
字型是上面webFont指定的Starburst.ttf字型,大小是20
        Text {
            font.family: webFont.name; font.pixelSize: 20
        }
上面忽略的程式碼如下,分別定義了text文字內容、字型顏色、文字寬度以及按照單詞進行換行:
            text: myText
            color: "lightsteelblue"
            width: parent.width

            wrapMode: Text.WordWrap

fonts/availableFonts.qml檔案

availableFonts檔案中就是一個ListView,用來顯示所有的Font格式

Rectangle {
    width: 320; height: 480; color: "steelblue"

    ListView {....}
}

ListView簡述

A ListView displays data from models created from built-in QML types like ListModel and XmlListModel, or custom model classes defined in C++ that inherit from QAbstractItemModel or QAbstractListModel.
A ListView has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. Items in a ListView are laid out horizontally or vertically. List views are inherently flickable because ListView inherits from Flickable.

從上面的描述,我們可以知道ListView用來顯示從model中提供的資料,然後按照delegate的方式進行顯示。

顯示的資料來源於Qt.fontFamilies()

//! [model]
        model: Qt.fontFamilies()
//! [model]

顯示的方式是每行高40,寬和列表等寬,居中顯示,顏色白色,大小20
        delegate: Item {
            height: 40; width: ListView.view.width
            Text {
                anchors.centerIn: parent
                text: modelData
//! [delegate]
                font.family: modelData
//! [delegate]
                font.pixelSize: 20
                color: "white"
            }
        }
注意,這裡的字型以及文字內容都是modelData,就是說每種字型都是使用自己的字型格式來顯示字型名稱。

令人有點疑惑不解的是modelData這個變數是哪裡來的,通過查詢文件,我們瞭解到這是一個內建變數,用來表示model中的每一個元素
Models that do not have named roles (such as the QStringList model shown below) will have the data provided via the modelData role. The modelData role is also provided for models that have only one role. In this case the modelData role contains the same data as the named role.
吐槽:幫助文件中沒有任何和modelData有關的內容,官網上又是語焉不詳的,在這個知識點上,Qt的幫助做的太差了。

fonts/banner.qml檔案

和fonts/font.qml相反的是,這一個qml中主要定義了一個Row:

Rectangle {
    id: screen

    property int pixelSize: screen.height * 1.25
    property color textColor: "lightsteelblue"
    property string text: "Hello world! "

    width: 320; height: 480
    color: "steelblue"

    Row {....}
}
開始的3個成員變數分別定義了字型大小、字型顏色以及文字內容。

下面的Row中,顯示了三個Text(內容一樣,吐槽),然後定義了一個NumberAnimation動畫,不斷實現向左側平移的動畫(注意,這裡改變的x是針對整個Row元素的,因此如果把視窗拉長,是會看到3個Text一起向左平移的)(平移的長度竟然是一個Text的width,而參與平移的是三個Text,吐槽)
    Row {
        y: -screen.height / 4.5

        NumberAnimation on x { from: 0; to: -text.width; duration: 6000; loops: Animation.Infinite }
        Text { id: text; font.pixelSize: screen.pixelSize; color: screen.textColor; text: screen.text }
        Text { font.pixelSize: screen.pixelSize; color: screen.textColor; text: screen.text }
        Text { font.pixelSize: screen.pixelSize; color: screen.textColor; text: screen.text }
    }

imgtag/imgtag.qml檔案

這一節非常好玩,重點演示了圖片和文字在一起的各種排版效果,先看檔案的整體結構,主要包括是一個Flikable,以及三個鍵盤事件響應函式

Rectangle {
    id: main
    width: 320; height: 480
    focus: true
    color: "#dedede"

    property var hAlign: Text.AlignLeft

    Flickable {...}

    Keys.onUpPressed: main.hAlign = Text.AlignHCenter
    Keys.onLeftPressed: main.hAlign = Text.AlignLeft
    Keys.onRightPressed: main.hAlign = Text.AlignRight
}

Flickable簡述

The Flickable item places its children on a surface that can be dragged and flicked, causing the view onto the child items to scroll. This behavior forms the basis of Items that are designed to show large numbers of child items, such as ListView and GridView.
簡單立即,Flikable就是在一個較小的視窗下顯示一個較大的內容,然後這個內容是可以拖動的。

    Flickable {
        anchors.fill: parent
        contentWidth: parent.width
        contentHeight: col.height + 20

        Column {....}
    }
這裡的Flickable只有一個Column子元素,使用contentWidth和contentHeight描述可以拖動的範圍。因為contentWidth等於parent.width,則在左右方向上不可拖動;contentHeight等於子元素col的高度+20,表示可以拖動子元素col離開底面20個畫素(此處如果我們改變20為200,經測試可以拖動到更高的位置)。
但是,這裡有一個疑問的地方,父元素竟然可以訪問子元素的屬性,更何況col此時還沒有建立(位於下面幾行)?我的理解,QML中的所有元素都是全域性的,但是有結構上從屬關係,從訪問的角度上是可以通過id直接訪問的;而且這裡設定contentHeight也可以放到Column後面,畢竟這個是針對Flickable的一個屬性設定。

具體的Column資料如下:
字型加粗,插入圖片,圖片和文字的排版方式預設底對齊

            TextWithImage {
                text: "This is a <b>happy</b> face<img src=\"images/face-smile.png\">"
            }
字型加粗,插入圖片,設定圖片和文字是居中對齊
            TextWithImage {
                text: "This is a <b>very<img src=\"images/face-smile-big.png\" align=\"middle\"/>happy</b> face vertically aligned in the middle."
            }
插入圖片,並設定圖片的寬高進行縮放
            TextWithImage {
                text: "This is a tiny<img src=\"images/face-smile.png\" width=\"15\" height=\"15\">happy face."
            }
插入兩個圖片,分別是頂對齊和底對齊
            TextWithImage {
                text: "This is a<img src=\"images/starfish_2.png\" width=\"50\" height=\"50\" align=\"top\">aligned to the top and a<img src=\"images/heart200.png\" width=\"50\" height=\"50\">aligned to the bottom."
            }
插入多個圖片,全部是居中對齊,設定不同的寬高進行縮放
            TextWithImage {
                text: "Qt logos<img src=\"images/qtlogo.png\" width=\"55\" height=\"60\" align=\"middle\"><img src=\"images/qtlogo.png\" width=\"37\" height=\"40\" align=\"middle\"><img src=\"images/qtlogo.png\" width=\"18\" height=\"20\" align=\"middle\">aligned in the middle with different sizes."
            }
插入多個圖片,全部是底對齊,設定不同的寬高進行縮放
            TextWithImage {
                text: "Some hearts<img src=\"images/heart200.png\" width=\"20\" height=\"20\" align=\"bottom\"><img src=\"images/heart200.png\" width=\"30\" height=\"30\" align=\"bottom\"> <img src=\"images/heart200.png\" width=\"40\" height=\"40\"><img src=\"images/heart200.png\" width=\"50\" height=\"50\" align=\"bottom\">with different sizes."
            }
插入網路圖片,居中對齊,並設定寬高進行縮放
            TextWithImage {
                text: "Resized image<img width=\"48\" height=\"48\" align=\"middle\" src=\"http://qt-project.org/images/qt13a/Qt-logo.png\">from the internet."
            }
插入網路圖片,居中對齊
            TextWithImage {
                text: "Image<img align=\"middle\" src=\"http://qt-project.org/images/qt13a/Qt-logo.png\">from the internet."
            }
指定高度(但是文字字型以及圖片大小均不變),並進行垂直居中(如果在該TextWithImage外面套一個Rectangle並設定好背景色,那麼顯示效果就很清晰了),顯示文字和圖片
            TextWithImage {
                height: 120
                verticalAlignment: Text.AlignVCenter
                text: "This is a <b>happy</b> face<img src=\"images/face-smile.png\"> with an explicit height."
            }
上面使用的TextWithImage也是一個自定義的Component,單獨定義在TextWithImage.qml檔案中:
Text {
    width: parent.width
    font.pointSize: 14
    wrapMode: Text.WordWrap
    textFormat: Text.StyledText
    horizontalAlignment: main.hAlign
}
從程式碼上可以看出TextWithImage這個Component指定了寬度、字型大小、換行方式、文字格式是使用格式化的方式(即支援HTML標籤)以及水平上的對齊方式保持和主視窗一致
注意,此處程式碼上使用了跨檔案的id,對於程式碼的耦合性上考慮非常不建議。

最後添加了幾個按鍵事件響應函式,即分別通過左上右來改變整個Rectangle的佈局格式:左對齊、居中對齊、右對齊。
    Keys.onUpPressed: main.hAlign = Text.AlignHCenter
    Keys.onLeftPressed: main.hAlign = Text.AlignLeft
    Keys.onRightPressed: main.hAlign = Text.AlignRight
注意,這裡的hAlign並不是內建成員變數,但是為什麼改變這個屬性的值就能修改文字的對齊方式呢?原因就在於,TextWithImage的horizontalAlignment屬性使用main.hAlign變數的值,即在imgtag.qml檔案中改變hAlign變數的值,然後在TextWithImage.qml檔案中使用。啊,多麼操蛋的設計。

styledtext-layout.qml檔案

該檔案演示瞭如何使用文字排版中的按行進行詳細排版的方法,檔案的主結構只有一個Text:

Rectangle {
    id: main
    width: 320; height: 480
    focus: true

    property real offset: 0
    property real margin: 8

    Text {....}
}
吐槽:offset欄位沒有使用,留之何用。

Text欄位詳細定義了頁邊距為10、換行方式為按單詞進行換行、字型是Times New Roman、字型大小是14、文字採用富文字格式、水平對齊方式為自動調整文字間的空格以滿足每行兩端對齊(類似報紙排版),然後建立了一個非常非常長的text,其中有各種富文字標籤,以及自定義了一個onLineLaidOut用來詳細的進行行排版。

    Text {
        id: myText
        anchors.fill: parent
        anchors.margins: 10
        wrapMode: Text.WordWrap
        font.family: "Times New Roman"
        font.pixelSize: 14
        textFormat: Text.StyledText
        horizontalAlignment: Text.AlignJustify

        text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer at ante dui <a href=\"http://www.digia.com\">www.digia.com</a>.<br/>Curabitur ante est, pulvinar quis adipiscing a, iaculis id ipsum. Nunc blandit condimentum odio vel egestas.<br><ul type=\"bullet\"><li>Coffee<ol type=\"a\"><li>Espresso<li>Cappuccino<li>Latte</ol><li>Juice<ol type=\"1\"><li>Orange</li><li>Apple</li><li>Pineapple</li><li>Tomato</li></ol></li></ul><p><font color=\"#434343\"><i>Proin consectetur <b>sapien</b> in ipsum lacinia sit amet mattis orci interdum. Quisque vitae accumsan lectus. Ut nisi turpis, sollicitudin ut dignissim id, fermentum ac est. Maecenas nec libero leo. Sed ac leo eget ipsum ultricies viverra sit amet eu orci. Praesent et tortor risus, viverra accumsan sapien. Sed faucibus eleifend lectus, sed euismod urna porta eu. Quisque vitae accumsan lectus. Ut nisi turpis, sollicitudin ut dignissim id, fermentum ac est. Maecenas nec libero leo. Sed ac leo eget ipsum ultricies viverra sit amet eu orci."

//! [layout]
        onLineLaidOut: {....}
//! [layout]
    }

文字text內容詳細分析包括以下內容:
  1. 純文字:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer at ante dui
  2. 超連結:<a href=\"http://www.digia.com\">www.digia.com</a>.
  3. 換行:<br/>Curabitur ante est, pulvinar quis adipiscing a, iaculis id ipsum. Nunc blandit condimentum odio vel egestas.<br>
  4. 無符號列表:<ul type=\"bullet\"><li>Coffee<ol type=\"a\"><li>Espresso<li>Cappuccino<li>Latte</ol><li>Juice<ol type=\"1\"><li>Orange</li><li>Apple</li><li>Pineapple</li><li>Tomato</li></ol></li></ul>
  5. 段落:<p>
  6. 字型顏色設定:<font color=\"#434343\">
  7. 斜體:<i>Proin consectetur <b>sapien</b> in ipsum lacinia sit amet mattis orci interdum. Quisque vitae accumsan lectus. Ut nisi turpis, sollicitudin ut dignissim id, fermentum ac est. Maecenas nec libero leo. Sed ac leo eget ipsum ultricies viverra sit amet eu orci. Praesent et tortor risus, viverra accumsan sapien. Sed faucibus eleifend lectus, sed euismod urna porta eu. Quisque vitae accumsan lectus. Ut nisi turpis, sollicitudin ut dignissim id, fermentum ac est. Maecenas nec libero leo. Sed ac leo eget ipsum ultricies viverra sit amet eu orci.

吐槽:文字標籤不配對,此處難道是測試Qt對於不標準的格式的相容性嗎??

lineLaidOut訊號和onLineLaidOut事件響應函式

This signal is emitted for each line of text that is laid out during the layout process. The specified line object provides more details about the line that is currently being laid out.
This gives the opportunity to position and resize a line as it is being laid out. It can for example be used to create columns or lay out text around objects.
The corresponding handler is onLineLaidOut.
通過官方說明,我們可以瞭解,lineLaidOut訊號是在每行文字準備佈局的時候觸發,開發人員可以通過自定義onLineLaidOut事件響應函式來進行個性化的按行進行排版。

        onLineLaidOut: {
            line.width = width / 2  - (margin)

            if (line.y + line.height >= height) {
                line.y -= height - margin
                line.x = width / 2 + margin
            }
        }
示例程式碼中,是將每行的寬度減半再減去一個邊距值,即只顯示在左半邊;但是如果左半邊已經超出下面的邊界怎麼辦?通過調整line.x和line.y來排版到空白的右半邊,具體的效果圖如下:

注意:右半邊部分的字型是斜體,顏色和左半邊也不一樣,所以視覺效果差異很大。

總結

學到的知識點:
  1. Component元素以及onCompleted事件響應函式
  2. 一個Qt動畫的基本構成
  3. 瞭解了SequentialAnimation動畫、NumberAnimation動畫、PauseAnimation動畫以及ScriptAction動作
  4. 如何載入字型(使用name和source,包括本地和網路)
  5. 如何設定文字的樣式(字型、大小、顏色、加粗、斜體、對齊、大小寫設定)
  6. 學習了怎麼使用ListView
  7. 學習了Column和Row的使用方式
  8. 學習了使用property定義成員變數
  9. 學習了怎麼使用Flickable
  10. 學習了富文字格式下,如何進行排版(文字和圖片交叉、設定圖片大小、設定文字和圖片的對齊方式)
  11. 學習了Text元素內如何針對每行進行排版
經過大概3天的時間,將Text下的6個Example進行了仔細學習,基本覆蓋了Text的方方面面。Example寫的比較詳細和全面,但是也有很多坑和槽點。