如何使 QGraphicsView 变得轻弹?

How can a QGraphicsView be made flickable?

QML 中的 Flickable 类型允许滚动体验类似于智能手机中的菜单:如果您开始平移并在停止移动之前松开触摸屏,内容将继续滚动一段时间,然后他们逐渐停下来。

我在使用 QWidgets 时没有找到等效项。

QGraphicsView 可以通过使用 QGraphicsView::ScrollHandDrag 进行平移来滚动,但是只要我松开屏幕,滚动就会停止,无论我滚动的速度有多快。

有没有办法通过 QGraphicsView 实现与 Flickable 在 QML 中提供的相似的用户体验?

我能想到的唯一解决方案是使用 QSwipeGesture,存储过去的运动,当用户松开屏幕时,根据最后几个 100 毫秒的运动计算矢量,然后设置滚动条坐标到计算出的位置,然后使用QPropertyAnimation以避免QGraphicsView瞬间跳到最终位置。然后必须仔细计算 QPropertyAnimation 的速度以匹配用户仍在触摸屏幕时的最后速度。

有更简单的方法吗?

这是具有自动滚动功能的 QWidget 的最小完整示例。它提供(演示)以下功能:

  1. 可以用鼠标左键拖动内容。
  2. 如果在运动中释放鼠标左键,则启用自动滚动。 (鼠标左键再次禁用自动滚动。)
  3. 如果在按下鼠标左键时鼠标离开视口,则启用自动滚动。因此,鼠标指针和视口之间的距离定义了滚动步长,即鼠标指针离视口越远,滚动速度越快。

自动滚动是使用 QTimer 并基于上次鼠标移动事件中的拖动距离完成的。

小部件内容只是一个网格,因此可以以某种方式看到滚动。

#include <QAbstractScrollArea>
#include <QApplication>
#include <QMainWindow>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QTimer>

#include <iostream>
using namespace std;

template <typename T>
static T clip(T value, T min, T max)
{
  return value < min ? min : value > max ? max : value;
}

class View: public QAbstractScrollArea {

  private:
    int _w, _h; // width and height of contents
    ulong _dtScr; // interval for autoscrolling
    int _x0, _y0; // mouse position at left button down
    int _xView0, _yView0; // view port origin at left button down
    int _x, _y; // mouse position at last mouse drag
    ulong _t; // time stamp at last mouse drag [ms]
    int _dx, _dy; // distance for kinetic effect
    ulong _dt; // delta time for kinetic effect [ms]
    Qt::Orientations _scr; // enabled autoscroll directions
    QTimer _qTimer; // autoscroll timer

  public:

    View();
    virtual ~View();

  protected:

    virtual void resizeEvent(QResizeEvent *pQEvent);
    virtual void paintEvent(QPaintEvent *pQEvent);
    virtual void mousePressEvent(QMouseEvent *pQEvent);
    virtual void mouseMoveEvent(QMouseEvent *pQEvent);
    virtual void mouseReleaseEvent(QMouseEvent *pQEvent);

  private:

    void updateScrollbars();
    void autoScroll();
};

View::View():
  QAbstractScrollArea(),
  _w(10000), _h(10000), _dtScr(50), _scr(0)
{
  _qTimer.setInterval(_dtScr);
  QObject::connect(&_qTimer, &QTimer::timeout,
    this, &View::autoScroll);
}

View::~View() { }

void View::resizeEvent(QResizeEvent *pQEvent)
{
  QAbstractScrollArea::resizeEvent(pQEvent);
  updateScrollbars();
}

void View::paintEvent(QPaintEvent *pQEvent)
{
  int xOffs = horizontalScrollBar()->value();
  int yOffs = verticalScrollBar()->value();
  int spc = 32;
  // draw some contents (straight forward, without clipping)
  QPainter painter(viewport());
  for (int y = 0; y < _h; y += spc) {
    painter.drawLine(0, y - yOffs, _w, y - yOffs);
  }
  for (int x = 0; x < _w; x += spc) {
    painter.drawLine(x - xOffs, 0, x - xOffs, _h);
  }
}

