kivy 是一款 python 的GUI开发框架,特点是样式可以和代码分离,绘图功能基于OpenGL编写,可以用来开发比较炫的界面,并且可以打包为Android、iOS等移动应用,还能运行在Linux、Windows、MacOS上。
这篇博客将记录我使用kivy开发的经验,所以会持续更新,且篇幅较长,是一篇kivy开发的技术笔记,欢迎大家留言交流。
我们塔尔旺科技有限公司准备开发一款能对图片进行画框标注的程序,因为是内部使用,所以选用 python kivy 作为GUI框架。之前从网上已经得知 kivy 虽然有专业团队支撑,但是框架不如 pyQt 或者 wxWindow 成熟,由于太容易上手,并且很喜欢kivy的样式分离的模式,所以依然决定使用一次。
首选需要阅读 kivy 入门教程,读一遍Introduction 和 Programming Guide,写几个小例子,基本就差不多了。
kivy 中的几个概念还是比较容易理解,如果做过 android 开发,会发现和 android 程序的开发模式很相似。例如都有 app 生命周期,通过重写 app 的 on_start() 等方法可以在各种生命周期状态下执行操作。
主要的概念有:
1. Application
2. Widget
3. Event
4. Property
5. KV Design Language
6. Layout
7. OpenGL Drawing Instructions
上面这些内容,都可以在 kivy 入门教程 中找到。
下面记录的就是我们在开发中积累的一些经验了。
这个布局,会看子 Widget 的 pos_hint
和 size_hint
,依据他们来计算子 Widget 的位置和大小,还可以用子 Widget 的 pos
属性来指定自己的位置 (用的是窗口坐标系)。
pos_hint
和 size_hint
这两个属性的取值都是 0 到 1,并且 size_hint 默认值是 1 , 所以可以用 FloatLayout 实现完全填充父容器的效果。
因此,可以简单的理解为:
1. FloatLayout 的子 Widget 可以定位到任意位置
2. FloatLayout 的子 Widget 大小可以充满整个父容器,或为父容器的几分之一。
这个布局需要注意,它只能设置一种位置关系,所有的子容器都使用该关系进行定位。
定位的参数要靠 AnchorLayout 对象的 anchor_x 和 anchor_y 属性来确定,而不是像其他的 Layout 类,依靠子 Widget 的 pos_hint 属性来设置Widget的位置。这给使用带来了不便,也是容易误解的一大问题。
小技巧:可以用 FloatLayout + AnchorLayout 来实现居中的效果。例如下面的KV language:
: # float layout
AnchorLayout: # 小技巧,居中子Widget
LabelManager: # relative layout
id: label_manager
size_hint: None, None
size: '800dp', '800dp'
# canvas:
# Color:
# rgb: 0, 1, 0
# Rectangle:
# pos: (0, 0) # relative 的容器,位置要使用0,0,对自己的canvas已经使用了局部坐标系
# size: self.size
ImageWidget:
id: image
label_manager: label_manager
pos_hint: {'x':0, 'y':0}
size: self.parent.size
Button:
pos_hint: {'x':0, 'y':0}
size_hint: None, None
size: '150dp', '50dp'
text: '打开图片目录'
font_size: '16sp'
color: .5, .7, .9, 1
on_press: root.show_load()
当然,我们还有一个更简单的居中方法:
: # float layout
LabelManager: # relative layout
id: label_manager
size_hint: None, None
size: '800dp', '800dp'
center: self.parent.center
RelativeLayout 可以让子Widget的定位坐标原点变成Widget的左下角,而不是顶层窗口的左下角;当RelativeLayout的位置移动时,子Widget的位置会联动,以保持父子位置不变。
触控点的坐标:如果一个Widget的父容器有RelativeLayout、ScatterLayout、Scatter 和ScrollView 那么当触控(touch)事件传递到这些容器的子容器时,坐标点会被转换为容器坐标系的数值,而不再是顶层窗口的坐标了。要注意的是,对于这些特殊容器而言,触控坐标不会转换,只有他们的子Widget会收到转换后的坐标。
文档中用三种坐标系统来描述这些关系,分别是:
Window Coordinates 窗口坐标系
是最顶层窗口的坐标系,以窗口的左下角为原点。
Parent Coordinates 父坐标系
距离本Widget最近的一个特殊父容器的局部坐标系。
Local / Widget Coordinates 局部或Widget坐标系
以本Widget的左下角为原点的坐标系。
Widget 类提供了4个方法来对这三种坐标系进行转换,分别是:
to_widget()
接收窗口坐标,返回局部坐标。to_window()
接收局部坐标 ,返回窗口坐标。to_parent()
接收局部坐标,返回父坐标。to_local()
接收父坐标,返回局部坐标。kivy 的 Clock
类提供了几种时钟调度机制,分别是 Clock.scheduled_once()
, Clock.schedule_interval()
和Clock.create_trigger()
。
使用的例子:
def my_callback(dt):
pass
// call my_callback every 0.5 seconds
event = Clock.schedule_interval(my_callback, 0.5)
// call my_callback in 5 seconds
event2 = Clock.schedule_once(my_callback, 5)
event_trig = Clock.create_trigger(my_callback, 5)
event_trig()
// unschedule using cancel
event.cancel()
// unschedule using Clock.unschedule
Clock.unschedule(event2)
原来是因为把自定义的Widget
放到了 FloatLayout
下,FloatLayout
会根据 size_hit
管理子 Widget 的大小,所以自己修改的 size 不起作用。解决的方法是设置子widget的size_hit 为 (None, None)
,需要在创建对象时作为构造函数的参数传入,例如:
BBox(size_hint=(None, None))
但是不能是设置 pos_hint=(None, None)
否则报错,想想也对,因为 FloatLayout 的主要作用就是能根据子Widget的 pos_hint 来定位,如果不用这个属性,那干脆就不用 FloatLayout 好了。
分下面几步即可实现:
1. 重写 App.on_start() 方法
2. 在其中调用 self.root_window.maximize() 即可。
代码如下:
def on_start(self):
self.root_window.maximize()