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 = master
        master.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 // 2
            col = i % 2
            ttk.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)}")
                return
            
            self.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:  # 用户取消保存
                return
            
            with 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅若红尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值