一.点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:
配置完可在 Tools -> External Tools 查看
1.3 配置打包器插件Pyinstaller
作用: 最终打包成可执行文件(.app 或 .exe)
安装插件:
pip3 install pyinstaller
如上 1.2 操作选择Tools>External Tools> '+' 新增扩展工具
Name: 输入名称
Program: 当前工程路径/venv/bin/pyinstaller
Arguments: -F -w .py
Working directory:
五.修改生成弹窗代码及完善脚本
1.1 生成UI代码
pycharm 工具栏 Tools -> External Tools -> 选中Designer插件 将会打开组件的窗口,选新建Main Window,
然后可以根据自己的需要拖拽组件到主布局,最后保存生成一个 xxx.ui的文件
最后利用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目录下