Category: User Interface

QML Panorama

December 30th, 2010

Últimamente hay mucho revuelo y movimiento relacionado a lo que es mobile, pero de todo ese ruido muy pocas cosas son realmente originales o proponen un cambio de paradigma, una de esas excepciones es la nueva UI de Windows Phone 7 y aunque posiblemente nunca compre, use o desarrolle para WP7 hay un par de conceptos interesantes que me gustaría tomar prestados, como por ejemplo los controles Panorama (ver imagen abajo) y Pivot para poder usarlos en mis aplicaciones QML.

Panorama Control

Vamos a comenzar con el control Panorama, implementarlo no es para nada complicado, como verán hay algunas propiedades para configurar el look and feel, el contenido o el elemento que hará de contenedor de los PanoramaItems, también tenemos un control Flickable que utilizaremos para mover horizontalmente los PanoramaItems hijos y para finalizar tenemos un elemento Text que usaremos para mostrar el titulo del control Panorama y una Grid en donde se irán acomodando los PanoramaItems.

Panorama.qml
import Qt 4.7

Rectangle {
    id: container
    width:parent.width
    height:parent.height
    default property alias content: content.children
    property string title: "panorama"
    property int titleSize:48
    property string titleFont: ""
    property color titleColor:"#fff"
    property color background:"#333"
    property string backgroundImage:""
    property string backgroundFillMode:"Tile"

    color: container.background

    Flickable {
        width:parent.width
        height:parent.height
        contentWidth: content.width;
        contentHeight: container.height
        boundsBehavior: "StopAtBounds"
        Image{
            source: container.backgroundImage
            fillMode: container.backgroundFillMode
            Grid{
                width: content.width
                height: content.height
                rows:2
                Text {
                    id: title
                    text: container.title
                    font.pointSize: container.titleSize
                    font.family: container.titleFont
                    color: container.titleColor
                }
                Grid {
                    id: content
                    spacing:10
                }
            }
        }
    }
}

El control PanoramaItem es aun mas sencillo como se ve en el código a continuación.

PanoramaItem.qml
import Qt 4.7

Item {
    id: container
    default property alias content: content.children
    property string title: "panoramaItem"
    property int titleSize: 21
    property color titleColor: "#fff"

    height: wrapper.height

    Grid{
        id: wrapper
        rows:2
        Text {
            id: title
            width: container.width
            text: container.title
            smooth: true
            wrapMode: "Wrap"
            font.pointSize: container.titleSize
            color: container.titleColor
        }
        Rectangle {
            id: content
            width: container.width
            height: container.height - title.height            
        }
    }
}

Bien, ahora que ya tenemos nuestros controles, es momento de usarlos, para ello solo debemos crear un control Panorama, establecer el titulo, el tipo y tamaño del font, una imagen de fondo si es que vamos a usar una, para luego agregar los PanoramaItems que deseemos con su respectivos contenidos.

PanoramaDemo.qml
import Qt 4.7

Rectangle {
    id: screen
    width: 360
    height: 640

    Panorama{
        id:myPanorama
        title: "my Panorama"
        backgroundImage:"images/bg.jpg"
        PanoramaItem{
            width: screen.width -30
            title:"first item"
            titleSize: 21
            
            // Content here
            
        }
        PanoramaItem{
            width: screen.width
            title:"second Item"
            titleSize: 21
            
            // Content here
            
        }
        PanoramaItem{
            width: screen.width
            title:"third Item"
            titleSize: 21
            
            // Content here
  
        }
    }    
}

En la imagen a continuación pueden ver el resultado de el ejemplo y en el vídeo su funcionamiento.

QML Panorama in action

Como verán duplicar el comportamiento básico de estos controles no fue para nada difícil, aunque esto es solo un simple ejemplo, con muchas cosas aun por pulir y funcionalidades por implementar para lograr una emulación completa de las funcionalidades de los controles originales, aun sirve para demostrar la capacidades de QML y como con poco código se pueden lograr resultados interesantes.

User Interface , , , , , 2 responses

Intro a Sencha Touch

December 9th, 2010

