QWidget独立窗口抗锯齿圆角窗口的一个实现方案

由于 QWidget::setMask 接口设置圆角不支持抗锯齿,所以通常会使用透明窗口加圆角背景,但圆角背景不能满足对子控件的裁剪,子控件与圆角区域重叠的部分还是能显示出来。当然对于大多数窗口,留出足够的边距也是可以接受。

对一些特殊场景,比如QComboBox的列表框,UI设计师强烈要求圆角,列表与它的容器不能有边距,常规办法就很难做到。笔者在经过长时间的研究,有了一个可能的方案。

最终实现效果如下图,可以看到,列表项区域,滚动条区域也能够正常显示圆角。

android圆形锯齿怎么消除 屏幕圆角锯齿_圆角

注意,该方案可能不适用一些场景:

  1. 特殊的平台或Qt配置
  2. 复杂窗口且有性能要求
  3. 窗口尺寸较大(可以优化)
  4. 有嵌入式窗口、OpenGL、QWindow等

方案

基本原理

Qt的每个独立窗口,默认都是在一张图片上,层叠绘制所有子控件。通常我们自绘控件时,几乎不会使用QPainter::setCompositionMode设置其他混合模式,会出现比较奇怪的效果。但如果使用透明背景窗口,使用混合模式其实跟在QPixmap或QImage上绘制一样。

另外一点,当一个控件重绘时,由于底层的绘制会影响到上层透明合成,所以Qt会从下到上按顺序绘各个控件的脏区域。

所以理论上,如果在一个窗口上增加一个全尺寸的遮罩,重绘时使用混合模式就可以实现对一些像素的清除,且支持抗锯齿。

代码步骤

  1. 创建一个QWidget作为遮罩
    遮罩置于顶层,鼠标设置透传(WA_TransparentForMouseEvents)
    遮罩跟随窗口尺寸大小同步变化,保持一致。安装事件过滤器即可(installEventFilter)
  2. 重写paintEvent,利用混合模式清除圆角像素
    以下绘制逻辑比较直接,建议优化
//创建一个图片,填充透明色
	QPixmap pix(this->size());
	pix.fill(QColor(0,0,0,0));
	// 在改图片上填充一个圆角区域,需要设置抗锯齿
	QPainter painter(&pix);
	painter.setRenderHint(QPainter::Antialiasing);
	QPainterPath path;
	//这里圆角区域需要根据dpi、size调整
	path.addRoundedRect(QRectF(pix.rect()).adjusted(0.5, 0.5, -0.5, -0.5), 10, 10); 
	painter.fillPath(path, Qt::white);
	painter.end();
	// 在窗口上绘制该圆角图片
	painter.begin(this);
	painter.setRenderHint(QPainter::Antialiasing);
	// 该混合模式会根据source像素的透明度,调整目标的透明度
	painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
	painter.drawPixmap(0, 0, pix);
	// 恢复默认混合模式,绘制边框,如果没有则不用
	painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
	painter.setPen(QPen(QColor(0xCA64EA), 1.0));
	painter.drawPath(path);

优化方向

  1. 性能优化
    上面的示例,使用了一整张图片对窗口像素进行混合模式的运算,且每次子控件重绘都会引起遮罩的重绘,性能比较差。可以考虑仅在四周设置圆角的遮罩。
  2. 圆角绘制优化
    本文使用了一个不透明的圆角区域对窗口设置裁剪,圆角的参数是固定在代码里的。实际QSS是可以设置窗口的圆角,因此可以借助QSS来生成一张圆角图片,就避免代码里包含固定数值。