Search code examples
qtqmlqtquick2qtquickcontrolsqtquickextras

Trying to center items programmatically in a ListView


I would like to have centered items in QML ListView and therefore I've added following code of my ListView:

import QtQuick 2.0
import QtMultimedia 5.5
import QtQuick.Controls 1.3
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
import QtTest 1.1

Rectangle {
    id: ueKeypad

    width: ueMainColumnLayout.implicitWidth+2*radius
    height: ueMainColumnLayout.implicitHeight+2*radius

    color: "grey"

    radius: 8

    border.color: "#99c6f0"
    border.width: 4

    ColumnLayout {
        id: ueMainColumnLayout

        anchors.fill: parent
        anchors.margins: radius

        spacing: 4

        RowLayout {
            id: ueTextLayout

            Text {
                id: ueStaffLoginText

                text: qsTr("Staff Login")

                verticalAlignment: Text.AlignVCenter
                horizontalAlignment: Text.AlignHCenter

                font.family: "Padauk"
                textFormat: Text.RichText

                font.pointSize: 16
                font.bold: true

                color: ueKeypad.border.color

                Layout.fillWidth: true
            }   // ueStaffLoginText
        }   // ueTextLayout

        RowLayout {
            id: uePeopleViewLayout

            ListView {
                id: uePeopleView

                keyNavigationWraps: true

                spacing: 4

                antialiasing: true

                model: uePeopleModel

                Layout.fillWidth: true
                Layout.fillHeight: false

                //Layout.minimumWidth: 64
                Layout.minimumHeight: 64
                //Layout.preferredWidth: 96
                Layout.preferredHeight: 96
                //Layout.maximumWidth: 128
                Layout.maximumHeight: 128

                orientation: ListView.Horizontal
                layoutDirection: Qt.LeftToRight

                snapMode: ListView.SnapToItem

                highlightRangeMode: ListView.ApplyRange

                Component.onCompleted: {
                    var newIndex=(count%2==0)?(count/2):(Math.round(count/2));

                    positionViewAtIndex(newIndex, ListView.Center);
                    currentIndex=newIndex;
                    print(newIndex)
                }   // onCompleted - center items

                delegate: Rectangle {
                        id: uePersonDelegate

                        width: 32
                        height: 32

                        ColumnLayout {
                            id: uePersonDelegateMainLayout

                            anchors.fill: parent
                            anchors.margins: radius

                            RowLayout {
                                id: uePersonDelegateImageLayout

                                Image {
                                    id: uePersonImage

                                    antialiasing: true

                                    fillMode: Image.PreserveAspectFit

                                    source: "image://uePeopleModel/"+model.ueRoleImage
                                }   // uePersonImage
                            }   // uePersonDelegateImageLayout

                            RowLayout {
                                id: uePersonDelegateNameLayout

                                Text {
                                    id: ueTextPersonName

                                    color: "#ffffff"

                                    text: model.ueRoleName

                                    font.bold: true
                                    font.pixelSize: 16

                                    verticalAlignment: Text.AlignVCenter
                                    horizontalAlignment: Text.AlignHCenter
                                }   // ueTextPersonName
                            }   // uePersonDelegateNameLayout
                        }   // uePersonDelegateMainLayout
                    }   // uePersonDelegate

                add: Transition {
                    NumberAnimation {
                        property: "opacity";
                        from: 0;
                        to: 1.0;
                        duration: 100
                    }   // NumberAnimation

                    NumberAnimation {
                        property: "scale";
                        from: 0;
                        to: 1.0;
                        duration: 100
                    }   // NumberAnimation
                }   // Transition

                displaced: Transition {
                    NumberAnimation {
                        properties: "x,y";
                        duration: 100;
                        easing.type: Easing.OutBounce
                    }   // NumberAnimation
                }   // Transition
            }   // uePeopleView
        }   // uePeopleViewLayout

        RowLayout {
            id: ueTumblerLayout

            Tumbler {
                id: ueLoginKeypadTumbler

                Layout.fillWidth: true
                Layout.fillHeight: false

                height: 100

                antialiasing: true

                TumblerColumn {
                    id: ueNumericTumblerColumnDigit1000

                    model: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                }   // ueNumericTumblerColumnDigit1000

                TumblerColumn {
                    id: ueNumericTumblerColumnDigit100

                    model: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                }   // ueNumericTumblerColumnDigit100

                TumblerColumn {
                    id: ueNumericTumblerColumnDigit10

                    model: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                }   // ueNumericTumblerColumnDigit10

                TumblerColumn {
                    id: ueNumericTumblerColumnDigit1

                    model: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                }   // ueNumericTumblerColumnDigit1
            }   // ueLoginKeypadTumbler
        }   // ueTumblerLayout

        RowLayout {
            id: ueButtonsLayout

            Button {
                id: ueButtonLogin

                Layout.fillWidth: true

                text: qsTr("Login")
            }   // ueButtonLogin

            Button {
                id: ueButtonClear

                Layout.fillWidth: true

                text: qsTr("Clear")
            }   // ueButtonClear

            Button {
                id: ueButtonQuitApp

                Layout.fillWidth: true

                text: qsTr("Quit")
            }   // ueButtonQuitApp
        }   // ueButtonsLayout
    } // ueMainColumnLayout

    states: [
        State {
            name: "ueStateLoginOk"

            PropertyChanges {
                target: ueKeypad
                border.color: "#00ff00"
            }

            PropertyChanges {
                target: ueLoginText
                color: "#00ff00"
            }
        },  // ueStateLoginOk

        State {
            name: "ueStateLoginOkFailed"

            PropertyChanges {
                target: ueKeypad
                border.color: "#ff0000"
            }

            PropertyChanges {
                target: ueLoginText
                color: "#ff0000"
            }
        }   // ueStateLoginOkFailed
    ]   // states
}   // ueKeypad