Sencha Touch es un framework para el desarrollo de aplicaciones mobile centrado en WebKit, Sencha Touch hace uso extensivo de CSS3 para todo lo referente a las animaciones y los styles que por suerte son fácilmente personalizables y mas aun si usan Sass, ya que cambiando el valor de unas cuantas variables podemos modificar totalmente la apariencia de una aplicación, estas características y funcionalidades han sido tremendamente optimizadas para lograr que todo quepa en aproximadamente 80k.

Sencha Touch esta disponible tanto en un versión con licencia comercial como con una licencia Open Source, mas precisamente la GPL v3.

Componentes

Una de las principales ventajas de Sencha Touch es la cantidad de controles que incluye, todos ellos muy fáciles de usar y personalizar.

Touch Events

Sencha Touch incluye los eventos que comúnmente mas se usan en dispositivos touch:

Sencha Touch Events

Layouts

Existe dos conceptos utilizados por Sencha Touch, uno es el de panel, el cual actúa de contenedor de componentes, el otro es el de layout el cual se aplica a un panel y especifica como deben mostrarse lo objetos que se encuentran dentro de el.

Los layouts que incluye Sencha Touch son:

Sencha Touch Layouts

Models, Stores & DataViews

Quizás las características mas poderosas de Sencha Touch son todas aquellas relacionadas con el manejo de datos, por un lado tenemos los Models que nos permiten representar un objeto, las validaciones que se deben aplicar cuando se usa en un formulario y las relaciones que este tiene con otros objetos.

Los Stores son una colección de registros los cuales utilizan un proxy para leer y escribir data, por ultimo los DataView nos permite poblar de data una template con los datos de un Store.

Manos a la obra

En el siguiente ejemplo pueden ver como utilizamos los diferentes elementos antes detallados para crear una simple liste anidada.

index.js
Ext.setup({
    onReady: function() {
        Ext.regModel('Player', {
	    fields: [
		{name : "name"},
		{name : "position"},
		{name : "number"},
		{name : "caps"},
		{name : "age"},
		{name : "picture"}
	    ]
        });

        var itemTemplate = new Ext.XTemplate(
            '<tpl for=".">',
             '{number}',' {name}',
            '</tpl>');
            
        var detailTemplate = new Ext.XTemplate(
            '<div class="detail">',
            	'<img src="{picture}"/><br/>',
                '{number}', ' {name} <br/>',
                'Position: {position} <br/>',
                'Age: {age} ',
                'Caps: {caps} ',
            '</div>'
        );

        var playersStore = new Ext.data.Store({
            model: "Player",
            proxy: {
                type: 'ajax',
                url: 'http://localhost:3000/players.json',
            	reader: {
                	type: 'json',
                	record: 'player'
                }
            },
            autoLoad:true
        });

        var navBar = new Ext.Toolbar({
            itemId: 'navBar',
            ui: 'light',
            dock: 'top',
            items: [
                {
                    text: 'Back',
                    ui: 'back',
                    handler: function(){
                    panel.layout.prev({type: 'slide', direction: 'right'});
                    }
                }
            ]
        });
        
        var detailPanel = new Ext.Panel({
            tpl: detailTemplate,
            dockedItems: [ navBar ]
        });
           
        var showDetail = function(record, btn, index) {
            navBar.setTitle(record.get('name'));
            detailPanel.update(record.data);
            panel.setActiveItem(1);
        }
        
        var listPanel = {
            dockedItems: [
                {
                    title: 'Players',
                    xtype: 'toolbar',
                    ui: 'light',
                    dock: 'top'
                }
            ],
            layout: 'fit',
            scroll: 'vertical',
            items: [
                {
                    xtype: 'list',
                    store: playersStore,
                    itemTpl:itemTemplate,
                    singleSelect: true,
                    onItemDisclosure: showDetail
                }
            ]
        };
        
        panel = new Ext.Panel({
	    fullscreen: true,
	    layout: 'card',
	    cardSwitchAnimation: 'slide',
	    items: [ listPanel, detailPanel ]
	    });
	}
});

Al correr el ejemplo, veremos algo como esto:

Sencha Touch

Sencha Touch, viene con un style por default, que es el que usamos en el ejemplo anterior, pero ademas viene con dos styles mas, uno que se ajusta mejor al look and feel de Android y otro para iOS, cambiar entro uno y otro es solo cuestión de reemplazar la stylesheet.

