kivy android打包buildozer.spec GUI配置
这个适合刚刚学习kivyd的道友使用,后面看情况更新
代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
import configparser
import os
import json # 新增导入class BuildozerConfigTool:def __init__(self, master):self.master = mastermaster.title("Buildozer配置工具")# 创建主容器框架self.left_frame = ttk.Frame(master)self.left_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")self.right_frame = ttk.Frame(master)self.right_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")# 调整各部分的父容器self.create_basic_settings(self.left_frame)self.create_advanced_settings(self.right_frame)self.create_dependencies_section(self.left_frame)self.create_permissions_section(self.left_frame)self.create_file_selection(self.right_frame)self.create_action_buttons(master)# 配置网格布局权重master.columnconfigure(0, weight=1)master.columnconfigure(1, weight=1)master.rowconfigure(0, weight=1)# 初始化配置解析器self.config = configparser.ConfigParser()# 新增配置存储路径self.config_path = os.path.expanduser("~/.buildozer_gui_config.json")# 更新默认权限列表(去掉前缀)self.default_permissions = ["INTERNET","ACCESS_WIFI_STATE","WRITE_EXTERNAL_STORAGE","READ_EXTERNAL_STORAGE","CAMERA","BLUETOOTH","BLUETOOTH_ADMIN","ACCESS_COARSE_LOCATION","ACCESS_FINE_LOCATION","QUERY_ALL_PACKAGES"]self.default_dependencies = ["kivy","requests","https://github.com/kivymd/KivyMD/archive/master.zip","python3","asynckivy","asyncgui","exceptiongroup","materialyoucolor"]# 初始化时加载默认权限(移动到load_last_config之前)for perm in self.default_permissions:self.permissions_list.insert(tk.END, perm)# 初始化时加载默认依赖(在load_last_config之前)for dep in self.default_dependencies:self.dependencies_list.insert(tk.END, dep)self.load_last_config() # 加载上次配置(会覆盖默认值)def create_basic_settings(self, parent):frame = ttk.LabelFrame(parent, text="基本设置")frame.pack(fill="x", padx=5, pady=5)# 调整条目,移除源码目录entries = [("应用标题:", "title_entry", 30),("包名:", "package_entry", 30),("包域名:", "package_domain_entry", 30),("版本号:", "version_entry", 15),("包含扩展:", "include_exts_entry", 40) # 仅保留包含扩展]for i, (label, var, width) in enumerate(entries):row = i // 2col = i % 2ttk.Label(frame, text=label).grid(row=row, column=col*2, sticky="w")entry = ttk.Entry(frame, width=width)entry.grid(row=row, column=col*2+1, padx=2, pady=2, sticky="ew")setattr(self, var, entry)# 确保包含扩展默认值初始化(保留文本仅修改颜色)self.include_exts_entry.delete(0, tk.END)self.include_exts_entry.insert(0, "py,png,jpg,kv,ttf")self.include_exts_entry.config(foreground="gray")# 调整焦点事件绑定self.include_exts_entry.bind("<FocusIn>", lambda e: self.clear_placeholder(self.include_exts_entry, "py,png,jpg,kv,ttf"))self.include_exts_entry.bind("<FocusOut>", lambda e: self.restore_placeholder(self.include_exts_entry, "py,png,jpg,kv,ttf"))def create_dependencies_section(self, parent):frame = ttk.LabelFrame(parent, text="依赖管理")frame.pack(fill="both", expand=True, padx=5, pady=5)# 列表高度调整self.dependencies_list = tk.Listbox(frame, height=5)self.dependencies_list.pack(fill="both", expand=True)# 按钮布局btn_frame = ttk.Frame(frame)btn_frame.pack(fill="x")ttk.Button(btn_frame, text="添加依赖", command=self.add_dependency).pack(side=tk.LEFT)ttk.Button(btn_frame, text="删除选中", command=self.remove_dependency).pack(side=tk.LEFT)def create_permissions_section(self, parent):frame = ttk.LabelFrame(parent, text="Android权限")frame.pack(fill="both", expand=True, padx=5, pady=5)self.permissions_list = tk.Listbox(frame, height=5)self.permissions_list.pack(fill="both", expand=True)btn_frame = ttk.Frame(frame)btn_frame.pack(fill="x")ttk.Button(btn_frame, text="添加权限", command=self.add_permission).pack(side=tk.LEFT)ttk.Button(btn_frame, text="删除选中", command=self.remove_permission).pack(side=tk.LEFT)def create_file_selection(self, parent):frame = ttk.LabelFrame(parent, text="静态文件包含")frame.pack(fill="both", expand=True, padx=5, pady=5)self.file_listbox = tk.Listbox(frame, height=8)self.file_listbox.pack(fill="both", expand=True)btn_frame = ttk.Frame(frame)btn_frame.pack(fill="x")ttk.Button(btn_frame, text="选择文件", command=self.select_files).pack(side=tk.LEFT)ttk.Button(btn_frame, text="清除列表", command=self.clear_files).pack(side=tk.LEFT)def create_action_buttons(self, parent):btn_frame = ttk.Frame(parent)btn_frame.grid(row=1, column=0, columnspan=2, pady=5)ttk.Button(btn_frame, text="生成配置文件", command=self.generate_config).pack(side=tk.LEFT, padx=10)ttk.Button(btn_frame, text="初始化配置", command=self.reset_to_default).pack(side=tk.LEFT, padx=10)ttk.Button(btn_frame, text="退出", command=self.master.quit).pack(side=tk.LEFT, padx=10)def create_advanced_settings(self, parent):frame = ttk.LabelFrame(parent, text="显示设置")frame.pack(fill="x", padx=5, pady=5)# 仅保留显示相关设置ttk.Label(frame, text="屏幕方向:").grid(row=0, column=0, sticky="w")self.orientation_var = tk.StringVar(value="portrait")ttk.Combobox(frame, textvariable=self.orientation_var,values=["portrait", "landscape"], width=18).grid(row=0, column=1)ttk.Label(frame, text="全屏模式:").grid(row=1, column=0, sticky="w")self.fullscreen_var = tk.BooleanVar(value=False)ttk.Checkbutton(frame, variable=self.fullscreen_var).grid(row=1, column=1, sticky="w")# 以下是各功能方法实现def add_dependency(self):dep = simpledialog.askstring("添加依赖", "输入Python依赖项:")if dep:self.dependencies_list.insert(tk.END, dep)def remove_dependency(self):for i in reversed(self.dependencies_list.curselection()):self.dependencies_list.delete(i)def add_permission(self):perm = simpledialog.askstring("添加权限", "输入Android权限:")if perm:# 自动去除android.permission.前缀if perm.startswith("android.permission."):perm = perm[len("android.permission."):]self.permissions_list.insert(tk.END, perm)def remove_permission(self):for i in reversed(self.permissions_list.curselection()):self.permissions_list.delete(i)def select_files(self):files = filedialog.askopenfilenames(title="选择静态文件",filetypes=[("All files", "*.*")])for f in files:# 转换为相对路径并统一路径分隔符rel_path = os.path.relpath(f).replace("\\", "/")self.file_listbox.insert(tk.END, rel_path)def clear_files(self):self.file_listbox.delete(0, tk.END)def save_current_config(self):config_data = {"title": self.title_entry.get(),"package": self.package_entry.get(),"version": self.version_entry.get(),"dependencies": list(self.dependencies_list.get(0, tk.END)),"permissions": list(self.permissions_list.get(0, tk.END)),"files": list(self.file_listbox.get(0, tk.END)),"package_domain": self.package_domain_entry.get(),"include_exts": self.include_exts_entry.get()}try:with open(self.config_path, "w", encoding='utf-8') as f:json.dump(config_data, f, ensure_ascii=False, indent=2)except Exception as e:messagebox.showwarning("配置保存失败", f"保存配置时出错: {str(e)}")def load_last_config(self):if os.path.exists(self.config_path):try:with open(self.config_path, encoding='utf-8') as f:config = json.load(f)# 处理可能包含前缀的历史权限配置stored_permissions = config.get("permissions", [])cleaned_perms = []for p in stored_permissions:if p.startswith("android.permission."):cleaned_perms.append(p.split(".")[-1])else:cleaned_perms.append(p)combined_perms = list(set(cleaned_perms + self.default_permissions))self.permissions_list.delete(0, tk.END)for perm in combined_perms:self.permissions_list.insert(tk.END, perm)self.title_entry.insert(0, config.get("title", ""))self.package_entry.insert(0, config.get("package", ""))self.version_entry.insert(0, config.get("version", ""))# 处理依赖加载逻辑saved_deps = config.get("dependencies", [])if saved_deps:self.dependencies_list.delete(0, tk.END) # 清空默认依赖for dep in saved_deps:self.dependencies_list.insert(tk.END, dep)for f in config.get("files", []):self.file_listbox.insert(tk.END, f)self.package_domain_entry.insert(0, config.get("package_domain", ""))# 处理包含扩展的加载逻辑saved_exts = config.get("include_exts", "")if saved_exts:self.include_exts_entry.delete(0, tk.END)self.include_exts_entry.insert(0, saved_exts)self.include_exts_entry.config(foreground="black")else:# 保留默认值但显示为灰色self.include_exts_entry.delete(0, tk.END)self.include_exts_entry.insert(0, "py,png,jpg,kv,ttf")self.include_exts_entry.config(foreground="gray")except Exception as e:messagebox.showwarning("配置加载失败", f"无法加载历史配置: {str(e)}")def generate_config(self):try:# 固定源码目录为当前目录source_dir = "."# 处理包含扩展(空值时使用默认)include_exts = self.include_exts_entry.get()if not include_exts or include_exts == "py,png,jpg,kv,ttf":include_exts = "py,png,jpg,kv,ttf"# 验证必填字段required_fields = {"应用标题": self.title_entry.get(),"包名": self.package_entry.get(),"版本号": self.version_entry.get(),"包域名": self.package_domain_entry.get()}missing = [name for name, value in required_fields.items() if not value]if missing:messagebox.showwarning("缺少必填项", f"请填写以下必填字段:\n{', '.join(missing)}")returnself.config["app"] = {"title": required_fields["应用标题"],"package.name": required_fields["包名"],"version": required_fields["版本号"],"package.domain": required_fields["包域名"],"source.dir": source_dir, # 硬编码为当前目录"source.include_exts": include_exts,"log_level":2,"warn_on_root":1,"android.allow_backup":True,"osx.kivy_version":"1.9.1","osx.python_version":3}# 处理可选的文件包含配置include_patterns = self.file_listbox.get(0, tk.END)if include_patterns:self.config["app"]["source.include_patterns"] = ", ".join(include_patterns)else:self.config["app"]["#source.include_patterns"] = "(未配置文件包含模式)"# 添加默认注释的Android配置self.config["app"]["#android.api"] = "33"self.config["app"]["#android.minapi"] = "21"self.config["app"]["#android.sdk"] = "24"self.config["app"]["#android.ndk"] = "23b"self.config["app"]["#android.ndk_api"] = "21"self.config["app"]["android.archs"] = "arm64-v8a, armeabi-v7a"# 修改默认权限列表(保持无前缀)default_permissions = ["ACCESS_NETWORK_STATE","INTERNET","CAMERA","BLUETOOTH","BLUETOOTH_ADMIN"]# 合并权限(直接使用无前缀格式)user_permissions = list(self.permissions_list.get(0, tk.END))all_permissions = list(set(default_permissions + user_permissions))# 新增依赖项配置(添加这部分代码)requirements = list(self.dependencies_list.get(0, tk.END))if requirements:self.config["app"]["requirements"] = ", ".join(requirements)else:self.config["app"]["#requirements"] = "(未配置依赖项)"# 添加屏幕方向配置self.config["app"]["orientation"] = self.orientation_var.get()# 新增全屏配置(添加这行代码)self.config["app"]["fullscreen"] = "1" if self.fullscreen_var.get() else "0"# Android权限配置(直接使用无前缀)self.config["app"]["android.permissions"] = ", ".join(all_permissions)# 保存文件save_path = filedialog.asksaveasfilename(defaultextension=".spec",filetypes=[("Buildozer spec", "*.spec")])if not save_path: # 用户取消保存returnwith open(save_path, "w") as f:self.config.write(f)# 保存当前配置self.save_current_config()messagebox.showinfo("成功", "配置文件已生成!")except Exception as e:messagebox.showerror("错误", f"生成配置失败:{str(e)}")# 通用占位符处理方法def clear_placeholder(self, entry, default_text):if entry.get() == default_text:entry.config(foreground="black")def restore_placeholder(self, entry, default_text):if not entry.get().strip():entry.insert(0, default_text)entry.config(foreground="gray")def reset_to_default(self):# 清空所有输入框self.title_entry.delete(0, tk.END)self.package_entry.delete(0, tk.END)self.package_domain_entry.delete(0, tk.END)self.version_entry.delete(0, tk.END)# 重置源码目录和扩展名self.source_dir_entry.delete(0, tk.END)self.source_dir_entry.insert(0, ".")self.source_dir_entry.config(foreground="gray")# 重置包含扩展self.include_exts_entry.delete(0, tk.END)self.restore_placeholder(self.include_exts_entry, "py,png,jpg,kv,ttf")# 重置依赖列表(完全恢复默认)self.dependencies_list.delete(0, tk.END)for dep in self.default_dependencies:self.dependencies_list.insert(tk.END, dep)# 重置权限列表(显示默认权限)self.permissions_list.delete(0, tk.END)for perm in self.default_permissions:self.permissions_list.insert(tk.END, perm)# 清空所有列表(增加文件列表清除)self.file_listbox.delete(0, tk.END)# 重置显示设置self.orientation_var.set("portrait")self.fullscreen_var.set(False)# 强制清空文件列表(新增)self.clear_files()# 删除保存的配置文件if os.path.exists(self.config_path):try:os.remove(self.config_path)except Exception as e:messagebox.showwarning("配置清除失败", f"无法删除历史配置: {str(e)}")if __name__ == "__main__":root = tk.Tk()root.geometry("600x700")app = BuildozerConfigTool(root)root.mainloop()
成品
[app]
title = apply
package.name = bn
version = 0.0.1
package.domain = org.kivy
source.dir = .
source.include_exts = py,png,jpg,kv,ttf
log_level = 2
warn_on_root = 1
android.allow_backup = True
osx.kivy_version = 1.9.1
osx.python_version = 3
source.include_patterns = fonts/simkai.ttf
#android.api = 33
#android.minapi = 21
#android.sdk = 24
#android.ndk = 23b
#android.ndk_api = 21
android.archs = arm64-v8a, armeabi-v7a
requirements = kivy, requests, https://github.com/kivymd/KivyMD/archive/master.zip, python3, asynckivy, asyncgui, exceptiongroup, materialyoucolor
orientation = portrait
fullscreen = 0
android.permissions = BLUETOOTH, CAMERA, INTERNET, READ_EXTERNAL_STORAGE, BLUETOOTH_ADMIN, ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE, QUERY_ALL_PACKAGES, WRITE_EXTERNAL_STORAGE, ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION