Kivy笔记

Kivy命名规则

1、类继承App的类名(小写)(除去App)+ .kv
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.graphics import Color,Rectangle
from kivy.properties import ColorProperty, ListProperty


class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.rows = 6
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.button = Button(size=(.4, .63), text='iPaoMi', background_color=(0, 0, 1), font_size=15, color=(1, 0, 1))
self.button.bind(on_press=self.on_push)

self.add_widget(self.button)

with self.password.canvas.before:
Color(1, 0.5, 0, 0.5)
self.password = Rectangle(size=self.password.size, pos=self.password.pos)

def on_push(self, inst):
print('inst'+inst.text)
self.button.text = inst.text
self.button.height=12


class MyApp(App):
def build(self):
self.root = root = LoginScreen()
return root


if __name__ == '__main__':
MyApp().run()

创建的kv文件则为 my.kv
代码分离规则:
定义页面:
使用<>:创建一个页面,页面和py里的类一致。
例如:
且使用缩进标识控件和属性的隶属关系。

<LoginScreen>:
    BoxLayout:
        Button:
    BoxLayout:
        Button:
<LoginScreen>:

使用kv文件则无须使用Py文件进行编写布局,可直接如下:

1
2
class LoginScreen(App):
pass

其实kv文件读取我们也可以直接将文件内容以字符串形式写在任意变量内,任何使用,如下

from kivy.lang import Builder
Builder.load_string("""
    <LoginScreen>:
        BoxLayout:
            Button:
        BoxLayout:
            Button:
    <LoginScreen>:
""")
class LoginScreen(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

class MyApp(App):
    def build(self):
        return LoginScreen()   # Kivy会自动缩放根控件,

if __name__ == "__main__":
    MyApp().run()

Kivy 中文显示

Kivy 属性

位置和尺寸(大小)

尺寸 (大小)

大小有两种写法一种是 size 另一种是 size_hint

size

传入的值是一个固定的宽高值

size_hint

传入的是按照当前窗口比例的宽高值,一般写做如下:

size_hint: .4, .5
或者
size_hint: 0.4, 0.5

位置

位置也有和大小一样的两种属性,一种pos另一个是pos_hint

pos
pos: 
pos_hint

按照宽高比例的写法是允许省略0的写法。
比例值的计算是以左下角为坐标系起点(0,0),横向为x轴,纵向为y,假设宽为W,高为L,则窗内任意一点的比例值为[W/x,L/y]
相对于控件, x轴上有三条边可以确定位置:

  •    左边界x
    
  •    正中间线center_x
    
  •    右边界right
    

同样,y轴也有三条可以确定位置:

  •    上边界top
    
  •    正中间线center_y
    
  •    下边界y
    

X轴和Y轴各有三种他们两两组合共有9种写法可以确定一个位置。

pos_hint: {'x': .8, 'y': .4}
或
pos_hint: {'right': .7, 'top': .3}
或者
pos_hint: {'center_y': .7, 'center_x': .3}

以上x、y、right、top、center_y、center_x都是以控件为主,距离布局窗体的X轴和Y轴。

布局

FloatLayout

py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout


class FloatLayoutWidget(FloatLayout):
def __init__(self):
super(FloatLayoutWidget, self).__init__()


class FloatLayoutApp(App):
def build(self):
return FloatLayoutWidget()


if __name__ == '__main__':
FloatLayoutApp().run()

kv文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 自定义按钮
<Button>
font_size: 40 # 字体大小
size_hint: .3, .4 # 按钮大小

<FloatLayoutWidget> # 定义页面
canvas: # 设置背景颜色
Color:
rgba: [.2, .4, .5, .6]

Rectangle:
size: self.size
pos: self.pos

Button: # 使用自定义按钮
text: "BT1" # 按钮显示文本
background_color: 1, 0, 0, 1 # 按钮背景颜色
pos_hint: {'x': 0, 'top': 1} # 按钮位置

Button:
text: "BT2"
background_color: .2, .3, .4, .5
pos_hint: {'x': .35, 'y': .3}

Button:
text: "BT3"
background_color: .5, .4, .3, .2
pos_hint: {'x': .7, 'bottom': 0}

Button:
text: "BT4"
background_color: 1, 0, 0, 1
pos_hint: {'x': 0.7, 'top': 1}

Button:
text: "BT5"
background_color: 1, 0, 0, 1
pos_hint: {'x': 0, 'bottom': 1}

BoxLayout

盒子布局,可以将部件水平或者垂直排列的布局。类似于安卓的线性布局。如果没有限制任何大小,部件将会以10px间距平分父窗口大小。

只用Python进行布局的话如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# !/usr/bin/env python3
# -*- coding: utf8 -*-


from kivy.app import App
from kivy.graphics import Rectangle, Color
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button


class BoxLayoutWidget(BoxLayout):
def __init__(self):
super(BoxLayoutWidget, self).__init__()

# 设置背景颜色
with self.canvas:
Color(.4, .5, .02, .3)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect, size=self.update_rect)

# 新建一个指定尺寸的按钮
button1 = Button(text='Hello BoxLayout1', size_hint=(.3, .2), pos=(200, 40), background_color=(1, 1, 1, 1))
button2 = Button(text='Hello BoxLayout2', size_hint=(.2, .2), pos=(200, 40), background_color=(.6, .5, .8, .3))

# 将按钮加到布局内
self.add_widget(button1)
self.add_widget(button2)

def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size


class BoxLayoutApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
BoxLayoutApp().run()

或者配合kv文件进行布局:

python代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# !/usr/bin/env python3
# -*- coding: utf-8 -*-


from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)


class BoxLayoutTApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
BoxLayoutTApp().run()

kv文件如下:(kv文件创建遵守kv命名规则)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<Button>
font_size: 50

<BoxLayoutWidget>

canvas:
Color:
rgba: [.2, .4, .5, .6]

Button:
text: "BT1"
background_color: 1, 0, 0, 1

Button:
text: "BT2"
background_color: 1, 1, 1, 1

Button:
text: "BT3"
background_color: 0, 1, 0, 1

Button:
text: "BT4"
background_color: 1, 0, 1, 1

Button:
text: "BT5"
background_color: 0, 1, 1, 1

默认BoxLayout排版是纵向排版,若需要改为横向排版则可在KV文件里添加参数orientation: "vertical" 若是使用python实现则可以将实例化BoxLayout时,添加参数BoxLayout(orientation="vertical")

BoxLayout间距

BoxLayout布局中间距有两种形式:

  • 布局和子级之间填充是需要使用padding,默认为[0, 0, 0, 0] ,四个参数分别为 [padding_left, padding_top, padding_right, padding_bottom] 从右下脚开始顺时针一一对应。(左,上,右,下);同时padding还接受两个参数形式[padding_horizontal, padding_vertical] 分别是水平边距和竖直边距;或者一个参数形式[padding]代表周围边距。
  • 子级和子级之间填充需要使用spacing, 默认为 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<Button>
font_size: 10

<BoxLayoutWidget>
orientation: "vertical" # 横向排版
padding: [10, 20, 30, 40] # 设置间距
canvas:
Color:
rgba: [.2, .4, .5, .6]

Button:
text: "BT1" # 显示文本,.KV默认是不支持中文
background_color: 1, 0, 0, 1

Button:
text: "BT2"
background_color: 1, 1, 1, 1
BoxLayout:
orientation: "vertical" # 设置间距
spacing: 20
Button:
text: "BT3"
background_color: 0, 1, 0, 1

Button:
text: "BT4"
background_color: 1, 0, 1, 1

Button:
text: "BT5"
size_hint_y: .15 # 设置按钮大小
background_color: 0, 1, 1, 1

AnchorLayout布局(锚点布局)

锚点布局可以将子部件放在 左上、中上、右上、左中、正中、右中、左下、中下、右下 ,9个位置处。只需要指定anchor_xanchor_y属性即可。

  • anchor_x 默认值为center,可以且只可以接受leftrightcenter,分别为左、右和中。
    • anchor_y 默认值为center,可以且只可以接受topbottomcenter,分别为上、下和中。

python代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.button import Button
from kivy.graphics import Rectangle, Color


class AnchorLayoutWidget(AnchorLayout):
def __init__(self):
super(AnchorLayoutWidget, self).__init__()

# 设置颜色
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect, size=self.update_rect)

# 嵌套第一个布局
anchor_first = AnchorLayout(anchor_x='left', anchor_y='top')
anchor_first.add_widget(Button(text='left-top', size_hint=[.3, .3], background_color=[0, 0, 0, 1]))

# 嵌套第二个布局
anchor_second = AnchorLayout(anchor_x='center', anchor_y='top')
anchor_second.add_widget(Button(text='center-top', size_hint=[.3, .3], background_color=[0, 0, 1, 1]))

# 嵌套第三个布局
anchor_third = AnchorLayout(anchor_x='right', anchor_y='top')
anchor_third.add_widget(Button(text='right-top', size_hint=[.3, .3], background_color=[0, 1, 1, 1]))

# 嵌套第四个布局
anchor_fourth = AnchorLayout(anchor_x='left', anchor_y='center')
anchor_fourth.add_widget(Button(text='left-center', size_hint=[.3, .3], background_color=[1, 1, 0, 1]))

# 嵌套第五个布局
anchor_fifth = AnchorLayout(anchor_x='center', anchor_y='center')
anchor_fifth.add_widget(Button(text='center-center', size_hint=[.3, .3], background_color=[0, 1, 0, 1]))

# 嵌套第六个布局
anchor_sixth = AnchorLayout(anchor_x='right', anchor_y='center')
anchor_sixth.add_widget(Button(text='right-center', size_hint=[.3, .3], background_color=[1, 0, 1, 1]))

# 嵌套第七个布局
anchor_seventh = AnchorLayout(anchor_x='left', anchor_y='bottom')
anchor_seventh.add_widget(Button(text='left-bottom', size_hint=[.3, .3], background_color=[0, 1, .5, 1]))

# 嵌套第八个布局
anchor_eighth = AnchorLayout(anchor_x='center', anchor_y='bottom')
anchor_eighth.add_widget(Button(text='center-bottom', size_hint=[.3, .3], background_color=[1, .5, 1, .5]))

# 嵌套第九个布局
anchor_ninth = AnchorLayout(anchor_x='right', anchor_y='bottom')
anchor_ninth.add_widget(Button(text='right-bottom', size_hint=[.3, .3], background_color=[.5, 1, 1, .5]))

self.add_widget(anchor_first)
self.add_widget(anchor_second)
self.add_widget(anchor_third)
self.add_widget(anchor_fourth)
self.add_widget(anchor_fifth)
self.add_widget(anchor_sixth)
self.add_widget(anchor_seventh)
self.add_widget(anchor_eighth)
self.add_widget(anchor_ninth)

def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size


class AnchorLayoutApp(App):
def build(self):
return AnchorLayoutWidget()


if __name__ == '__main__':
AnchorLayoutApp().run()

或者配合kv文件进行布局:
python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# !/usr/bin/env python3
# -*- coding: utf-8 -*-


from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)


class BoxLayoutTApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
BoxLayoutTApp().run()

kv文件如下:

<Button>
    font_size: 10
    size_hint: .3, .3

<AnchorLayoutWidget>
    padding: 20

    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        Button:
            background_color: 0, 0, 0, 1
            text: 'left-top'

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'top'
        Button:
            text: 'center-top'
            background_color: 0, 0, 1, 1

    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        Button:
            text: 'right-top'
            background_color: 0, 1, 1, 1

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'center'
        Button:
            text: 'center-center'
            background_color: 1, 1, 0, 1

    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'center'
        Button:
            text: 'left-center'
            background_color: 0, 1, 0, 1

    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'center'
        Button:
            text: 'right-center'
            background_color: 1, 0, 1, 1

    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'bottom'
        Button:
            text: 'left-bottom'
            background_color: 0, 1, .5, 1

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'bottom'
        Button:
            text: 'center-bottom'
            background_color: 1, .5, 1, .5

    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'bottom'
        Button:
            text: 'right-bottom'
            background_color: .5, 1, 1, .5

GridLayout (网格布局)

可以将子部件排列成多行多列的矩阵,根据布局配置按照子部件的索引为每一个子部件分配位置。新建网格布局的时候,需要设置具体的行(cols)和列(rows)数,作为约束。否则设置是无效的。
设置具体的行列后,当子部件变化时,布局就会根据改值进行扩展,但是总数不会超过设置的上限值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.graphics import Rectangle, Color


class GridLayoutWidget(GridLayout):
def __init__(self):
super(GridLayoutWidget, self).__init__()

with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect, size=self.update_rect)

self.cols = 3
self.rows = 3

for i in range(8):
btn = Button(text=f'BTN_{i}', background_color=(0.2, .2, .4))
self.add_widget(btn)

def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size


class GridLayoutApp(App):
def build(self):
return GridLayoutWidget()


if __name__ == '__main__':
GridLayoutApp().run()

配合kv文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.gridlayout import GridLayout


class GridLayoutWidget(GridLayout):
def __init__(self):
super(GridLayoutWidget, self).__init__()


class GridLayoutApp(App):
def build(self):
return GridLayoutWidget()


if __name__ == '__main__':
GridLayoutApp().run()

kv代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<Button>
font_size: 10
size_hint: .3, .4

<GridLayoutWidget>
rows:3
cols:3
padding: 15
spacing: 20
Button:
background_color: 0, 1, 0, 1
text: 'btn_1'

Button:
background_color: .1, 0, 1, 1
text: 'btn_2'

Button:
background_color: 0, .5, .4, 1
text: 'btn_3'

Button:
background_color: 0, 1, 0, .5
text: 'btn_4'


GridLayout设置布局大小

GridLayout布局种,可以使用部件本身属性为其指定大小,若要对部件设置固定宽度,在设置之前则需要将size_hint_x设置为None

GridLayout为部件指定大小

需要使用col_force_default属性,其默认值False,表示不使用列默认宽度。需要将col_force_default设置为True。并且在没有给子部件设置widthsize_hint_x的属性的情况下,使用默认的列宽。列宽的默认值为0。所以还需要指定一个具体的值,使用col_default_width可以设置成功。同上,对与设置行高也可以相同的方法将row_force_default设置True,并且row_default_height设置值。

设置列宽行高

布局的宽高

可以使用cols_minimum设置属性的列宽,cols_minimum属性接受的值是一个字典,字典的键为列号,值为像素值。例如:cols_minimum:{0: 130, 1:140}
将第一列设置为130像素,第二列设置为140像素。
执行一下代码后,可以发现,从执行顺序的优先级来说,可以按照如下顺序: col_default_width < cols_minimum < width
同理,也可以通过rows_minimum设置属性的行高rows_minimum: {0: 50}
可参考如下kv代码:

<Button>
    font_size: 50
    size_hint: .3, .4


<GridLayoutWidget>

    rows:3  # 设置行数
    cols:3  # 设置列数
    padding: 15 # 设置间距
    spacing: 20 # 设置间距
    col_force_default: True # 强制使用默认列宽
    col_default_width: 200 # 设置默认,列宽
    row_force_default: True # 强制使用默认行高
    row_default_height: '34px' # 设置默认行高
    
    cols_minimum:{0: 130, 1:140}  # 设置列宽
    rows_minimum: {0: 50} # 设置行高
    canvas:
        Color:
            rgba: .3,.3,.5,.5
        Rectangle:
            size: self.size
            pos: self.pos
    Button:
        background_color: 0, 1, 0, 1
        text: 'btn_1'
        size_hint_x: None
        width: '300px'

    Button:
        background_color: .1, 0, 1, 1
        text: 'btn_2'

    Button:
        background_color: 0, .5, .4, 1
        text: 'btn_3'

    Button:
        background_color: 0, 1, 0, .5
        text: 'btn_4'

PageLayout 布局

PageLayou布局和前面几种布局方式是有差异的,他更倾向与动态布局,使用这个布局可以创建一个简单的多页面布局。可以在这些布局之间随意跳转,此布局每个子部件都作为一个单独的页面,所以此布局并不支持size_hintpos_hint
例如如下:
会生成一个可以自由左右拉的按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.pagelayout import PageLayout
from kivy.app import App
from kivy.uix.button import Button


class PageLayoutWidget(PageLayout):
def __init__(self, **kwargs):
super(PageLayoutWidget, self).__init__()

btn = Button(text='btn1', background_color=[0.3,.9,.2,1])
btn2 = Button(text='btn2', background_color=[.1,.5,.4,1])

self.add_widget(btn)
self.add_widget(btn2)


class PageLayoutPyApp(App):
def build(self):
return PageLayoutWidget()


if __name__ == '__main__':
PageLayoutPyApp().run()

KV实现布局

# pagelayout.kv
<PageLayoutWidget>
    border: '100dp'  # 设置边界,如果不设置默认为50dp
    Button:
        text:'Page0'
        background_color: 0.3, .2, .5, 1

    Button:
        text:'Page1'
        background_color: 0.4, .4, .6, 1

    Button:
        text:'Page2'
        background_color: 0.6, .6, .8, 1

    Button:
        text:'Page3'
        background_color: 0.8, .8, 1, 1

Python配合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.pagelayout import PageLayout


class PageLayoutWidget(PageLayout):
def __init__(self):
super(PageLayoutWidget, self).__init__()


class PageLayoutApp(App):
def build(self):
return PageLayoutWidget()


if __name__ == '__main__':
PageLayoutApp().run()

PageLayout布局基本属性

  • border : 指定两边边界大小,如果我们没有指定的话默认是50dp.前面的kv中也对这进行写入,测试时可以将对应行去掉后进行测试。
  • page: 设置默认显示哪一页、
  • swipe_threshold: 设置翻页灵敏度 例如: swipe_threshold: .8
  • anim_kwargs: 属性设置’t‘翻页动画和’d‘持续时间,例如: anim_kwargs: {'d':10, 't': 'linear'}。 这边t中有哪些参数暂时不做过多说明,后期会细说。

可以尝试下,当以上属性都设置了,页面之间切换的动画被延长到10秒了,并且翻页的灵敏度也要滑动到80%时以上才会被判定为翻页的动作。