Sencha Touch styles

Only for WebKit

Sencha Touch tiene muchas cosas a favor, es fácil de usar, es flexible y cuenta con muchas características y funciones interesantes, pero solo funciona con WebKit, y es algo que para mi es molesto, porque muchas de estas funciones y características, podrían funcionar en Firefox, Fennec y en menor medida Opera si ademas de usar las extensiones de WebKit usaran las de los demás fabricantes, o podrían simularse usando Javascript, así que tengan en cuenta esta restricción ante de lanzarse de lleno a desarrollar con el. Quizás en un futuro esto cambie, pero por ahora, si usamos un browser que no se base en WebKit, solo obtendremos este resultado.

Sencha Touch running on Fennec

Recursos

User Interface, Web Design , , , 4 responses

Master Details View con QML

November 22nd, 2010

Lo bueno de QML es que permite que casos de uso como una simple vista Maestro Detalle pueda ser mejorada agregando animaciones y layouts personalizados con muy poco esfuerzo.

El ejemplo a implementar es muy simple, vamos a usar un archivo XML como data source y para presentar los datos vamos a usar un GridView en donde seleccionaremos el item a mostrar en en el ListView que hace las veces de details view.

La acción central ocurren en el delegate usado para poblar el GridView el cual espera a que se haga click en alguno de los items, para pasar el index al ListView y disparar la animación que remueve el GridView de la escena para cambiar la opacidad del ListView de 0 a 1 para poder hacerlo visible, por ultimo hay en elemento Text que hará de botón para volver a la vista original.

import Qt 4.7

Rectangle {
    width: 360
    height: 640
    id:container

    XmlListModel {
        id: oranjeModel
        source: "http://localhost:3002/players.xml"
        query: "/players/player"
        XmlRole {
            name: "name"
            query: "name/string()"
        }
        XmlRole {
            name: "number"
            query: "number/string()"
        }
        XmlRole {
            name: "position"
            query: "position/string()"
        }
        XmlRole {
            name: "picture"
            query: "picture/string()"
        }
        XmlRole {
            name: "bio"
            query: "bio/string()"
        }
    }

    Component {
        id: oranjeDelegate
        Item {
            width: 80
            height: 110
            Image {
                source: picture
                width: parent.width
                height: parent.height
                fillMode: "Stretch"
                opacity: 0.5
            }
            MouseArea {
                anchors.fill: parent
                focus: true
                onClicked: {
                    oranjeDetails.currentIndex = index
                    container.state = "detailsView"
                }
            }
        }
    }

    Component {
        id: oranjeDetailsDelegate
        Grid {
            rows: 3
            spacing: 6

            Row {
                Image {
                    source: picture
                    width: 160
                    height: 230
                    fillMode: "Stretch"
                }

                Column {
                    Text {
                        text: number
                        font.pointSize: 24
                    }
                    Text {
                        text: name
                        font.pointSize: 18
                    }
                    Text {
                        text: position
                        font.pointSize: 14
                    }
                }
            }

            Row {
                Text {
                    text: bio
                    font.pointSize: 8
                    width: container.width
                    wrapMode: "Wrap"
                }
            }

            Row {
                Text {
                    text: "Back"
                    font.pointSize: 12
                    color: "#ed1e24"
                    width: container.width
                    MouseArea {
                        anchors.fill: parent
                        onClicked: container.state = ""
                    }
                }
            }

        }
    }

    GridView {
        id: oranjeList
        model: oranjeModel
        delegate: oranjeDelegate
        width: container.width
        height: container.height
        cellWidth:90
        cellHeight: 120
    }

    ListView{
        id: oranjeDetails
        model: oranjeModel
        delegate: oranjeDetailsDelegate
        opacity: 0
    }

    states: [
        State {
            name: "detailsView"
            PropertyChanges {
                target: oranjeList
                x: -container.width
            }
        }
    ]

    transitions: [
        Transition {
            from: ""
            to: "detailsView"
            NumberAnimation {
                properties: "x"
                easing.type: "OutBounce"
            }
            PropertyAnimation {
                target: oranjeDetails
                property: "opacity"
                to: 1
            }
        },
        Transition {
            from: "detailsView"
            to: ""
            NumberAnimation {
                properties: "x"
                easing.type: "OutSine"
                duration: 250
            }
            PropertyAnimation {
                target: oranjeDetails
                property: "opacity"
                to: 0
            }
        }
    ]
}
Development, User Interface , , No response

