部署jenkins

在RedHat 9.4上部署Jenkins,你需要遵循一系列步骤来确保Jenkins能够成功安装并运行。以下是一个详细的步骤指南:

一、准备工作

  1. 检查系统要求

    • 确保RedHat 9.4系统满足Jenkins的最低系统要求,包括JDK版本(通常建议Java 8及以上版本,但具体取决于Jenkins的版本)。
    • 确认系统已安装wget、curl等必要的工具,以便下载和安装软件。
  2. 安装Java(如果尚未安装):

    • Jenkins需要Java环境来运行。你可以通过RedHat的包管理器安装Java。例如,使用yum安装OpenJDK:
      1
      sudo yum install java-11-openjdk
    • 验证Java安装:
      1
      java -version

二、安装Jenkins

  1. 添加Jenkins仓库

    • RedHat 9.4可能不是最新的RedHat版本,但你可以尝试使用Jenkins提供的官方仓库。如果官方仓库不支持RedHat 9.4,你可能需要下载Jenkins的RPM包或使用其他方式安装。
    • 如果Jenkins官方仓库支持,你可以通过以下命令添加Jenkins仓库:
      1
      2
      sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
      sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
    • 注意:这些命令可能因Jenkins仓库的更新而有所变化,请访问Jenkins官网获取最新信息。
  2. 通过YUM安装Jenkins

    • 如果Jenkins仓库已添加,你可以使用yum命令安装Jenkins:
      1
      sudo yum install jenkins
    • 安装完成后,Jenkins服务将自动启动。如果没有自动启动,你可以使用以下命令启动它:
      1
      sudo systemctl start jenkins
  3. 配置Jenkins端口(可选)

    • 如果需要更改Jenkins的默认端口(8080),你可以编辑Jenkins的配置文件。但请注意,RedHat 9.4可能使用较旧的systemd版本,配置方式可能与较新系统略有不同。
    • 通常,Jenkins的配置文件位于/etc/sysconfig/jenkins/etc/default/jenkins,但具体取决于你的系统配置。

三、访问Jenkins

  1. 开放防火墙端口(如果防火墙已启用):

    • 如果你更改了Jenkins的端口,确保防火墙允许访问该端口。
    • 使用firewall-cmd(如果你的系统使用firewalld)或相应的防火墙管理工具来开放端口。
  2. 访问Jenkins Web界面

    • 在浏览器中访问http://<your-server-ip>:8080(如果更改了端口,请相应更改URL)。
    • 首次访问时,Jenkins会要求你输入管理员密码。你可以在/var/lib/jenkins/secrets/initialAdminPassword文件中找到这个密码。
  3. 安装插件和配置Jenkins

    • 按照屏幕上的指示安装推荐的插件。
    • 创建一个管理员用户并配置Jenkins。

四、后续步骤

  • 安装和配置必要的插件:根据你的需求安装Maven Integration、Git Parameter、NodeJS等插件。
  • 配置全局工具:在Jenkins的系统管理中配置JDK、Maven、Git等全局工具的路径。
  • 创建和配置项目:根据你的项目需求创建Jenkins项目,并配置源码管理、构建触发器、构建环境等。

请注意,由于RedHat 9.4可能是一个较旧的版本,某些步骤或命令可能与最新版本的RedHat或Jenkins有所不同。如果遇到问题,请查阅Jenkins和RedHat的官方文档或社区论坛以获取帮助。

linux上部署kubernetes

在Linux上部署Kubernetes可以通过多种方式实现,包括使用kubeadm、Kubespray、Minikube、Rancher等。这里,我将详细介绍如何使用kubeadm在裸机上部署一个基本的Kubernetes集群。Kubeadm是Kubernetes官方提供的用于快速安装和初始化集群的工具。

步骤 1: 准备环境

1.1 系统要求

  • 推荐使用Ubuntu 18.04 LTS 或 CentOS 7 以上的Linux发行版。
  • 确保所有节点已安装Docker(1.13.1+)和kubelet、kubeadm、kubectl(版本兼容)。
  • 禁用swap(Kubernetes 1.8+ 不支持具有启用swap的节点)。
  • 确保节点之间的时间同步。

1.2 安装Docker

1
2
3
4
5
6
7
8
9
10
11
# Ubuntu 示例
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install -y docker-ce

# 启动Docker服务并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

1.3 安装kubeadm, kubelet, kubectl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设置Kubernetes仓库
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

# 安装kubeadm, kubelet, kubectl
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl # 阻止自动升级

# 启动kubelet
sudo systemctl enable --now kubelet

步骤 2: 初始化主节点

在选定的主节点上运行以下命令:

1
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=<master-node-ip>

注意替换<master-node-ip>为你的主节点的IP地址。此命令将初始化集群,并生成一个kubeadm join命令,用于将工作节点加入到集群。

步骤 3: 配置kubectl

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

步骤 4: 安装Pod网络插件

Kubernetes需要网络插件来确保Pod之间可以相互通信。

1
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

步骤 5: 添加工作节点

在每个工作节点上,使用从主节点获得的kubeadm join命令加入集群。

步骤 6: 验证集群状态

在主节点上,运行以下命令以验证集群是否运行正常:

1
kubectl get nodes

后续步骤

  • 根据需要安装Helm、Metallb、Ingress Controller等Kubernetes工具。
  • 部署你的应用程序到Kubernetes集群。

这些步骤概述了使用kubeadm在Linux上部署Kubernetes集群的基本过程。每个步骤都可能需要根据你的具体环境和需求进行调整。

关键路径法简介

关键路径法简介

一个大型工程或项目包括很多活动,关键路径是项目中时间最长的活动顺序,决定着可能的项目最短工期。

关键路径法(Critical path method,CPM) 是一种基于进度网络模型的方法,用网络图表示各项活动之间的相互关系,获得在一定工期、成本、资源约束条件下的最优进度安排。

关键路径法源于美国杜邦公司对于项目管理控制成本、减少工期的研究。1959年,Kelly 和 Walker 在论文 Critical Path Planning and Scheduling 中提出了关键路径法的基本原理和方法:计算所有活动的工期,确定其最早开始 ES 和最早结束 EF、最晚开始 LS 和最晚结束 LF 的时间,按照活动的相互关系形成顺序的网络逻辑 图,找到必须的最长路径即为关键路径。

关键路径法将项目分解成为多个独立的活动并确定每个活动的工期,然后用逻辑关系(结束-开始、结束-结束、开始-开始和开始-结束)将活动连接。首先使用正推法(Forward pass),从起点开始向后计算,依次计算每个顶点(事件)的最早开始时间 ES;然后再使用逆推法(Backward pass),从终点开始向前计算,依次计算每个顶点(事件)的最迟结束时间 LF。进而可以求出每条边(工序)的最早结束时间 EF 和最迟开始时间 LS。最早开始时间 ES 和最晚开始时间 LS 相等的边,就是关键路径上的边,对应的工序是关键工序。

  • ES:最早开始时间(Earliest Start),指某项活动能够开始的最早时间,取决于该项活动的所有紧前工作的结束时间,由顺推法计算 ES = max{EF(preceding activities)}。
  • EF:最早结束时间(Earliest Finish),指某项活动能够完成的最早时间。EF = ES+DU, DU为该活动的持续时间。
  • LF:最迟结束时间(Latest Finish),指为了保证整个项目按期完成的某项活动必须完成的最晚时间,取决于该项活动的所有紧后工作的最迟开始时间,由逆推法计算 LF = min{LS(successor activities)}。
  • LS:最迟开始时间(Latest Start),指为了保证整个项目按期完成的某项活动必须开始的最迟时间。LS = LF -DU,DU为该活动的持续时间。
  • TF:总时差(Total float time),指在不影响总工期的条件下,一个活动可以利用的机动时间。TF = LF - EF。
  • FF:自由时差(Free float time),指在不影响紧后工作最早开始时间的条件下,一个活动可能被延迟的时间。FF = min{ES(successor activities)} - EF。
    由关键路径法得到的最早/最晚的开始/结束时间并不一定就是项目进度计划,而是把既定的参数(活动持续时间、逻辑关系、提前量、滞后量和其它制约条件)输入进度模型后所得到的结果,表明活动可以在该时段内实施。

早期关键路径法的表示方法都是箭线法(ADM),随着计算机的发展,前导图(PDM)逐渐成为主流方法。

关键路径法

某项目工程由 11项作业组成(分别用 A、B、…K表示),其计划完成时间及作业间相互关系如下表所示。建立计划网络图,并计算完成该项目的最短时间。

作业 计划完成天数 紧前工序 作业 计划完成天数 紧前工序
A 5 G 21 B,E
B 10 H 35 B,E
C 11 I 25 B,E
D 4 B J 15 F,G,I
E 4 A K 20 F,G
F 15 C,D . . .
本案例问题来自:司守奎、孙兆亮,数学建模算法与应用(第2版),P62-68,例4.16-4.18,国防工业出版社。

问题分析: 用如下图所示的计划网络图表示问题描述的各项作业及作业间的相互关系。图中的顶点表示作业开始或结束的事件,顶点之间的边(箭线)表示一项作业,边的权值表示该项作业的完成时间。虚线边表示虚拟作业。 该计划网络图的关键路径长度,即为完成该项目的最短时间。

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
# mathmodel23_v1.py
# Demo23 of mathematical modeling algorithm
# Demo of critical path method (CPM) with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-25

import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx # 导入 NetworkX 工具包

# 1. 拓扑序列(topological sequence) 和 关键路径(critical path)
# Activity on edge network(AOE), 顶点表示事件或状态,有向边表示活动
DG = nx.DiGraph() # 创建:空的 有向图
DG.add_weighted_edges_from([(1, 2, 5), (1, 3, 10), (1, 4, 11),
(2, 5, 4),
(3, 4, 4), (3, 5, 0),
(4, 6, 15),
(5, 6, 21), (5, 7, 25), (5, 8, 35),
(6, 7, 0), (6, 8, 20),
(7, 8, 15)]) # 向图中添加多条赋权边: (n1,n2,weight)
lenNodes = len(DG.nodes) # 顶点数量
topoSeq = list(nx.topological_sort(DG)) # 拓扑序列
nodeCP = list(nx.dag_longest_path(DG)) # 关键路径(节点)
lenCP = nx.dag_longest_path_length(DG) # 关键路径的长度
edgesCP=[]
for k in range(1,len(nodeCP)):
edgesCP.append((nodeCP[k-1],nodeCP[k]))

print("拓扑序列:{}".format(topoSeq)) # [1, 3, 4, 2, 5, 6, 7, 8]
print("关键路径的顶点:{}".format(nodeCP)) # [1, 3, 5, 6, 8]
print("关键路径的边:{}".format(edgesCP)) # [(1, 3), (3, 5), (5, 6), (6, 8)]
print("关键路径长度:{}".format(lenCP)) # 51

fig, ax = plt.subplots(figsize=(8,6))
pos = {1:(0,4), 2:(5,7), 3:(5,4), 4:(5,1), 5:(10,7), 6:(10,1), 7:(15,4), 8:(20,4)} # 指定顶点位置
edgesDG = DG.edges
edgesDashed = [(3,5),(6,7)]
edgesSolid = list(set(edgesDG)-set(edgesDashed))
labels = nx.get_edge_attributes(DG, 'weight')
# nx.draw(DG, pos, with_labels=True, node_color='skyblue') # 绘制有向图
nx.draw_networkx_nodes(DG, pos, node_color='orange',node_size=400) # 设置指定顶点的颜色、宽度
nx.draw_networkx_labels(DG, pos) # 设置指定顶点的标签
nx.draw_networkx_edges(DG, pos, edgelist=edgesSolid, edge_color='dimgrey', style='solid') # 设置指定边的颜色、线型
nx.draw_networkx_edges(DG, pos, edgelist=edgesDashed, edge_color='grey', style='dashed') # 设置指定边,虚线
nx.draw_networkx_edge_labels(DG, pos, edge_labels=labels, font_color='dimgrey') # 显示边的权值
ax.set_title("Project network graph by youcans@xupt")
ax.text(16, 0, "youcans-xupt", color='gainsboro')
plt.xlim(-2, 22)
plt.ylim(-1, 9)
plt.axis('on')
plt.show() # YouCans, XUPT

程序运行结果:

拓扑序列:[1, 3, 4, 2, 5, 6, 7, 8]
关键路径的顶点:[1, 3, 5, 6, 8]
关键路径的边:[(1, 3), (3, 5), (5, 6), (6, 8)]
关键路径长度:51

原文链接:(https://blog.csdn.net/youcans/article/details/118754623)

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() 重新执行

Linux多线程下载

Linux多线程下载

有时候我们在下载一个较大的东西上传服务器的时候,有一种操作是先使用本地电脑下载,然后上传到服务器,或者直接使用服务器上的wget进行安装,但是使用本地下载然后上传的话,如果受到本地网络或者服务器的限制,要么可能下载速度慢或者上传速度慢,而且可能会出现等待时间过长的情况,那如果我们使用linux服务器上比较常见见的下载工具curl和wget,但是两者都不支持多线程, 断点续传的作用不见得能发挥到最大。给大家介绍两款linux中多线程下载工具axelaria2c,开启多线程疯狂下载。

我们先来介绍下axel

axel

我们这边以Centos7系统为例,其他系统类似,只要使用对应的安装命令安装正确的包,就可以使用了。

CentOS 7安装Axel:

目前yum官方源上没有Axel,我们无法通过yum直接进行安装,但是我们可以通过安装epel第三方源然后后yum安装axel下载工具。

1
yum install axel

大家也可以选择使用以下网址将rpm包下载后将包包上传,然后进行手动安装。

1
https://pkgs.org/download/axel

然后使用

1
2
3
rpm -ivh 包名字(本地包位置以及名字)
或者
rpm -Uvh 包连接(远程rpm包,命令会自动下载后安装)

CentOS 6安装Axel:

32位CentOS 6执行下面命令:

1
2
wget -c http://mirrors.sohu.com/dag/redhat/el6/en/i386/rpmforge/RPMS/axel-2.4-1.el6.rf.i686.rpm
rpm -ivh axel-2.4-1.el6.rf.i686.rpm

64位CentOS 6执行下面命令:

1
2
wget -c http://mirrors.sohu.com/dag/redhat/el6/en/x86_64/rpmforge/RPMS/axel-2.4-1.el6.rf.x86_64.rpm
rpm -ivh axel-2.4-1.el6.rf.x86_64.rpm

Debian/Ubuntu安装Axel:

1
apt-get install axel

Fedora安装Axel

1
dnf install axel

使用

简略使用.”-n”线程数、”-o”下载保存目录、”-a”简略下载条。我们以下载一个GITHUB上的Odoo为例,这个包大概149MB,我正常下载速度在30k左右。

image-20210915133508036

用axel开10个下载:

image-20210915142502988

image-20210915142728672

本想使用相同的下载,但是由于我系统安装的axel过低,下载Https的时候一直出现以下情况:

image-20210915142803607

我看了下资料好像是我安装的axel版本较低,我尝试安装更高版本的,但是由于都是第三方包,且包对不上版本,所以我就拿另一个作为比对,我们可以看到,单个线程下载的速度可以到达3万Kb左右。而且如果下载过程中下载中断可以再执行下载命令即可恢复上次的下载进度。

参数

1
2
3
4
5
6
7
8
9
10
11
12
--max-speed=x , -s x 最高速度x 
--num-connections=x , -n x 连接数x
--output=f , -o f 下载为本地文件f
--search[=x] , -S [x] 搜索镜像
--header=x , -H x 添加头文件字符串x(指定 HTTP header)
--user-agent=x , -U x 设置用户代理(指定 HTTP user agent)
--no-proxy , -N 不使用代理服务器
--quiet , -q 静默模式
--verbose ,-v 更多状态信息
--alternate , -a Alternate progress indicator
--help ,-h 帮助
--version ,-V 版本信息

aria2c

aria2就更强大了,他提供了Windows、Mac和Linux三个版本的,在Windows上实现多线程下载。使用命令./aria2c –v,查看Enabled Feature。如下图所示,支持BT,GZip, HTTPS, Medssage Digest下载。aria2是一个轻量级的多协议多源命令行下载实用程序。可以通过内置的JSON-RPC和XML-RPC接口操作aria2。重点圈出来HTTPS,是因为我想下载的一些文件是基于HTTPS。

image-20210915143730187

下载

https://github.com/aria2/aria2/releases
根据平台选择相应的版本就可以了

下载相关配置

进入 mkdir ~/.aria2

最大同时下载任务数, 运行时可修改, 默认:5
max-concurrent-downloads=5

同一服务器连接数, 添加时可指定, 默认:1
max-connection-per-server=15

最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
min-split-size=10M

单个任务最大线程数, 添加时可指定, 默认:5
split=5

整体下载速度限制, 运行时可修改, 默认:0
#max-overall-download-limit=0

单个任务下载速度限制, 默认:0
#max-download-limit=0

整体上传速度限制, 运行时可修改, 默认:0
max-overall-upload-limit=20kb

单个任务上传速度限制, 默认:0
max-upload-limit=5kb

禁用IPv6, 默认:false
disable-ipv6=true

禁用https证书检查
check-certificate=false
#运行覆盖已存在文件
allow-overwrite=true
#自动重命名
auto-file-renaming

进度保存相关
从会话文件中读取下载任务
input-file=/etc/aria2/aria2.session

在Aria2退出时保存错误/未完成的下载任务到会话文件
save-session=/etc/aria2/aria2.session

定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
save-session-interval=30

RPC相关设置
启用RPC, 默认:false
enable-rpc=true

允许所有来源, 默认:false
rpc-allow-origin-all=true

允许非外部访问, 默认:false
rpc-listen-all=true

事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
#event-poll=select

RPC监听端口, 端口被占用时可以修改, 默认:6800
rpc-listen-port=6800

保存上传的种子文件
rpc-save-upload-metadata=false

BT/PT下载相关
当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
#follow-torrent=true

BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
listen-port=51413

单个种子最大连接数, 默认:55
#bt-max-peers=55

打开DHT功能, PT需要禁用, 默认:true
enable-dht=true

打开IPv6 DHT功能, PT需要禁用
enable-dht6=false

DHT网络监听端口, 默认:6881-6999
#dht-listen-port=6881-6999

本地节点查找, PT需要禁用, 默认:false
bt-enable-lpd=true

种子交换, PT需要禁用, 默认:true
enable-peer-exchange=true

每个种子限速, 对少种的PT很有用, 默认:50K
#bt-request-peer-speed-limit=50K

客户端伪装, PT需要
peer-id-prefix=-UT341-
user-agent=uTorrent/341(109279400)(30888)

当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
seed-ratio=1.0

强制保存会话, 话即使任务已经完成, 默认:false
较新的版本开启后会在任务完成后依然保留.aria2文件
#force-save=false

BT校验相关, 默认:true
#bt-hash-check-seed=true

继续之前的BT任务时, 无需再次校验, 默认:false
bt-seed-unverified=true

保存磁力链接元数据为种子文件(.torrent文件), 默认:false
#bt-save-metadata=false
#仅下载种子文件
bt-metadata-only=true
#通过网上的种子文件下载,种子保存在内存
follow-torrent=mem

使用方法

一般下载

使用aria2 下载文件,只需在命令后附加地址即可如:

aria2c http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.6.tar.bz2

分段下载

利用 aria2的分段下载功能可以加快文件的下载速度,对于下载大文件时特别有用。为了使用aria2的分段下载功能,你需要在命令中指定s选项

aria2c -s 2 http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.6.tar.bz2

这将使用2个连接来下载该文件。s后面的参数值介于1~5之间,你可以根据实际情况选择。

断点续传

在命令中使用c选项可以断点续传文件。

aria2c -c http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.6.tar.bz2

下载 torrent 文件

你也可以使用 aria2 下载BitTorrent文件如:

aria2c -0 gutsy.torrent http://cdimage.ubuntu.com/daily-live/current/gutsy-desktop-i386.iso.torrent

文件读取连接下载

从 files.txt 中获取下载链接进行下载, files.txt 中可以包含多个链接如:

aria2c -i files.txt

字符串匹配的KMP算法

记一次RedHat文件夹访问权限设定

起因

家里有闲置的电脑,于是我给他装上了RedHat 9.4,准备将一些资料存到上面,于是我装Samba服务将磁盘共享出来,又不想直接使用root权限进行登录,于是做了一个其他账户;
这一系列做完后,又将Hexo迁移到Linux上,于是我在Samba共享的磁盘下面给出了一个文件夹用于hexo,比较Windows写MD比较顺手嘛。本着不同账户管理的思想,于是我又建了一个Hexo账户,用于正对Hexo的部署和生成,此时就发现不同账户生成的文件夹和文件,归属权和操作权在不同终端或者Windows资源管理器上没权限操作,于是我就给文件夹指定了特地账户可以完全访问的权限。

开始操作

为了实现在 Red Hat 系统上自动给指定文件夹及其所有子文件夹和文件设置两个特定账户的完全访问权限(读、写、执行),可以使用 bash 脚本结合 chownchmodsetfacl 命令。
注意,chown 命令用于改变文件或目录的所有者,而完全访问权限(读、写、执行)通常通过调整权限(使用 chmod)和访问控制列表(使用 setfacl)来实现。
以下是三个命令的一些基本用法;

chown (改变某个文件或目录的所有者和所属的组)

chownchange owner),其功能是命令改变某个文件或目录的所有者和所属的组,该命令可以向某个用户授权,使该用户变成指定文件的所有者或者改变文件所属的组。

