Qt模仿手机QQ列表功能

简单的用Qt模仿手机QQ列表功能,旨在模仿其置顶和删除两个功能,不注重模仿其绘图样式!
主要难点:
1、对于自定义Model的操作,包括但不限于插入、删除等。
2、对于自定义Delegate的操作,包括但不限于paint()函数的重写、水平与垂直滚动条对paint()函数的影响、以及置顶和删除信号的触发。
效果图
在这里插入图片描述
头文件

#ifndef QWHQQLISTVIEW_H
#define QWHQQLISTVIEW_H

/*
    简单模仿手机QQ列表的置顶和删除功能
 */

#include <QListView>
#include <QStyledItemDelegate>
#include <QAbstractListModel>

class QQModel : public QAbstractListModel
{
    Q_OBJECT
public:
    struct QQData
    {
        QPixmap m_icon;             //图标
        QString m_text;             //文本
        QString m_topText;          //右侧文本1:置顶区域文本
        QString m_removeText;       //右侧文本2:删除区域文本
        int m_margin;               //图标边距

        QColor m_iconBorderColor;   //图标边框颜色
        QColor m_bgColor;           //背景颜色
        QColor m_textColor;         //文本颜色
        QColor m_bgTopRectColor;    //右侧文本1区域背景:置顶区域背景色
        QColor m_bgRemoveRectColor; //右侧文本2区域背景:删除区域背景色
        QColor m_topTextColor;      //右侧文本1区域文本:置顶区域文本颜色
        QColor m_removeTextColor;   //右侧文本2区域文本:删除区域文本颜色

        bool m_pressed;             //鼠标是否按下
        bool m_canLSlide;           //是否可以左滑
        int m_offsetX;              //鼠标移动的距离
        QPoint m_pressPoint;        //鼠标按下的点

        QQData()
        {
            m_icon = QPixmap(":/1.JPG");             //图标
            m_text = QStringLiteral("山上山,水中水"); //文本
            m_topText = QStringLiteral("置顶");      //右侧文本1:置顶区域文本
            m_removeText = QStringLiteral("删除");   //右侧文本2:删除区域文本
            m_margin = 5;                           //图标边距

            m_iconBorderColor = Qt::gray;           //图标边框颜色
            m_bgColor = Qt::green;                  //背景颜色
            m_textColor = Qt::black;                //文本颜色
            m_bgTopRectColor = Qt::gray;            //置顶区域背景色
            m_bgRemoveRectColor = Qt::red;          //删除区域背景色
            m_topTextColor = Qt::white;             //置顶区域文本颜色
            m_removeTextColor = Qt::white;          //删除区域文本颜色

            m_pressed = false;                      //鼠标是否按下
            m_canLSlide = true;                     //是否可以左滑
            m_offsetX = 0;
            m_pressPoint = QPoint(0, 0);
        }
    };

    QQModel(QObject *parent = 0);
    ~QQModel();

    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
    //实现删除操作时,必须重写此函数,Qt文档中以removeRows来介绍
    bool removeRow(int row, const QModelIndex &parent = QModelIndex());
    //实现插入操作时,必须重写此函数,Qt文档中以insertRows来介绍
    bool insertRow(int row, /*const QModelIndex &parent = QModelIndex()*/QQData &data);

    //自定义函数
    void appendData(QQData data);
    void setDatas(const QList<QQData> &datas);
private:
    QList<QQData> m_datas;
};
Q_DECLARE_METATYPE(QQModel::QQData)

class QQDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    QQDelegate(QObject *parent = 0);
    ~QQDelegate();

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);

    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;

public:
    //设置水平滑动条改变的值,用于更新绘图项索引
    void setHorizontalValue(int value);
    //设置垂直滑动条改变的值,用于更新绘图项索引
    void setVerticalValue(int value);
    //设置是否显示分隔线
    void setSpaceLineVisible(bool visible);
    //设置分隔线颜色
    void setSpaceLineColor(QColor color);
    //设置分隔线高度
    void setSpaceLineHeight(int height);
signals:
    //置顶
    void topItem(const QModelIndex &index);
    //删除
    void removeItem(const QModelIndex &index);
    //刷新,重绘
    void updateItem(const QModelIndex &index);