RelativeLayout布局

相对布局,此布局的操作方式与前面的FloatLayout基本是相同的,只是在定位的时候我们使用的x,center_x,right,y,center_y,top这些属性是想对于他的父布局的,而不是相对于窗口。
其实任何布局,当布局的位置属性更改时,小部件都会移动。
接下来我们看看如何初始化一个RelativeLayou布局。
老样子,使用python代码和kv代码文件两种方式实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Color, Rectangle


class MyButton(Button):
# 自定义按钮属性,将公共属性提出来
def __init__(self, **kwargs):
super(MyButton, self).__init__(**kwargs)
self.font_size = 20
self.size_hint = [.2, .2]


class RelativelayoutWidget(RelativeLayout):
pass


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(pos=self.pos, size=self.size)
# 绑定事件
self.bind(pos=self.update_rect, size=self.update_rect)

box_relative = RelativelayoutWidget()

btn1 = MyButton(text='btn1', pos_hint={'right': 1, 'top': 1}, background_color=(.1, .2, .3, 1))
btn2 = MyButton(text='btn2', pos_hint={'x': 0, 'top': 1}, background_color=(.1, .2, .3, 1))
RelativeBtn3 = MyButton(text='RelativeBtn3', pos_hint={'center_x': .5, 'center_y': .5},
background_color=(.1, .2, .3, 1))
RelativeBtn4 = MyButton(text='RelativeBtn4', pos_hint={'x': 0, 'y': 0}, background_color=(.1, .2, .3, 1))
RelativeBtn5 = MyButton(text='RelativeBtn5', pos_hint={'right': 1, 'y': 0},
background_color=(.1, .2, .3, 1))

for i in [btn1, btn2, RelativeBtn3, RelativeBtn4, RelativeBtn5]:
box_relative.add_widget(i)

self.add_widget(BoxLayout())
self.add_widget(box_relative)

def update_rect(self, *args):
# 设置背景尺寸, 可忽略
self.rect.pos = self.pos
self.rect.size = self.size


class RelativeLayoutApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
RelativeLayoutApp().run()

以上代码运行后,会出现半边是空白,另外半边是有在四个角落和中间五个按钮,那是因为我们把按钮在第42行的时候,全部加在了Relativelayout布局内了,然后右边放空一个BoxLayout布局进行占位,不加任何控件,因为Relativelayout布局里面的控件是按照Relativelayout布局的位置和大小进行定位的,并非按照我们最开始初始化的大的那个BoxLayout布局进行定位的,所以里面的控件都是相对于Relativelayout布局的位置进行放置。
接下一样,我们用kv文件配合python 的方式去实现上面的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)


class RelativeLayoutApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
RelativeLayoutApp().run()

kv代码

<Button>
    font_size:20
    size_hint: .2,.3

<BoxLayoutWidget>

    canvas:
        Color:
            rgba: 1,1,1,1

        Rectangle:
            size: self.size
            pos: self.pos

    BoxLayout:

    RelativeLayout:
        Button:
            text:"btn1"
            background_color: .1,.2,.3,1
            pos_hint:{'right':1, 'top':1}

        Button:
            text:"btn2"
            background_color: .1,.2,.3,1
            pos_hint:{'x': 0, 'top': 1}

        Button:
            text:"RelativeBtn3"
            background_color: .1,.2,.3,1
            pos_hint:{'center_x': .5, 'center_y': .5}

        Button:
            text:"RelativeBtn4"
            background_color: .1,.2,.3,1
            pos_hint:{'x': 0, 'y': 0}

        Button:
            text:"RelativeBtn5"
            background_color: .1,.2,.3,1
            pos_hint:{'right': 1, 'y': 0}

ScatterLayout 分散布局

ScatterLayout布局与RelativeLayout布局相似,当变更位置的时候,布局内的小部件也会随着父布局一起相对更改,因为这个布局主要由Scatter小部件实现,所以是可以实现自由的平移,旋转,缩放等布局的。
我们来看下, 使用Python是如何实现的。

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.scatterlayout import ScatterLayout
from kivy.uix.image import AsyncImage
from kivy.graphics import Rectangle, Color


class ScatterLayoutWidget(ScatterLayout):
    pass


class BoxLayoutWidget(BoxLayout):
    def __init__(self, **kwargs):
        super(BoxLayoutWidget, self).__init__(**kwargs)

        with self.canvas:
            Color(1, 1, 1, 1)
            self.rect = Rectangle(pos=self.pos, size=self.size)
            self.bind(pos=self.update_rect, size=self.update_rect)

        scatter_layout = ScatterLayoutWidget()

        image = AsyncImage(source='https://blogcdn.sea-whales.cn/blog/typecho/11.jpg')

        scatter_layout.add_widget(image)

        self.add_widget(scatter_layout)

    def update_rect(self, *args):
        self.rect.pos = self.pos
        self.rect.size = self.size


class ScatterLayoutApp(App):
    def build(self):
        return BoxLayoutWidget()


if __name__ == '__main__':
    ScatterLayoutApp().run()

运行以上python代码,将会出现一个图片,且在窗体中间加载,我们可以使用鼠标单击图片,模拟手指点击进行图片的拖动和缩放、旋转操作。

kv配合python实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)


class ScatterLayoutApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
ScatterLayoutApp().run()

kv文件

<BoxLayoutWidget>
    canvas:
        Color:
            rgba: 1,1,1,1

    ScatterLayout:
        AsyncImage:
            source: 'https://blogcdn.sea-whales.cn/blog/typecho/11.jpg'

    ScatterLayout:
        Image:
            source: 'scatter_image.jpg'

StackLayout 堆栈布局

堆栈布局中只要不超过布局的范围,就可以进行垂直或者水平的排列子项,并且各个小部件的大小可以不需要相同,StackLayout布局可以通过orientation属性进行指定布局方向,默认值为lr-tb
我们看看堆栈排列有哪些排序排列。

  • 按行排列:lr-tb,lr-bt,rl-tb,rl-bt
  • 按列排序:tb-lr,bt-lr,tb-rl,bt-rl

我们看看上面的l、r、t、b 分别代表什么。

  • l 代表的是’left’的缩写,代表左
  • r 代表的是’right’的缩写,代表右
  • t 代表的是’top’的缩写,代表上
  • b 代表的是’bottom’的缩写,代表下

那么我们就可以将’lr’理解为从左到右,’tb’理解为从上到下,以此类推。
‘lr-tb’可以看成先按行从左到右,当前窗口行满后再按列从上到下,那么’tb-lr’则可以看成先按列从上到下,当前窗口列满后,按从左到右排列。其他方式,以此类推。

接下来我们来写一个’lr-tb’排列,不同大小的布局,且按照’lr-tb’方式布局长度递增。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# !/usr/bin/env python3
# -*- coding: utf-8 -*-


from kivy.uix.button import Button
from kivy.uix.stacklayout import StackLayout
from kivy.app import App
from kivy.graphics import Rectangle, Color


class StackLayoutWidget(StackLayout):
def __init__(self, **kwargs):
self.orientation = 'lr-tb'

super(StackLayoutWidget, self).__init__(**kwargs)

with self.canvas:
Color(1, 1, 1, 1) # 设置底色为白色

self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self.update_rect, pos=self.update_rect)

for i in range(30):
# 将按钮的高度控制在20%,长度初始为50,随着按钮越多按钮越大递增。
btn = Button(text=f'Btn_f{i}', width=50 + i * 8, size_hint=(None, 0.20))
self.add_widget(btn)

def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size


class StackLayoutApp(App):
def build(self):
return StackLayoutWidget()


if __name__ == '__main__':
StackLayoutApp().run()

前面已经写了很多从Python 文件改为Python和kv文件配合的情况了,这个我就不单独写了。
我们来看看StackLayout有哪些属性。

  • orientation: 前面我们说过,这个属性是用于决定布局内子部件的排列方式,也说明了具体有哪些属性。
  • padding:这个属性有看前面的也不陌生,是用于控制布局和部件之间的间距的参数。接受的是一个list,可以是四个和两个和一个。四个时分别是[padding_left, padding_top, padding_right, padding_bottom] 左、上、右、下 。两个时分别是[padding_horizontal,padding_vertical] 水平、垂直 两个方向。一个时代表控件四个方向的间距。他们的默认值都为0
  • spacing: 这属性和前面的属性也是一样的用于控制部件和部件之间间距。他可以接受两个和一个参数,也是list。不同数量参数和padding表示的一致。

以上三个参数StackLayout常用的属性,还有几个就是布局会根据子部件自动设置一些属性。我们来看下分别是哪些(顾名思义即可):

  • minimum_height:最低高度。
  • minimum_width: 最小宽度
  • minimum_size:最小尺寸(可同时设置最低高度和最小宽度)

以上就是StackLayout参见的属性,就不做例子进行解释了。如果是按照前面一步步看过来的,也能很快理解意思。

例子:计时器

布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class ClockBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super(ClockBoxLayout, self).__init__(**kwargs)


class ClockApp(App):
def build(self):
return ClockBoxLayout()


