diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 7e13989..d232f7e 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -46,6 +46,8 @@ encoding//\u754C\u9762\u7F8E\u5316/QSS\u7F8E\u5316\u4F8B\u5B50/QPushButton\u6309 encoding//\u754C\u9762\u7F8E\u5316/QScrollBar\u6EDA\u52A8\u6761\u6837\u5F0F/ScrollBar.py=utf-8 encoding//\u7A0B\u5E8F\u91CD\u542F/AutoRestart.py=utf-8 encoding//\u7A97\u53E3\u91CD\u542F/RestartMainWindow.py=utf-8 +encoding//\u817E\u8BAF\u89C6\u9891\u70ED\u64AD\u5217\u8868/TencentMovieHotPlay.py=utf-8 +encoding//\u817E\u8BAF\u89C6\u9891\u70ED\u64AD\u5217\u8868/TencentMovieHotPlay_Flow.py=utf-8 encoding//\u81EA\u52A8\u66F4\u65B0/mylibs/testlibs.py=utf-8 encoding//\u81EA\u52A8\u66F4\u65B0/test.py=utf-8 encoding//\u81EA\u5B9A\u4E49QWidget\u7684QSS\u6837\u5F0F/CustomPaintWidget.py=utf-8 diff --git a/README.md b/README.md index 6590a9a..c126466 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ - [1.16 自定义import](自定义import/) - [1.17 下拉选择联动](下拉选择联动/) - [1.18 人脸描点检测](人脸描点检测/) + - [1.19 腾讯视频热播列表](腾讯视频热播列表/) ### [2.QGraphicsView练习](QGraphicsView练习/) - [2.1 世界地图](QGraphicsView练习/世界地图) diff --git a/腾讯视频热播列表/README.md b/腾讯视频热播列表/README.md new file mode 100644 index 0000000..b7a47cb --- /dev/null +++ b/腾讯视频热播列表/README.md @@ -0,0 +1,23 @@ +# 腾讯视频热播列表 + +简单思路说明: + + - 利用QScrollArea滚动显示,QGridLayout或者FlowLayout做布局来放置自定义的Widget + - QNetworkAccessManager异步下载网页和图片 + - QScrollArea滚动到底部触发下一页加载 + +自定义控件说明: + + - 主要是多个layout和控件的结合,其中图片QLabel为自定义,通过setPixmap设置图片,重写paintEvent绘制底部渐变矩形框和白色文字 + - 字体颜色用qss设置 + - 图标利用了QSvgWidget显示,可以是svg 动画(如圆形加载图) + + +# 截图 + +使用QGridLayout 固定列数效果图 + +![截图1](ScreenShot/1.gif) + +使用自定义布局FlowLayout 自动列数效果图 +![截图2](ScreenShot/2.gif) \ No newline at end of file diff --git a/腾讯视频热播列表/ScreenShot/1.gif b/腾讯视频热播列表/ScreenShot/1.gif new file mode 100644 index 0000000..2acc4a9 Binary files /dev/null and b/腾讯视频热播列表/ScreenShot/1.gif differ diff --git a/腾讯视频热播列表/ScreenShot/2.gif b/腾讯视频热播列表/ScreenShot/2.gif new file mode 100644 index 0000000..df28c68 Binary files /dev/null and b/腾讯视频热播列表/ScreenShot/2.gif differ diff --git a/腾讯视频热播列表/TencentMovieHotPlay.py b/腾讯视频热播列表/TencentMovieHotPlay.py new file mode 100644 index 0000000..c300ac0 --- /dev/null +++ b/腾讯视频热播列表/TencentMovieHotPlay.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Created on 2018年2月4日 +@author: Irony."[讽刺] +@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 +@email: 892768447@qq.com +@file: TencentMovieHotPlay +@description: +''' +import os +import sys +import webbrowser + +from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal +from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ + QBrush, QPaintEvent, QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ + QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\ + QAbstractSlider + +from lxml.etree import HTML # @UnresolvedImport + + +__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" +__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" +__Version__ = "Version 1.0" + +# offset=0,30,60,90 +Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" + +# 播放量图标 +Svg_icon_play_sm = ''' + + +'''.encode() + +Svg_icon_loading = ''' + + + + + + + + + + + + + + + + + +'''.encode() + +# 主演 +Actor = '''{title} ''' + + +class CoverLabel(QLabel): + + def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) +# super(CoverLabel, self).__init__( +# ''.format(os.path.abspath(cover_path)), *args, **kwargs) + self.setCursor(Qt.PointingHandCursor) + self.setScaledContents(True) + self.setMinimumSize(220, 308) + self.setMaximumSize(220, 308) + self.cover_path = cover_path + self.cover_title = cover_title + self.video_url = video_url + self.setPixmap(QPixmap(cover_path)) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.video_url) + + def paintEvent(self, event): + super(CoverLabel, self).paintEvent(event) + if hasattr(self, "cover_title") and self.cover_title != "": + # 底部绘制文字 + painter = QPainter(self) + rect = self.rect() + # 粗略字体高度 + painter.save() + fheight = self.fontMetrics().height() + # 底部矩形框背景渐变颜色 + bottomRectColor = QLinearGradient( + rect.width() / 2, rect.height() - 24 - fheight, + rect.width() / 2, rect.height()) + bottomRectColor.setSpread(QGradient.PadSpread) + bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) + bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) + # 画半透明渐变矩形框 + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(bottomRectColor)) + painter.drawRect(rect.x(), rect.height() - 24 - + fheight, rect.width(), 24 + fheight) + painter.restore() + # 距离底部一定高度画文字 + font = self.font() or QFont() + font.setPointSize(8) + painter.setFont(font) + painter.setPen(Qt.white) + rect.setHeight(rect.height() - 12) # 底部减去一定高度 + painter.drawText(rect, Qt.AlignHCenter | + Qt.AlignBottom, self.cover_title) + + +class ItemWidget(QWidget): + + def __init__(self, cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): + super(ItemWidget, self).__init__(*args, **kwargs) + self.setMaximumSize(220, 380) + self.setMaximumSize(220, 380) + self.img_path = img_path + self.cover_url = cover_url + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + # 图片label + self.clabel = CoverLabel(cover_path, figure_info, video_url, self) + layout.addWidget(self.clabel) + + # 片名和分数 + flayout = QHBoxLayout() + flayout.addWidget(QLabel(figure_title, self)) + flayout.addItem(QSpacerItem( + 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) + layout.addLayout(flayout) + + # 主演 + layout.addWidget( + QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) + + # 播放量 + blayout = QHBoxLayout() + count_icon = QSvgWidget(self) + count_icon.setMaximumSize(16, 16) + count_icon.load(Svg_icon_play_sm) + blayout.addWidget(count_icon) + blayout.addWidget( + QLabel(figure_count, self, styleSheet="color: #999999;")) + layout.addLayout(blayout) + + def setCover(self, path): + self.clabel.setCoverPath(path) + self.clabel.setPixmap(QPixmap(path)) +# self.clabel.setText(''.format(os.path.abspath(path))) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(220, 380) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "clabel"): + if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 + return super(ItemWidget, self).event(event) + + +class GridWidget(QWidget): + + Page = 0 + loadStarted = pyqtSignal(bool) + + def __init__(self, *args, **kwargs): + super(GridWidget, self).__init__(*args, **kwargs) + self._layout = QGridLayout(self, spacing=20) + self._layout.setContentsMargins(20, 20, 20, 20) + # 异步网络下载管理器 + self._manager = QNetworkAccessManager(self) + self._manager.finished.connect(self.onFinished) + + def load(self): + if self.Page == -1: + return + self.loadStarted.emit(True) + # 延迟一秒后调用目的在于显示进度条 + QTimer.singleShot(1000, self._load) + + def _load(self): + print("load url:", Url.format(self.Page * 30)) + url = QUrl(Url.format(self.Page * 30)) + self._manager.get(QNetworkRequest(url)) + + def onFinished(self, reply): + # 请求完成后会调用该函数 + req = reply.request() # 获取请求 + iwidget = req.attribute(QNetworkRequest.User + 1, None) + path = req.attribute(QNetworkRequest.User + 2, None) + html = reply.readAll().data() + reply.deleteLater() + del reply + if iwidget and path and html: + # 这里是图片下载完毕 + open(path, "wb").write(html) + iwidget.setCover(path) + return + # 解析网页 + self._parseHtml(html) + self.loadStarted.emit(False) + + def splist(self, src, length): + # 等分列表 + return (src[i:i + length] for i in range(len(src)) if i % length == 0) + + def _parseHtml(self, html): + # encoding = chardet.detect(html) or {} + # html = html.decode(encoding.get("encoding","utf-8")) + html = HTML(html) + # 查找所有的li list_item + lis = html.xpath("//li[@class='list_item']") + if not lis: + self.Page = -1 # 后面没有页面了 + return + lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准 + row_count = int(self._layout.count() / 6) # 行数 + print("lack_count:", lack_count) + self.Page += 1 # 自增+1 + if lack_count != 0: # 上一次没有满足一行6个,需要补齐 + lack_li = lis[:lack_count] + lis = lis[lack_count:] + self._makeItem(lack_li, row_count) # 补齐 + if lack_li and lis: + row_count += 1 + self._makeItem(lis, row_count) # 完成剩下的 + else: + self._makeItem(lis, row_count) + + def _makeItem(self, li_s, row_count): + li_s = self.splist(li_s, 6) + for row, lis in enumerate(li_s): + for col, li in enumerate(lis): + a = li.find("a") + video_url = a.get("href") # 视频播放地址 + img = a.find("img") + cover_url = "http:" + img.get("r-lazyload") # 封面图片 + figure_title = img.get("alt") # 电影名 + figure_info = a.find("div/span") + figure_info = "" if figure_info is None else figure_info.text # 影片信息 + figure_score = "".join(li.xpath(".//em/text()")) # 评分 + # 主演 + figure_desc = "主演:" + \ + "".join([Actor.format(**dict(fd.items())) + for fd in li.xpath(".//div[@class='figure_desc']/a")]) + # 播放数 + figure_count = ( + li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + path = "cache/{0}.jpg".format( + os.path.splitext(os.path.basename(video_url))[0]) + cover_path = "pic_v.png" + if os.path.isfile(path): + cover_path = path + iwidget = ItemWidget(cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, path, self) + self._layout.addWidget(iwidget, row_count + row, col) + + +class Window(QScrollArea): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + self.setFrameShape(self.NoFrame) + self.setWidgetResizable(True) + self.setAlignment(Qt.AlignCenter) + self._loadStart = False + # 网格窗口 + self._widget = GridWidget(self) + self._widget.loadStarted.connect(self.setLoadStarted) + self.setWidget(self._widget) + # 连接竖着的滚动条滚动事件 + self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) + # 进度条 + self.loadWidget = QSvgWidget( + self, minimumHeight=120, minimumWidth=120, visible=False) + self.loadWidget.load(Svg_icon_loading) + + def setLoadStarted(self, started): + self._loadStart = started + self.loadWidget.setVisible(started) + + def onActionTriggered(self, action): + # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 + # 同时防止多次加载同一个url + if action != QAbstractSlider.SliderMove or self._loadStart: + return + # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + # 可以下一页了 + self._widget.load() + + def resizeEvent(self, event): + super(Window, self).resizeEvent(event) + self.loadWidget.setGeometry( + int((self.width() - self.loadWidget.minimumWidth()) / 2), + int((self.height() - self.loadWidget.minimumHeight()) / 2), + self.loadWidget.minimumWidth(), + self.loadWidget.minimumHeight() + ) + + +if __name__ == "__main__": + os.makedirs("cache", exist_ok=True) + app = QApplication(sys.argv) + w = Window() + w.show() + w._widget.load() + sys.exit(app.exec_()) diff --git a/腾讯视频热播列表/TencentMovieHotPlay_Flow.py b/腾讯视频热播列表/TencentMovieHotPlay_Flow.py new file mode 100644 index 0000000..fb6b223 --- /dev/null +++ b/腾讯视频热播列表/TencentMovieHotPlay_Flow.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Created on 2018年2月4日 +@author: Irony."[讽刺] +@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 +@email: 892768447@qq.com +@file: TencentMovieHotPlay_Flow +@description: +''' +import os +import sys +import webbrowser + +from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal +from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ + QBrush, QPaintEvent, QPixmap +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ + QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider + +from flowlayout import FlowLayout # @UnresolvedImport +from lxml.etree import HTML # @UnresolvedImport + + +__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" +__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" +__Version__ = "Version 1.0" + +# offset=0,30,60,90 +Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" + +# 播放量图标 +Svg_icon_play_sm = ''' + + +'''.encode() + +Svg_icon_loading = ''' + + + + + + + + + + + + + + + + + +'''.encode() + +# 主演 +Actor = '''{title} ''' + + +class CoverLabel(QLabel): + + def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): + super(CoverLabel, self).__init__(*args, **kwargs) +# super(CoverLabel, self).__init__( +# ''.format(os.path.abspath(cover_path)), *args, **kwargs) + self.setCursor(Qt.PointingHandCursor) + self.setScaledContents(True) + self.setMinimumSize(220, 308) + self.setMaximumSize(220, 308) + self.cover_path = cover_path + self.cover_title = cover_title + self.video_url = video_url + self.setPixmap(QPixmap(cover_path)) + + def setCoverPath(self, path): + self.cover_path = path + + def mouseReleaseEvent(self, event): + super(CoverLabel, self).mouseReleaseEvent(event) + webbrowser.open_new_tab(self.video_url) + + def paintEvent(self, event): + super(CoverLabel, self).paintEvent(event) + if hasattr(self, "cover_title") and self.cover_title != "": + # 底部绘制文字 + painter = QPainter(self) + rect = self.rect() + # 粗略字体高度 + painter.save() + fheight = self.fontMetrics().height() + # 底部矩形框背景渐变颜色 + bottomRectColor = QLinearGradient( + rect.width() / 2, rect.height() - 24 - fheight, + rect.width() / 2, rect.height()) + bottomRectColor.setSpread(QGradient.PadSpread) + bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) + bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) + # 画半透明渐变矩形框 + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(bottomRectColor)) + painter.drawRect(rect.x(), rect.height() - 24 - + fheight, rect.width(), 24 + fheight) + painter.restore() + # 距离底部一定高度画文字 + font = self.font() or QFont() + font.setPointSize(8) + painter.setFont(font) + painter.setPen(Qt.white) + rect.setHeight(rect.height() - 12) # 底部减去一定高度 + painter.drawText(rect, Qt.AlignHCenter | + Qt.AlignBottom, self.cover_title) + + +class ItemWidget(QWidget): + + def __init__(self, cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): + super(ItemWidget, self).__init__(*args, **kwargs) + self.setMaximumSize(220, 420) + self.setMaximumSize(220, 420) + self.img_path = img_path + self.cover_url = cover_url + layout = QVBoxLayout(self) + layout.setContentsMargins(10, 20, 10, 0) + # 图片label + self.clabel = CoverLabel(cover_path, figure_info, video_url, self) + layout.addWidget(self.clabel) + + # 片名和分数 + flayout = QHBoxLayout() + flayout.addWidget(QLabel(figure_title, self)) + flayout.addItem(QSpacerItem( + 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) + layout.addLayout(flayout) + + # 主演 + layout.addWidget( + QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) + + # 播放量 + blayout = QHBoxLayout() + count_icon = QSvgWidget(self) + count_icon.setMaximumSize(16, 16) + count_icon.load(Svg_icon_play_sm) + blayout.addWidget(count_icon) + blayout.addWidget( + QLabel(figure_count, self, styleSheet="color: #999999;")) + layout.addLayout(blayout) + + def setCover(self, path): + self.clabel.setCoverPath(path) + self.clabel.setPixmap(QPixmap(path)) +# self.clabel.setText(''.format(os.path.abspath(path))) + + def sizeHint(self): + # 每个item控件的大小 + return QSize(220, 420) + + def event(self, event): + if isinstance(event, QPaintEvent): + if event.rect().height() > 20 and hasattr(self, "clabel"): + if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 + # print("start download img:", self.cover_url) + req = QNetworkRequest(QUrl(self.cover_url)) + # 设置两个自定义属性方便后期reply中处理 + req.setAttribute(QNetworkRequest.User + 1, self) + req.setAttribute(QNetworkRequest.User + 2, self.img_path) + self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 + return super(ItemWidget, self).event(event) + + +class GridWidget(QWidget): + + Page = 0 + loadStarted = pyqtSignal(bool) + + def __init__(self, *args, **kwargs): + super(GridWidget, self).__init__(*args, **kwargs) + self._layout = FlowLayout(self) # 使用自定义流式布局 + # 异步网络下载管理器 + self._manager = QNetworkAccessManager(self) + self._manager.finished.connect(self.onFinished) + + def load(self): + if self.Page == -1: + return + self.loadStarted.emit(True) + # 延迟一秒后调用目的在于显示进度条 + QTimer.singleShot(1000, self._load) + + def _load(self): + print("load url:", Url.format(self.Page * 30)) + url = QUrl(Url.format(self.Page * 30)) + self._manager.get(QNetworkRequest(url)) + + def onFinished(self, reply): + # 请求完成后会调用该函数 + req = reply.request() # 获取请求 + iwidget = req.attribute(QNetworkRequest.User + 1, None) + path = req.attribute(QNetworkRequest.User + 2, None) + html = reply.readAll().data() + reply.deleteLater() + del reply + if iwidget and path and html: + # 这里是图片下载完毕 + open(path, "wb").write(html) + iwidget.setCover(path) + return + # 解析网页 + self._parseHtml(html) + self.loadStarted.emit(False) + + def splist(self, src, length): + # 等分列表 + return (src[i:i + length] for i in range(len(src)) if i % length == 0) + + def _parseHtml(self, html): + # encoding = chardet.detect(html) or {} + # html = html.decode(encoding.get("encoding","utf-8")) + html = HTML(html) + # 查找所有的li list_item + lis = html.xpath("//li[@class='list_item']") + if not lis: + self.Page = -1 # 后面没有页面了 + return + self.Page += 1 + self._makeItem(lis) + + def _makeItem(self, lis): + for li in lis: + a = li.find("a") + video_url = a.get("href") # 视频播放地址 + img = a.find("img") + cover_url = "http:" + img.get("r-lazyload") # 封面图片 + figure_title = img.get("alt") # 电影名 + figure_info = a.find("div/span") + figure_info = "" if figure_info is None else figure_info.text # 影片信息 + figure_score = "".join(li.xpath(".//em/text()")) # 评分 + # 主演 + figure_desc = "主演:" + \ + "".join([Actor.format(**dict(fd.items())) + for fd in li.xpath(".//div[@class='figure_desc']/a")]) + # 播放数 + figure_count = ( + li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] + path = "cache/{0}.jpg".format( + os.path.splitext(os.path.basename(video_url))[0]) + cover_path = "pic_v.png" + if os.path.isfile(path): + cover_path = path + iwidget = ItemWidget(cover_path, figure_info, figure_title, + figure_score, figure_desc, figure_count, video_url, cover_url, path, self) + self._layout.addWidget(iwidget) + + +class Window(QScrollArea): + + def __init__(self, *args, **kwargs): + super(Window, self).__init__(*args, **kwargs) + self.resize(800, 600) + self.setFrameShape(self.NoFrame) + self.setWidgetResizable(True) + self.setAlignment(Qt.AlignCenter) + self._loadStart = False + # 网格窗口 + self._widget = GridWidget(self) + self._widget.loadStarted.connect(self.setLoadStarted) + self.setWidget(self._widget) + # 连接竖着的滚动条滚动事件 + self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) + # 进度条 + self.loadWidget = QSvgWidget( + self, minimumHeight=120, minimumWidth=120, visible=False) + self.loadWidget.load(Svg_icon_loading) + + def setLoadStarted(self, started): + self._loadStart = started + self.loadWidget.setVisible(started) + + def onActionTriggered(self, action): + # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 + # 同时防止多次加载同一个url + if action != QAbstractSlider.SliderMove or self._loadStart: + return + # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 + if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): + # 可以下一页了 + self._widget.load() + + def resizeEvent(self, event): + super(Window, self).resizeEvent(event) + self.loadWidget.setGeometry( + int((self.width() - self.loadWidget.minimumWidth()) / 2), + int((self.height() - self.loadWidget.minimumHeight()) / 2), + self.loadWidget.minimumWidth(), + self.loadWidget.minimumHeight() + ) + + +if __name__ == "__main__": + os.makedirs("cache", exist_ok=True) + app = QApplication(sys.argv) + w = Window() + w.show() + w._widget.load() + sys.exit(app.exec_()) diff --git a/腾讯视频热播列表/flowlayout.py b/腾讯视频热播列表/flowlayout.py new file mode 100644 index 0000000..ef1d54e --- /dev/null +++ b/腾讯视频热播列表/flowlayout.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + + +############################################################################# +## +# Copyright (C) 2013 Riverbank Computing Limited. +# Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +## +# This file is part of the examples of PyQt. +## +# $QT_BEGIN_LICENSE:BSD$ +# You may use this file under the terms of the BSD license as follows: +## +# "Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +# the names of its contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +## +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +# $QT_END_LICENSE$ +## +############################################################################# + + +from PyQt5.QtCore import QPoint, QRect, QSize, Qt +from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton, QSizePolicy, + QWidget) + + +class Window(QWidget): + def __init__(self): + super(Window, self).__init__() + + flowLayout = FlowLayout() + flowLayout.addWidget(QPushButton("Short")) + flowLayout.addWidget(QPushButton("Longer")) + flowLayout.addWidget(QPushButton("Different text")) + flowLayout.addWidget(QPushButton("More text")) + flowLayout.addWidget(QPushButton("Even longer button text")) + self.setLayout(flowLayout) + + self.setWindowTitle("Flow Layout") + + +class FlowLayout(QLayout): + def __init__(self, parent=None, margin=0, spacing=-1): + super(FlowLayout, self).__init__(parent) + + if parent is not None: + self.setContentsMargins(margin, margin, margin, margin) + + self.setSpacing(spacing) + + self.itemList = [] + + def __del__(self): + item = self.takeAt(0) + while item: + item = self.takeAt(0) + + def addItem(self, item): + self.itemList.append(item) + + def count(self): + return len(self.itemList) + + def itemAt(self, index): + if index >= 0 and index < len(self.itemList): + return self.itemList[index] + + return None + + def takeAt(self, index): + if index >= 0 and index < len(self.itemList): + return self.itemList.pop(index) + + return None + + def expandingDirections(self): + return Qt.Orientations(Qt.Orientation(0)) + + def hasHeightForWidth(self): + return True + + def heightForWidth(self, width): + height = self.doLayout(QRect(0, 0, width, 0), True) + return height + + def setGeometry(self, rect): + super(FlowLayout, self).setGeometry(rect) + self.doLayout(rect, False) + + def sizeHint(self): + return self.minimumSize() + + def minimumSize(self): + size = QSize() + + for item in self.itemList: + size = size.expandedTo(item.minimumSize()) + + margin, _, _, _ = self.getContentsMargins() + + size += QSize(2 * margin, 2 * margin) + return size + + def doLayout(self, rect, testOnly): + x = rect.x() + y = rect.y() + lineHeight = 0 + + for item in self.itemList: + wid = item.widget() + spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, + QSizePolicy.PushButton, Qt.Horizontal) + spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, + QSizePolicy.PushButton, Qt.Vertical) + nextX = x + item.sizeHint().width() + spaceX + if nextX - spaceX > rect.right() and lineHeight > 0: + x = rect.x() + y = y + lineHeight + spaceY + nextX = x + item.sizeHint().width() + spaceX + lineHeight = 0 + + if not testOnly: + item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) + + x = nextX + lineHeight = max(lineHeight, item.sizeHint().height()) + + return y + lineHeight - rect.y() + + +if __name__ == '__main__': + + import sys + + app = QApplication(sys.argv) + mainWin = Window() + mainWin.show() + sys.exit(app.exec_()) diff --git a/腾讯视频热播列表/pic_v.png b/腾讯视频热播列表/pic_v.png new file mode 100644 index 0000000..516b3c2 Binary files /dev/null and b/腾讯视频热播列表/pic_v.png differ