用Beeware做一个app

分享用Beeware制作一个给儿童学识字的安卓App的经验。
1.从拼多多来的一个认字大王3000字得到启示,每一页为小学1到5年的生字,通过朗读字和2个组词,然后就小孩按页面上的字,达到让小孩认字的目的。我认为可以制作一个App,让孩子直接用,不用购买这类点读机器。当然这里面每页大约有50多个字,让小孩要找很久,这也是要改进的因素。
2.考虑最近在学习python,我研究了几种可以让python编写安卓App的工具,发现kivy很难安装,可能需要翻墙,一直安装不成功,而Beeware虽然功能比较少,但容易安装使用,果断采用Beeware。
3.Beeware和Python的安装过程省略。采集小学生汉字组词拼音也省略,制作数据库也省略:

一、开始的准备工作:

1.创建数据库hz

CREATE TABLE IF NOT EXISTS hz (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hz VARCHAR(4),
py VARCHAR(10),
zc1 VARCHAR(20),
zc2 VARCHAR(20),
zc3 VARCHAR(20),
zc4 VARCHAR(20),
zc5 VARCHAR(20),
kw VARCHAR(60),
nj VARCHAR(20),
cs INTEGER)

后面的sc是后来加上去的,记录wav文件的播放时长。

2.使用pyttsx3生成声音

像识字3000中说:哪个是天,蓝天、天空的天。

import pyttsx3
import sqlite3
import wave

conn = sqlite3.connect('16hz.db')
c = conn.cursor()
# 创建TTS引擎对象
engine = pyttsx3.init()
engine.setProperty('audio_format', 'wav')
gid = 1
while gid <= 1000:
 cursor = c.execute("SELECT id, hz, py, zc1, zc2, zc3, kw  from hz where id ="+str(gid))
 row = cursor.fetchone()
 text = "哪个是"  
 text = text + row[1] + ','
 text = text + row[3] + ','
 if len(row[4]) > 0:
  text = text + row[4] + ','
 #if len(row[5]) > 0:
 # text = text + row[5] + ','
 text = text + '的' + row[1]
 engine.save_to_file(text, './sy1/'+str(gid)+'.wav')
 engine.runAndWait()
 gid = gid + 1
gid = 1001
while gid <= 2000:
 cursor = c.execute("SELECT id, hz, py, zc1, zc2, zc3, kw  from hz where id ="+str(gid))
 row = cursor.fetchone()
 text = "哪个是"  
 text = text + row[1] + ','
 text = text + row[3] + ','
 if len(row[4]) > 0:
  text = text + row[4] + ','
 #if len(row[5]) > 0:
 # text = text + row[5] + ','
 text = text + '的' + row[1]
 engine.save_to_file(text, './sy2/'+str(gid)+'.wav')
 engine.runAndWait()
 gid = gid + 1
gid = 2001
while gid <= 2791:
 cursor = c.execute("SELECT id, hz, py, zc1, zc2, zc3, kw  from hz where id ="+str(gid))
 row = cursor.fetchone()
 text = "哪个是"  
 text = text + row[1] + ','
 text = text + row[3] + ','
 if len(row[4]) > 0:
  text = text + row[4] + ','
 #if len(row[5]) > 0:
 # text = text + row[5] + ','
 text = text + '的' + row[1]
 engine.save_to_file(text, './sy3/'+str(gid)+'.wav')
 engine.runAndWait()
 gid = gid + 1
input("please input any key to exit!")
conn.close()

同样通过pyttsx3生成“你答对了”,“你答错了”的语音,本来在PC上用是不用生成声音再播放出来的,但如果跨平台,像Android是无法使用TTS,或者可以使用,但我不知道怎么办。

二、中间各种尝试,如做成一个命令行的运行模式,用Qpython在手机上运行,等等,这里省略。

三、也是最后的版本

安装beeware,用briefcase new开始做一个app,将上面的数据库、sy声音文件搬到src里面。然后修改app.py文件为(里面有一些注释,帮助大家读懂程序):

from functools import partial
import toga
from toga.style.pack import COLUMN, LEFT, RIGHT, ROW, Pack
import sqlite3
import random
from time import time, sleep
from pathlib import Path
import miniaudio
import datetime
import threading
#import shutil
#导入必要的库,其中这个miniaudio库是可以在Android上运行的,我尝试了很多播放声音的库,只有这个能被Beeware编译到安卓程序上