if __name__ == '__main__':
# 设置页面背景
from kivy.core.window import Window

Window.clearcolor = [.8, .8, .8, 1]
ClockApp().run()

kv文件

<ClockBoxLayout>:
    orientation: 'vertical'

    Label:
        id: time_label_id
        text: '[b]00[/b]:00:00'
        font_size: 60
        markup: True

计时器

具有暂停和重置功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from time import strftime
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.clock import Clock


class ClockBoxLayout(AnchorLayout):
def __init__(self, **kwargs):
super(ClockBoxLayout, self).__init__(**kwargs)
self.timing_flag = False
self.timing_seconds = 0
self.on_start()

def on_start(self):
# 设置clock 每0秒执行一次,代表实时进行不间断刷新。
Clock.schedule_interval(self.update_time, 0)

def update_time(self, nap):
if self.timing_flag:
self.timing_seconds += nap
# 通过ID获取time_label_id的控件,并且设置text的属性值
self.ids.time_label_id.text = strftime('[b]%H[/b]:%M:%S')
m, s = divmod(self.timing_seconds, 60)
self.ids.stopwatch.text = ('%02d:%02d.[size=40]%02d[/size]' % (int(m), int(s), int(s * 100 % 100)))

def start_on_stop(self):
self.ids.start_stop_button_id.text = 'Start' if self.timing_flag else 'Stop'
self.timing_flag = not self.timing_flag

def reset_clock(self):
if self.timing_flag:
self.ids.start_stop_button_id.text = 'Start'
self.timing_flag=False
self.timing_seconds = 0


class ClockApp(App):
def build(self):
return ClockBoxLayout()


if __name__ == '__main__':
# 设置页面背景
from kivy.core.window import Window

Window.clearcolor = [.8, .8, .8, 1]
ClockApp().run()

kv文件

<MyButton@Button>
    font_size: 25
    bold: True
    border: (2,2,2,2)

<ClockBoxLayout>:
    # 异步加载背景图像 
    AsyncImage:
        source: 'https://blogcdn.sea-whales.cn/blog/typecho/11.jpg'

    BoxLayout:
        # 设置布局
        orientation: 'vertical'

        Label:
            id: time_label_id   # 添加ID属性,通过ID属性进行对控件的获取
            text: '[b]00[/b]:00:00'
            font_size: 60
            markup: True   # 

        BoxLayout:
            orientation: 'horizontal'
            padding: 20
            spacing: 20
            size_hint:(1, None)
            height: 90

            MyButton:
                id: start_stop_button_id 
                text:'start'
                on_press: root.start_on_stop()

            MyButton:
                text: 'Reset'
                on_press: root.reset_clock()  # 触发事件

        Label:
            id: stopwatch
            text: '00:00.[size=40]00[/size]'
            font_size: 60
            markup: True

图形绘制

页面背景

看过布局的朋友们,一定发现了,在创建一个widget控件后,为布局设置背景色或者经常会有一个with self.canvas并且加了几个属性和绑定了事件,这些其实是我们在生成一个控件后,Kivy自动生成的一个类似画布的。我们通过对画布的更改可以设置颜色、尺寸、背景图等。
这里说明一下,canvas 学过HTML5的同学肯定觉得眼熟,但是实际两个是不相同的。
HTML的Canvas的定义是:

HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像。
画布是一个矩形区域,您可以控制其每一像素。
canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

而Kivy的Canvas本质是一组在坐标空间的指令容器,可以理解成坐标空间中一个无限的绘画板,通过添加指令来绘制图形。
我们先来使用纯色进行设置背景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.graphics import Rectangle, Color


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)

with self.canvas:
Color(1, 1, 1, 1)
Rectangle(pos=self.pos, size=self.size)


class BoxLayoutApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
BoxLayoutApp().run()

运行上面代码可以发现,在布局中,左下角有一块100*100的白色小方块,这时候我们就完成了基础的颜色设置,但是我们需要把白色填满整个方块。其实以上代码的操作是初始化一个BoxLayou然后,初始化后其实一个黑色的背景,然后在with里面新加一个画布,对画布初始化为白色背景。如果我们要填满整个窗口只需要在初始化好画布之后,对画布进行大小重定义即可实现覆盖。参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.graphics import Rectangle, Color


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)

with self.canvas:
# 设置颜色值,通常百分比形式比较难把握,我们可以选择将rgb的色值除255,
# 例如:海蓝宝石(#7FFFD4)色的rgb的色值是(127,255,212)写成Kivy的Color就写成
# Color(127/255,255/255,212/255,0),最后一个值是设置透明度的一般选择0~1之间的值。
# 0不透明,1为透明
Color(127 / 255, 255 / 255, 212 / 255, 1)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect, size=self.update_rect)

def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size


class BoxLayoutApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
BoxLayoutApp().run()

老样子,我们看看KV文件配合的情况下如何实现以上样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App


class BoxLayoutWidget(BoxLayout):
def __init__(self, **kwargs):
super(BoxLayoutWidget, self).__init__(**kwargs)


class ColorApp(App):
def build(self):
return BoxLayoutWidget()


if __name__ == '__main__':
ColorApp().run()

kv文件

1
2
3
4
5
6
7
8
<BoxLayoutWidget>
canvas:
Color:
rgba: [127 / 255, 255 / 255, 212 / 255, 1]

Rectangle:
size: self.size
pos: self.pos

还可以在有底色的情况下,使用背景图片。
只需把前面Python代码中19行新增一个参数即可如下:

self.rect = Rectangle(pos=self.pos, size=self.size, source='canvas_image.jpg')

或者kv文件中Rectangle下新增source: 'canvas_image.jpg',其中canvas_image.jpg是图片名称和地址。

基本图形绘制

前面,我们使用纯色背景以及使用图片来将Canvas这个画布进行上色和调整等,接下来我们试试在画好的画布中进行简单图形的绘制,比如。矩形、椭圆等

矩形

其实我们前面也一直在画这个矩形,只是有的部分把他填充了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# !/usr/bin/env python3
# -*- coding: utf-8 -*-


from kivy.uix.relativelayout import RelativeLayout
from kivy.app import App


class RelativeWidget(RelativeLayout):
def __init__(self, **kwargs):
super(RelativeWidget, self).__init__(**kwargs)


class DrawRectangleApp(App):
def build(self):
return RelativeWidget()


if __name__ == '__main__':
DrawRectangleApp().run()

drawrectangle.kv

<RelativeWidget>
    canvas:
        Color:
            rgba: [1,1,1,1]
        Rectangle:
            size: self.width*0.2, self.height*.15
            pos: self.x+10, self.y+10
椭圆
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.uix.relativelayout import RelativeLayout
from kivy.app import App


class RelativeWidget(RelativeLayout):
def __init__(self, **kwargs):
super(RelativeWidget, self).__init__(**kwargs)


class DrawEllipseApp(App):
def build(self):
return RelativeWidget()


if __name__ == '__main__':
DrawEllipseApp().run()

drawellipse.kv

<RelativeWidget>
    canvas:
        Color:
            rgba: [1,1,1,1]
        Ellipse:
            size: self.width*.35, self.height*.35
            pos: self.x+250, self.top-400
半圆及多边形

其实半圆的画法和椭圆基本一致。只是增加了三个新属性。

  • angle_start: 开始线角度,开始方向与y轴的角度。
  • angle_end: 结束线角度,结束方向与y轴的角度。一般angle_end的大小要大于angle_start,若不大于则需要加 360° ,此时Kivy会顺时针画图形。否则则是逆时针画图形。
  • egments: 多边形的边数,可以用来画三角形,六边形等形状。

例如,我们改下前面的椭圆,在它旁边加多一个六边形,一个三角形以及一个扇形还有正圆。Python代码我们继续使用椭圆的代码。改下Kv代码就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<RelativeWidget>
canvas:
Color:
rgba: [1,1,1,1]
Ellipse: # 正圆
size: self.width*0.20, self.width*0.20
pos: self.x+20, self.top-200

Ellipse: # 正六边形
size: self.height*0.25, self.height*0.25
pos: self.x+200, self.top-200
segments: 6

Ellipse: # 超过180°的顺时针扇形
size: self.width*0.25, self.height*.25
pos: self.x+400, self.top-200
angle_start: 120
angle_end: 420

Ellipse: # 正三角
size: self.height*.25, self.height*.25
pos: self.x+400, self.top-400
segments: 3

Ellipse: # 正半圆
size: self.height*0.25, self.height*.25
pos: self.x+200, self.top-400
angle_start: 0
angle_end: 180

Ellipse: # 椭圆
size: self.height*0.30, self.width*.15
pos: self.x+20, self.top-400
多边形

前面使用了画圆的方式使用参数segments来画多边形,这些多变形画出来它的边长是随着圆的尺寸决定的,长度是不能自己决定的。
Kivy还提供了一种方式,用于指定各边的顶点坐标,可以绘制特殊边的多边形,但是值支持四个坐标点,可用于菱形、平行四边形、梯形等。

一样,我们值更改Kv文件如下:

<RelativeWidget>
    canvas:
        Color:
            rgba: [1,1,1,1]

        Quad:
            points: 400,250, 640,280, 480,500, 380,520  # 设置顶点坐标。 按X轴,Y轴的顺序读取顶点坐标。

Quad中的points会按照x轴,y轴的的顺序,读取点的位置,并且在画布canvas上绘制一个多边形。

点和线

kv文件

<RelativeWidget>
    canvas:
        Color:
            rgba: [1,1,1,1]

        Line:
            points: 310,350, 310,280,  360,350, 510,350

        Point:
            points:300,200, 300,400
            pointsize: 5

其实就像数学里面理解的,所有的线是由无数个点挨着排列形成的,同理面是由线挨着排列形成的。我们也可以用点和线画前面的图形。
例如:

<RelativeWidget>
    canvas:
        Color:
            rgba: [1,1,1,1]

        Line:  # 线
            points: 110,150, 310,80

        Point: # 点
            points:100,200, 180,260
            pointsize: 5  # 点大小

        Line: # 椭圆
            ellipse: 210,320, 80,60, 120,420,180
            width: 2  # 线宽

        Line: # 圆
            circle: 350,350, 40,0, 360,180
            width: 1.5 

        Line: # 矩形
            rectangle: 410,310, 80, 70

        Line:
            points: 510,310, 540,390, 590,320
            close: True  # 是否闭合

KV中Line对应参数说明:

  • ellipse:210, 320 表示椭圆的位置;80, 60 代表椭圆的宽和长;120, 420,180分别对应: angel_start, angel_end, segments.
  • circle: 350,350 表示圆心的位置;40表示圆的半径;0,360,180分别对应: angel_start, angel_end, segments.
  • rectangle: 420, 310 表示矩形位置左下角的顶点; 80,70,代表宽高
  • points: 510, 310 代表第一个点的位置,以此类推。

Cancas 属性

前面我们已经初步了解了Canvas画布基本绘制功能。我们来深入了解下他有哪些属性。
在Kivy里,每个小部件和布局基本都有他的CanvasCanvas.beforecanvas.after,其实我们可以将canvas看作在坐标空间种,一个无限的绘图板,通过添加绘图指令来绘制想要的图形。Kivy的所有部件都是共享一个坐标空间的,且不限于窗口或者屏幕的大小。
我们前面学习的时候也已经试过给画布和小部件设置背景以及显示的颜色。而且我们还能添加不同Instructions指令来达到不同的页面效果。

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Rectangle, Color
from kivy.graphics.instructions import InstructionGroup


class RelativeWidget(RelativeLayout):
    def __init__(self, **kwargs):
        super(RelativeWidget, self).__init__(**kwargs)
        blue = InstructionGroup()
        blue.add(Color(1, 0, 0, .6))
        blue.add(Rectangle(pos=self.pos, size=(300,300)))
        self.canvas.add(blue)

        green = InstructionGroup()
        green.add(Color(0, 1, 0, 0.4))
        green.add(Rectangle(pos=(300, 300), size=(300, 300)))
        self.canvas.add(green)


class AttributeApp(App):
    def build(self):
        return RelativeWidget()


if __name__ == '__main__':
    AttributeApp().run()

通常情况,我们在使用完canvas画布后,还需使用clear()方法清除所有加载在画布种的Instructions指示类型。
我们可以把上面的代码改成with语法,效果是一致的。

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Rectangle, Color
from kivy.graphics.instructions import InstructionGroup


class RelativeWidgetWith(RelativeLayout):
    def __init__(self, **kwargs):
        super(RelativeWidgetWith, self).__init__(**kwargs)
        with self.canvas:
            Color(1, 0, 0, .6)
            Rectangle(pos=self.pos, size=(300, 300))

            Color(0, 1, 0, 0.4)
            Rectangle(pos=(300, 300), size=(300, 300))


class AttributeApp(App):
    def build(self):
        return RelativeWidgetWith()


if __name__ == '__main__':
    AttributeApp().run()

还有一些常用的属性:

Canvas属性 说明
add(Instructions c) 将Instructions类型的c添加到Canvas中
clear() 删除所有Instructions
get_group(str groupname) 返回特定组下所有Instructions
insert(int index, Instructions c) 指定位置插入c
indexof(Instructions c) 返回c下标
length() 返回canvas的长度
remove() 删除指定的c
remove_group(str groupname) 删除该组下所有的c

在Kivy每个小部件都有属性canvas,除了这个包括有canvas.beforecanvas.after属性,用法的话基本和canvas一致。只是在运行顺序上的优先级不同。
大概顺序是:

  • canvas.before > canvas > widget(canvas.befor,canvas,canvas.after) > canvas.after

旋转、平移和缩放空间坐标

在canvas 中是可以使用Rotate指令来控制旋转操作,他与ScatterLayout布局不同,它是对整个坐标空间,因此所有的子部件都是会受到影响。在使用整个参数时,需要指定三个参数;

  • axis: 设置用于旋转的轴,通常为z轴(0,0,1)
  • angle: 设置旋转度数
  • origin: 设置旋转参考点

来试试效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# 选择平移缩放


from kivy.uix.gridlayout import GridLayout
from kivy.app import App


class RotateGridLayoutWidget(GridLayout):
def __init__(self, **kwargs):
super(RotateGridLayoutWidget, self).__init__(**kwargs)


class RotateTranslateZoomApp(App):
def build(self):
return RotateGridLayoutWidget()


if __name__ == '__main__':
RotateTranslateZoomApp().run()

rotatetranslatezoom.kv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<MyImage@Image>
source:'rotatetranslatezoom.jpg'
pos: self.parent.pos
size_hit: .5, .4
canvas:
Color:
rgba: 1,0,0,.5
Line:
rectangle: self.x, self.y, self.width,self.height

<RotateGridLayoutWidget>
cols: 2
canvas:

Color:
rgba:(1,1,1,1)
Rectangle:
pos: self.pos
size: self.size

Button:
text: 'col:1 row:1'

FloatLayout:
canvas:
Rotate:
axis:(0,0,1)
angle: 60
origin: self.center
Color:
rgba:(1,1,1,1)
Rectangle:
pos: self.pos
size: self.size
MyImage:

Button:
text: 'col:1 row:2'

以上布局,会出现两个按钮和一张图片。其中按钮按列排列。但是由于我们在FloatLayout中使用了Rotate这样导致了画布进行了旋转。我们看到的效果是图像是沿着中点,逆时针旋转了60度的情况。并且由于旋转的是在Button前面加入的,所以导致后方的空间坐标系也收到了影响,也连带着旋转了。而且点击第二个按钮黑色部分发现无效。而是需要点击按钮本身的位置,也就是第一个按钮的垂直排列的列下方才有效。为了避免这个情况,我们需要引入两个新的命令。

  • PushMatrix : 保存上下文环境。
  • PopMatrix : 恢复上下文环境。
    就像上面的栗子:
    我们在对某个图像的画布或者其他进行操作的时候。就会导致代码排序在它之后的内容受到影响。这个时候我们就可以使用前面两个命令对环境进行保存和回复。
    我们来改下KV文件:
<MyImage@Image>
    source:'rotatetranslatezoom.jpg'
    pos: self.parent.pos
    size_hit: .5, .4
    canvas:
        PushMatrix
        Rotate:
            axis:(0,0,1)
            angle: 60
            origin: self.center
        Color:
            rgba: 1,0,0,.5
        Line:
            rectangle: self.x, self.y, self.width,self.height
        PopMatrix

<RotateGridLayoutWidget>
    cols: 2
    canvas:

        Color:
            rgba:(1,1,1,1)
        Rectangle:
            pos: self.pos
            size: self.size

    Button:
        text: 'col:1 row:1'

    FloatLayout:
        MyImage:


    Button:
        text: 'col:1 row:2'

以上文件运行后,会发现,红色矩形框旋转了60度。后面的按钮以及图像均不会受到影响,这是因为Rotate指令我们是放在canvas中的,是在屏幕上以及确定位置之后才开始执行的,并且在显示的时候,就使用前面的恢复环境命令恢复了。但是我们旋转的时候,就是为了让图像旋转。现在的情况却是图像并未旋转,这个时候,可以来试试前面我们学过的属性canvas.beforcanvas.after我们只需要在位置还没确定的时候,就旋转,这样图像就会在画布旋转之后进行图像的显示。这样就能达到指定效果了。我们在改动下KV文件:

<MyImage@Image>
    source:'rotatetranslatezoom.jpg'
    pos: self.parent.pos
    size_hit: .5, .4
    canvas.before:  # 使用canvas.before 命令
        PushMatrix  # 保存当前上下文环境
        Rotate:
            axis:(0,0,1)  # 旋转
            angle: 60     # 角度
            origin: self.center  # 起点
    canvas:
        Color:
            rgba: 1,0,0,.5
        Line:
            rectangle: self.x, self.y, self.width,self.height

    canvas.after:
        PopMatrix  # 恢复上下文环境

<RotateGridLayoutWidget>
    cols: 2
    canvas:

        Color:
            rgba:(1,1,1,1)
        Rectangle:
            pos: self.pos
            size: self.size

    Button:
        text: 'col:1 row:1'

    FloatLayout:
        MyImage:


    Button:
        text: 'col:1 row:2'