Integrando QML y Javascript

November 11th, 2010

Una de las características interesantes de QML es la posibilidad de usar Javascript para agregar funcionalidades extras de forma rápida y fácil, básicamente contamos con un entorno igual al que se encuentra en cualquier browser moderno con algunas pequeñas adiciones.

Vamos a comenzar con un ejemplo simple, la idea de este ejemplo es crear una app que muestre numero aleatorios cuando se presione la barra espaciadora o cuando se haga click sobre el elemento Text que mostrara los numeros.

Javascript Inline

Para comenzar vamos a mostrar como integrar el código Javascript de forma inline, esta practica se aconseja siempre y cuando el código a añadir no sea muy extenso, ya que sino la legibilidad y mantenibilidad del código se resiente.

Pasemos ahora al código, en un nuevo proyecto pegamos el siguiente código, como verán luego de declarar un Rectangle y dentro del mismo, agregue un función que genera números aleatorios en este caso hasta el numero 100, luego viene el elemento Text encargado de mostrar el numero aleatorio, el cual contiene un MouseArea que cuando se dispara el evento onClicked toma un numero generado por la función Javascript y se lo asigna al elemento Text, lo mismo pasa a continuación con la función Keys.onPressed seteada para dispararse cuando se presiona la barra espaciadora.

Numerator.qml
import Qt 4.7

Rectangle {
    width: 640
    height: 360
    id:container

    function rand (n){
        return (Math.floor(Math.random() * n + 1));
    }

    Text{
        id: start
        text: "click!"
        font.pointSize: 128
        font.family: "Arial"
        font.bold: true
        color: "#cc0000"
        focus: true
        anchors.horizontalCenter: container.horizontalCenter
        anchors.verticalCenter: container.verticalCenter

        MouseArea {
            id: mouseRegion
            anchors.fill: start
            onClicked: {
                start.text = RandomFunctions.rand (100);
            }
        }

        Keys.onPressed: {
            if (event.key == Qt.Key_Space) {
                start.text = rand(100);
            }
        }
    }
}

Javascript Standalone

Cuando el código Javascript crece en tamaño o si se quiere mantener todo mas ordenado la opción es poner el código Javascript en uno o mas archivos separados, si vamos al caso de nuestro ejemplo podemos mover la función que genera los numero aleatorios a un archivo aparte, en este caso yo lo llame generator.qml, un tip a tener en cuenta es que por convención los nombres de los archivos .js deben comenzar con minúscula.

generator.js
function rand (n){
      return (Math.floor(Math.random() * n + 1));
}

Para poder hacer uso de las funciones ubicadas en un archivo separado debemos hacer un import con la ruta del archivo .js y setear un “alias” el cual debe ser único para cada archivo que importemos, este “alias” lo usaremos para poder invocar las funciones que albergue el archvio .js, un ejemplo de ello pueden verlo en el MouseArea o en la Keys.onPressed del código a continuación.

Numerator.qml
import Qt 4.7
import "generator.js" as RandomFunctions

Rectangle {
    width: 640
    height: 360
    id:container

    Text{
        id: start
        text: "click!"
        font.pointSize: 128
        font.family: "Arial"
        font.bold: true
        color: "#cc0000"
        focus: true
        anchors.horizontalCenter: container.horizontalCenter
        anchors.verticalCenter: container.verticalCenter

        MouseArea {
            id: mouseRegion
            anchors.fill: start
            onClicked: {
                start.text = RandomFunctions.rand (100);
            }
        }

        Keys.onPressed: {
            if (event.key == Qt.Key_Space) {
                start.text = RandomFunctions.ran (100);
            }
        }
    }
}

Como verán el integrar Javascript con QML es muy fácil y nos permite potenciar las posibilidades de QML, en la documentación oficial pueden encontrar mas información, ejemplos y buenas practicas para aplicar a nuestros desarrollos.

Development, User Interface , , , No response

States en QML

November 6th, 2010