private:
    int m_horizontialValue;     //水平滑动条改变的值
    int m_verticalValue;        //垂直滑动条改变的值
    QModelIndex m_preIndex;     //保存之前鼠标按下时的item索引
    QColor m_spaceLineColor;    //项分隔线颜色
    int m_spaceLineHeight;      //项分隔线的线条高度

    bool m_spaceLineVisible;    //是否显示分隔线
};

class QWHQQListView : public QListView
{
    Q_OBJECT
public:
    explicit QWHQQListView(QWidget *parent = nullptr);

    //设置是否显示分隔线
    void setSpaceLineVisible(bool visible);
    //设置分隔线颜色
    void setSpaceLineColor(QColor color);
    //设置分隔线高度
    void setSpaceLineHeight(int height);

    //置顶
    void setTopItem(const QModelIndex &index);
    //删除
    void setRemoveItem(const QModelIndex &index);
signals:
    //置顶,收到m_delegate的信号后,向外转发的信号
    void topItem(const QModelIndex &index);
    //删除,收到m_delegate的信号后,向外转发的信号
    void removeItem(const QModelIndex &index);
public slots:
    //水平滑动条改变时,为m_delegate赋值,使得能够正确绘制item
    void onHValueChanged(int value);
    //垂直滑动条改变值,为m_delegate赋值,使得能够正确绘制item
    void onVValueChanged(int value);
    //置顶,收到m_delegate的信号后,向外转发
    void onTopItem(const QModelIndex &index);
    //删除,收到m_delegate的信号后,向外转发
    void onRemoveItem(const QModelIndex &index);
private:
    QQDelegate *m_delegate;
};

#endif // QWHQQLISTVIEW_H

cpp文件

#include "qwhqqlistview.h"
#include <QPainter>
#include <QEvent>
#include <QMouseEvent>
#include <QtMath>
#include <QScrollBar>
#include <QDebug>

QWHQQListView::QWHQQListView(QWidget *parent) : QListView(parent)
{
    m_delegate = new QQDelegate(this);
    setItemDelegate(m_delegate);

    //当水平滑动条滑动时,使paint函数能够正确得到绘图区域
    QScrollBar *hBar = horizontalScrollBar();
    connect(hBar, SIGNAL(valueChanged(int)), this, SLOT(onHValueChanged(int)));
    //当垂直滑动条滑动时,使paint函数能够正确得到绘图区域
    QScrollBar *vBar = verticalScrollBar();
    connect(vBar, SIGNAL(valueChanged(int)), this, SLOT(onVValueChanged(int)));
    //删除项
    connect(m_delegate, SIGNAL(removeItem(const QModelIndex &)), this, SLOT(onRemoveItem(const QModelIndex &)));
    //置顶项
    connect(m_delegate, SIGNAL(topItem(const QModelIndex &)), this, SLOT(onTopItem(const QModelIndex &)));
}

void QWHQQListView::setSpaceLineVisible(bool visible)
{
    m_delegate->setSpaceLineVisible(visible);
}

void QWHQQListView::setSpaceLineColor(QColor color)
{
    m_delegate->setSpaceLineColor(color);
}

void QWHQQListView::setSpaceLineHeight(int height)
{
    m_delegate->setSpaceLineHeight(height);
}

void QWHQQListView::setTopItem(const QModelIndex &index)
{
    if (index.model()->rowCount() == 1)
        return;

    QQModel::QQData data = index.data(Qt::DisplayRole).value<QQModel::QQData>();
    data.m_offsetX = 0;

    QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
    QQModel *qqModel = dynamic_cast<QQModel *>(model);
    qqModel->removeRow(index.row());
    qqModel->insertRow(0, data);
}

void QWHQQListView::setRemoveItem(const QModelIndex &index)
{
    QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
    QQModel *qqModel = dynamic_cast<QQModel *>(model);
    qqModel->removeRow(index.row());
}

void QWHQQListView::onHValueChanged(int value)
{
    m_delegate->setHorizontalValue(value);
}

void QWHQQListView::onVValueChanged(int value)
{
    m_delegate->setVerticalValue(value);
}

void QWHQQListView::onTopItem(const QModelIndex &index)
{
    emit topItem(index);
}

void QWHQQListView::onRemoveItem(const QModelIndex &index)
{
    emit removeItem(index);
}

QQDelegate::QQDelegate(QObject *parent) : QStyledItemDelegate (parent)
{
    m_verticalValue = 0;
    m_horizontialValue = 0;
    m_spaceLineColor = QColor(128, 128, 128);   //项分隔线颜色
    m_spaceLineHeight = 1;                      //项分隔线的线条高度
}

