How to create an animated, variable size accordion

2020-02-02 02:14发布

问题:

I want to create an animated accordion-like element that expands on click. Here's how it should work.

When the user clicks one of the red rectangles, the green rectangle which is the actual content, should expand. I want this expansion to be animated. The height of the contents of the green rectangles could be different for each red header.

I have been able to implement the click-to-expand behavior, but there's no animation. Here is the code I currently have.

AccordionElement.qml

import QtQuick 2.5
import QtQuick.Layouts 1.1

ColumnLayout {
    id: rootElement

    property string title: ""
    property bool isOpen: false
    default property alias accordionContent: contentPlaceholder.data

    anchors.left: parent.left; anchors.right: parent.right

    // Header element
    Rectangle {
        id: accordionHeader
        color: "red"
        anchors.left: parent.left; anchors.right: parent.right
        height: 50
        MouseArea {
            anchors.fill: parent
            Text {
                text: rootElement.title
                anchors.centerIn: parent
            }
            cursorShape: Qt.PointingHandCursor
            onClicked: {
                rootElement.isOpen = !rootElement.isOpen
            }
        }
    }

    // This will get filled with the content
    ColumnLayout {
        id: contentPlaceholder
        visible: rootElement.isOpen
        anchors.left: parent.left; anchors.right: parent.right
    }
}

And this is how it is used from the parent element:

Accordion.qml

ColumnLayout {
    Layout.margins: 5

    visible: true

    AccordionElement {
        title: "Title1"
        accordionContent: Rectangle {
            anchors.left: parent.left; anchors.right: parent.right
            height: 20
            color: "green"
        }
    }
    AccordionElement {
        title: "Title2"
        accordionContent: Rectangle {
            anchors.left: parent.left; anchors.right: parent.right
            height: 50
            color: "green"
        }
    }

    AccordionElement {
        title: "Title3"
        accordionContent: Rectangle {
            anchors.left: parent.left; anchors.right: parent.right
            height: 30
            color: "green"
        }
    }

    // Vertical spacer to keep the rectangles in upper part of column
    Item {
        Layout.fillHeight: true
    }
}

This produces the following result (when all rectangles are expanded):

Ideally I would like the green rectangles to roll out of the red rectangles (like paper out of a printer). But I am stuck on how to do this. I have tried several approaches using the height property, and I got the green rectangle to disappear but the white space remains under the red rectangle.

Any help would be appreciated. Is there an approach I'm missing?

回答1:

Here is a quick and simple example:

// AccItem.qml
Column {
  default property alias item: ld.sourceComponent
  Rectangle {
    width: 200
    height: 50
    color: "red"
    MouseArea {
      anchors.fill: parent
      onClicked: info.show = !info.show
    }
  }
  Rectangle {
    id: info
    width: 200
    height: show ? ld.height : 0
    property bool show : false
    color: "green"
    clip: true
    Loader {
      id: ld
      y: info.height - height
      anchors.horizontalCenter: info.horizontalCenter
    }
    Behavior on height {
      NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
    }
  }
}

// Acc.qml
Column {
  spacing: 5
  AccItem {
    Rectangle {
      width: 50
      height: 50
      radius: 50
      color: "blue"
      anchors.centerIn: parent
    }
  }
  AccItem {
    Rectangle {
      width: 100
      height: 100
      radius: 50
      color: "yellow"
      anchors.centerIn: parent
    }
  }
  AccItem {
    Rectangle {
      width: 75
      height: 75
      radius: 50
      color: "cyan"
      anchors.centerIn: parent
    }
  }
}

You are needlessly over-complicating it with the anchors and the layouts. It doesn't seem the problem calls for any of those.

Update: I slightly refined the implementation, compared to the initial one the content would actually slide out of the header as paper out of printer rather than simply being unveiled, and also removed the source of a false positive binding loop warning.