Layouts, ListViews y Cards

Entender los diferentes modos de situar cosas en una página

Organización del contenido

Ahora que entendemos cómo funcionan las páginas, es hora de añadir cosas a la nuestra. Revisaremos una serie de componentes y elementos de diseño importantes que serán de utilidad para diseñar nuestra aplicación.

¡No se asuste por los grandes trozos de código! Repasaremos todo lo que no hayamos cubierto antes y, al final de esta sección, tendrá una aplicación de aspecto impecable.

ListViews

Si ha usado alguna vez Discover, NeoChat o las Preferencias del sistema de Plasma, se habrá encontrado con un ListView. En pocas palabras, las ListViews le permiten mostrar datos en una lista.

Kirigami.CardsListView {
    id: layout
    model: kountdownModel
    delegate: kountdownDelegate
}

Eso parece críptico, pero no se preocupe. Empecemos desde arriba.

Lo primero que notará es que estamos usando Kirigami.CardsListView. Se trata de un ListView que nos permite mostrar tarjetas fácilmente en una lista. Sin embargo, las ListViews están pensadas para mostrar datos tomados de un modelo (para rellenarse automáticamente a partir de un conjunto de datos al que apuntamos). Ahí es donde entra en juego la propiedad model: en este ejemplo, apunta a kountdownModel.

Modelo

ListModel {
    id: kountdownModel
    // Cada ListElement es un elemento de la lista que contiene información
    ListElement { name: "Dog birthday!!"; description: "Big doggo birthday blowout."; date: 100 }
}

Un modelo define la forma en que se estructura una entrada de datos. Al observar nuestro ListElement anterior, podemos ver cómo se estructuran los elementos de kountdownModel: contienen un nombre, una descripción y una fecha. Los dos primeros son solo cadenas de texto y el tercero es un número que vamos a usar como marcador de posición.

Los modelos también son útiles porque se pueden modificar mediante el uso de varios métodos. Algunos importantes son:

  • ListModelName.append(jsobject yourobject) añade un objeto JavaScript que se proporciona al ListModel y lo coloca tras el último elemento del modelo. Para que esto ocurra de forma correcta, debe proporcionar un objeto JavaScript con las propiedades correctas y los tipos de datos correspondientes.
  • ListModelName.get(int index) devuelve el JSObject que hay en la posición del índice indicado.
  • ListModelName.remove(int index, int count) elimina el JSObject situado en la posición de índice indicada, y tantos después de dicha posición de índice como se indican (1 incluye solo el JSObject de la posición indicada).
  • ListModelName.set(int index, jsobject yourobject) modifica el elemento de la posición de índice indicada con los valores proporcionados en yourobject. Las mismas reglas se aplican con .append.

Delegado

El delegado gestiona cómo se mostrarán los datos de su ListModel en el ListView. Los elementos CardsListView están diseñados teniendo en cuenta a los delegados de tipo tarjeta y, de hecho, hemos usado un elemento Kirigami.AbstractCard como delegado en el extracto anterior.

Los delegados reciben automáticamente las propiedades de los ListElements que hemos especificado en nuestro modelo. Por lo tanto, podemos referirnos simplemente a las propiedades name, description y date de nuestros ListElements como si fueran una variable convencional dentro de nuestro delegado.

Construyendo nuestra tarjeta de delegado

Component {
    id: kountdownDelegate
    Kirigami.AbstractCard {
        contentItem: Item {
            // implicitWidth/Height definen la anchura/altura natural de un elemento si no se indica
            // otra anchura o altura. La siguiente preferencia define el tamaño preferido de un
            // componente en función de su contenido
            implicitWidth: delegateLayout.implicitWidth
            implicitHeight: delegateLayout.implicitHeight
            GridLayout {
                id: delegateLayout
                anchors {
                    left: parent.left
                    top: parent.top
                    right: parent.right
                }
                rowSpacing: Kirigami.Units.largeSpacing
                columnSpacing: Kirigami.Units.largeSpacing
                columns: root.wideScreen ? 4 : 2

                Kirigami.Heading {
                    Layout.fillHeight: true
                    level: 1
                    text: (date < 100000) ? date : i18n("%1 days", Math.round((date-Date.now())/86400000))
                }

                ColumnLayout {
                    Kirigami.Heading {
                        Layout.fillWidth: true
                        level: 2
                        text: name
                    }
                    Kirigami.Separator {
                        Layout.fillWidth: true
                        visible: description.length > 0
                    }
                    Controls.Label {
                        Layout.fillWidth: true
                        wrapMode: Text.WordWrap
                        text: description
                        visible: description.length > 0
                    }
                }
                Controls.Button {
                    Layout.alignment: Qt.AlignRight
                    Layout.columnSpan: 2
                    text: i18n("Edit")
                    // onClicked: pendiente de hacer... ¡pronto!
                }
            }
        }
    }
}