resources_folder = Path(__file__).joinpath("../resources/").resolve()
resources_folder1 = Path(__file__).joinpath("../resources/sy1").resolve()
resources_folder2 = Path(__file__).joinpath("../resources/sy2").resolve()
resources_folder3 = Path(__file__).joinpath("../resources/sy3").resolve()
db_filepath = resources_folder.joinpath("16hzqw.db")
#db_filepath = '/storage/emulated/0/Pictures/16hzqw.db'
#上面是数据库和音频文件的存放位置
#oppo版 需要用电脑调试应删除下面5行
'''
if Path('/storage/emulated/0/Documents/16hzqw.db').exists():
    db_filepath = '/storage/emulated/0/Documents/16hzqw.db'
else:
    shutil.copy(db_filepath, "/storage/emulated/0/Documents/16hzqw.db")
    db_filepath = '/storage/emulated/0/Documents/16hzqw.db'

#使用手机将数据库存放在手机的文档文件夹中,如果不这样做,虽然程序能够运行,但是不能保存每次运行程序的数据,包括写入、更新,不知道为什么。
这里还有个说明,如果采用Android7的系统或者一些手机、平板(我的oppo手机不用),需要
首先要在AndroidManifest.xml文件的application 标签下 加一条属性 android:requestLegacyExternalStorage=“true”
还有读写权限:见https://blog.csdn.net/qq_51108920/article/details/126153395
这个xml文件在运行briefcase create android后在生成的learn2read build learn2read android gradle app src main中,在运行briefcase build android前要修改好才生效,learn2read是我的程序名,藏得够深的吧,修改后还要在手机app权限中设置一下,还是比较复杂的。
'''

global duihz, duipy, duizc1, duizc2, duizc3,duizc4,duizc5, duid, kw, jj
global stime, etime, ytime, begintime, testcs, cuo_cs, yshen, cid, tjcs
names = globals()
cid = 0
yshen = 0
testcs = 0
cuo_cs = 0
stime = time()
begintime = time()
rid = 0
cuot = 0
global njt
njt = '一年级'
#上面是全局参数和默认值
def bfsy(hzid,idsc):
    if hzid < 1001:
        stream = miniaudio.stream_file(str(resources_folder1.joinpath(str(hzid) + '.wav')))
    elif hzid > 1000 and hzid < 2001:
        stream = miniaudio.stream_file(str(resources_folder2.joinpath(str(hzid) + '.wav')))
    elif hzid > 2000 and hzid < 3001:
        stream = miniaudio.stream_file(str(resources_folder3.joinpath(str(hzid) + '.wav')))
    elif hzid > 3000 and hzid < 4000:
        stream = miniaudio.stream_file(str(resources_folder3.joinpath('cuo.wav')))
        #大于3000为错,其余为对
    else:
        stream = miniaudio.stream_file(str(resources_folder3.joinpath('dui.wav')))
    with miniaudio.PlaybackDevice() as device:
        device.start(stream)
        sleep(idsc)
#播放声音函数
def maxmin(xzng):
    if xzng < 9:
        yshen = 0
    else:
        yshen = 1
    if xzng == 1 or xzng ==9:
        njt = '一年级'
    elif xzng ==2 or xzng ==10:
        njt = '二年级'
    elif xzng == 3 or xzng ==11:
        njt = '三年级'
    elif xzng == 4 or xzng ==12:
        njt = '四年级'
    elif xzng == 5 or xzng ==13:
        njt = '五年级'
    elif xzng == 6 or xzng ==14:
        njt = '六年级'
    else:
        njt = '二年级'
    return(njt,yshen)

njt,yshen = maxmin(3)
#选择年级和有声无声的函数,默认是3
def u_record(testcs,cuo_cs,etime,begintime,cid):
    if testcs > 0:
        conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
        c = conn.cursor()                
        c.execute("UPDATE record SET ts = ?,ct = ?,mt = ? WHERE cid = ?;", (testcs,cuo_cs,int((etime-begintime) / testcs),cid))
        conn.commit()
        c.close()
        conn.close()
