首页 > 其他分享 >自定义的Qt轮播图控件

自定义的Qt轮播图控件

时间:2022-12-07 20:23:02浏览次数:44  
标签:控件 thisImage Qt 自定义 width MPicturePlayer painter thisRect QRectF

该控件是模仿了一个名叫QCoolPage的开源项目里的轮播图控件,但是实现方式跟它的完全不同。QCoolPage里是用QPushButton和QLabel加上自定义styleSheet实现的;而我是用自定义控件加QPainter自己绘制的。如果想看它的具体实现方法可以网上搜索QCoolPage。我这里的实现是一次只显示5张图(总的图片数量可以超过5张),点击鼠标可以轮播图片。实现类对外提供了切换图片的函数,也可以用定时器定时切换图片。在VS2015和Qt5.9上测试通过。通过此案例可以学习较复杂的Qt动画。下面是效果图:

上代码,头文件:

class MPicturePlayer : public QWidget
{
    Q_OBJECT

public:
    MPicturePlayer(QWidget* parent = 0);
    void append(const QImage& iimage);
    void play(bool isToRight);

private:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void setMoveProgress(qreal pro);
    void setOpacity(qreal opac);
    QRectF paintStand(QPainter* painter, const QSize& baseSize, int i);
    QRectF paintMoveRight(QPainter* painter, const QSize& baseSize, int i);
    QRectF paintMoveLeft(QPainter* painter, const QSize& baseSize, int i);

private:
    static const QPointF vecl; /* 左侧3张图中心移动向量 */
    static const QPointF vecr; /* 右侧3张图中心移动向量 */
    static const qreal lengths[]; /* 每张图片距中心的距离 */
    static const qreal scales[]; /* 每张图片的缩放系数 */
    int currIndex;
    qreal moveRate;
    qreal opacity;
    QPoint pressPt;
    QRectF rect2Or4; /* 图片2或4的UI矩形 */
    QVector<QImage> images;
};

CPP文件:

const QPointF MPicturePlayer::vecl(0.99619469, -0.08715574); /* 左侧3张图中心移动向量 */
const QPointF MPicturePlayer::vecr(0.99619469, 0.08715574); /* 右侧3张图中心移动向量 */
const qreal MPicturePlayer::lengths[] = { -340, -220, -100, 0, 100, 220, 340 }; /* 每张图片距中心的距离 */
const qreal MPicturePlayer::scales[] = { 0.3, 0.6, 0.85, 1, 0.85, 0.6, 0.3 }; /* 每张图片的缩放系数 */

MPicturePlayer::MPicturePlayer(QWidget* parent) :
    QWidget(parent)
{
    currIndex = 0;
    moveRate = 0;
    opacity = 0;
}

void MPicturePlayer::append(const QImage& iimage)
{
    if (images.empty())
    {
        images.push_back(iimage);
        return;
    }
    QSize exist = images.first().size();
    if (exist == iimage.size())
    {
        images.push_back(iimage);
    }
    else
    {
        images.push_back(iimage.scaled(exist));
    }
}

//---------------------------------------------------------------------------------------
// 这里的图片轮播一次只显示5张图,点击鼠标可以轮播图片。为了绘制动画,我们定义了7个
// 数据(lengths和scales),正常情况最边上2个不显示图片。画控件的时候,从两边向中间
// 绘制以保证中间那张图在最上方。见下面的变量indexs。
// 另外,图片大小要配合控件大小才能有良好的显示效果。
//---------------------------------------------------------------------------------------
void MPicturePlayer::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(0, 0, width(), height(), Qt::black);
    int count = images.size();
    if (count == 0)
    {
        return; /* 无图 */
    }
    if (moveRate == 1.0)
    {
        currIndex = (currIndex + count - 1) % count;
        moveRate = 0;
    }
    if (moveRate == -1.0)
    {
        currIndex = (currIndex + 1) % count;
        moveRate = 0;
    }
    QSize baseSize = images.first().size().scaled(size() * 0.6, Qt::KeepAspectRatio);
    int indexs[] = { 0, 6, 1, 5, 2, 4, 3 }; /* 绘制图片的顺序 */
    if (moveRate > 0)
    {
        /* 默认的indexs是先画左2后画右4 */
        /* 这会导致右4在上方,从而使动画出现明显的可见的顿挫感 */
        /* 所以在图片右移时先画右4后画左2 */
        std::swap(indexs[4], indexs[5]);
    }
    for (auto i : indexs)
    {
        QRectF thisRect;
        if (moveRate == 0 && i != 0 && i != 6)
        {
            thisRect = paintStand(&painter, baseSize, i);
        }
        if (moveRate > 0 && i != 6) /* 图片向右移 */
        {
            thisRect = paintMoveRight(&painter, baseSize, i);
        }
        if (moveRate < 0 && i != 0) /* 图片向左移 */
        {
            thisRect = paintMoveLeft(&painter, baseSize, i);
        }
        if (i != 3)
        {
            int alpha = 32 * abs(i - 3);
            painter.fillRect(thisRect, QColor(0, 0, 0, alpha));
        }
    }
}