implicitWidth e implicitHeight

Kirigami.AbstractCard {
    contentItem: Item {
        implicitWidth: delegateLayout.implicitWidth
        implicitHeight: delegateLayout.implicitHeight
        GridLayout {
            id: delegateLayout
            ...
        }
    }
}

Si observamos nuestra Kirigami.AbstractCard, las primeras propiedades que definimos son implicitWidth e implicitHeight. Las hemos definido como delegateLayout.implicitWidth y delegateLayout.implicitHeight; es decir, las implicitWidth e implicitHeight del elemento GridLayout. Las anchuras y alturas implícitas son propiedades que se establecen como de modo por omisión; es decir, si no se define una anchura o una altura explícitas para estos componentes. Por ello, hemos definido la implicitWidth y la implicitHeight de nuestro Kirigami.AbstractCard al de GridLayout a continuación para asegurar que el GridLayout no sobresale fuera de la tarjeta.

Diseños

El GridLayout está dentro del componente Item que hemos proporcionado para la propiedad contentItem. Este es el elemento que contiene lo que se mostrará en la tarjeta.

También necesitamos escoger un diseño para nuestros componentes, de modo que no se apilen unos encima de otros. Existen tres tipos principales entre los que podemos escoger:

  • ColumnLayout organiza los componentes verticalmente, en una única columna
  • RowLayout organiza los componentes horizontalmente, en una única fila
  • GridLayout organiza los componentes en una cuadrícula con una composición de su elección

Con ColumnLayout y RowLayout, todo lo que tenemos que hacer es escribir nuestros componentes dentro del componente Layout. Como puede ver, hemos optado por un diseño en cuadrícula, lo que implica un poco más de trabajo manual.

GridLayout {
	id: delegateLayout
	anchors {
		left: parent.left
		top: parent.top
		right: parent.right
	}
	rowSpacing: Kirigami.Units.largeSpacing
	columnSpacing: Kirigami.Units.largeSpacing
	columns: root.wideScreen ? 4 : 2
    ...
}

Lo primero que puede ver es nuestro anchor. El sistema de anclaje de QtQuick proporciona una forma útil para asegurarse de que sus componentes estén colocados en ciertas partes de un componente principal. Hemos anclado nuestro GridLayout a la izquierda, arriba y derecha de la tarjeta principal, asegurando que nuestro contenido se extienda por toda la tarjeta.

A continuación, especificamos el espacio entre las filas y columnas dentro de nuestra cuadrícula para que nuestros componentes no se amontonen. Kirigami proporciona una serie de útiles unidades predefinidas para usar con este propósito:

Unidad de KirigamiPíxeles
smallSpacing4px
largeSpacing8px
gridUnit18px

También hemos usado un condicional aquí para variar el número de columnas de nuestra cuadrícula dependiendo de la pantalla que estemos usando. Si usamos una pantalla ancha (es decir, un monitor de computadora o un teléfono en horizontal), la cuadrícula tendrá 4 columnas, de lo contrario tendrá 2.

Componentes interiores

Podríamos crear tres etiquetas dentro de nuestro componente delegado y darlo por terminado. Pero eso no se vería particularmente bien.

GridLayout {
    ...

    Kirigami.Heading {
        Layout.fillHeight: true
        level: 1
        text: date
    }

    ColumnLayout {
        Kirigami.Heading {
            Layout.fillWidth: true
            level: 2
            text: name
        }

        Kirigami.Separator {
            Layout.fillWidth: true
            visible: description.length > 0
        }

        Controls.Label {
            Layout.fillWidth: true
            wrapMode: Text.WordWrap
            text: description
            visible: description.length > 0
        }
    }

    Controls.Button {
        Layout.alignment: Qt.AlignRight
        Layout.columnSpan: 2
        text: i18n("Edit")
    }
}

  • Izquierda, Kirigami.Heading: usa date de ListElement como cabecera de nivel 1.
  • Centro, ColumnLayout: tiene una Kirigami.Heading que muestra el nombre de la tarea; un Kirigami.Separator, que proporciona la línea horizontal; y una Controls.Label, que muestra una descripción opcional de la tarea. Los últimos dos componentes tienen una propiedad visible, que comprueba si la descripción está vacía o no para mostrar los componentes según el resultado de description.length > 0.
  • Derecha, Controls.Button: un botón que hará algo... ¡en breve!

Nuestra aplicación hasta ahora

¡Así que ahí está nuestra tarjeta básica!

Con estos pasos hemos sentado las bases para añadir toda la funcionalidad a nuestra aplicación.