解决列表滑动卡死问题
项目运行环境:window10 系统的平板,使用到qt 列表(仿照android列表),但是qwidget对于触屏操作支持个人感觉不是太好。列表滑动时有加速和拖动效果,测试发现,快速滑动没有结束或者拖动没有恢复时,返回父界面,再次进入子界面会卡死。困扰很久,大量测试最后发现在状态更新后增加滚动停止能解决问题,记录一下,同时也给遇到同样问题的小伙伴一种解决思路
对于有滚动的界面或者控件,需要滚动加速或者拖动就要用到QScroller类。QScroller类是控制触屏滑动类,此类构造方法是私有的开发无法自己创建和释放,这个就是导致问题的地方。
让我们看看问题
手指点击屏幕会相应触屏事件
QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state,
QObject *watched,
QEvent *event)
{
Q_UNUSED(watched);
......忽略
// Check for an active scroller at globalPos
if (inputType == QScroller::InputPress) {
//拿到滚动类对象,可能有n个对象
const auto activeScrollers = QScroller::activeScrollers();
for (QScroller *as : activeScrollers) {
if (as != scroller) {
QRegion scrollerRegion;
if (QWidget *w = qobject_cast<QWidget *>(as->target()))//取得数据
......忽略
}
}
}
......忽略
return result;
}
as->target()方法
QObject *QScroller::target() const
{
Q_D(const QScroller);
return d->target; //d指针为空程序崩溃
}
d指针为什么是空呢?
全局对象
typedef QMap<QObject *, QScroller *> ScrollerHash;
//存放QScroller对象指针
//创建QScroller方法,可以看到qt_allScrollers其实只会存放一个对象
Q_GLOBAL_STATIC(ScrollerHash, qt_allScrollers)
//存放每一次事件对应的QScroller对象指针,如果只有一个控件其实指向的对象是同一个
Q_GLOBAL_STATIC(QList<QScroller *>, qt_activeScrollers)
保存滚动事件qt_activeScrollers
void QScrollerPrivate::setState(QScroller::State newstate)
{
Q_Q(QScroller);
bool sendLastScroll = false;
if (state == newstate)
return;
qScrollerDebug() << q << "QScroller::setState(" << stateName(newstate) << ')';
......忽略
qSwap(state, newstate);
......忽略
//每一次触屏滑动产生事件依次是
//QScroller::Pressed
//QScroller::Dragging
//QScroller::Scrolling
//如果正在滚动中(QScroller::Scrolling),再次点击就会产生拖动事件(QScroller::Dragging),
//此处意味着qt_activeScrollers可以保存很多QScroller对象指针,这就是一个雷
if (state == QScroller::Dragging || state == QScroller::Scrolling)
qt_activeScrollers()->push_back(q);
else
qt_activeScrollers()->removeOne(q);
emit q->stateChanged(state);
}
析构方法
/*!
\internal
*/
QScroller::~QScroller()
{
Q_D(QScroller);
#ifndef QT_NO_GESTURES
QGestureRecognizer::unregisterRecognizer(d->recognizerType);
// do not delete the recognizer. The QGestureManager is doing this.
d->recognizer = 0;
#endif
qt_allScrollers()->remove(d->target);
qt_activeScrollers()->removeOne(this);//NND 移除当前对象,意味着其他指针还在这个全局对象中
delete d_ptr;
}
只要qt_activeScrollers()存放多个指针而且是指向同一个对象则再次触屏滑动就会出现空指针而崩溃
一个半残的解决方法后续有空再找一找完美的解决方法
触屏滑动需要设置
this->grabGesture(Qt::SwipeGesture, Qt::GestureFlag::DontStartGestureOnChildren);
QScroller::grabGesture(this, QScroller::TouchGesture);
//禁止列表控件拖动
QScrollerProperties properties = QScroller::scroller(this)->scrollerProperties();
QVariant overshootPolicy = QVariant::fromValue<QScrollerProperties::OvershootPolicy>(QScrollerProperties::OvershootAlwaysOff); //关闭拖动
properties.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, overshootPolicy);
QScroller::scroller(this)->setScrollerProperties(properties); //设置触屏滚动属性,防止拖动和滚动相互影响
重要的地方
connect(QScroller::scroller(this), &QScroller::stateChanged, this, [=](const QScroller::State &newState)
{
qDebug() << "activeScrollers-count:" << QScroller::activeScrollers().count();
qDebug() << "stateChanged" << newState;
if (QScroller::activeScrollers().count() > 2)
{
QList<QScroller *> scrollers = QScroller::activeScrollers();
foreach(auto bean, scrollers)
{
qDebug() << "more :" << bean->state();
bean->stop();
}
}
//能产生两个事件绝对是要停止滚动的,这样才能保证退出再进入不会崩溃
if (newState == QScroller::Pressed || newState == QScroller::Inactive)
{
QList<QScroller *> scrollers = QScroller::activeScrollers();
foreach(auto bean, scrollers)
{
qDebug() << "stop :" << bean->state();
bean->stop();
}
}
});
控件析构时
this->ungrabGesture(Qt::SwipeGesture);
QScroller::ungrabGesture(this);
QList<QScroller *> scrollers = QScroller::activeScrollers();
foreach(auto bean, scrollers)
{
if (bean != nullptr)
{
qDebug() << "~:" << bean->state();
bean->stop();
}
//emit destroyed(bean);
}
滚动加速还有另一种方法就是滑动时主动调用QScroller的stop方法禁止默认的滚动然后调用scrollTo方法自己滚动