简单的用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;
}