网络加载点9图预编译处理(Qt + PyCharm)

一.点9图(9-patch)

是一种可拉伸的位图图像格式,因其必须以.9.png为扩展名进行保存而得名. 点9图的本质其实就是一张标准的PNG格式图片,而与其他普通PNG格式图片的不同之处在于,点9图在其图片的四周额外包含了1像素宽的黑色边框,用于定义图片的可拉伸的区域与可绘制的区域,以实现根据视图内容自动调整图片大小的效果.

二.为什么不能直接从网络加载点9图

原因:

安卓在打包成apk的过程会对资源图片进行的预编译处理,而从网络直接下载的资源图片则没进行预处理.
只有已预编译过的点9图(源类型转换为已编译类型)才能被Android系统识别并处理,不然会没有设置的拉伸效果.

代码端:

还需要Android端使用 NinePatchDrawable来加载

具体解析参考链接

三.点9图的预处理

使用 android sdk 中, appt 工具进行预编译处理.

命令:

aapt s -i inputfile -o outputfile

四.Qt环境配置

1.1 安装 Qt

brew install qt

1.2 配置UI生成插件Designer

作用: 生成弹窗代码

配置插件,在qt安装的目录下. 在pycharm设置选项中,选择Tools>External Tools> '+' 新增扩展工具

Name: 输入名称
Program: /opt/homebrew/Cellar/qt/xx/bin/Designer.app
Working directory: ProjectFileDir

配置完可在 Tools -> External Tools 查看

1.3 配置打包器插件Pyinstaller

作用: 最终打包成可执行文件(.app 或 .exe)

安装插件:

pip3 install pyinstaller

如上 1.2 操作选择Tools>External Tools> '+' 新增扩展工具

Name: 输入名称
Program: 当前工程路径/venv/bin/pyinstaller
Arguments: -F -w FileNameWithoutExtension.py
Working directory: FileDir

五.修改生成弹窗代码及完善脚本

1.1 生成UI代码

pycharm 工具栏 Tools -> External Tools -> 选中Designer插件 将会打开组件的窗口,选新建Main Window,
然后可以根据自己的需要拖拽组件到主布局,最后保存生成一个 xxx.ui的文件

Designer显示效果

最后利用py里面的工具 pyuic将 .ui文件转化为 py文件. 在项目中可以找到
项目 -> venv -> bin -> pyuic (当前我的是pyuic6)

xxx/xxx/pyuic6 untitled.ui -o uniti.py

最终生成一个uniti.py文件(文章中UiMainWindow部分的代码)

1.2 主要代码

主文件:

import sys

from PyQt6.QtWidgets import QApplication, QMainWindow
from nine_patch_window import UiMainWindow


class MyMainWindow(QMainWindow, UiMainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)


app = QApplication(sys.argv)  # 实例化一个应用对象
window = MyMainWindow()
window.show()
sys.exit(app.exec())  # 确保主循环安全退出

弹窗样式及编译逻辑:

# -*- coding: utf-8 -*-
import os
import subprocess
import sys

from PIL import Image
from PyQt6 import QtCore, QtWidgets

#  窗体
class UiMainWindow(object):
    def setupUi(self, main_window):
        main_window.setObjectName("MainWindow")
        main_window.resize(1104, 697)
        self.centralwidget = QtWidgets.QWidget(main_window)
        self.centralwidget.setObjectName("centralwidget")
        self.plainTextEdit = QtWidgets.QPlainTextEdit(self.centralwidget)
        self.plainTextEdit.setGeometry(QtCore.QRect(200, 310, 651, 271))
        self.plainTextEdit.setObjectName("plainTextEdit")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(200, 60, 461, 31))
        self.textEdit.setObjectName("textEdit")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(690, 70, 141, 23))
        self.pushButton.setObjectName("pushButton")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(200, 290, 54, 12))
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(390, 20, 101, 16))
        self.label_2.setObjectName("label_2")
        main_window.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(main_window)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1104, 23))
        self.menubar.setObjectName("menubar")
        main_window.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(main_window)
        self.statusbar.setObjectName("statusbar")
        main_window.setStatusBar(self.statusbar)

        self.retranslateUi(main_window)
        QtCore.QMetaObject.connectSlotsByName(main_window)

    def translate_out(self):
        file = self.textEdit.toPlainText()
        trans29patch(file, self.plainTextEdit)

    def retranslateUi(self, main_window):
        _translate = QtCore.QCoreApplication.translate
        main_window.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "9patch图编译"))
        self.label.setText(_translate("MainWindow", "日志输出"))
        self.label_2.setText(_translate("MainWindow", "输入文件路径"))
        self.pushButton.clicked.connect(self.translate_out)


