Layouts, ListViews y Cards
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.
Nota
Since QML is built on top of JavaScript, many of this language's features are available for use in QML files. However, JavaScript variables have to be prefixed withproperty
in QML.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 columnaRowLayout
organiza los componentes horizontalmente, en una única filaGridLayout
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 Kirigami | Píxeles |
---|---|
smallSpacing | 4px |
largeSpacing | 8px |
gridUnit | 18px |
Nota
KDE's Visual Design Group (VDG) has a lot more information about different units defined within Plasma and Kirigami on the Human Interface Guidelines site.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
: usadate
deListElement
como cabecera de nivel 1. - Centro,
ColumnLayout
: tiene unaKirigami.Heading
que muestra el nombre de la tarea; unKirigami.Separator
, que proporciona la línea horizontal; y unaControls.Label
, que muestra una descripción opcional de la tarea. Los últimos dos componentes tienen una propiedadvisible
, que comprueba si la descripción está vacía o no para mostrar los componentes según el resultado dedescription.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.