如何在Ubuntu QML应用中设计像微信对话那样的UI

我们知道像微信那样的带有气球的对话框对于一些聊天的应用来说非常好。在很多即时通讯的应用中可以用到。在今天的文章中,我们将介绍如何使用QML来实现这样的界面。


为了方便,我们可以采用Ubuntu SDK中的“QtQuick App with QML UI (qmake)”这个模版来实现一个模版的应用。为了能够创建一个TextBalloon的控件,我们使用了C++代码:

textballoon.h


#ifndef TEXTBALLOON_H
#define TEXTBALLOON_H

#include <QtQuick>

class TextBalloon : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(bool rightAligned READ isRightAligned WRITE setRightAligned NOTIFY rightAlignedChanged)

    public:
        TextBalloon(QQuickItem *parent = 0);
        void paint(QPainter *painter);

        bool isRightAligned();
        void setRightAligned(bool rightAligned);

    private:
        bool rightAligned;

    signals:
        void rightAlignedChanged();
};

#endif

textballoon.cpp


#include "textballoon.h"

TextBalloon::TextBalloon(QQuickItem *parent)
    : QQuickPaintedItem(parent)
    , rightAligned(false)
{
}

void TextBalloon::paint(QPainter *painter)
{
    QBrush brush(QColor("#007430"));
    QBrush white(QColor("#FFFFFF"));


    if (rightAligned)
    {
        painter->setBrush(brush);
        painter->setPen(Qt::NoPen);
        painter->setRenderHint(QPainter::Antialiasing);
        painter->drawRoundedRect(10, 0, boundingRect().width() - 19, boundingRect().height(), 10, 10);

        const QPointF points[3] = {
            QPointF(boundingRect().width() - 10.0, 10.0),
            QPointF(boundingRect().width(), 20.0),
            QPointF(boundingRect().width() - 10.0, 30.0),
        };

        painter->drawConvexPolygon(points, 3);
    }
    else
    {
        painter->setBrush(white);
        painter->setPen(Qt::NoPen);
        painter->setRenderHint(QPainter::Antialiasing);
        painter->drawRoundedRect(10, 0, boundingRect().width() - 19, boundingRect().height(), 10, 10);

        const QPointF points[3] = {
            QPointF(10,10),
            QPointF(0, 20),
            QPointF(10, 30),
        };

        painter->drawConvexPolygon(points, 3);
    }
}

bool TextBalloon::isRightAligned()
{
    return this->rightAligned;
}

void TextBalloon::setRightAligned(bool rightAligned)
{
    this->rightAligned = rightAligned;
}


在main.cpp中,注册该类:

   qmlRegisterType<TextBalloon>("TextBalloon", 1, 0, "TextBalloon");

这样这个TextBalloon就可以在QML中被利用。我们的QML界面非常简单:

import QtQuick 2.0
import Ubuntu.Components 1.1


Item {
    id: root

    property int contentWidth: width *.6

    ListModel {
        id: balloonModel
    }

    ListView {
        id: balloonView
        anchors.bottom: controls.top
        anchors.bottomMargin: 2
        anchors.top: parent.top
        clip:true

        delegate: MyDelegate {}

        model: balloonModel
        spacing: units.gu(4)
        width: parent.width
    }

    Rectangle {
        id: controls

        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.margins: 1
        anchors.right: parent.right
        border.width: 2
        color: "white"
        height: parent.height * 0.15

        Text {
            anchors.centerIn: parent
            text: "Add another balloon"
        }

        MouseArea {
            anchors.fill: parent
            hoverEnabled: true
            onClicked: {
                balloonModel.append({"balloonWidth": Math.floor(Math.random() * 200 + 100),
                                     "content": "this is cool"
                                     })
                balloonView.positionViewAtIndex(balloonView.count -1, ListView.End)
            }
            onEntered: {
                parent.color = "#8ac953"
            }
            onExited: {
                parent.color = "white"
            }
        }
    }

    Component.onCompleted: {
        console.log("contentWidth: " + root.contentWidth);
        balloonModel.append({"balloonWidth": root.contentWidth,
                             "content": "this is a text, this is a perfect world to play with, and I love to play the world"
                             });
        balloonModel.append({"balloonWidth": root.contentWidth,
                             "content": "this is a text, this is a perfect world to play with, and I love to play the world"
                             });

    }
}

上面是一个ListView,下面是一个按钮来动态生成一些ListView中的项。我们的ListView的delegate设计稍微麻烦一点:

import QtQuick 2.0
import TextBalloon 1.0
import Ubuntu.Components 1.1
import QtQuick.Layouts 1.1

Item {
    id: delegate
    width: ListView.view.width
    height: txt.contentHeight + 20
    property bool rightAligned: index % 2 == 0 ? false : true

    RowLayout {
        spacing: units.gu(2)
        anchors.right: index % 2 == 0 ? undefined : parent.right

        Image {
            id: leftImg
            width: root.contentWidth*.2
            height: width
            anchors.top:parent.top
            source: "images/pic1.jpg"
            visible: delegate.rightAligned ? false : true
            fillMode: Image.PreserveAspectCrop
            Layout.maximumWidth:root.contentWidth*.2
            Layout.maximumHeight: root.contentWidth*.2
        }

        Text {
            id: txt
            anchors.top: parent.top
            anchors.topMargin: units.gu(1)
            width: root.contentWidth
            wrapMode: Text.WordWrap
            text: content
//            horizontalAlignment: delegate.rightAligned ? Text.AlignRight : Text.AlignLeft
            font.pixelSize: units.gu(3)

            Layout.maximumWidth: root.contentWidth

            TextBalloon {
                anchors.fill: parent
                z: -1
                rightAligned: delegate.rightAligned
                anchors.margins: -units.gu(1.5)
            }
        }

        Image {
            id: rightImg
            anchors.top:parent.top
            width: root.contentWidth*.2
            height: width
            source: "images/pic2.jpg"
            visible: delegate.rightAligned ? true : false
            fillMode: Image.PreserveAspectCrop
            Layout.maximumWidth:root.contentWidth*.2
            Layout.maximumHeight: root.contentWidth*.2
        }
    }
}


这里我们使用RowLayout,也是非常tricky的一个设计。运行我们的应用:

   

整个项目的源码在: https://github.com/liu-xiao-guo/weixin_new