void View::mousePressEvent(QMouseEvent *pQEvent)
{
  if (pQEvent->button() == Qt::LeftButton) {
    // stop auto-scroll
    _qTimer.stop();
    // remember start values of dragging
    _x0 = _x = pQEvent->x(); _y0 = _y = pQEvent->y();
    _xView0 = horizontalScrollBar()->value();
    _yView0 = verticalScrollBar()->value();
    _dt = _dx = _dy = 0;
    _t = pQEvent->timestamp();
  }
}

void View::mouseMoveEvent(QMouseEvent *pQEvent)
{
  if (pQEvent->buttons() & Qt::LeftButton) {
    int x = pQEvent->x(), wView = viewport()->width();
    _dt = 0; _scr = 0;
    if (x < 0) { // scroll to right
      _dx = x; _scr |= Qt::Horizontal;
    } else if (x >= wView) { // scroll to left
      _dx = x + 1 - wView; _scr |= Qt::Horizontal;
    } else { // horizontal dragging
      int dX = pQEvent->x() - _x0;
      horizontalScrollBar()->setValue(
        clip(_xView0 - dX, 0, _w - wView));
      // store values kinetic effect
      _dx = x - _x; _x = x;
    }
    int y = pQEvent->y(), hView = viewport()->height();
    if (y < 0) { // scroll down
      _dy = y; _scr |= Qt::Vertical;
    } else if (y >= hView) { // scroll up
      _dy = y + 1 - hView; _scr |= Qt::Vertical;
    } else { // vertical dragging
      int dY = y - _y0;
      verticalScrollBar()->setValue(
        clip(_yView0 - dY, 0, _h - hView));
      // store values kinetic effect
      _dy = y - _y; _y = y;
    }
    if (_scr) { // scrolling activated
      _dt = _dtScr;
      if (!_qTimer.isActive()) _qTimer.start();
    } else { // store values kinetic effect
      _dt = pQEvent->timestamp() - _t;
      _qTimer.stop();
    }
    _t = pQEvent->timestamp();
  }
}

void View::mouseReleaseEvent(QMouseEvent *pQEvent)
{
  if (pQEvent->button() == Qt::LeftButton) {
    // check whether autoscrolling shall be enabled
    if (_dt) {
      // convert values to interval of autoscrolling
      _dx = _dx * (double)_dtScr / _dt;
      _dy = _dy * (double)_dtScr / _dt;
      _scr
        = Qt::Orientation((_dx != 0) * Qt::Horizontal)
        | Qt::Orientation((_dy != 0) * Qt::Vertical);
      if (_scr) _qTimer.start();
    }
  }
}

void View::updateScrollbars()
{
  QSize sizeView = viewport()->size();
  QScrollBar *pQScrBarH = horizontalScrollBar();
  pQScrBarH->setRange(0, _w - sizeView.width());
  pQScrBarH->setPageStep(sizeView.width());
  QScrollBar *pQScrBarV = verticalScrollBar();
  pQScrBarV->setRange(0, _h - sizeView.height());
  pQScrBarV->setPageStep(sizeView.height());
}

void View::autoScroll()
{
  if (_scr & Qt::Horizontal) {
    int xView = horizontalScrollBar()->value();
    _xView0 = clip(xView - _dx, 0, _w - viewport()->width());
    if (xView == _xView0) _scr &= ~Qt::Horizontal;
    else horizontalScrollBar()->setValue(_xView0);
  }
  if (_scr & Qt::Vertical) {
    int yView = verticalScrollBar()->value();
    _yView0 = clip(yView - _dy, 0, _h - viewport()->height());
    if (_yView0 == yView) _scr &= ~Qt::Vertical;
    else verticalScrollBar()->setValue(_yView0);
  }
  if (!_scr) _qTimer.stop();
}

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  QMainWindow win;
  View view;
  win.setCentralWidget(&view);
  win.show();
  return app.exec();
}

在 Windows 10 上使用 VS2013 和 Qt 5.7 进行编译和测试。

ui->graphicsView->setAttribute(Qt::WA_AcceptTouchEvents,true);

然后选择最符合您需求的:

QScroller::grabGesture(ui->graphicsView, QScroller::LeftMouseButtonGesture);

QScroller::grabGesture(ui->graphicsView,QScroller::TouchGesture);