网络加载点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文件路径,点编译:
最终的效果
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容