QQDelegate::~QQDelegate()
{

}

void QQDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.data(Qt::DisplayRole).canConvert<QQModel::QQData>())
    {
        painter->save();

        QStyleOptionViewItem  view_option(option);
        if (view_option.state & QStyle::State_HasFocus)
            view_option.state = view_option.state ^ QStyle::State_HasFocus;
        QStyledItemDelegate::paint(painter, view_option, index);

        QQModel::QQData data = index.data(Qt::DisplayRole).value<QQModel::QQData>();
        double width = option.rect.width();
        double height =option.rect.height();

        painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
        painter->translate(data.m_offsetX - m_horizontialValue, height * (index.row() - m_verticalValue));
        QFont font;
        font.setPixelSize(height * 0.6);
        painter->setFont(font);
        //绘制背景
        painter->setPen(Qt::NoPen);
        painter->setBrush(data.m_bgColor);
        painter->drawRect(QRect(0, 0, width + 2 * height, height));
        //绘制图标
        QRectF iconRect = QRectF(data.m_margin, data.m_margin, height - 2 * data.m_margin, height - 2 * data.m_margin);
        painter->setPen(QPen(data.m_iconBorderColor, 1));
        painter->setBrush(data.m_icon.scaled(iconRect.width(), iconRect.height()));
        painter->drawEllipse(iconRect);
        //绘制文本
        painter->setPen(data.m_textColor);
        painter->setBrush(Qt::NoBrush);
        QRect textRect = QRect(height, 0, width - height, height);
        painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, data.m_text);
        //绘制右侧置顶区域
        painter->setPen(Qt::NoPen);
        painter->setBrush(data.m_bgTopRectColor);
        QRect topRect = QRect(width, 0, height, height);
        painter->drawRect(topRect);
        //绘制右侧删除区域
        painter->setBrush(data.m_bgRemoveRectColor);
        QRect removeRect = QRect(width + height, 0, height, height);
        painter->drawRect(removeRect);
        //绘制右侧置顶区域文本
        font.setPixelSize(height * 0.4);
        painter->setFont(font);
        painter->setPen(data.m_topTextColor);
        painter->drawText(topRect, Qt::AlignCenter, data.m_topText);
        //绘制右侧删除区域文本
        painter->setPen(data.m_removeTextColor);
        painter->drawText(removeRect, Qt::AlignCenter, data.m_removeText);
        //绘制底部分割线
        painter->setPen(QPen(m_spaceLineColor, m_spaceLineHeight));
        painter->setBrush(Qt::NoBrush);
        int y = option.rect.height() - m_spaceLineHeight;
        painter->drawLine(QPoint(0, y), QPoint(option.rect.width(), y));

        painter->restore();
    }
    else
    {
        QStyledItemDelegate::paint(painter, option, index);
        return;
    }
}