Para quienes hayan o estén trabajado con Flash, Flex, WPF o Silverlight el tema de los States es algo conocido y de uso diario, en QML un State nos permite cambiar propiedades de un elemento, disparar un script usando StateChangeScript, modificar el parent utilizando ParentChanges, cambiar la alineación usando AnchorChanges o sobrescribir un signal handler utilizando PropertyChanges, todo ello cuando ocurra un evento determinado ya sea por nosotros en código o por el usuario cuando interactúa con la aplicación.

Vamos a comenzar con un ejemplo simple, la idea es crear un botón, mas que nada porque es un elemento de uso común el cual cuenta con varios estados con los que podemos jugar y porque QML no trae un elemento Button definido así que si necesitamos hacer uso de uno vamos a tener que crearlo nosotros, esto puede parecer un poco molesto, pero por suerte es un proceso fácil y cuyo código puede ser reutilizado posteriormente.

Vamos a empezar creando un proyecto con Qt Creator, luego de crearlo vamos a añadir un archivo QML llamado Button.qml, una cosa a tener en cuenta es que el nombre que usemos para el archivo sera el que usaremos para invocar el elemento que creemos con el, en este caso Button.

El próximo paso sera pegar el siguiente código en el archivo Button.qml

Button.qml
import Qt 4.7

Item {
    id: container
    signal clicked
    property string text

    Rectangle {
        id: button
        color: "#00aeef"
        border.width: 1
        radius: 4
        width: container.width
        height: container.height
    }

    MouseArea {
        id: mouseRegion
        anchors.fill: button
        onClicked: {
            container.clicked();
        }
    }

    Text {
        color: "#fff"
        anchors.centerIn: button
        font.pixelSize: 12
        text: container.text
    }

    states: [
        State {
            name: "Pressed"
            when: mouseRegion.pressed == true
            PropertyChanges {
                target: button
                color: "#333"
            }
        }
    ]
 }

El código comienza con el import de rigor y sigue con un Item, el cual es el mas básico de los visual items en QML, continua con el signal que no es mas que un handler usado por el MouseArea y que detecta las acciones hechas por el mouse dentro de si, y por ultimo en esta primera parte del código tenemos la declaración de la propiedad text la cual usaremos para setear el texto a mostrar por el botón.

A continuación tenemos un elemento Rectangle que sera nuestro botón en si, sigue el MouseArea que esta configurado para que ocupe toda la superficie del Rectangle y que contiene el handler onClicked el cual se dispara cuando se hace click con el botón izquierdo del mouse, por ultimo tenemos un elemento Text que hace las veces de label del botón.

Y para finalizar llegamos a la sección de states donde tenemos un State cuyo nombre es Pressed y se dispara cuando el MouseArea ha recibido un click el cual a través de la PropertyChanges que apunta al botón cambien la propiedad color del mismo.

Bien, ya tenemos nuestro botón listo, utilizarlo es tan simple como invocar un elemento Button, en el código que sigue pueden ver un ejemplo de como hacerlo.

Main.qml
import Qt 4.7

Rectangle {
    width: 200
    height: 200
    id: container
    Button {
        width: 100;
        height: 25;
        text: 'Hello World';
        anchors.horizontalCenter: container.horizontalCenter;
        anchors.verticalCenter: container.verticalCenter;
    }
}

Animación

Bien como se ve en el ejemplo anterior el uso de states es bastante simple e intuitivo, con states se pueden hacer muchas mas cosas y pueden aprender mas de ellos viendo la documentación, pero para ir cerrando este post y como adelanto de los que vienen vamos a agregarle una pequeña animación al State utilizado por nuestro botón para hacer mas suave la transición entre el estado por default y Pressed, para ello solamente hay que agregar el siguiente código luego de la sección de states.

Button.qml
transitions: [
    Transition {
        PropertyAnimation { properties: "color"; duration: 300 }
    }
]

La animación que usamos es bastante simple lo único que hacemos es crear una Transition y setear las propiedades que serán animadas por la PropertyAnimation y su duración.

El resultado de todo esto debería de verse de la siguiente manera:

States en QML

Recuerden darse una vuelta por los ejemplos y por la documentación de Qt Quick, en la cual hay mas información acerca de states.

Development, User Interface , , No response