使用上面的kv文件,就会发现图像和矩形框都一起旋转了。这样就达到了,旋转的同时连小部件也旋转。当然会发现我们为什么不把PopMatrix写在canvas的末行,而是大费周章的写个canvas.after,这里我就不解释了结合前面的知识点,大家可以尝试下,将PopMatrix写在canvas末行,看看是什么效果。

试过了旋转,我们来看看平移:
canvas平移指令是Translate除了属性以外,该指令和我们前面用的Rotate指令基本是一致。
在使用Translate时,是需要指定X轴,Y轴,Z轴上的移动距离,如果不指定,则默认不移动。我们再拿前面的旋转代码给他加上平移属性看看。

Translate:
    x: -50
    y: 100
    z:0

就不贴完整代码了,大概就是在Rotate属性结束下方另起一行,加上以上四行代码。我们会发现,图片的位置会向左平移了50个像素,将按钮盖住了,并且也向上移动了100个像素。如果这里我们将z轴的值进行改变,会发现图像不见了,这是因为图像的位置已经不在画布上了。
同理缩放(Scale)也是和前面一致,我们只需要指定xyz轴上的缩放倍数即可:

Scale:
    xyz:(1.25,1.30,0.5)

如上,则是x轴和y轴分别缩放1.25和1.30.倍,z轴缩放0.5倍。

实战–画板

我们先来做一个初始画板,先不添加其他复杂元素,来实现能写字的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line, Color


class DrawCanvasWidget(Widget):
def __init__(self, **kwargs):
super(DrawCanvasWidget, self).__init__(**kwargs)
"""设置画笔的默认颜色为黑色"""
self.canvas.add(Color(rgb=[0, 0, 0]))
self.line_width = 2

def on_touch_down(self, touch):
"""触摸显示轨迹"""
if Widget.on_touch_down(self, touch):
return
with self.canvas:
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)

def on_touch_move(self, touch):
"""连线"""
if 'current_line' in touch.ud:
touch.ud['current_line'].points += (touch.x, touch.y)


class PaintApp(App):
def build(self):
self.draw_canvas_widget = DrawCanvasWidget()
return self.draw_canvas_widget


if __name__ == '__main__':
PaintApp().run()

Kivy提供了on_touch_downon_touch_move方法来实现监听屏幕点击和屏幕移动触发事件

1
2
3
4
5
6
7
<DrawCanvasWidget>:
canvas.before:
Color:
rgba:[1,1,1,1]
Rectangle:
pos: self.pos
size: self.size

以上能够实现使用一种黑色画笔在白色画板上进行写字。接下来,我们试试能否通过选择颜色去替换画笔颜色,然后进行绘画。
Kivy种设置颜色的方法一般是使用rgba(r红色,g绿色,b蓝色,alpha名度)一般我们就是使用颜色和255进行比值,得到百分比。我们也可以直接使用十六进制进行表示。当需要大量颜色的时候,Kivy在utils包内也提供了 get_color_from_hex()方法进行16进制和百分比转换,我们使用当时时候传入十六进制字符串即可。
我们可以选择在python内使用,还是在kv文件内使用,使用方法如下:
python文件内:

from kivy.utils import get_color_from_hex
get_color_from_hex('#98feab')

kv文件内使用:

#:import C kivy.utils.get_color_from_hex
Button:
    background_color: C('#98feab')

我们可以在画板类里面写上一个方法来改变canvas画布颜色的方法。不是canvas.before背景:

def chang_color(self, new_color):
    """调色"""
    self.canvas.add(Color(*new_color))

通过chang_color方法在init中初始化一个颜色。在kv文件设置按钮,通过按钮单击调用chang_color激活颜色的改变。
具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line, Color
from kivy.utils import get_color_from_hex


class DrawCanvasWidget(Widget):
def __init__(self, **kwargs):
super(DrawCanvasWidget, self).__init__(**kwargs)
"""设置画笔的默认颜色为黑色"""
self.change_color(get_color_from_hex('#12aced'))
self.line_width = 2

def on_touch_down(self, touch):
"""触摸显示轨迹"""
if Widget.on_touch_down(self, touch):
return
with self.canvas:
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)

def on_touch_move(self, touch):
"""连线"""
if 'current_line' in touch.ud:
touch.ud['current_line'].points += (touch.x, touch.y)

def change_color(self, new_color):
"""调色"""
self.canvas.add(Color(*new_color))


class PaintApp(App):
def build(self):
self.draw_canvas_widget = DrawCanvasWidget()
return self.draw_canvas_widget


if __name__ == '__main__':
PaintApp().run()

kv文件

#:import C kivy.utils.get_color_from_hex   # 引入颜色转换方法

<BottomColorButton@ToggleButton>:
    group: 'color'
    background_normal: ''
    background_down: ''
    border: (3, 3, 3, 3)
    on_release: app.draw_canvas_widget.change_color(self.background_color)


<DrawCanvasWidget>:
    canvas.before:
        Color:
            rgba: [1, 1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size

    BoxLayout:
        id: bottom_box
        orientation: 'horizontal'
        padding: 2
        spacing: 2
        size: root.width, 40

        BottomColorButton:
            background_color: C('#19caad')
            state: 'down'                     # 按钮状态

        BottomColorButton:
            background_color: C('#8cc7b5')

        BottomColorButton:
            background_color: C('#a0eee1')

        BottomColorButton:
            background_color: C('#bee7e9')

        BottomColorButton:
            background_color: C('#beedc7')

        BottomColorButton:
            background_color: C('#d6d5b7')

        BottomColorButton:
            background_color: C('#d1ba74')

        BottomColorButton:
            background_color: C('#e6ceac')

        BottomColorButton:
            background_color: C('#ecad9e')

        BottomColorButton:
            background_color: C('#f4606c')

        BottomColorButton:
            background_color: C('#3498db')

        BottomColorButton:
            background_color: C('#1abc9c')

        BottomColorButton:
            background_color: C('#2ecc71')

        BottomColorButton:
            background_color: C('#f1c40f')

        BottomColorButton:
            background_color: C('#e67e22')

        BottomColorButton:
            background_color: C('#e74c3c')

        BottomColorButton:
            background_color: C('#9b59bc')

        BottomColorButton:
            background_color: C('#ecf0f1')

        BottomColorButton:
            background_color: C('#95a5a6')

        BottomColorButton:
            background_color: C('#000000')

有了画笔颜色的改变,我们接下来看看改变画笔的粗细。
同理,使用与改变颜色相同方法做一个画笔粗细:

    def change_line_width(self, line_width='Normal'):
        self.line_width = {'Thin': 1, 'Normal': 2, 'Thick': 4}[line_width]
1
2
3
4
5
6
7
8
9
<LineWidthButton@ToggleButton>:
group: 'line_width'
color: C('#2c3e50')
background_color: C('#ecf0f1')
background_normal: ''
background_down: ''
border: (3, 3, 3, 3)
on_release: app.draw_canvas_widget.change_line_width(self.text)

画布内新增改变按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

BoxLayout:
orientation: 'horizontal'
padding: 2
spacing: 2
x: 0
top: root.top
size_hint: None,None
size: 280, 44

LineWidthButton:
text: 'Thin'

LineWidthButton:
text: 'Normal'
state: 'down'

LineWidthButton:
text: 'Thick'

新增清空画板功能;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line, Color
from kivy.utils import get_color_from_hex
# 添加边框样式
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.togglebutton import ToggleButton


# 因为已经在这边添加了ToggleButton的公共类,提取出公共属性的两个就不需要使用ToggleButton而是可以直接使用
# FrameToggleButton即可
class FrameToggleButton(ToggleButton):
"""当前按钮添加边框"""

def do_press(self):
"""点击改变状态"""
if self.state == 'normal':
ToggleButtonBehavior.do_press(self)


class DrawCanvasWidget(Widget):
def __init__(self, **kwargs):
super(DrawCanvasWidget, self).__init__(**kwargs)
"""设置画笔的默认颜色为黑色"""
self.change_color(get_color_from_hex('#12aced'))
self.change_line_width()

def on_touch_down(self, touch):
"""触摸显示轨迹"""
if Widget.on_touch_down(self, touch):
return
with self.canvas:
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)

def on_touch_move(self, touch):
"""连线"""
if 'current_line' in touch.ud:
touch.ud['current_line'].points += (touch.x, touch.y)

def change_color(self, new_color):
"""调色"""
self.last_color = new_color
self.canvas.add(Color(*new_color))

def change_line_width(self, line_width='Normal'):
self.line_width = {'Thin': 1, 'Normal': 2, 'Thick': 4}[line_width]

def clear_canvas(self):
saved = self.children[:]
self.clear_widgets()
self.canvas.clear()
for widget in saved:
self.add_widget(widget)
self.change_color(self.last_color)


class PaintApp(App):
def build(self):
self.draw_canvas_widget = DrawCanvasWidget()
return self.draw_canvas_widget


if __name__ == '__main__':
PaintApp().run()


#:import C kivy.utils.get_color_from_hex

