In the previous article, “How to Use ListView and Navigate to Other pages in QML,” we covered various ways to navigate to other pages in ListView. In this article, I will introduce how to create an Expandable ListView. In this way, the ListView does not have to navigate to other pages, but it can be displayed by taking over the entire page with state control.

\

First we can use the Ubuntu SDK to create a Simple “QML App with Simple UI (QMLProject)” project. Our main.qml is very simple:

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 {} } } }Copy the code

\

As the code above shows, we need a Model. To do this, we create the following recipesModel.qml file:

RecipesModel.qml

The import QtQuick 2.0 ListModel {ListElement {title: "Pancakes" picture: "the content/pics/Pancakes. JPG" ingredients:"<html>
                       <ul>
                        <li> 1 cup (150g) self-raising flour
                        <li> 1 tbs caster sugar
                        <li> 3/4 cup (185ml) milk
                        <li> 1 egg
                       </ul>
                      </html>"
        method: "<html>
                  <ol>
                   <li> Sift flour and sugar together into a bowl. Add a pinch of salt.
                   <li> Beat milk and egg together, then add to dry ingredients. Beat until smooth.
                   <li> Pour mixture into a pan on medium heat and cook until bubbles appear on the surface.
                   <li> Turn over and cook other side until golden.
                  </ol>
                 </html>"
    }
    ListElement {
        title: "Fruit Salad"
        picture: "content/pics/fruit-salad.jpg"
        ingredients: "* Seasonal Fruit"
        method: "* Chop fruit and place in a bowl."
    }
    ListElement {
        title: "Vegetable Soup"
        picture: "content/pics/vegetable-soup.jpg"
        ingredients: "<html>
                       <ul>
                        <li> 1 onion
                        <li> 1 turnip
                        <li> 1 potato
                        <li> 1 carrot
                        <li> 1 head of celery
                        <li> 1 1/2 litres of water
                       </ul>
                      </html>"
        method: "<html>
                  <ol>
                   <li> Chop vegetables.
                   <li> Boil in water until vegetables soften.
                   <li> Season with salt and pepper to taste.
                  </ol>
                 </html>"
    }
    ListElement {
        title: "Hamburger"
        picture: "content/pics/hamburger.jpg"
        ingredients: "<html>
                       <ul>
                        <li> 500g minced beef
                        <li> Seasoning
                        <li> lettuce, tomato, onion, cheese
                        <li> 1 hamburger bun for each burger
                       </ul>
                      </html>"
        method: "<html>
                  <ol>
                   <li> Mix the beef, together with seasoning, in a food processor.
                   <li> Shape the beef into burgers.
                   <li> Grill the burgers for about 5 mins on each side (until cooked through)
                   <li> Serve each burger on a bun with ketchup, cheese, lettuce, tomato and onion.
                  </ol>
                 </html>"
    }
    ListElement {
        title: "Lemonade"
        picture: "content/pics/lemonade.jpg"
        ingredients: "<html>
                       <ul>
                        <li> 1 cup Lemon Juice
                        <li> 1 cup Sugar
                        <li> 6 Cups of Water (2 cups warm water, 4 cups cold water)
                       </ul>
                      </html>"
        method: "<html>
                  <ol>
                   <li> Pour 2 cups of warm water into a pitcher and stir in sugar until it dissolves.
                   <li> Pour in lemon juice, stir again, and add 4 cups of cold water.
                   <li> Chill or serve over ice cubes.
                  </ol>
                 </html>"}}Copy the code


\

\

Here, we can see that in text, we can use HTML format to format our text. This is very useful for our various displays.

\

Our key design lies in the file 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]}Copy the code


\

In the delegate, it has two states:

\

  • The default List mode. In this mode, it displays only one image and title
  • Detailed schema. In this mode, in addition to the above image and title, the details and method in the model are displayed

The state in detailed mode is:

\

       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 }
        }
Copy the code

Here, be sure to note:

\

PropertyChanges { target: recipe.ListView.view; explicit: true; contentY: { console.log("listView.contentY: " + listView.contentY); return recipe.y + listView.contentY; }}Copy the code

This will help us move the current item to the ListView window.

\

We run our application:

\

      \

\

In the second figure above, click the “Close” button to return to List mode.

\

The code for the entire project is at: github.com/liu-xiao-gu…

\

\