#记录每次的题数,错题数,每题用多少秒,每个界面用一个cid  
def u_cs1(duid):
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()  
    query_sql = "update hz set cs1 = cs1 + 1 where id= ?"
    c.execute(query_sql, (str(duid),))
    conn.commit()
    c.close()
    conn.close()
#记录这个汉字的练习次数+1
def u_cs(addcs,duid):
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    query_sql = "update hz set cs = cs + ? where id= ?"
    c.execute(query_sql, (str(addcs),str(duid),))
    conn.commit()
    c.close()
    conn.close()
#为某个汉字加积分
def j_cs(addcs,duid):
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    query_sql = "update hz set cs = cs - ? where id= ?"
    c.execute(query_sql, (str(addcs),str(duid),))
    conn.commit()
    c.close()
    conn.close()
#为某个汉字减少积分
def tj_cs():
    if globals()['testcs'] == 0:
        conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
        c = conn.cursor()
        query_str = 'select rq,sum(ts),sum(ct),round(avg(mt),0) from record group by rq order by rq desc limit 0,4;'
        cursor = c.execute(query_str)
        rows = cursor.fetchall()
        stext3 = ""
        for row in rows:
            stext3 += "%s:做%d题,错%d题,平均用%d秒\n" % (row[0],row[1],row[2],row[3])
        c.close()
        conn.close()
        globals()['tjcs'] = stext3
    else:
        stext3 = globals()['tjcs']
    return(stext3)
#在界面的下面显示最近4天学习总题数等信息,方便家长检查孩子的学习情况
def set_all(njt):
    fid = random.randint(0, 16)
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    cursor = c.execute(
        "SELECT id, hz, py, zc1, zc2, zc3, zc4, zc5, kw, sc from (SELECT *,rank() over (PARTITION by hz order by id desc) as rank FROM hz where nj like '%" + njt + "%' order by cs asc) where rank = 1 limit " + str(fid) + ",16")
    rows = cursor.fetchall()
    hz = []
    py = []
    zc1 = []
    zc2 = []
    zc3 = []
    zc4 = []
    zc5 = []
    hzid = []
    kw = []
    idsc = []
    i = 1
    for row in rows:
        if i < 17:
            hzid.append(row[0])
            hz.append(row[1])
            py.append(row[2])
            zc1.append(row[3])
            zc2.append(row[4])
            zc3.append(row[5])
            zc4.append(row[6])
            zc5.append(row[7])
            kw.append(row[8])
            idsc.append(row[9])
        i = i + 1
    ii = list(range(1,17))
    random.shuffle(ii)
    c.close()
    conn.close()
    return ii,hzid,hz,py,zc1,zc2,zc3,zc4,zc5,kw,idsc
