在前面的文章“如何在QML中使用ListView并导航到其它页面中”中,我们已经介绍了各种在ListView中导航到其它页面的方法。在这篇文章中,我来介绍如何建立一个expandable的ListView。通过这样的方法,ListView可以不用导航到其它的页面中,但是它可以通过状态的控制占据整个页面,而得到显示。
首先我们可以使用Ubuntu SDK来创建一个最简单的“QML App with Simple UI (qmlproject)”项目。我们的Main.qml非常简单:
Main.qml
import QtQuick 2.4 import Ubuntu.Components 1.2 /*! \brief MainView with a Label and Button elements. */ MainView { // objectName for functional testing purposes (autopilot-qt5) objectName: "mainView" // Note! applicationName needs to match the "name" field of the click manifest applicationName: "expandinglist.liu-xiao-guo" /* This property enables the application to change orientation when the device is rotated. The default is false. */ //automaticOrientation: true // Removes the old toolbar and enables new features of the new header. // useDeprecatedToolbar: false width: units.gu(60) height: units.gu(85) Page { id: mainpage title: i18n.tr("expandinglist") flickable: null ListView { id: listView anchors.fill: parent clip: true model: RecipesModel {} delegate: RecipesDelegate {} } } }
就像上面的代码显示的那样,我们需要一个model。为此,我们创建了如下的RecipesModel.qml文件:
RecipesModel.qml
import QtQuick 2.0 import Ubuntu.Components 1.2 // Delegate for the recipes. This delegate has two modes: // 1. List mode (default), which just shows the picture and title of the recipe. // 2. Details mode, which also shows the ingredients and method. //Component { // id: recipeDelegate //! [0] Item { id: recipe // Create a property to contain the visibility of the details. // We can bind multiple element's opacity to this one property, // rather than having a "PropertyChanges" line for each element we // want to fade. property real detailsOpacity : 0 //! [0] width: ListView.view.width height: units.gu(10) // A simple rounded rectangle for the background Rectangle { id: background x: 2; y: 2; width: parent.width - x*2; height: parent.height - y*2 color: "ivory" border.color: "orange" radius: 5 } // This mouse region covers the entire delegate. // When clicked it changes mode to 'Details'. If we are already // in Details mode, then no change will happen. //! [1] MouseArea { anchors.fill: parent onClicked: { console.log("recipe.y: " + recipe.y ); console.log("origin.y: " + listView.originY ); recipe.state = 'Details'; } } // Lay out the page: picture, title and ingredients at the top, and method at the // bottom. Note that elements that should not be visible in the list // mode have their opacity set to recipe.detailsOpacity. Row { id: topLayout x: 10; y: 10; height: recipeImage.height; width: parent.width spacing: 10 Image { id: recipeImage width: units.gu(8); height: units.gu(8) source: picture } //! [1] Column { width: background.width - recipeImage.width - 20; height: recipeImage.height spacing: 5 Text { text: title font.bold: true; font.pointSize: units.gu(2) } SmallText { text: "Ingredients" font.bold: true opacity: recipe.detailsOpacity } SmallText { text: ingredients wrapMode: Text.WordWrap width: parent.width opacity: recipe.detailsOpacity } } } //! [2] Item { id: details x: 10; width: parent.width - 20 anchors { top: topLayout.bottom; topMargin: 10; bottom: parent.bottom; bottomMargin: 10 } opacity: recipe.detailsOpacity //! [2] SmallText { id: methodTitle anchors.top: parent.top text: "Method" font.pointSize: 12; font.bold: true } Flickable { id: flick width: parent.width anchors { top: methodTitle.bottom; bottom: parent.bottom } contentHeight: methodText.height clip: true Text { id: methodText; text: method; wrapMode: Text.WordWrap; width: details.width } } Image { anchors { right: flick.right; top: flick.top } source: "content/pics/moreUp.png" opacity: flick.atYBeginning ? 0 : 1 } Image { anchors { right: flick.right; bottom: flick.bottom } source: "content/pics/moreDown.png" opacity: flick.atYEnd ? 0 : 1 } //! [3] } // A button to close the detailed view, i.e. set the state back to default (''). TextButton { y: 10 anchors { right: background.right; rightMargin: 10 } opacity: recipe.detailsOpacity text: "Close" onClicked: recipe.state = ''; } states: State { name: "Details" PropertyChanges { target: background; color: "white" } PropertyChanges { target: recipeImage; width: 130; height: 130 } // Make picture bigger PropertyChanges { target: recipe; detailsOpacity: 1; x: 0 } // Make details visible PropertyChanges { target: recipe; height: listView.height } // Fill the entire list area with the detailed view // Move the list so that this item is at the top. PropertyChanges { target: recipe.ListView.view; explicit: true; contentY: { console.log("listView.contentY: " + listView.contentY); return recipe.y + listView.contentY; } } // Disallow flicking while we're in detailed view PropertyChanges { target: recipe.ListView.view; interactive: false } } transitions: Transition { // Make the state changes smooth ParallelAnimation { ColorAnimation { property: "color"; duration: 500 } NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width" } } } // } //! [3] }
在这里,我们可以看到在文字中,我们可以使用html格式来格式化我们的文字。这对我们多样的显示是非常有用的。
我们最关键的设计在于RecipesDelegate.qml文件:
RecipesDelegate.qml
import QtQuick 2.0 import Ubuntu.Components 1.2 // Delegate for the recipes. This delegate has two modes: // 1. List mode (default), which just shows the picture and title of the recipe. // 2. Details mode, which also shows the ingredients and method. //Component { // id: recipeDelegate //! [0] Item { id: recipe // Create a property to contain the visibility of the details. // We can bind multiple element's opacity to this one property, // rather than having a "PropertyChanges" line for each element we // want to fade. property real detailsOpacity : 0 //! [0] width: ListView.view.width height: units.gu(10) // A simple rounded rectangle for the background Rectangle { id: background x: 2; y: 2; width: parent.width - x*2; height: parent.height - y*2 color: "ivory" border.color: "orange" radius: 5 } // This mouse region covers the entire delegate. // When clicked it changes mode to 'Details'. If we are already // in Details mode, then no change will happen. //! [1] MouseArea { anchors.fill: parent onClicked: { console.log("recipe.y: " + recipe.y ); console.log("origin.y: " + listView.originY ); recipe.state = 'Details'; } } // Lay out the page: picture, title and ingredients at the top, and method at the // bottom. Note that elements that should not be visible in the list // mode have their opacity set to recipe.detailsOpacity. Row { id: topLayout x: 10; y: 10; height: recipeImage.height; width: parent.width spacing: 10 Image { id: recipeImage width: units.gu(8); height: units.gu(8) source: picture } //! [1] Column { width: background.width - recipeImage.width - 20; height: recipeImage.height spacing: 5 Text { text: title font.bold: true; font.pointSize: units.gu(2) } SmallText { text: "Ingredients" font.bold: true opacity: recipe.detailsOpacity } SmallText { text: ingredients wrapMode: Text.WordWrap width: parent.width opacity: recipe.detailsOpacity } } } //! [2] Item { id: details x: 10; width: parent.width - 20 anchors { top: topLayout.bottom; topMargin: 10; bottom: parent.bottom; bottomMargin: 10 } opacity: recipe.detailsOpacity //! [2] SmallText { id: methodTitle anchors.top: parent.top text: "Method" font.pointSize: 12; font.bold: true } Flickable { id: flick width: parent.width anchors { top: methodTitle.bottom; bottom: parent.bottom } contentHeight: methodText.height clip: true Text { id: methodText; text: method; wrapMode: Text.WordWrap; width: details.width } } Image { anchors { right: flick.right; top: flick.top } source: "content/pics/moreUp.png" opacity: flick.atYBeginning ? 0 : 1 } Image { anchors { right: flick.right; bottom: flick.bottom } source: "content/pics/moreDown.png" opacity: flick.atYEnd ? 0 : 1 } //! [3] } // A button to close the detailed view, i.e. set the state back to default (''). TextButton { y: 10 anchors { right: background.right; rightMargin: 10 } opacity: recipe.detailsOpacity text: "Close" onClicked: recipe.state = ''; } states: State { name: "Details" PropertyChanges { target: background; color: "white" } PropertyChanges { target: recipeImage; width: 130; height: 130 } // Make picture bigger PropertyChanges { target: recipe; detailsOpacity: 1; x: 0 } // Make details visible PropertyChanges { target: recipe; height: listView.height } // Fill the entire list area with the detailed view // Move the list so that this item is at the top. PropertyChanges { target: recipe.ListView.view; explicit: true; contentY: { console.log("listView.contentY: " + listView.contentY); return recipe.y + listView.contentY; } } // Disallow flicking while we're in detailed view PropertyChanges { target: recipe.ListView.view; interactive: false } } transitions: Transition { // Make the state changes smooth ParallelAnimation { ColorAnimation { property: "color"; duration: 500 } NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width" } } } // } //! [3] }
在这个delegate里,它有两个状态:
- 默认的List模式。在这种模式下,它只显示一个图片及title
- 详细模式。在这种模式下,除了显示上面的图片和title以外,还显示model中的ingredients及method
在详细模式下的状态为:
states: State { name: "Details" PropertyChanges { target: background; color: "white" } PropertyChanges { target: recipeImage; width: 130; height: 130 } // Make picture bigger PropertyChanges { target: recipe; detailsOpacity: 1; x: 0 } // Make details visible PropertyChanges { target: recipe; height: listView.height } // Fill the entire list area with the detailed view // Move the list so that this item is at the top. PropertyChanges { target: recipe.ListView.view; explicit: true; contentY: { console.log("listView.contentY: " + listView.contentY); return recipe.y + listView.contentY; } } // Disallow flicking while we're in detailed view PropertyChanges { target: recipe.ListView.view; interactive: false } }
在这里,一定要注意:
PropertyChanges { target: recipe.ListView.view; explicit: true; contentY: { console.log("listView.contentY: " + listView.contentY); return recipe.y + listView.contentY; } }
可以帮我们把当前的项移到ListView的窗口中。
我们运行我们的应用:
在上面的第二个图中,点击“Close”按钮,就可以回到List模式。
整个项目的代码在:git clone https://gitcafe.com/ubuntu/expandinglist.git
时间: 2024-10-03 16:58:12