QRectF MPicturePlayer::paintStand(QPainter* painter, const QSize& baseSize, int i)
{
    int count = images.size();
    QPointF center(width() * 0.5, height() * 0.5);
    QPointF thisPos = center + lengths[i] * (i < 3 ? vecl : vecr);
    QSize thisSize = baseSize * scales[i];
    QRectF thisRect;
    thisRect.setX(thisPos.x() - thisSize.width() * 0.5);
    thisRect.setY(thisPos.y() - thisSize.height() * 0.5);
    thisRect.setSize(thisSize);
    int hitIndex = (currIndex + count + (i - 3)) % count;
    painter->drawImage(thisRect, images[hitIndex]);
    return thisRect;
}

//---------------------------------------------------------------------------------------
// 图片向右移动
//---------------------------------------------------------------------------------------
QRectF MPicturePlayer::paintMoveRight(QPainter* painter, const QSize& baseSize, int i)
{
    int count = images.size();
    QPointF center(width() * 0.5, height() * 0.5);
    qreal value = lengths[i] + (lengths[i + 1] - lengths[i]) * moveRate;
    qreal scale = scales[i] + (scales[i + 1] - scales[i]) * moveRate;
    QPointF thisPos = center + value * (i < 3 ? vecl : vecr);
    QSize thisSize = baseSize * scale;
    QRectF thisRect;
    thisRect.setX(thisPos.x() - thisSize.width() * 0.5);
    thisRect.setY(thisPos.y() - thisSize.height() * 0.5);
    thisRect.setSize(thisSize);
    if (i == 2)
    {
        rect2Or4 = thisRect;
    }
    int hitIndex = (currIndex + count + (i - 3)) % count;
    QImage thisImage = images[hitIndex];
    if (i == 3) /* 3必须在2后面绘制 */
    {
        QRectF cross = thisRect.intersected(rect2Or4);
        qreal uiCross = cross.width(); /* 这里是左侧重叠 */
        QRectF uiLeft(thisRect.x(), thisRect.y(), uiCross, thisRect.height());
        QRectF uiRight(thisRect.x() + uiCross, thisRect.y(), thisRect.width() - uiCross, thisRect.height());
        qreal imCross = uiCross * (thisImage.width() / thisRect.width());
        QRectF imLeft(0, 0, imCross, thisImage.height());
        QRectF imRight(imCross, 0, thisImage.width() - imCross, thisImage.height());
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(uiLeft, thisImage, imLeft);
        painter->restore();
        painter->drawImage(uiRight, thisImage, imRight);
    }
    else if (i == 5)
    {
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(thisRect, thisImage);
        painter->restore();
    }
    else
    {
        painter->drawImage(thisRect, thisImage);
    }
    return thisRect;
}

//---------------------------------------------------------------------------------------
// 图片向左移动
//---------------------------------------------------------------------------------------
QRectF MPicturePlayer::paintMoveLeft(QPainter* painter, const QSize& baseSize, int i)
{
    int count = images.size();
    QPointF center(width() * 0.5, height() * 0.5);
    qreal value = lengths[i] + (lengths[i - 1] - lengths[i]) * -moveRate;
    qreal scale = scales[i] + (scales[i - 1] - scales[i]) * -moveRate;
    QPointF thisPos = center + value * (i > 3 ? vecr : vecl);
    QSize thisSize = baseSize * scale;
    QRectF thisRect;
    thisRect.setX(thisPos.x() - thisSize.width() * 0.5);
    thisRect.setY(thisPos.y() - thisSize.height() * 0.5);
    thisRect.setSize(thisSize);
    if (i == 4)
    {
        rect2Or4 = thisRect;
    }
    int hitIndex = (currIndex + count + (i - 3)) % count;
    QImage thisImage = images[hitIndex];
    if (i == 3)
    {
        QRectF cross = thisRect.intersected(rect2Or4);
        qreal uiCross = cross.width(); /* 这里是右侧重叠 */
        QRectF uiLeft(thisRect.x(), thisRect.y(), thisRect.width() - uiCross, thisRect.height());
        QRectF uiRight(thisRect.x() + thisRect.width() - uiCross, thisRect.y(), uiCross, thisRect.height());
        qreal imCross = uiCross * (thisImage.width() / thisRect.width());
        QRectF imLeft(0, 0, thisImage.width() - imCross, thisImage.height());
        QRectF imRight(thisImage.width() - imCross, 0, imCross, thisImage.height());
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(uiRight, thisImage, imRight);
        painter->restore();
        painter->drawImage(uiLeft, thisImage, imLeft);
    }
    else if (i == 1)
    {
        painter->save();
        painter->setOpacity(opacity);
        painter->drawImage(thisRect, thisImage);
        painter->restore();
    }
    else
    {
        painter->drawImage(thisRect, thisImage);
    }
    return thisRect;
}