def trans29patch(file_path, out_edit: QtWidgets.QPlainTextEdit):
    os_name = os.uname()[0]
    # 判断系统
    if os_name == 'Darwin':
        print('Mac OS X系统')
        if str(file_path).startswith('file://'):
            file_path = str(file_path).replace('file://', '')
    elif os_name == 'Windows':
        print('Windows系统')
        if str(file_path).startswith('file:///'):
            file_path = str(file_path).replace('file:///', '')

    print('file_path:' + file_path)

    # 校验是否合法
    if len(file_path) <= 0:
        out_edit.appendPlainText("文件路径不能为空!")
        return

    if not os.path.isfile(file_path):
        out_edit.appendPlainText("找不到文件,请输入正确文件路径!")
        return

    img = Image.open(file_path)
    if img.width != 134 or img.height != 128:
        out_edit.appendPlainText("图片分辨率不对!分辨率应为(134 * 128)")
        return

    file = os.path.join(file_path)
    path_str = os.path.dirname(file)
    path = os.path.join(path_str, "out")

    if not file.endswith(".9.png"):
        out_edit.appendPlainText("文件:" + file + " -> 不符合.9格式!\n")
        return

    if not os.path.exists(path):
        os.makedirs(path)

    # 编译后输出文件及路径
    output_name = os.path.basename(file).split(".")[0] + "_out.png"
    output_file_path = os.path.join(path, output_name)

    def get_aapt_path():
        if getattr(sys, 'frozen', False):
            # 如果应用程序被 PyInstaller 打包
            return os.path.join(sys._MEIPASS, 'aapt')
        else:
            # 在开发环境中的路径
            return 'aapt'

    aapt_path = get_aapt_path()

    command = [aapt_path, "s", "-i", file, "-o", output_file_path]

    # 执行预编译图片 命令为 aapt s -i fileInput -o outPut
    stdout, stderr = run_aapt_command(command)

    if stderr:
        out_edit.appendPlainText("编译失败日志:\n" + stderr)

    if os.path.exists(output_file_path):
        out_edit.appendPlainText("编译成功,文件路径:" + output_file_path)


def run_aapt_command(command):
    try:
        result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
        # 获取标准输出
        stdout_output = result.stdout
        # 获取错误输出
        stderr_output = result.stderr
        return stdout_output, stderr_output
    except Exception as e:
        # 如果命令返回非零状态码,打印错误信息
        print("Error occurred while running aapt command:")
        print(e)
        return None, None

六.打包生成可执行文件

1.spec文件生成

pyi-makespec main.py

2.编辑spec配置文件

把我们sdk里面的aapt可执行文件拷贝一份放到项目跟目录下
导入其他文件写法

datas=[("aapt",'.')],

如下:

a = Analysis(
    ['main.py'], #如多个文件 ['main.py', 'xx.py' , 'xxx.py'] 将其他py文件一起加入到这个数组参数中
    pathex=[],
    binaries=[],
    datas=[("aapt",'.')], #如多个文件 [("aapt",'.'), ("xxx",'.')] 
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
)
3.生成可执行文件
1.基于spec生成可执行文件

运行如下命令:

pyinstaller main.spec

2.直接命令生成可执行文件

运行如下命令:

pyinstaller --onefile --windowed --icon=xxx.jpeg --add-data 'aapt':'.' xxx.py xxx.py

--onefile 生成单个可执行文件
--windowed 使用 GUI 模式,从而生成一个应用程序而不是一个命令行程序(可选)
--icon=应用图片
--add-data = aapt文件
最后拼接的是多个py文件

3.可执行文件生成目录

如果顺利将会在项目跟目录 生成 dist文件里面包含可执行文件
mac环境下
默认生成 main(unix可执行程序)/main.app

4.可执行文件权限处理

发现如果打开不了需要给可执行文件开启权限:

chmod +x unix执行文件

如果生成的是 main.app则执行文件在main.app/Contents/MacOS目录下

5.拖拽/输入9patch文件路径,点编译:
最终的效果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容