chmod (变更文件或目录的权限)

chmodchange mode),其功能是改变文件或目录权限的命令。默认只有文件的所有者和管理员可以设置文件权限,普通用户只能管理自己文件的权限属性。

setfacl (精确的控制文件权限的分配)

setfaclset file access control list 即”设置文件访问控制列表“) 其功能是可以更加精确的控制文件权限的分配,比如让某一个用户对某一个文件具有某种权限。

总的来说setfacl命令可以用来细分linux下的文件权限。
chmod命令可以把文件权限分为u,g,o三个组,而setfacl可以对每一个文件或目录设置更精确的文件权限。
比如:让某一个用户对某一个文件具有某种权限。
这种独立于传统的u,g,o的rwx权限之外的具体权限设置叫ACLAccess Control List

ACL可以针对单一用户、单一文件或目录来进行r,w,x的权限控制,对于需要特殊权限的使用状况有一定帮助。
如,某一个文件,不让单一的某个用户访问。
使用setfacl前,要先确保你的系统支持 ACL(访问控制列表),这可以通过运行 getfacl 命令来检查。如果系统提示找不到该命令,你可能需要安装 acl 包。

1
2
3
[hexo@sea-whales seawhales]$ getfacl
Usage: getfacl [-aceEsRLPtpndvh] file ...
Try `getfacl --help' for more information.

如果不支持可以执行以下命令,我这边是已经安装了所以没有过程:

1
2
3
4
5
6
7
8
[root@sea-whales ~]# yum install acl -y
正在更新 Subscription Management 软件仓库。
上次元数据过期检查:0:59:08 前,执行于 2024年07月14日 星期日 20时01分07秒。
软件包 acl-2.3.1-4.el9.x86_64 已安装。
依赖关系解决。
无需任何处理。
完毕!
[root@sea-whales ~]#

执行命令

1
setfacl -R -m u:"hexo":rwx "/home/hexo"

于是我就想如果有很多用户那就得执行很多一样的命令于是我就把他写成脚本,假设想让 /path/to/directory 目录及其所有子目录和文件对 user1user2 拥有完全访问权限(即读、写、执行)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

# 设定目标目录
TARGET_DIR="/path/to/directory"

# 设定需要添加权限的用户
USERS=("user1" "user2")

# 设置目录和文件的初始权限
# 例如,给所有者读写执行权限,给组和其他用户读执行权限
# 注意:你可能需要根据实际情况调整这些权限
chmod -R 755 "$TARGET_DIR"

# 遍历用户列表,为每个用户设置ACL
for USER in "${USERS[@]}"; do
# 设置用户对目录的完全控制(rwx)
# 注意:对于目录,通常需要执行权限来进入
setfacl -R -m u:"$USER":rwx "$TARGET_DIR"
# 如果需要,你还可以设置默认ACL,以便新创建的文件和目录也继承这些权限
setfacl -R -d -m u:"$USER":rwx "$TARGET_DIR"
done

echo "权限设置完成,$TARGET_DIR 目录及其所有子目录和文件现在对 ${USERS[*]} 拥有完全访问权限。"

注意

  1. 脚本中的 chmod -R 755 "$TARGET_DIR" 设置了目录和文件的初始权限。你可能需要根据实际情况调整这些权限。
  2. setfacl -R -m u:"$USER":rwx "$TARGET_DIR" 命令给每个用户设置了对目录和文件的完全访问权限(读、写、执行)。-R 选项表示递归地应用到所有子目录和文件。
  3. setfacl -R -d -m u:"$USER":rwx "$TARGET_DIR" 命令设置了默认 ACL,这意呀着在 $TARGET_DIR 下新创建的文件和目录将自动继承这些 ACL 规则。
  4. 请确保 $TARGET_DIRUSERS 数组中的值根据你的需求进行了正确的设置。
  5. 运行此脚本可能需要 root 权限,因为修改文件系统的权限和 ACL 通常需要超级用户权限。

保存此脚本到一个文件中,例如 set_directory_permissions.sh,然后通过运行 chmod +x set_directory_permissions.sh 使其可执行,最后以 root 用户身份执行它:sudo ./set_directory_permissions.sh

MongoDB安装记录和错误解决

我使用的是RedHat 9.4 安装Mongodb

1
2
[root@sea-whales ~]# cat /etc/redhat-release
Red Hat Enterprise Linux release 9.4 (Plow)

1. MongoDB安装

Mongodb有以下几种安装方式:

  1. 使用yum安装
  2. 手动下载并安装
  3. 使用docker安装

1.1 使用yum安装

一般情况推荐YUM安装,因为它可以确保您安装的是官方支持的最新版本,并且方便进行后续的更新和维护。

添加MongoDB官方YUM仓库

首先,需要创建一个新的YUM仓库配置文件,例如/etc/yum.repos.d/mongodb-org-7.0.repo(注意版本号可能根据MongoDB的最新版本而变化)。
在该文件中,添加MongoDB的官方仓库信息,包括仓库名称、基础URL、GPG密钥等。
我使用的是mongodb-org-7.0,所以我的配置文件如下:

1
2
3
4
5
6
7
[root@sea-whales ~]# cat /etc/yum.repos.d/mongodb-org-7.0.repo
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/7.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-7.0.asc

注:可以将上面的版本号替换为希望安装的MongoDB版本号

  1. 安装依赖包
    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
    [root@sea-whales ~]# yum install -y mongodb-org
    [root@sea-whales ~]# sudo yum install -y mongodb-org
    正在更新 Subscription Management 软件仓库。
    上次元数据过期检查:4:10:05 前,执行于 2024年07月14日 星期日 07时04分06秒。
    依赖关系解决。
    =================================================================================================================================================================================================================
    软件包 架构 版本 仓库 大小
    =================================================================================================================================================================================================================
    安装:
    mongodb-org x86_64 7.0.12-1.el9 mongodb-org-7.0 9.3 k
    安装依赖关系:
    mongodb-database-tools x86_64 100.9.5-1 mongodb-org-7.0 28 M
    mongodb-mongosh x86_64 2.2.12-1.el8 mongodb-org-7.0 56 M
    mongodb-org-database x86_64 7.0.12-1.el9 mongodb-org-7.0 9.4 k
    mongodb-org-database-tools-extra x86_64 7.0.12-1.el9 mongodb-org-7.0 14 k
    mongodb-org-mongos x86_64 7.0.12-1.el9 mongodb-org-7.0 24 M
    mongodb-org-server x86_64 7.0.12-1.el9 mongodb-org-7.0 35 M
    mongodb-org-tools x86_64 7.0.12-1.el9 mongodb-org-7.0 9.3 k

    事务概要
    =================================================================================================================================================================================================================
    安装 8 软件包

    总下载:143 M
    安装大小:642 M
    下载软件包:
    (1/8): mongodb-org-7.0.12-1.el9.x86_64.rpm 15 kB/s | 9.3 kB 00:00
    (2/8): mongodb-org-database-7.0.12-1.el9.x86_64.rpm 12 kB/s | 9.4 kB 00:00
    (3/8): mongodb-database-tools-100.9.5-1.x86_64.rpm 12 MB/s | 28 MB 00:02
    (4/8): mongodb-org-database-tools-extra-7.0.12-1.el9.x86_64.rpm 14 kB/s | 14 kB 00:01
    (5/8): mongodb-mongosh-2.2.12.x86_64.rpm 14 MB/s | 56 MB 00:03
    (6/8): mongodb-org-tools-7.0.12-1.el9.x86_64.rpm 21 kB/s | 9.3 kB 00:00
    (7/8): mongodb-org-server-7.0.12-1.el9.x86_64.rpm 12 MB/s | 35 MB 00:02
    (8/8): mongodb-org-mongos-7.0.12-1.el9.x86_64.rpm 6.6 MB/s | 24 MB 00:03
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    总计 23 MB/s | 143 MB 00:06
    运行事务检查
    事务检查成功。
    运行事务测试
    事务测试成功。
    运行事务
    准备中 : 1/1
    安装 : mongodb-org-database-tools-extra-7.0.12-1.el9.x86_64 1/8
    运行脚本: mongodb-org-server-7.0.12-1.el9.x86_64 2/8
    安装 : mongodb-org-server-7.0.12-1.el9.x86_64 2/8
    运行脚本: mongodb-org-server-7.0.12-1.el9.x86_64 2/8
    Created symlink /etc/systemd/system/multi-user.target.wants/mongod.service → /usr/lib/systemd/system/mongod.service.

    安装 : mongodb-org-mongos-7.0.12-1.el9.x86_64 3/8
    安装 : mongodb-org-database-7.0.12-1.el9.x86_64 4/8
    安装 : mongodb-mongosh-2.2.12-1.el8.x86_64 5/8
    运行脚本: mongodb-database-tools-100.9.5-1.x86_64 6/8
    安装 : mongodb-database-tools-100.9.5-1.x86_64 6/8
    运行脚本: mongodb-database-tools-100.9.5-1.x86_64 6/8
    安装 : mongodb-org-tools-7.0.12-1.el9.x86_64 7/8
    安装 : mongodb-org-7.0.12-1.el9.x86_64 8/8
    运行脚本: mongodb-org-7.0.12-1.el9.x86_64 8/8
    验证 : mongodb-database-tools-100.9.5-1.x86_64 1/8
    验证 : mongodb-mongosh-2.2.12-1.el8.x86_64 2/8
    验证 : mongodb-org-7.0.12-1.el9.x86_64 3/8
    验证 : mongodb-org-database-7.0.12-1.el9.x86_64 4/8
    验证 : mongodb-org-database-tools-extra-7.0.12-1.el9.x86_64 5/8
    验证 : mongodb-org-mongos-7.0.12-1.el9.x86_64 6/8
    验证 : mongodb-org-server-7.0.12-1.el9.x86_64 7/8
    验证 : mongodb-org-tools-7.0.12-1.el9.x86_64 8/8
    已更新安装的产品。

    已安装:
    mongodb-database-tools-100.9.5-1.x86_64 mongodb-mongosh-2.2.12-1.el8.x86_64 mongodb-org-7.0.12-1.el9.x86_64 mongodb-org-database-7.0.12-1.el9.x86_64
    mongodb-org-database-tools-extra-7.0.12-1.el9.x86_64 mongodb-org-mongos-7.0.12-1.el9.x86_64 mongodb-org-server-7.0.12-1.el9.x86_64 mongodb-org-tools-7.0.12-1.el9.x86_64

    完毕!
    到这里使用yum安装mongodb就完成了

1.2 手动下载安装

不推荐,因为需要补充各种依赖包,手动下载MongoDB的RPM包并使用rpm或yum localinstall命令进行安装。这种方法需要您从MongoDB官网或其他可靠源下载适用于Red Hat 9.4的RPM包。
打开MongoDB的官方网站,并导航到MongoDB下载页面。通常,可以在首页的导航栏中找到“Download”或“Downloads”链接。

在下载页面上,根据操作系统版本(Red Hat Enterprise Linux 9.x 或兼容的发行版)和架构(x86_64)选择适合的MongoDB RPM包。注意,MongoDB提供了社区版和企业版,这里我们以社区版为例。
点击下载按钮开始下载RPM包。下载完成后,将在浏览器指定的下载位置找到该RPM包文件,文件名可能类似于mongodb-org-server-7.0.12-1.el9.x86_64.rpm

使用rpm命令安装

将包放到Linux系统目录中,我这里是放在了/home/ftp目录下,cd命令切换到/home/ftp目录下

1
cd /home/ftp

使用rmp命令安装MongoDB

1
sudo rpm -ivh mongodb-org-server-7.0.12-1.el9.x86_64.rpm

这里,-i表示安装,-v表示详细输出,-h表示显示安装进度。

注意:如果安装过程中提示缺少依赖项,可以按照上面yum安装过程中的输出继续下载依赖,进行安装,可能需要先安装这些依赖项,或者考虑使用yum localinstall命令来自动解决依赖问题。

使用yum localinstall命令安装

yum localinstall命令实际上是yum的一个别名,用于安装本地RPM包并自动解决依赖关系。

1
sudo yum localinstall mongodb-org-server-7.0.12-1.el9.x86_64.rpm

yum会检查RPM包的依赖项,并尝试从配置的仓库中下载并安装缺失的依赖项。

1.3使用Docker安装

如果系统已经安装了Docker,可以通过Docker来安装MongoDB。这种方式的好处是可以轻松地在容器中运行MongoDB,便于版本控制和隔离。(由于本文主要讲介绍MongoDB,所以这里就不详细介绍Docker的安装和配置了)

拉取MongoDB镜像

使用docker pull命令拉取MongoDB镜像:

1
docker pull mongo:latest

或者指定版本号拉取特定版本的MongoDB镜像。
会有输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@sea-whales ~]# docker pull mongo:latest
latest: Pulling from library/mongo
7b1a6ab2e44d: Pull complete
90eb44ebc60b: Pull complete
5085b59f2efb: Pull complete
c7499923d022: Pull complete
019496b6c44a: Pull complete
c0df4f407f69: Pull complete
351daa315b6c: Pull complete
a233e6240acc: Pull complete
a3f57d6be64f: Pull complete
dd1b5b345323: Pull complete
Digest: sha256:5be752bc5f2ac4182252d0f15d74df080923aba39700905cb26d9f70f39e9702
Status: Downloaded newer image for mongo:latest
docker.io/library/mongo:latest

启动MongoDB

到这里我们基本就是已经安装好Mongodb了,如果只是内部使用就直接启动就好了,MongoDB已经给出了默认配置,只需要启动就可以使用,执行下面命令启动MongoDB并且查看状态,当然只有yum安装的和手动下载安装的才能使用,Docker安装的无法使用下面的命令进行查看,要依赖docker的命令进行查看;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@sea-whales ~]# systemctl start mongod
[root@sea-whales ~]# systemctl status mongod
● mongod.service - MongoDB Database Server
Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; preset: disabled)
Active: active (running) since Sat 2024-07-13 16:39:42 CST; 18h ago
Docs: https://docs.mongodb.org/manual
Main PID: 1102 (mongod)
Memory: 303.8M
CPU: 8min 51.519s
CGroup: /system.slice/mongod.service
└─1102 /usr/bin/mongod -f /etc/mongod.conf

7月 13 16:39:42 sea-whales systemd[1]: Started MongoDB Database Server.
7月 13 16:39:59 sea-whales mongod[1102]: {"t":{"$date":"2024-07-13T08:39:59.209Z"},"s":"I", "c":"CONTROL", "id":74845>

运行MongoDB容器

如果是使用docker安装的则使用docker run命令运行MongoDB容器,并指定必要的参数,如端口映射、数据卷等

1
docker run --name mongodb-container -d -p 27017:27017 mongo

这里-p 27017:27017表示将容器的27017端口映射到宿主机的27017端口。
使用docker ps命令查看正在运行的容器,确认MongoDB容器已启动。
使用MongoDB客户端连接到容器中的MongoDB实例。

配置MongoDB

MongoDB的配置文件通常位于/etc/mongod.conf,可以通过编辑该文件来配置MongoDB的行为。

通用配置

  • systemLog:

    • destination: 指定日志输出的目标,如filesyslog
    • path: 日志文件的路径(如果destinationfile)。
    • logAppend: 是否追加到日志文件,默认为true
  • storage:

    • dbPath: 数据文件的存储路径。
    • journal:
      • enabled: 是否启用journaling,用于数据恢复。
    • engine: 存储引擎类型,如wiredTiger
  • net:

    • port: MongoDB监听的端口号,默认为27017。
    • bindIp: MongoDB绑定的IP地址,默认为127.0.0.1(仅限本地访问)。
  • security:

    • authorization: 是否启用访问控制,enabled表示需要认证。
    • keyFile: 集群内部身份验证使用的密钥文件路径。
  • replication:

    • replSetName: 复制集的名称,用于设置复制集。
  • processManagement:

    • fork: 是否以守护进程方式运行MongoDB,对于生产环境推荐设置为true
    • pidFilePath: 进程ID文件的路径。
  • operationProfiling:

    • mode: 性能分析的模式,如slowOp(仅记录慢操作)。
    • slowOpThresholdMs: 慢操作的阈值(毫秒)。

示例

以下是我使用一个简化的/etc/mongod.conf配置文件:

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
# MongoDB配置文件

# 系统日志配置
systemLog:
destination: file
path: "/var/log/mongodb/mongod.log"
logAppend: true

# 存储引擎配置
storage:
dbPath: "/var/lib/mongodb"
journal:
enabled: true
engine: wiredTiger

# 网络配置
net:
port: 27017
bindIp: 127.0.0.1

# 安全配置
security:
authorization: "enabled"

# 复制集配置(如果适用)
# replication:
# replSetName: "myReplSet"

# 进程管理配置
processManagement:
fork: true
pidFilePath: "/var/run/mongodb/mongod.pid"

# 性能分析配置(可选)
# operationProfiling:
# mode: slowOp
# slowOpThresholdMs: 100

注意,应该根据自己的需求和MongoDB的版本来调整这些配置。要查看所有可用的配置项及其当前值,可以直接查看/etc/mongod.conf文件,或者使用MongoDB的--config选项与mongod命令结合来指定一个配置文件,并通过--verbose选项获取更多输出信息。
但是,请注意,--verbose选项通常用于命令行启动MongoDB时增加日志输出的详细程度,而不是直接列出配置文件中的所有配置项。要列出所有配置项及其默认值,可能需要查阅MongoDB的官方文档。

配置好之后需要执行下面的命令使得配置生效

1
2
[root@sea-whales ~]# sudo systemctl daemon-reload
[root@sea-whales ~]# sudo systemctl restart mongod

如果修改后不执行daemon-reload就直接执行restart命令,可能有一个警告,提示你要你先执行daemon-reload,然后再执行restart命令。

1
2
[root@sea-whales ~]# sudo systemctl restart mongod
Warning: The unit file, source configuration file or drop-ins of mongod.service changed on disk. Run 'systemctl daemon-reload' to reload units.

Docker安装后配置MongoDB

docker安装mongodb后,需要修改配置文件的话需要进行一些操作,一般有两种方法:

方法一:直接编辑容器内的配置文件

  1. 定位配置文件
    MongoDB的配置文件通常位于容器内的/etc/mongod.conf路径下。

  2. 复制配置文件到本地
    使用docker cp命令将容器中的配置文件复制到本地,以便进行修改。命令格式如下:

    1
    docker cp <容器ID或名称>:/etc/mongod.conf /path/to/local/mongod.conf

    这里<容器ID或名称>是MongoDB容器的ID或名称,/path/to/local/mongod.conf是本地文件系统的目标路径。

  3. 修改配置文件
    使用文本编辑器(如Vim、Nano、Sublime Text等)打开复制到本地的mongod.conf文件,并修改需要的配置项。常见的配置项包括端口号、数据存储路径、日志级别等。

  4. 将修改后的配置文件复制回容器
    使用docker cp命令将修改后的配置文件复制回容器中的原始位置。命令格式如下:

    1
    docker cp /path/to/local/mongod.conf <容器ID或名称>:/etc/mongod.conf
  5. 重启MongoDB容器
    使用docker restart命令重启MongoDB容器,使修改生效。命令格式如下:

    1
    docker restart <容器ID或名称>

方法二:使用Docker卷(Volume)映射配置文件

这种方法不需要进入容器内部,而是通过Docker卷将宿主机上的目录映射到容器内的配置文件目录。

  1. 在宿主机上创建配置文件
    在宿主机上创建一个目录,用于存放MongoDB的配置文件。例如,创建/path/to/mongodb/config目录,并将MongoDB的配置文件(如mongod.conf)放入该目录中。

  2. 启动MongoDB容器时使用卷映射
    在启动MongoDB容器时,使用-v--volume参数将宿主机上的配置文件目录映射到容器内的配置文件目录。例如:

    1
    docker run -d --name mongodb -p 27017:27017 -v /path/to/mongodb/config:/etc/mongod.conf mongo:latest

    注意:这里的映射路径可能需要根据实际情况进行调整,因为MongoDB的配置文件通常位于/etc/mongod.conf,但上述命令中的映射可能会覆盖整个/etc/mongod.conf文件而不是目录。实际上,你可能需要创建一个包含mongod.conf的目录,并将其映射到容器内的相应目录,如/etc/mongodb(这取决于MongoDB容器内部的目录结构)。正确的命令可能类似于:

    1
    docker run -d --name mongodb -p 27017:27017 -v /path/to/mongodb/config:/etc/mongodb mongo:latest

    并确保宿主机上的/path/to/mongodb/config目录中包含mongod.conf文件。

  3. 重启MongoDB容器(如果容器已经运行):
    如果MongoDB容器已经在运行,并且你想要应用新的配置文件,你需要先停止容器,删除旧的容器(可选,但推荐以避免配置冲突),然后使用新的配置文件重新创建并启动容器。

注意事项
  • 在修改配置文件之前,建议备份原始配置文件,以防万一需要恢复。
  • 确保修改后的配置文件语法正确,并且符合MongoDB的要求。
  • 如果使用Docker卷映射配置文件,请确保宿主机上的配置文件路径和容器内的映射路径正确无误。
  • MongoDB的配置项非常多,具体可配置的选项请参考MongoDB的官方文档。

访问MongoDB

访问基本以及使用这边就不多描述了,基本的操作就是通过mongo命令行工具来操作数据库了,具体操作可以参考官方文档。

2. MongoDB错误解决

错误1:MongoDB启动失败

错误原因:MongoDB启动失败,可能是端口被占用,也可能是MongoDB的配置文件错误。
解决方法:

  1. 检查端口是否被占用
    1
    lsof -i:27017
  2. 修改MongoDB的配置文件
    修改MongoDB的配置文件,将端口号改为其他端口号,例如:27018
  3. 重启MongoDB

错误2:

我遇到一个错误就是在修改MongoDB的配置文件的bindIp项目为0.0.0.0后,重启MongoDB的时候出现了如下错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@sea-whales ~]# sudo systemctl status mongod
× mongod.service - MongoDB Database Server
Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; preset: disabled)
Active: failed (Result: exit-code) since Sun 2024-07-14 11:24:58 CST; 3s ago
Duration: 79ms
Docs: https://docs.mongodb.org/manual
Process: 59341 ExecStart=/usr/bin/mongod $OPTIONS (code=exited, status=14)
Main PID: 59341 (code=exited, status=14)
CPU: 65ms

7月 14 11:24:58 sea-whales systemd[1]: Started MongoDB Database Server.
7月 14 11:24:58 sea-whales systemd[1]: mongod.service: Main process exited, code=exited, status=14/n/a
7月 14 11:24:58 sea-whales systemd[1]: mongod.service: Failed with result 'exit-code'.

然后就算我修改回127.0.0.1后也是是报告一样的错误;
通过查询以下是AI给出的分析:

从您提供的日志信息来看,MongoDB 服务在启动时遇到了问题,并且以状态码 14 退出。状态码 14 通常不是一个标准的 MongoDB 退出码,但在某些情况下,它可能与系统级的问题或 MongoDB 试图执行某些不被允许的操作有关。这里有几个可能的解决步骤和检查点:

  1. 检查 MongoDB 配置文件
  • 确保 MongoDB 的配置文件(如 /etc/mongod.conf 或指定的配置文件路径)没有错误。
  • 检查配置文件中的路径设置(如 dbPathlogPath)是否正确,且 MongoDB 进程有权限访问这些路径。
  1. 查看 MongoDB 日志文件
  • MongoDB 的日志文件通常会提供更多关于为什么服务无法启动的详细信息。检查 /var/log/mongodb/mongod.log 或您配置文件中指定的日志路径。
  1. 检查系统资源
  • 确保系统有足够的磁盘空间、内存和 CPU 资源来运行 MongoDB。
  • 检查是否有其他服务或进程占用了 MongoDB 所需的端口(默认是 27017)。
  1. 检查 SELinux 或 AppArmor 设置
  • 如果您的系统启用了 SELinux 或 AppArmor,它们可能会阻止 MongoDB 正常启动。检查相关的安全策略或暂时禁用它们以测试是否是安全设置导致的问题。
  1. 检查 MongoDB 权限
  • 确保 MongoDB 的可执行文件(如 /usr/bin/mongod)和数据库文件目录的权限设置正确。
  • MongoDB 通常需要 root 权限来绑定到低端口(如 27017),但建议通过 systemd 或其他服务管理工具以非 root 用户身份运行。
  1. 检查 systemd 单元文件
  • 查看 /usr/lib/systemd/system/mongod.service 文件中的 ExecStart 行,确保 $OPTIONS 变量正确设置,并且没有语法错误。
  • 尝试手动运行 MongoDB 服务命令(如 /usr/bin/mongod --config /etc/mongod.conf)来检查是否可以直接从命令行启动。
  1. 重新启动 MongoDB 服务
  • 在进行任何更改后,尝试重新启动 MongoDB 服务:sudo systemctl restart mongod.service
  1. 查看系统日志
  • 检查 /var/log/syslog 或其他系统日志文件,看是否有与 MongoDB 相关的更详细的错误消息。
    如果上述步骤都不能解决问题,您可能需要考虑重新安装 MongoDB 或寻求更具体的帮助,例如在 MongoDB 社区论坛或支持渠道中询问。

于是我尝试使用/usr/bin/mongod -f /etc/mongod.conf启动,然后在另一个终端执行tail -f /var/log/mongodb/mongod.log命令进行日志的实时查看,也没发现任何报错信息。

通过各种尝试,最后居然是因为MongoDB数据目录的权限和所有权正确,只需要执行下面两句命令后,在进行重启就可以正常启动了;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@sea-whales ~]# sudo chown -R mongod:mongod /var/lib/mongo
[root@sea-whales ~]# sudo chmod -R 755 /var/lib/mongo
[root@sea-whales ~]# systemctl status mongod
● mongod.service - MongoDB Database Server
Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; preset: disabled)
Active: active (running) since Sun 2024-07-14 13:02:36 CST; 4s ago
Docs: https://docs.mongodb.org/manual
Main PID: 61154 (mongod)
Memory: 176.5M
CPU: 1.582s
CGroup: /system.slice/mongod.service
└─61154 /usr/bin/mongod -f /etc/mongod.conf

7月 14 13:02:36 sea-whales systemd[1]: Started MongoDB Database Server.

这个问题是我没想到的,因为在前面安装之后MongoDB是可以正常启动的,但是后来我修改了MongoDB的配置文件,然后重启了MongoDB,就出现了这个错误,都没想到会是因为权限的问题。

分析

经过一些回想,我想到了原来在安装的时候MongoDB我是使用root进行安装的,所以权限是默认的,但是后来
我在做服务的时候默认将mongod服务以mongod用户和组的权限运行,就是下面配置文件中 [Service] 中的UserGroup配置项,导致权限问题,所以导致无法启动服务;

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
[root@sea-whales ~]# cat /usr/lib/systemd/system/mongod.service
[Unit]
Description=MongoDB Database Server
Documentation=https://docs.mongodb.org/manual
After=network-online.target
Wants=network-online.target

[Service]
User=mongod
Group=mongod
Environment="OPTIONS=-f /etc/mongod.conf"
# Environment="MONGODB_CONFIG_OVERRIDE_NOFORK=1"
EnvironmentFile=-/etc/sysconfig/mongod
ExecStart=/usr/bin/mongod -f $OPTIONS
# $OPTIONS
RuntimeDirectory=mongodb
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# locked memory
LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for mongod as specified in
# https://docs.mongodb.com/manual/reference/ulimit/#recommended-ulimit-settings

[Install]
WantedBy=multi-user.target
[root@sea-whales ~]#