Qt小组件 - 5 图片懒加载样例

发布于:2025-07-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

加载本地大图片会有卡顿现象,因为imageLabel是同步加载

imageLabelQt小组件 - 3 imageLabel

Qt小组件 - 2(布局)瀑布流布局,GridLayout,FlowLayout

LoadImageThread最好使用线程池代替否则创建线程过多阻碍主线程刷新

# coding: utf-8
import sys
from pathlib import Path
from typing import List, Union

from PySide6.QtCore import QRect, QThread, Signal, Qt, QThreadPool, QTimer
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QVBoxLayout, QApplication, QScrollArea, QWidget

from components import ImageLabel, WaterfallLayout


class LoadImageThread(QThread):
    loadSignal = Signal(QImage)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.url = None

    def run(self):
        if not self.url:
            return
        if isinstance(self.url, (str, Path)):
            url = str(self.url)
            image = QImage(url)
        else:
            image = self.url
        self.loadSignal.emit(image)

    def setUrl(self, url):
        self.url = url
        self.start()


class LazyLoadThread(QThread):
    loadSignal = Signal(object, object)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.isStopped = False
        self.rect = QRect()
        self.labels = []  # type: List[LazyLoadImage]

    def run(self):
        try:
            self.verify()
        except Exception as e:
            print(e)

    def setData(self, rect: QRect, labels: List['LazyLoadImage']):
        self.rect = rect
        self.labels = labels
        self.start()

    def verify(self):
        viewport_rect: QRect = self.rect

        for item in self.labels:
            item_rect = item.geometry()
            if viewport_rect.intersects(item_rect):
                self.loadSignal.emit(item, True)
            else:
                self.loadSignal.emit(item, False)

    def stop(self):
        self.isStopped = True
        self.terminate()


class LazyLoadImage(ImageLabel):
    def _postInit(self):
        self.dataSource = None
        self.loadThread = LoadImageThread(self)
        self.loadThread.loadSignal.connect(self.setImage)
        self.finished.connect(self.on_finished)

    def setDataSource(self, source: str):
        self.dataSource = source

    def setImageUrl(self, url: str):
        self.loadThread.setUrl(url)
        
    def setLoading(self, loading: bool):
        if loading and self.isNull():
            if self.dataSource.startswith('http'):
                self.setUrl(self.dataSource)
            else:
                self.setImageUrl(self.dataSource)
        else:
            if self.isNull():
                return
            self.setImageUrl(QImage())


class LazyLoadScrollArea(QScrollArea):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.labels = []
        self.timer = QTimer(self) # 避免多次加载
        self.lazy_load_thread = LazyLoadThread(self)
        self.lazy_load_thread.loadSignal.connect(self.on_load_signal)
        self.setWidget(QWidget())
        self.widget().setLayout(WaterfallLayout())
        self.setWidgetResizable(True)
        self.verticalScrollBar().valueChanged.connect(self.start)

        self.timer.timeout.connect(self.updateLazy)
        self.timer.setSingleShot(True)
        self.timer.setInterval(150)

    def on_load_signal(self, item: LazyLoadImage, loading: bool):
        try:
            item.setLoading(loading)
            item.finished.connect(lambda: item.scaledToWidth(item.width()))
        except Exception as e:
            print(e)

    def start(self):
        self.timer.stop()
        self.timer.start()

    def updateLazy(self):
        rect = self.viewport().rect()
        rect.translate(self.horizontalScrollBar().value(), self.verticalScrollBar().value())
        self.lazy_load_thread.setData(rect, self.labels)
        self.updateGeometry()

    def add_image(self, url: str):
        image_label = LazyLoadImage()
        image_label.setDataSource(url)
        image_label.setScaledContents(True)
        image_label.setMinimumHeight(400)
        self.labels.append(image_label)
        layout = self.widget().layout()
        layout.addWidget(image_label)
        if layout.sizeHint().height() < self.height():
            self.start()

    def resizeEvent(self, event):
        self.start()
        super().resizeEvent(event)

    def closeEvent(self, event):
        self.lazy_load_thread.stop()
        self.timer.stop()
        super().closeEvent(event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    scroll_area = LazyLoadScrollArea()
    scroll_area.setWindowTitle("图片懒加载示例")
    scroll_area.resize(400, 600)

    # 添加一些图片URL到滚动区域
    image_urls = [
        "http://browser9.qhimg.com/bdm/1024_632_0/t010824ab8b5cdfa138.jpg",
        "http://browser9.qhimg.com/bdm/512_316_0/t010448c46c1ecf7cab.jpg",
        "http://browser9.qhimg.com/bdm/1024_632_0/t013a4ed4683039d101.jpg"
    ]
    for url in image_urls:
        scroll_area.add_image(url)

    for url in Path(r'G:\手机\壁纸\碧蓝航线\5.26立绘\Picture').glob('*.*'):
        scroll_area.add_image(url.as_posix())

    scroll_area.show()
    sys.exit(app.exec())

连续创建了2675个组件左右,初次打开需要10S左右的时间

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述