ansible自动化运维(三)jinja2模板roles角色管理
相关文章 |
---|
ansible自动化运维(一)简介及清单,模块-CSDN博客 |
ansible自动化运维(二)playbook模式详解-CSDN博客 |
ansible自动化运维(四)运维实战-CSDN博客 |
三.Ansible jinja2模板
Jinja2是Python的全功能模板引擎。在python的WEB开发中被广泛应用。
Ansible通常会使用jinja2模板来修改被管理主机的配置文件等。
3.1在Ansible 中的使用
使用Ansible的jinja2模板也就是使用template 模块,该模块和copy 模块一样,都是将文件复制到远端主机上去,但是区别在于,template 模块可以获取到文件中的变量,而copy则是原封不动的把文件内容复制过去。比如想把脚本中的变量名改成主机名,如果使用copy模块则推送过去的就是{{ ansible_fqdn }},不变,如果使用template,则会变成对应的主机名。
Ansible允许jinja2模板中使用条件判断和循环,但是不允许在playbook中使用。通常jinja2模板文件的后缀为.j2
3.2jinja2模板语法
3.2.1基础语法
(1)playbook文件使用template模块
(2)模板文件里面变量使用{{名称}},比如{{PORT}}或使用facts
(3){{}}也可以使用表达式,比如{{3+5}}{{3 in[1,2,3,4,5]-}}
说明:{{}}中的表达式其实就是python中的表达式,可以包括比较运算,算数运算,逻辑运算,成员运行。
该模板支持:
字符串:使用单引号或双引号; 数字:整数,浮点数; 列表:[item1, item2, ...] 元组:(item1, item2, ...) 字典:{key1:value1, key2:value2, ...} 布尔型:true/false 算术运算: +, -, *, /, //, %, ** 比较操作: ==, !=, >, >=, <, <= 逻辑运算: and, or, not |
模板通常都是通过引用变量来运用的
【实例】
- 创建模板文件:
首先,需要一个包含jinja2模板的文件。这个文件通常包含要插入变量或表达式的位置。可以在文件中是使用 {{}} 来包裹变量或表达式。
例如,创建一个名为my_template.j2的模板文件:
[root@host1 jinja2]# cat my_template.j2 ServerName {{ hostname }} Listen {{ port }} Debug {{ debug_mode }} |
2.在Playbook中使用模板:
在 Ansible Playbook中,可以使用template模块来加载模板文件并将变量传递给它。以下是一个示例Playbook:
[root@server jinja2]# cat jinja.yml --- - name: 使用Jinja2模板 hosts: node1 vars: hostname: example.com port: 8080 debug_mode: True tasks: - name: 生成配置文件 template: src: my_template.j2 dest: /etc/ansible/yml/jinja2/myapp.conf |
在这个示例中,我们使用了template模块,指定了模板文件的源(src)和目标(dest)。我们还传递了变量hostname、port和debug_mode,这些变量会在模板中替换 {{}} 中相应的位置。
3.运行Playbook:
运行上述Playbook,Ansible将使用模板文件生成 /etc/ansible/yml/jinja2/myapp.conf 配置文件,并将模板中的{{ }}替换为变量的值。
[root@server jinja2]# ansible-playbook jinja.yml |
查看结果为
root@host1 jinja2]# cat /etc/ansible/yml/jinja2/myapp.conf ServerName example.com Listen 8080 Debug True |
模板文件中的 {{ }} 不仅可以包含变量,还可以包含表达式,如您所述的比较运算、算数运算、逻辑运算等。这使得您可以把在模板中执行各种操作以生成需要的配置或文本。
3.2.2流程控制
条件判断
使用{% if %}和{% endif %}块来实现条件语句。以下是一个示例:
格式:
{% if EXPR %} 执行内容 {% else %} 执行内容 {% endif %} |
实例:
{% if is_production %} # 生产环境配置 DebugLevel: 0 {% else %} # 开发/测试环境配置 DebugLevel: 2 {% endif %} |
多条件判断
格式:
{% if EXPR %} 执行内容 {% elif EXPR %} 执行内容 {% else %} 执行内容 {% endif %} |
实例:
{% if is_production %} DebugLevel: 0 {% elif is_production %} DebugLevel: 1 {% else %} DebugLevel: 2 {% endif %} |
在这个示例中,根据is_production变量的值,将生成不同的配置。
循环表达式
可以使用{% for %}和{% endfor %}块来实现循环。以下是一个示例:
格式:
{% for i in EXPR %} 执行内容 {% endfor %} |
实例:
{% for item in list_items %} - {{ item }} {% endfor %} |
在这个示例中,list_items 是一个包含多个元素的列表,模板将循环遍历列表中的每个元素并生成相应的输出。
说明:默认不支持break和continue的,但是可以使用ansible的扩展选项,在配置/etc/ansible/ansible.cfg中的132行中:
jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n,jinja2.ext.loopcontrols
3.2.3 过滤器
Jinja2还支持过滤器,可以使用过滤器来对变量进行操作。例如,可以使用default过滤器来设置默认值:
{{ variable | default("default_value") }} |
或者,您可以使用length过滤器来获取列表的长度:
The list has {{ list_items | length }} items. |
3.2.4其他控制结构
Jinja2还支持其他控制结构,如{% include %}用于包含其他模板文件,以及{% macro %}和{% call %}用于定义和调用宏。
请注意,Jinja2语法和功能非常强大,支持许多高级用例,例如宏、继承、自定义过滤器等。要更深入地了解Jinja2模板的流程控制和功能,请查看Jinja2的官方文档。在Ansible中,可以将这些模板用于生成配置文件、编排任务等,以满足不同的需求和环境。
3.3template模板
template模块与copy模块的用法十分类似,只是更多用于jinja2模板的渲染,也就是模板文件中可以引用变量,实现对不同主机有定制化的配置。
copy与template的区别
- copy模块不替代参数,template模块替代参数
- template的参数几乎与copy的参数完全相同
常用参数
参数 | 解析 |
src | 指定本地jinja2模板文件的位置 |
dest | 指定目标远程主机路径 |
backup | 指定是否备份,默认值no |
mode | 设置权限 |
user | 设置用户 |
group | 设置用户组 |
3.4jinja2使用案例
比如需要实现对被控端主机安装redis服务,默认的redis服务只监听本地的127.0.0.1端口,换句话说,其他主机是不可以访问该redis服务器的,如何来解决这个问题呢?此时就可以使用jinja2的模板,在其中引用变量,使用template模块进行渲染。
本案例使用了:jinja2模板、templates模块、copy模块、yum模块、shell模块、service模块、vars定义变量、register变量注册、ignore_errors忽略错误、tags标签、when判断、notify 和 handlers通知与触发(处理程序)
- 创建一个自定义的Redis配置模板文件,如redis_conf.j2,并在其中修改Redis绑定地址以侦听所有IP地址;
[root@host1 redis]# cat redis_conf.j2 bind {{ ansible_host }} 127.0.0.1 port {{ redis_port }} protected-mode no tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize yes supervised no pidfile /var/run/redis_6379.pid loglevel notice |
在此示例中,我们使用了 bind {{ ansible_host }} 127.0.0.1 来告诉Redis服务只接受本地IP连接,并使用变量 redis_port 来指定Redis端口。
2.在Ansible Playbook中,使用template模块加载该模板并渲染它,然后将渲染后的配置文件复制到Redis配置文件目录。以下是一个示例Playbook:
[root@server redis]# cat redis.yml --- - name: 配置Redis服务器 hosts: node1 become: yes gather_facts: yes tasks: - name: 安装并启动Redis服务 service: name: redis state: started enabled: yes tags: - start_redis - redis_server - name: 重启Redis服务 service: name: redis state: restarted tags: - restart_redis - name: 停止Redis服务 service: name: redis state: stopped tags: - stop_redis - uninstall_redis - name: 卸载Redis服务 yum: name: redis state: absent tags: - uninstall_redis handlers: - name: 重启Redis service: name: redis state: restarted |
# 检查语法 [root@localhost redis]# ansible-playbook --syntax-check redis.yml # 列出任务列表 [root@localhost redis]# ansible-playbook --list-tasks redis.yml # 列出所有tags标签 [root@localhost redis]# ansible-playbook --list-tags redis.yml |
asks 解析:(列出tasks任务的时候后面也会包含tags标签,每个任务用的标签都有哪些)
tasks: Upload yum repo # 更新yum源 Upload epel repo # 更新yum扩展源 Check redis install # 检查是否安装过 redis Install redis server # 安装 redis Upload redis.conf # 更新 redis 配置文件 Start Redis server # 启动 redis Restart Redis server # 重启 redis Stop Redis Server # 停止 redis uninstall Redis Server # 卸载 redis |
tags 解析:(列出tasks任务的时候后面也会包含tags标签,每个任务用的标签都有哪些)
TASK TAGS: upload_repo # 更新yum源,包含:Upload yum repo,Upload epel repo upload_yum # 更新yum源 upload_epel # 更新yum扩展源 redis_server # 一键安装redis,包含:Upload yum repo、Upload epel repo、Check redis install、Install redis server、Upload redis.conf、Start Redis server check_redis # 检查是否安装 redis upload_redis.conf # 更新 redis 配置文件 install_redis # 安装 redis restart_redis # 重启 redis start_redis # 启动 redis stop_redis # 停止 redis uninstall_redis # 卸载 redis |
- 执行yml脚本
#执行更新yum源 [root@server redis]# ansible-playbook redis.yml -t upload_repo #检查是否安装过redis,会有报错说找不到,不会影响,里面有 ignore_errors忽略错误; [root@server redis]# ansible-playbook redis.yml -t check_redis # 执行安装 redis(不能单独执行redis,会报错,因为有一个when判断需要调用上面的查询是否有redis服务) [root@server redis]# ansible-playbook redis.yml -t check_redis,install_redis #执行更新redis配置文件 [root@server redis]# ansible-playbook redis.yml -t upload_redis.conf #执行完毕可以查看redis状态及redis端口是否启动,正常是都启动的; #执行停止redis [root@server redis]# ansible-playbook redis.yml -t stop_redis # 执行完毕可以查看redis状态及redis端口是否启动,如果没有那就是没问题,因为这是停止; #执行卸载redis [root@server redis]# ansible-playbook redis.yml -t uninstall_redis # 执行完可以使用: rpm -q redis 查看或使用 check_redis标签检查 # 执行一键安装redis [root@server redis]# ansible-playbook redis.yml -t redis_server # 执行完毕可以查看redis状态及redis端口是否启动,正常是都启动的; |
四.roles角色管理
在Ansible中,有一个roles的概念。roles并不是指定具体的东西,而是一种规范,将复杂的Playbook分割为多个文件的机制,简化复杂的Playbook编写,并且使Playbook的复用变得简单。
建议:每个roles最好只使用一个tasks这样方便调用,能够很好的做到解耦;
4.1Roles介绍与优势
一般情况下将roles写在 /etc/ansible/roles 中,也可以写在其他任意位置(写在其他位置要自己手动建立一个roles文件夹)
- 对于以上所有方式有个缺点 就是无法实现同时部署web、database、keepalived等不同服务或者不同服务器组合不同的应用就需要写多个yaml文件,很难实现灵活的调用
- roles用于层次性,结构化地组织playbook。roles能够根据层次结果自动装载变量文件、tasks以及handlers等。
- 要使用roles只需要在playbook中使用include指令即可。
- 简单来讲,roles就是通过分别将变量(vars)、文件(files)、任务(tasks)、模块(modules)以及处理器(handlers)放置于单独的目录中,并且可以便捷的include它们地一种机制。
- 角色一般用于基于主机构建服务的场景中,但是也可以用于构建守护进程等场景中。
4.2Roles的目录结构
建一个角色目录,用于演示:
[root@server redis]# mkdir -pv /etc/ansible/roles/{nginx,mysql,httpd}/{files,templates,vars,tasks,handlers,meta,default} |
看Roles的目录结构:
[root@server redis]# tree /etc/ansible/roles/ /etc/ansible/roles/ ├── httpd │ ├── default │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ └── vars ├── mysql │ ├── default │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ └── vars └── nginx ├── default ├── files ├── handlers ├── meta ├── tasks ├── templates └── vars 24 directories, 0 files |
目录解析:
/etc/ansible/roles/:存放roles的文件路径
httpd:存放apached服务的yml文件
mysql:存放mysql服务的yml文件
nginx:存放nginx服务的yml文件
default:此目录至少应该有一个名为main.yml的文件,用于设定默认变量;
files:存储由copy或者script等模块调用的文件或者脚本;
handlers:此目录中至少应该有一个名为main.yml的文件,用于定义各个handler;其他文件需要由main.yml进行包含调用;
meta:此目录中至少应该有一个名为main.yml的文件,定义当前角色的特殊设定以及依赖关系,其他文件需要由main.yml进行包含调用;
tasks:此目录中至少应该有一个名为main.yml的文件,用于定义各个task;其他文件需要由main.yml进行包含调用;
templates:存储由templates模块调用的模板文件;
vars:此目录至少应该有一个名为main,yml的文件,用于定义各个variable;其他的文件需要由main.yml进行包含调用;
4.3ansible-galaxy命令
ansible-galaxy命令用于管理roles,同时也可以在galaxy.ansible.com上下载别人写好的roles。
1,初始化roles的目录结构
[root@server redis]# ansible-galaxy init /etc/ansible/roles/webserver - Role /etc/ansible/roles/webserver was created successfully |
2.安装别人写好的roles
[root@server redis]# ansible-galaxy role install -p /etc/ansible/roles tenequm.mysql - downloading role 'mysql', owned by tenequm - downloading role from https://github.com/tenequm/ansible-mysql/archive/1.0.1.tar.gz - extracting tenequm.mysql to /etc/ansible/roles/tenequm.mysql - tenequm.mysql (1.0.1) was installed successfully [root@server etc]# cd ansible/roles/ [root@server roles]# ls mysql default files handlers meta tasks templates vars |
3.列出已安装的roles
[root@server roles]# ansible-galaxy list # /usr/share/ansible/roles # /etc/ansible/roles - webserver, (unknown version) - tenequm.mysql, 1.0.1 [WARNING]: - the configured path /root/.ansible/roles does not exist. |
4.查看指定roles的信息
[root@server ~]# ansible-galaxy info tenequm.mysql Role: tenequm.mysql description: Simply installs MySQL 5.7 on Xenial. commit: b3a7139ba44a91e9568345565e861e326e9d401e commit_message: Added priveleges configs for users. created: 2023-05-08T20:18:24.338543Z download_count: 238 github_branch: master github_repo: ansible-mysql github_user: tenequm id: 103 imported: 2017-06-08T21:57:26.659770-04:00 modified: 2023-10-10T00:48:33.420438Z path: (u'/root/.ansible/roles', u'/usr/share/ansible/roles', u'/etc/ansible/roles') upstream_id: 17029 username: tenequm |
5.删除一个roles
如果是使用的wget拉取的,那么删除他的哪个roles目录就可以;
[root@server ~]# ansible-galaxy remove /etc/ansible/roles/tenequm.mysql - successfully removed /etc/ansible/roles/tenequm.mysql |
都看到这了留个一键三连呗,谢谢你们啦
相关文章:

ansible自动化运维(三)jinja2模板roles角色管理
相关文章ansible自动化运维(一)简介及清单,模块-CSDN博客ansible自动化运维(二)playbook模式详解-CSDN博客ansible自动化运维(四)运维实战-CSDN博客 三.Ansible jinja2模板 Jinja2是Python的全功能模板引…...

队列+宽搜_429. N 叉树的层序遍历_二叉树最大宽度
429. N 叉树的层序遍历 定义一个队列q,将一层的节点入队,并记录节点个数。根据节点的个数,出队列,并将其孩子入队列。出完队列,队列当前剩余节点的个数就是下次出队列的次数。直到队列为空 /* // Definition for a Nod…...

Windows11安装及使用nvm
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Windows11安装nvm 前言一、简介二、下载三、安装1、双击运行,同意协议,点击Next2、选择nvm安装路径,此路径也是环境变量NVM_HOME的路径&am…...
(一)机器学习 - 入门
数据集 数据集是一组数据的集合,这些数据可以是数值型、文本型、图形型等多种形式。数据集通常用于统计分析、机器学习、科学研究、商业智能等领域,以发现数据中的模式、趋势和关联性。 数据集的组成: 变量(Variables)…...

【解决】k8s使用kubeadm初始化集群失败问题整理
执行提示命令,查看报错信息 journalctl -xeu kubelet1、错误:running with swap on is no 报错 "command failed" err"failed to run Kubelet: running with swap on is no 解决: swap未禁用,需要禁用swap&…...
apache-dubbo
dubbo 文档地址 dubbo 官方文档地址 https://dubbo.apache.org/zh-cn/docs/user/references/api.html nacos 官方文档地址 https://nacos.io/zh-cn/docs/quick-start.html nacos下载地址 https://github.com/alibaba/nacos/releases/download/2.3.0/nacos-server-2.3.0.…...

ECharts柱状图-柱图2,附视频讲解与代码下载
引言: 在数据可视化的世界里,ECharts凭借其丰富的图表类型和强大的配置能力,成为了众多开发者的首选。今天,我将带大家一起实现一个柱状图图表,通过该图表我们可以直观地展示和分析数据。此外,我还将提供…...

【新人系列】Python 入门(十六):正则表达式
✍ 个人博客:https://blog.csdn.net/Newin2020?typeblog 📝 专栏地址:https://blog.csdn.net/newin2020/category_12801353.html 📣 专栏定位:为 0 基础刚入门 Python 的小伙伴提供详细的讲解,也欢迎大佬们…...

HTML综合
一.HTML的初始结构 <!DOCTYPE html> <html lang"en"><head><!-- 设置文本字符 --><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><!-- 设置网页…...
孚盟云 MailAjax.ashx SQL注入漏洞复现
0x01 产品简介 上海孚盟软件有限公司是一家外贸SaaS服务提供商,也是专业的外贸行业解决方案专业提供商。 全新的孚盟云产品,让用户可以用云模式实现信息化管理,让用户的异地办公更加流畅,大大降低中小企业在信息化上成本,用最小的投入享受大型企业级别的信息化服务,主要…...

解决“VMware虚拟机报Intel VT-x”错误
今天,在windows系统上,打开VMware WorkStation v15软件里的虚拟机,弹出"Intel VT-x处于禁用状态"错误,如图(1)所示: 图(1) 虚拟机报"Intel VT-x"错误 问题原因:当前电脑的BIOS没有开启…...
NiceGUI `ui.table` 基础
NiceGUI ui.table 基础 ui.table 是 NiceGUI 提供的一个组件,用于在页面上展示数据表格 基本概念 官方简介 A table based on Quasar’s QTable component. 参数参考rows:list of row objects; 行对象列表columns:list of column objects (defaults to the colu…...

分布式 Raft算法 总结
前言 相关系列 《分布式 & 目录》《分布式 & Raft算法 & 总结》《分布式 & Raft算法 & 问题》 参考文献 《Raft一致性算法论文译文》《深入剖析共识性算法 Raft》 简介 Raft 木筏是一种基于日志复制实现的分布式容错&一致性算法。在Raft算法…...
C++ 中面向对象编程如何实现动态绑定?
在 C 中,动态绑定(Dynamic Binding)是通过 虚函数(virtual function) 和 多态性(polymorphism) 来实现的。这是面向对象编程的重要特性之一,它允许程序在运行时根据对象的实际类型调…...

微服务-01
1.认识微服务 1.1 单体架构 单体架构(monolithic structure):顾名思义,整个项目中所有功能模块都在一个工程中开发;项目部署时需要对所有模块一起编译、打包;项目的架构设计、开发模式都非常简单。 当项目…...

这是一个vue3 + scss的数字滚动效果
介绍: 当数字变化时,只改变变化的数字位,其余的不变,可以递增、递减、骤变、负数也可以,但是样式要根据具体的项目需求去改; 效果1、增加数字: 效果2、减少数字: 使用方法: <te…...
数字证书管理工具 openssl keytool
OPENSSL 命令 openssl command [ command_opts ] [ command_args ] 常用command: version 用于查看版本信息 enc 用于加解密 ciphers 列出加密套件 genrsa 用于生成私钥 -des|-des3|-idea:用来加密私钥文件的三种对称加密算法。 rsa …...

Polars数据聚合与旋转实战教程
在这篇博文中,我们的目标是解决数据爱好者提出的一个常见问题:如何有效地从Polars DataFrame中创建汇总视图,以便在不同时间段或类别之间轻松进行比较。我们将使用一个实际的数据集示例来探索实现这一目标的各种方法。 Polars简介 Polars 是…...
引用类型集合的深拷贝,无需手动写循环:Apache Commons Lang (SerializationUtils)
在java中,我们如果想要对引用类型的集合进行深拷贝。有一种方式,就是调用SerializationUtils Apache Commons Lang (SerializationUtils) Apache Commons Lang 提供了 SerializationUtils 类,可以利用 Java 的序列化机制来进行集合及其元素…...

HTML、CSS表格的斜表头样式设置title 画对角线
我里面有用到layui框架的影响,实际根据你自己的框架来小调下就可以 效果如下 上代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
Python爬虫(四):PyQuery 框架
PyQuery 框架详解与对比 BeautifulSoup 第一部分:PyQuery 框架介绍 1. PyQuery 是什么? PyQuery 是一个 Python 的 HTML/XML 解析库,它采用了 jQuery 的语法风格,让开发者能够用类似前端 jQuery 的方式处理文档解析。它的核心特…...

华为OD机考- 简单的自动曝光/平均像素
import java.util.Arrays; import java.util.Scanner;public class DemoTest4 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint[] arr Array…...

C#学习12——预处理
一、预处理指令: 解释:是在编译前由预处理器执行的命令,用于控制编译过程。这些命令以 # 开头,每行只能有一个预处理指令,且不能包含在方法或类中。 个人理解:就是游戏里面的备战阶段(不同对局…...

Vite 双引擎架构 —— Esbuild 概念篇
Vite 底层采用 双引擎架构,核心构建引擎是 Esbuild 和 Rollup,二者在开发和生产环境中分工协作,共同实现高性能构建。不可否认,作为 Vite 的双引擎之一,Esbuild 在很多关键的构建阶段(如依赖预编译、TS 语法转译、代码…...

详解鸿蒙Next仓颉开发语言中的动画
大家上午好,今天来聊一聊仓颉开发语言中的动画开发。 仓颉中的动画通常有两种方式,分别是属性动画和显示动画,我们今天以下面的加载动画为例,使用显示动画和属性动画分别实现一下,看看他们有什么区别。 显示动画 显示…...