bool QQDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    if (event->type() == QEvent::MouseButtonPress/* || event->type() == QEvent::MouseButtonDblClick*/)
    {
        if (m_preIndex.isValid() && m_preIndex != index && m_preIndex.row() < model->rowCount())
        {//m_preIndex.row() < model->rowCount(),避免保存之前的索引被删除,而造成索引越界(例如删除最后一个)
            //设置Model
            QQModel::QQData preData = m_preIndex.data(Qt::DisplayRole).value<QQModel::QQData>();
            preData.m_offsetX = 0;
            preData.m_canLSlide = true;
            model->setData(m_preIndex, QVariant::fromValue(preData), Qt::DisplayRole);
        }
        m_preIndex = index;
        QMouseEvent *e = dynamic_cast<QMouseEvent *>(event);
        QQModel::QQData data = index.data(Qt::DisplayRole).value<QQModel::QQData>();
        data.m_pressed = true;
        data.m_pressPoint = e->pos();
        model->setData(index, QVariant::fromValue(data), Qt::DisplayRole);
        if (!data.m_canLSlide)
        {//若不可以左滑,则证明已滑动到最右端
            int width = option.rect.width();
            int height = option.rect.height();
            QRect topRect = QRect(width - 2 * height - m_horizontialValue, height * (index.row() - m_verticalValue), height, height);
            QRect removeRect = QRect(width - height - m_horizontialValue, height * (index.row() - m_verticalValue), height, height);
            //鼠标点击置顶区域
            if (topRect.contains(data.m_pressPoint))
                emit topItem(index);
            //鼠标点击删除区域
            else if (removeRect.contains(data.m_pressPoint))
                emit removeItem(index);
        }
    }
    else if (event->type() == QEvent::MouseMove)
    {
        QMouseEvent *e = dynamic_cast<QMouseEvent *>(event);
        QQModel::QQData data = index.data(Qt::DisplayRole).value<QQModel::QQData>();
        if (!data.m_pressed)
        {//防止鼠标未松开的情况,在多个item之间移动
            //设置Model
            QQModel::QQData preData = m_preIndex.data(Qt::DisplayRole).value<QQModel::QQData>();
            preData.m_offsetX = 0;
            preData.m_canLSlide = true;
            model->setData(m_preIndex, QVariant::fromValue(preData), Qt::DisplayRole);
        }
        else
        {
            QMouseEvent *e = dynamic_cast<QMouseEvent *>(event);
            QPoint p = e->pos();
            int offsetX = p.x() - data.m_pressPoint.x();
            data.m_offsetX = offsetX;
            data.m_canLSlide ? data.m_offsetX += 0 : data.m_offsetX -= 2 * option.rect.height();
            if (data.m_canLSlide && offsetX < 0 && qAbs(offsetX) < 2 * option.rect.height())
                model->setData(index, QVariant::fromValue(data), Qt::DisplayRole);//可以左滑
            else if (!data.m_canLSlide && offsetX > 0 && qAbs(offsetX) < 2 * option.rect.height())
                model->setData(index, QVariant::fromValue(data), Qt::DisplayRole);//可以右滑
        }
    }
    else if (event->type() == QEvent::MouseButtonRelease)
    {
        QQModel::QQData data = index.data(Qt::DisplayRole).value<QQModel::QQData>();
        data.m_pressed = false;
        data.m_pressPoint = QPoint(0, 0);
        //左滑动
        if (data.m_offsetX < 0)
        {
            qAbs(data.m_offsetX) >= option.rect.height() ? data.m_offsetX = -2 * option.rect.height() : data.m_offsetX = 0;
        }
        else if (data.m_offsetX > 0)
        {
            qAbs(data.m_offsetX) >= option.rect.height() ? data.m_offsetX = 0 : data.m_offsetX = -2 * option.rect.height();
        }

        data.m_offsetX == -2 * option.rect.height() ? data.m_canLSlide = false : data.m_canLSlide = true;
        model->setData(index, QVariant::fromValue(data), Qt::DisplayRole);//可以右滑
    }

    return true;
}

QSize QQDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return QSize(option.rect.width(), 40);
}

void QQDelegate::setHorizontalValue(int value)
{
    m_horizontialValue = value;
}

void QQDelegate::setVerticalValue(int value)
{
    m_verticalValue = value;
}

void QQDelegate::setSpaceLineVisible(bool visible)
{
    m_spaceLineVisible = visible;
}

void QQDelegate::setSpaceLineColor(QColor color)
{
    m_spaceLineColor = color;
}

void QQDelegate::setSpaceLineHeight(int height)
{
    m_spaceLineHeight = height;
}

QQModel::QQModel(QObject *parent) : QAbstractListModel (parent)
{

}

QQModel::~QQModel()
{

}

int QQModel::rowCount(const QModelIndex &parent) const
{
    return m_datas.size();
}

QVariant QQModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::DisplayRole)
        return QVariant::fromValue(m_datas.at(index.row()));
    //else if (role == Qt::BackgroundRole)
    //    return index.data(Qt::DisplayRole).value<QQModel::QQData>().m_bgColor;

    return QVariant();
}

bool QQModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

    QQModel::QQData data = value.value<QQModel::QQData>();
    m_datas.replace(index.row(), data);
    emit dataChanged(index, index);

    return true;
}

bool QQModel::removeRow(int row, const QModelIndex &parent)
{
    beginRemoveRows(parent, row, row);
    m_datas.removeAt(row);
    endRemoveRows();
    return true;
}

bool QQModel::insertRow(int row, QQData &data)
{
    beginInsertRows(QModelIndex(), row, row);
    m_datas.insert(row, data);
    endInsertRows();
    return true;
}

void QQModel::appendData(QQModel::QQData data)
{
    m_datas.append(data);
}

void QQModel::setDatas(const QList<QQModel::QQData> &datas)
{
    m_datas = datas;
}
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浮生卍流年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值