#按年级返回16个汉字的拼音组词、课文、id、和音频文件的播放长度,等整个程序发完专门说明这个
def build(app):
    # 定义组件
    c_box = toga.Box()
    b1_box = toga.Box()
    b2_box = toga.Box()
    b3_box = toga.Box()
    b4_box = toga.Box()
    box = toga.Box()
    c_input = toga.TextInput(readonly=True,style=Pack(font_size=22))
    c_label = toga.Label("请选择几年级开始学习...", style=Pack(text_align=LEFT))
    c_label1 = toga.Label("", style=Pack(text_align=LEFT, font_size=12))
    c_label2 = toga.Label("", style=Pack(text_align=LEFT))
    c_label3 = toga.Label("", style=Pack(text_align=LEFT))
    #界面设置
    def bt1(bb, widget):
        xzng = int(bb)
        globals()['testcs'] += 1
        globals()['etime'] = time()
        globals()['ytime'] = int(etime - stime)
        #记录用时
        if rid == 0:
            current_date = datetime.datetime.now()
            formatted_date = current_date.strftime("%Y-%m-%d")
            formatted_time = current_date.strftime("%H:%M:%S")
            #每次rid=0时,生成一个记录,记录学习情况
            conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
            c = conn.cursor()
            c.execute("INSERT INTO record (rq,sj,ts,ct,mt) VALUES (?,?,?,?,?);", (formatted_date,formatted_time,0,0,0))
            conn.commit()
            cursor = c.execute("SELECT cid from record order by cid desc")
            row = cursor.fetchone()
            c.close()
            conn.close()
            globals()['cid'] = row[0]
            #提取cid到全局变量中
            globals()['testcs'] = 0
            #rid为0时,测试次数重计
            globals()['cuo_cs'] = 0
            #错误次数也重计
            globals()['begintime'] = time()
            #设定rid为0是的开始时间,可以计算做完16个汉字的时间
            globals()['njt'],globals()['yshen'] = maxmin(xzng)
            #将年级和有声的变量按选择存入到全局变量
            globals()['ii'],globals()['hzid'],globals()['hz'],globals()['py'],globals()['zc1'],globals()['zc2'],globals()['zc3'],globals()['zc4'],globals()['zc5'],globals()['kw'],globals()['idsc']=set_all(globals()['njt'])
            #将全部要用到的变量存入全局变量
            globals()['stime'] = time()
            #设定本次开始时间
            for i in range(1,17):
                setattr(names['button_' + str(i)],"text",hz[ii[i-1]-1])
                #设置按键名称,即把汉字显示在按钮上,汉字通过随机错排列,这个ii就是打乱了的1到16顺序,2个-1是因为最小是0
            globals()['rid'] = 1
            #rid变为1,算第一次,答对了rid才会增加1
            globals()['duid'] = hzid[rid-1]
            #这个是对的汉字在数据库中的id
            globals()['jj'] = ii.index(rid) + 1
            #这个变量是记忆对的汉字选择的按钮的编码,index是寻址,寻找rid在ii列中的位置,这个位置+1为按钮的参数
            if yshen == 0:
                if zc2[rid-1] == '':
                    c_value = py[rid-1] + "  " + zc1[rid-1].replace(hz[rid-1],'□')
                else:
                    zc = []
                    zc.append(zc1[rid-1])
                    zc.append(zc2[rid-1])
                    c_value = py[rid-1] + " " + zc1[rid-1].replace(hz[rid-1],'□') + "、" + zc2[rid-1].replace(hz[rid-1],'□')
                    if zc3[rid-1] != '':
                        zc.append(zc3[rid-1])
                        if zc4[rid-1] != '':
                            zc.append(zc4[rid-1])
                        if zc5[rid-1] != '':
                            zc.append(zc5[rid-1])
                        zc = random.sample(zc,2)
                        c_value = py[rid-1] + " " + zc[0].replace(hz[rid-1], '□')+ "、" + zc[1].replace(hz[rid-1],'□')
            #无声是在输入栏显示拼音和隐去本字的组词,这里面采用随机数,在多个组词中随机选择2个
            c_text = '来自:' + globals()['njt'] + '的' + kw[rid-1]
            #显示来自年级和课文
            c_label.text = c_text
            c_label3.text = tj_cs()
            #rid为0是,显示统计的4天学习情况
            if yshen == 0:
                c_input.value = c_value
            else:
                play_sound_background(hzid[rid-1],idsc[rid-1])
            #当选择有声时,用miniaudio发出声音,不使用sleep就不会播放声音,sleep的时长就是播放声音的时长,后台播放,虽然会重音但比较流畅
        #rid为0为一开始设置第一题
        else:
            text5 = "复习:"
            if rid >=1:
                text5 = text5 + hz[rid-1] + "(" + py[rid-1] + ") " + zc1[rid-1] + " " + zc2[rid-1] + " " + zc3[rid-1] + " " + zc4[rid-1] + " " + zc5[rid-1]
            #text5是复习内容
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/5a0fc873a934b85fdf72d49798ed291d.png)

 

![img](https://img-blog.csdnimg.cn/img_convert/d975350d046ad2a4ab6fe1c6885887ff.png)

![img](https://img-blog.csdnimg.cn/img_convert/46506ae54be168b93cf63939786134ca.png)

![img](https://img-blog.csdnimg.cn/img_convert/252731a671c1fb70aad5355a2c5eeff0.png)

![img](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png)

![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)**

731a671c1fb70aad5355a2c5eeff0.png)

![img](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png)

![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)**

<img src="https://img-community.csdnimg.cn/images/fd6ebf0d450a4dbea7428752dc7ffd34.jpg" alt="img" style="zoom:50%;" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值