<BottomColorButton@FrameToggleButton>:
    group: 'color'
    background_normal: ''
    background_down: ''
    border: (1, 1, 1, 1)
    on_release: app.draw_canvas_widget.change_color(self.background_color)

<LineWidthButton@FrameToggleButton>:
    group: 'line_width'
    color: C('#2c3e50')
    background_color: C('#ecf0f1')
    background_normal: ''
    background_down: ''
    border: (3, 3, 3, 3)
    on_release: app.draw_canvas_widget.change_line_width(self.text)


<DrawCanvasWidget>:
    canvas.before:
        Color:
            rgba: [1, 1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size

    BoxLayout:
        orientation: 'horizontal'
        padding: 2
        spacing: 2
        x: 0
        top: root.top
        size_hint: None,None
        size: 280, 44

        LineWidthButton:
            text: 'Thin'

        LineWidthButton:
            text: 'Normal'
            state: 'down'

        LineWidthButton:
            text: 'Thick'

        Button:
            text: 'Clear'
            on_release: root.clear_canvas()


    BoxLayout:
        id: bottom_box
        orientation: 'horizontal'
        padding: 2
        spacing: 2
        size: root.width, 40

        BottomColorButton:
            background_color: C('#19caad')
            state: 'down'

        BottomColorButton:
            background_color: C('#8cc7b5')

        BottomColorButton:
            background_color: C('#a0eee1')

        BottomColorButton:
            background_color: C('#bee7e9')

        BottomColorButton:
            background_color: C('#beedc7')

        BottomColorButton:
            background_color: C('#d6d5b7')

        BottomColorButton:
            background_color: C('#d1ba74')

        BottomColorButton:
            background_color: C('#e6ceac')

        BottomColorButton:
            background_color: C('#ecad9e')

        BottomColorButton:
            background_color: C('#f4606c')

        BottomColorButton:
            background_color: C('#3498db')

        BottomColorButton:
            background_color: C('#1abc9c')

        BottomColorButton:
            background_color: C('#2ecc71')

        BottomColorButton:
            background_color: C('#f1c40f')

        BottomColorButton:
            background_color: C('#e67e22')

        BottomColorButton:
            background_color: C('#e74c3c')

        BottomColorButton:
            background_color: C('#9b59bc')

        BottomColorButton:
            background_color: C('#ecf0f1')

        BottomColorButton:
            background_color: C('#95a5a6')

        BottomColorButton:
            background_color: C('#000000')

部分UX部件

前面我们已经对Layout布局,以及Canvas 画布进行了学习,我们接下来来看看,经常使用的一些部件,这些部件是图形界面设计的重要组成部分,合理使用可以更高效的开发程序。我们先从简单部件开始。

Button

button常用属性

属性 说明
background_color 按钮的背景颜色,rgba格式,默认为灰色
text 按钮显示的文本
font_size 文本字体的大小,默认为15sp
color 文本字体颜色,rgba格式,默认为白色[1,1,1,1]
state 按钮状态,默认为“normal”,可设置成“down”
disables 如果为True时则禁用按钮,默认为False
background_disabled_normal 默认为“kivy\tools\theming\defaulttheme\button_disabled_pressed.png”
background_disabled_down 默认为“kivy\tools\theming\defaulttheme\button_disabled.png”
background_down 按下按钮时显示的图形,默认为“kivy\tools\theming\defaulttheme\button_pressed.png”
background_normal 未按下按钮时显示的图像,默认为“kivy\tools\theming\defaulttheme\button.png”
border 与background_normal 和background_down 属性一起使用,可用于自定义背景。
触发事件
  • on_press:按下按钮触发该事件
  • on_release: 按下按钮并释放时,触发该事件

Label标签

Kivy中的Label小部件用于显示文本,目前只支持ASCII和Unicode编码字符(暂不支持中文),可以通过属性设置文本内容,字体,大小,颜色,对齐方式,换行以及标记文字等内容。

常用属性
属性 说明
text 标签显示文本大小,默认为空字符串
text_size 标签文本大小、默认为(None,None),表示无限制
font_name 要使用字体的文件名,可以是绝对路径或者resource_find解析的相对路径
font_size 文本字体的大小,以像素为单位,默认为15sp
bold 字体使用粗体,默认为False
italic 字体使用斜体,默认为False
color 字体颜色,格式为rgba,默认为白色[1,1,1,1]
halign 文本的水平对齐方式,默认为‘auto’,可选参数为: buttom,middle 或者 Center 和 top
markup 是否分割所有标记文本,默认为False
refs 使用[ref=xxx]xxx[/ref]标记部分文本,使用时需要将markup属性设置为True
underline 文本上添加下划线,默认为False
padding_x 小部件框内文本的水平填充,默认为0
padding_y 小部件框内文本的垂直填充,默认为0
padding 以(padding_x,padding_y)格式填充文字
texture 文本的纹理对象,属性更改时会自动呈现文本,默认为None
texture_size 文本纹理大小,由字体大小和文本确定
unicode_error 如何处理Unicode解码错误,可选参数为:“strict”, “replace”(默认)和“ignore”
strikethrough 在文本中添加删除线
strip 与python 内置函数一致,是否删除空格以及换行符,默认为False
outline_color 文本轮廓颜色,格式为rgba, 默认为[0,0,0]
outline_width 文本周围轮廓的宽度,单位为像素,默认为None表示不会渲染轮廓
max_lines 要使用的最大行数,默认为0,表示无限制
shorten 是否应该尽可能缩短文本内容,默认为False
shorten_from 在哪一侧缩短文本,默认为center,可选 left,right 和 center
split_str 当shorten 为True 时,差分字符串,默认为空字符串
is_shortened 是否以缩短事件的方式进行渲染,默认为False
line_height 文本的行高,默认为1.0
base_direction 文本基本方向,当halign为auto 时,会影响水平对齐,可设置参数为:None、ltr (从左到右)、rtl、weak_lte、weak_rtl
disabled_color 禁用小部件时文本轮廓的颜色,格式为rgb,默认为[0,0,0]
ellipsis_options 使用’···’缩短文本,使用时要设置markuo和shorten为True,默认为空字典{}
font_blended 使用混合字体,默认为True
font_context 字体上下文,默认为None表示该字体单独使用
font_family 字体系列,仅在使用font_context时使用,
font_features 将CSS格式的OpenType字体传给Pango ,默认为空字符。
font_hinting 渲染字体的提示选项,可选参数为’normal‘,’light’,’mono’和None
font_kerning 是否为字形渲染,启用字距调整,默认为True
mipmap 是否将OpenGL mipmapping应用于纹理,默认为False

开启以下需要将markup打开

可用标记 解释 可用标记 解释
[b][/b] 加粗 [i][/i] 斜体
[u][/u] 下划线 [s][/s] 删除线
[sub][/sub] 下标 [sup][/sup] 上标
[font=][/font] 更改字体 [size=][/size]] 大小
[color=#][/color] 颜色 [anchor=] 锚点,(x,y)
[ref=][/ref] 添加一个交互式引用区,引用一个方法 [text_language=<>][/text_language] 文本语言,例: “<zh_CN>”
&bl [ &br ]
&amp &
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#:import C kivy.utils.get_color_from_hex

<LabelBoxLayout>:
orientation: 'vertical'
Button:
text: 'Button'
on_press: root.on_press_btn()
on_release: root.on_release_btn()

Label:
id: lable_btn
text: 'The first step is one of awareness. It will be hard to make a change to positive thinking without being acutely intimate with the thoughts that run through your mind. Recently, I was amazed to discover deep buried emotions from negative thoughts that I had for fewer than 10 minutes. Without awareness, I would have carried the hurt and anger inside. Awareness helped me to bring them out to the open for me to deal with.'
font_size: '20sp'
color: C('#B0C4DE')
italic: True
text_size: cm(15), mm(200)
halign: 'right'
valign: 'middle'
strikethrough: True
Label:
canvas.before:
Color:
rgba: C('#DCDCDC')
Rectangle:
pos: self.pos
size: self.size
text: 'There are moments in life when [i]you miss someone [color=#"4169E1"]so much that you just want[/color] to pick them from your[/i] dreams and hug them for real! Dream what you want to dream;go where you want to go;be what you want to be,because you have only one life and one chance to do all the things you want to do.'
color: C('#FF0000')
font_size: '15sp'
bold: True
text_size: cm(10), mm(100)
halign: 'center'
valign: 'middle'
underline: True
line_height: 1.0
markup: True

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout


class LabelBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super(LabelBoxLayout, self).__init__(**kwargs)

def on_press_btn(self):
self.children[1].strikethrough = not self.children[1].strikethrough

def on_release_btn(self):
self.children[1].strikethrough = not self.children[1].strikethrough


class LabelBoxApp(App):
def build(self):
return LabelBoxLayout()


if __name__ == '__main__':
LabelBoxApp().run()
触发事件

Kivy中是使用ref 来标记触发事件的,点击ref包裹的文本,就可以触发“on_ref_press”事件。

设置Label 标签的触发事件

在使用ref之前,需要将markup设置为True

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.utils import get_color_from_hex


class LabelBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super(LabelBoxLayout, self).__init__(**kwargs)
        strtext = 'Awareness [color=#00FF00][ref=label][i]helped me[/i][/ref][/color] to [b]bring[/b] [s]them out[/s] to the open for me to deal with.'
        label_ref = Label(font_size='25sp',text=strtext, markup=True,
                          color=get_color_from_hex('#F0F8FF'))
        label_ref.bind(on_ref_press=self.on_press_btn_label_ref)
        self.add_widget(label_ref)

    def on_press_btn(self):
        self.children[1].strikethrough = not self.children[1].strikethrough

    def on_release_btn(self):
        self.children[1].strikethrough = not self.children[1].strikethrough

    def on_press_btn_label_ref(self, *args):
        self.children[0].strikethrough = not self.children[0].strikethrough


class LabelBoxApp(App):
    def build(self):
        return LabelBoxLayout()


if __name__ == '__main__':
    LabelBoxApp().run()
#:import C kivy.utils.get_color_from_hex

<LabelBoxLayout>:
    orientation: 'vertical'
    Button:
        text: 'Button'
        on_press: root.on_press_btn()
        on_release: root.on_release_btn()

    Label:
        id: lable_btn
        text: 'The [ref=lable]first step[/ref] is one of awareness. It will be hard to make a change to positive thinking without being acutely intimate with the thoughts that run through your mind. Recently, I was amazed to discover deep buried emotions from negative thoughts that I had for fewer than 10 minutes. Without awareness, I would have carried the hurt and anger inside. Awareness helped me to bring them out to the open for me to deal with.'
        font_size: '20sp'
        color: C('#B0C4DE')
        italic: True
        text_size: cm(15), mm(200)
        halign: 'right'
        valign: 'middle'
        on_ref_press:
            print('Click on_ref_press')
            root.on_release_btn()
        strikethrough: True
        markup: True
    Label:
        canvas.before:
            Color:
                rgba: C('#DCDCDC')
            Rectangle:
                pos: self.pos
                size: self.size
        text: 'There are moments in life when [i]you miss someone [color=#"4169E1"]so much that you just want[/color] to pick them from your[/i] dreams and hug them for real! Dream what you want to dream;go where you want to go;be what you want to be,because you have only one life and one chance to do all the things you want to do.'
        color: C('#FF0000')
        font_size: '15sp'
        bold: True
        text_size: cm(10), mm(100)
        halign: 'center'
        valign: 'middle'
        underline: True
        line_height: 1.0
        markup: True

单位介绍

px、dp和sp,这三个单位的区别在于,它们的定义各不相同:

  • px:指像素,是指基本原色素及其灰度的基本编码,是Pixel的缩写。像素是指基本原色素及其灰度的基本编码,由 Picture(图像)和 Element(元素)这两个单词的字母所组成的。当图片尺寸以像素为单位时,需要指定其固定的分辨率,才能将图片尺寸与现实中的实际尺寸相转换。
    例如:大多数网页制作常用图片分辨率为72,即每英寸像素为72,1英寸等于2.54厘米。
  • dp:安卓开发时的长度单位,Density-independent pixel,是安卓开发用的长度单位,1dp表示在屏幕像素点密度为160ppi时1px长度。。
  • sp:与缩放无关的抽象像素,是字体单位.scale-independent pixel,安卓开发用的字体大小单位。一般情况下可认为sp=dp。

Image图片

使用Image控件来显示图片,可以通过size和pos来设置属性和设置大小和位置,通过source属性来指定图片的相对路径,此外,如果图片大小过大的时候,为了防止卡顿,可以使用异步的方式加载,这我们前面在学习的时候,咱们有试过,使用异步加载图片。

我们先来看看Image有哪些属性。

属性
属性 属性
source 图片的文件名和路径
texture 图像的纹理对象,默认为None
color 图像颜色,格式为rgba,默认为[1,1,1,1]
texture_size 图像的纹理大小
allow_stretch 是否放打图像到边框,默认为False
anim_delay 动画延迟,默认为0.25秒 (4fps),若设置为-1,则停止
anim_loop 循环播放的次数,默认为0
image_ratio 图片比例
keep_data 是否存储原始图像数据,经常用在基于像素的碰撞检测,默认为False
keep_ratio 是否以忽略图像纵横比的 方式放大图像以适合图像框,默认为True
mipmap 是否要将OpenGL mipmapping 应用于纹理,默认为False
nocache 是否不将图像添加到内部缓存种,默认为False 表示添加到缓存中
norm_image_size 以保留比例的方式标准化图像大小,只读属性,不可更改
reload() 重新加载图像
# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class ImageBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super(ImageBoxLayout, self).__init__(**kwargs)


class ImageBoxApp(App):
    def build(self):
        return ImageBoxLayout()


if __name__ == '__main__':
    ImageBoxApp().run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<ImageBoxLayout>:
canvas:
Color:
rgba:[1,1,1,1]
Rectangle:
size:self.width+20, self.height+20
pos: self.x-10,self.y-10
source:'UxParts.jpg'

AsyncImage:
source:'https://blogcdn.sea-whales.cn/blog/typecho/17.jpg'
size_hint_y: None
width: 300
allow_stretch: True # 自适应

Image:
source:'https://blogcdn.sea-whales.cn/blog/typecho/17.jpg'
size_hint_y: None
width: 300
allow_stretch: True

TextInput 输入框

先来看看有哪些属性,在Kivy中,为了方便输入值的样式,Kivy 提供了大量的属性。接下来我们来看看有哪些:

属性 说明
text 文本内容
text_language 文本语言,例如: zh_cn,en_us,fr 和 ja
halign 文本的水平对齐方式,可以设置为auto、left、center和right等属性
multiline 是否显示多行文本,默认属性为True
allow_copy 是否允许赋值文本,默认为True
auto_indent 自动缩进,多行文字,默认属性为False
font_size 文本的字体大小,以像素为单位。默认为15sp
foreground_color 前景色,格式是rgba 默认为[0,0,0,1]
minimum_height 文本框内容的最小高度
password 是否用password_mask 替换显示密码,默认为False
password_mask 当password为True时,默认以“*”替换显示密码
readonly 是否将文本框属性设为只读,默认为Fasle
background_color 背景颜色,格式为rgba,默认为白色[1,1,1,1]
padding_x 文本水平填充,格式为[padding_left, padding_right] 或 [padding_horizontal],默认为[0,0]
padding_y 文本垂直填充,格式为 [padding_top, padding_bottom] 或 [padding_vertical],默认为[0,0]
padding 文本填充,格式为[padding_left, padding_top, padding_right, padding_bottom]或者[padding_horizontal,padding_vertical],默认属性为[6,6,6,6]
hint_text 提示文本,默认为空字符串
hint_text_color 提示文本的当前颜色,格式是rgba,默认为[.5,.5,.5,1]
input_filter 过滤输入,默认为None
paste() 插入剪贴板内容到当前光标位置
write_tab 使用Tap键将移到下一个小部件,否则进入文本框,默认为True
background_active 默认属性为textinput_active.png
background_disabled_normal 禁用Textinput时的背景图像,默认为textinput_disabled.png
handle_image_left 待处理的文本框左侧显示的图像
background_normal 失去焦点时显示的图像
handle_image_middle 待处理文本框中间显示图像
handle_image_right 待处理文本框右侧显示图像
replace_crlf 用LF自动替换CRLF 换行
select_all() 选中文本框显示的所有文本
selection_color 所选内容的当前颜色,格式为rgba
selection_from 所选开始光标索引,为只读 默认为None
selection_to 所选结束光标,默认为None 为只读
selection_text 当前选择内容,默认为空字符串,为只读
suggestion_text 当前行的末尾显示建议文本
tab_widtg 将TAB键替换指定数量的空格,默认为4个空格。
seletc_text(start,end) 选中文本框显示部分的内容。
base_direction 文本的基本方向,默认为None ,可设置为ltr,rtl,weak_ltr,weak_rtl
border 边框,默认属性为(4,4,4,4)
cancel_selection() 取消当前的选择。
use_bubble 是否使用剪切,复制、黏贴气泡,在移动设备默认为True,其他默认为False
use_handles 是否显示选择指示,在移动设备默认为True,其他默认为false
copy(date=’’) 将提供的值复制到剪贴板中
cursor 当前光标位置
cursor_blink 光标是否闪烁,默认为True
cursor_col 光标的当前列 只读
cursor_row 光标当前行 只读
cursor_color 光标的当前颜色,rgba格式默认为[1,0,0,1]
cursor_index(cursor=None) 返回文本/值中的光标索引
cursor_offset() 获取当前行 光标偏移量
cursor_width 光标当前宽度,默认为”1 sp“
cursor_pos 光标当前位置,以(x,y)为单位只读
cut() 将当前选择剪切到剪贴板
keyboard_suggestions 在键盘上方是否提供建议输入,默认为True,设置input_type属性后才会生效
line_height 计算一行文本最小高度,为只读属性
line_spacing 线条之间的空间。默认为0
on_double_tap() 双击文本框触发该事件
on_triple_tap() 三击文本框触发该事件
on_quad_touch() 在文本框内部四指触摸触发事件
do_redo() 重新执行