Now, print(newIndex) statement prints out correct value 3 (in my case, since at the moment I have 5 items), and I would like the 3rd item to be in the center of ListView and other two items on the left side and the right side. Is this possible? And out of scope of this question, why Transitions also does not work, taken from example?

I've also set highlightRangeMode: ListView.ApplyRange taken from comment hint .

Here is a screenshot of the problem:

QML Items not centered


Solution

  • The problem here is that you are trying to place ListView delegates according to your needs. That's plain wrong since the ListView (much like all the other views) is meant to do that for you, according to the ListView size. The ListView will always use all the available space, resulting in the delegates all positioned to the left, as you experienced.

    Instead of forcing the behaviour on the delegates, you should constrain the ListView size and position it to be in the center. Then, you can exploit highlightRangeMode with preferredHighlightBegin and preferredHighlightEnd from this answer to ensure the currently selected Item is in the center of the list. Also, by using StrictlyEnforceRange, you can force the selected Item to stay always in the center, which would result in an IMO better way to select the desired Item.

    Here is an example implementing this approach. Sizes are hard-coded for simplicity but can be easily parameterized. As said, I went a little bit further by setting the highlighting policy. Hope it helps.

    import QtQuick 2.4
    import QtQuick.Window 2.2
    import QtQuick.Layouts 1.1
    
    Window {
        id: win
        width: 300
        height: 300
        visible: true
    
        ColumnLayout {
            anchors.fill: parent
    
            Rectangle {
                Layout.fillWidth: true
                Layout.fillHeight: true
                color: "blue"
            }
    
            ListView {
                Layout.alignment: Qt.AlignCenter
                Layout.minimumWidth: 30 * 5 + 40
                Layout.preferredHeight: 50
                clip: true
                spacing: 15
                model: 10
                orientation: ListView.Horizontal
                delegate: Item {
                    width: 30
                    height: 50
                    Rectangle{
                        anchors.centerIn: parent
                        color: parent.ListView.isCurrentItem ? "red" : "steelblue"
                        width: 30
                        height: 30
                        Text {
                            text: index
                            anchors.centerIn: parent
                        }
                        scale: parent.ListView.isCurrentItem ? 1.5 : 1
                        Behavior on scale { NumberAnimation { duration: 200 } }
                    }
                }
                preferredHighlightBegin: width / 2 - 15
                preferredHighlightEnd: width / 2 + 15
                highlightRangeMode: ListView.StrictlyEnforceRange
                Component.onCompleted: currentIndex = count / 2
            }
    
            Rectangle {
                Layout.fillWidth: true
                Layout.fillHeight: true
                color: "yellow"
            }
        }
    }
    

    enter image description here