void MPicturePlayer::setMoveProgress(qreal pro)
{
    moveRate = pro; /* [0,1] */
    update();
}

void MPicturePlayer::setOpacity(qreal opac)
{
    opacity = opac; /* [0,1] */
    update();
}

void MPicturePlayer::mousePressEvent(QMouseEvent *event)
{
    pressPt = event->pos();
}

void MPicturePlayer::mouseReleaseEvent(QMouseEvent *event)
{
    if (pressPt == event->pos())
    {
        bool isClickLeft = (pressPt.x() < width() / 2);
        play(isClickLeft); /* 点的太快会有BUG */
    }
}

void MPicturePlayer::play(bool isToRight)
{
    QParallelAnimationGroup* group = new QParallelAnimationGroup(this);
    QVariantAnimation* ani1 = new QVariantAnimation(group);
    ani1->setStartValue(0.0);
    ani1->setEndValue(isToRight ? 1.0 : -1.0);
    ani1->setDuration(200);
    connect(ani1, &QVariantAnimation::valueChanged, this,
        [this](const QVariant& value) { setMoveProgress(value.toDouble()); });
    QVariantAnimation* ani2 = new QVariantAnimation(group);
    ani2->setStartValue(1.0);
    ani2->setEndValue(0.0);
    ani2->setDuration(200);
    connect(ani2, &QVariantAnimation::valueChanged, this,
        [this](const QVariant& value) { setOpacity(value.toDouble()); });
    group->addAnimation(ani1);
    group->addAnimation(ani2);
    group->start(QVariantAnimation::DeleteWhenStopped);
}

 

标签:控件,thisImage,Qt,自定义,width,MPicturePlayer,painter,thisRect,QRectF
From: https://www.cnblogs.com/mengxiangdu/p/16964426.html

相关文章

  • 自定义SRP(一)
    自定义SRP管线(一)创建RenderPipelineAsset创建自定义SRP管线,我们首先需要一个RenderPipelineAsset,这可以通过使用脚本继承RenderPipelineAsset这个抽象类来创建自己的Rend......
  • ES自定义评分机制:function_score查询详解
    一、function_score介绍主要用于让用户自定义查询相关性得分,实现精细化控制评分的目的。在ES的常规查询中,只有参与了匹配查询的字段才会参与记录的相关性得分score的计算。......
  • 开源web自定义表单有哪几个特点?
    目前,市场正在蓬勃发展中,作为企业,采用传统表单的弊端也逐渐显现出来,面临比较大的问题就是办公效率得不到提升。在各行各业正积极筹备朝着数字化方向转型的背景下,采用开源web......
  • 自定义指令(全局和局部作用)
    自定义指令分为两个字情景1.在某一个vue文件里,而不是全局2.在main.ts文件里注册全局的自定义指令vue文件<scriptsetup>import{ref,onMounted}from'vue'letn=ref(......
  • PHP 自定义 数组根据键去重
    去重前$result=$this->unique_array_by_key($result,"id");functionunique_array_by_key($array,$unique_key){$tmp_key[]=array();forea......
  • leaflet 用自定义pane实现图层顺序调整
    在Leaflet中,mappanes隐式地将图层组合在一起,而开发者并不知道这一点。这种分组允许Web浏览器以比单独处理图层更有效的方式同时处理多个图层。Mappanes使用z-ind......
  • Vue3必会技巧-自定义Hooks
    Vue3自定义Hooks定义:个人理解:一些可复用的方法像钩子一样挂着,可以随时被引入和调用以实现高内聚低耦合的目标,应该都能算是hook;为什么Vue3要用自定义Hook?:结论:就是为了......
  • 使用自定义类加载器打破双亲委派机制实现自定义String类
    今天我和大家如何用自定义加载器打破双亲委派机制。在介绍双亲委派机制前,我先来聊聊Java里面有几种类加载器。引导类加载器:Bootstrapclassloader扩展类加载器:ExtClassLoade......
  • 随笔(三)『SpringBoot自定义异常类』
    1、定义异常类,继承RuntimeExceptionpackagecom.baihua.common.exception;importlombok.Data;/***自定义异常*/@DatapublicclassMyExceptionextendsRunti......
  • gym——1自定义Gym环境并注册
    gym1——自定义Gym环境并注册目录gym1——自定义Gym环境并注册感谢开始Step0新建文件夹Step1:新建环境文件xxEnv.pyStep2:在env下的__init__.py下注册**Step3**......