首页
小游戏
壁纸
留言
视频
友链
关于
Search
1
上海市第八人民医院核酸检测攻略(时间+预约+报告)-上海
299 阅读
2
上海烟花销售点一览表2022-上海
241 阅读
3
新款的 Thinkbook 16+ 值不值得买?-知乎热搜
219 阅读
4
如何看待网传小米 MIUI 13 内置国家反诈中心 APP?-知乎热搜
214 阅读
5
窦唯到底厉害在哪里?-知乎热搜
192 阅读
免费代理IP
免费翻墙节点
文章聚合
掘金
知乎
IT之家
本地宝
观察者网
金山词霸
搜韵网
新华网
其他
登录
/
注册
Search
标签搜索
知乎热搜
IT之家热榜
广州
深圳
北京
观察者网头条
前端
上海
后端
知乎日报
Android
iOS
人工智能
阅读
工具资源
杭州
诗词日历
每日一句
郑州
设计
看啥
累计撰写
129,720
篇文章
累计收到
46
条评论
首页
栏目
免费代理IP
免费翻墙节点
文章聚合
掘金
知乎
IT之家
本地宝
观察者网
金山词霸
搜韵网
新华网
其他
页面
小游戏
壁纸
留言
视频
友链
关于
搜索到
1394
篇与
的结果
2022-10-20
Luminar Neo教程,如何使用 Luminar Share 导入图像?-掘金
欢迎观看 Luminar Neo中文版教程,小编带大家学习 Luminar Neo的基本工具和使用技巧,了解如何使用 Luminar Share 导入图像。有时想将智能手机上捕获的图像发送到 Luminar Neo 进行编辑。按照以下步骤连接 Luminar Neo 和 Luminar Share 以传输图像。打开 Luminar Neo 选择「文件」-「分享至」,然后选择「连接」以显示 Luminar Neo 中的 QR 码。在设备上启动 Luminar Share 应用程序并扫描屏幕上的二维码连接外部设备。 从 Luminar Share 应用程序将照片发送到 Luminar Neo。浏览设备以查找要传输的图像,可以选择单个或多个图像,按 Select 将图像传输到 Luminar Neo,发送到 Luminar Neo以确认选择。以上就是在 Luminar Neo 中使用 Luminar Share 导入图像的方法。以上文章来自[掘金]-[Mac121]本程序使用github开源项目RSSHub提取聚合!
2022年10月20日
0 阅读
0 评论
0 点赞
2022-10-20
flutter-底部TabBar与顶部TabBarView-掘金
前言就以我们平时比较常见的微信应用参考,我们比较常用的组件就是底部 tabbar 了,除此之外,就是顶部的类似于 tabbar 的组件了(有些地方叫顶部叫PageView),两者兼并的,就以掘金 app 首页为例,上下两个部分一目了然案例demo地址(Tabbar文件夹)下面就分别介绍这两种怎么使用的,以及怎么保存状态底部tabbar底部 tabbar 就是我们常见的微信底部的切换功能效果,如下所示其主要是依靠 BottomNavgationBar 、PageView、PageController,如下所示class NormalTabBar extends StatefulWidget { const NormalTabBar({Key? key}) : super(key: key); @override State createState() => _NormalTabBarState(); } class _NormalTabBarState extends State { //用于协调tabbar和内容的联动 final PageController _controller = PageController( initialPage: 0 //默认为0,可以不填写 ); //存放bottombar信息,避免编写过多重复ui代码 final List items = [ TabBarItem(title: '聊天', norImage: "images/tabbar_chat.png", selImage: "images/tabbar_chat_hl.png"), TabBarItem(title: '联系人', norImage: "images/tabbar_contact.png", selImage: "images/tabbar_contact_hl.png"), TabBarItem(title: '发现', norImage: "images/tabbar_discover.png", selImage: "images/tabbar_discover_hl.png"), TabBarItem(title: '我的', norImage: "images/tabbar_mine.png", selImage: "images/tabbar_mine_hl.png") ]; int _pageIndex = 0; @override Widget build(BuildContext context) { return Scaffold( bottomNavigationBar: BottomNavigationBar( currentIndex: _pageIndex, onTap: (int page) { setState(() { _pageIndex = page; }); _controller.jumpToPage(page); }, backgroundColor: Colors.white, type: BottomNavigationBarType.fixed, unselectedItemColor: Colors.black, selectedItemColor: Colors.cyanAccent, unselectedFontSize: 12, selectedFontSize: 12, items: items.map((e) { return BottomNavigationBarItem( label: e.title, icon: Image.asset(e.norImage, width: 20, height: 20,), activeIcon: Image.asset(e.selImage, width: 20, height: 20,), ); }).toList(), ), body: PageView( controller: _controller, //不设置默认可以左右活动,如果不想左右滑动如下设置,可以根据ios或者android来设置 physics: Platform.isAndroid ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(), children: const [ //设置内容页面即可,要和 bottomNavigationBar 数量一致 TabbarContainerView(color: Colors.yellow, name: '1',), TabbarItemInAppBarView(color: Colors.cyanAccent, name: "2",), TabbarContainerView(color: Colors.green, name: "3",), TabbarItemInAppBarView(color: Colors.blueAccent, name: "4",), ], ), ); } } 顶部tabbar顶部tabbar虽然没有底部那么常用,但是在不少app的一些场景也是用的不少,下面介绍下其使用,本质和tabbar功能类似,只不过出了另外一个组件,花不说了,效果如下所示其有两种方案,一种使用系统默认的,另外一种使用自定义的默认顶部tabbar实现主要通过 DefaultTabController、TabBar、TabBarView,如下所示class TabbarContainerView extends StatefulWidget { final Color color; final String name; const TabbarContainerView({Key? key, required this.color, required this.name}) : super(key: key); @override State createState() => _TabbarContainerViewState(); } //继承maxin AutomaticKeepAliveClientMixin 同时重写 wantKeepAlive 可以对当前页面状态保存,避免重新渲染初始化参数 class _TabbarContainerViewState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; final List tabNames = ["推荐", "订阅"]; @override void initState() { super.initState(); print(widget.name); } @override Widget build(BuildContext context) { super.build(context); //通过 DefaultTabController 可自动调节内部tabbar联动 return DefaultTabController( length: tabNames.length, child: Scaffold( appBar: AppBar( title: const Text("带Tabs的tabbar子页面"), //默认在下面放一排 Tab bottom: TabBar( //这个参数比较特殊,默认为false,不出屏元素多了会被挤压内容 //如果想支持滚动,边内部挤压,可以将此参数设置为true //isScrollable: true, //设置tabs标签 tabs: tabNames.map((e) => Tab(text: e,)).toList(), ), ), //使用 TabBarView 来声明我们的内容组件 body: TabBarView( children: tabNames.map((e) { return TabsContainer( name: widget.name, color: widget.color, ); }).toList(), ), ), ); } } 自定义顶部tabbar除了上面默认的一横框的 tabbar 之外,还有类似与淘宝首页的那种效果,这种也可以通过自定义tabbar实现,如下所示(由于测试子页面多出来一个返回,所以居中有异常,正常不会有这这情况,根据情况自行调整即可)其实现主要通过 TabController、TabBarView,而 bar 我们自定义,通过 TabController 调节联动即可其中 TabController比较特殊,需要集成自 SingleTickerProviderStateMixin(由于多继承mixin需要with),然后延迟初始化 late 避免编译错误即可class TabInfosItem { final String name; final int index; bool selected; TabInfosItem({ required this.name, required this.selected, required this.index }); } class TabbarItemInAppBarView extends StatefulWidget { final Color color; final String name; const TabbarItemInAppBarView({Key? key, required this.color, required this.name}) : super(key: key); @override State createState() => _TabbarItemInAppBarViewState(); } //继承 SingleTickerProviderStateMixin class _TabbarItemInAppBarViewState extends State with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { @override bool get wantKeepAlive => true; List tabs = [ TabInfosItem(name: "推荐", selected: true, index: 0), TabInfosItem(name: "订阅", selected: false, index: 1), ]; //late 延迟初始化,避免报错,默认初始化是会出现 this 指向不是对象的错误问题 late TabController _tabController; @override void initState() { super.initState(); //初始化TabController _tabController = TabController(length: tabs.length, vsync: this); print(widget.name); } @override Widget build(BuildContext context) { super.build(context); return Scaffold( //因为有返回键,所以居中有问题这里就不多设置了 appBar: AppBar( title: Container( alignment: Alignment.center, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: tabs.map((TabInfosItem item) { return //这里就不多介绍了,添加额外参数,自定根据点击状态调整颜色线条等 //通过 tabController联动 TextButton( onPressed: () { _tabController.animateTo(item.index); var tab = tabs.map((e) { if (e.index == item.index) { e.selected = true; }else { e.selected = false; } return e; }).toList(); setState(() {}); }, child: Container( width: 60, alignment: Alignment.center, child: Text(item.name, style: TextStyle(color: item.selected ? Colors.white : Colors.grey),), ), ); }).toList(), ), ), ), //TabBarView与默认的一样 body: TabBarView( controller: _tabController, children: tabs.map((e) { return TabsContainer( name: widget.name, color: widget.color, ); }).toList(), ), ); } } 看到上面是不是感觉底部tabbar也可以通过其定制了呢,没错,可以的,但为了减少代码和避免一些其他问题,还是使用系统的好一些,除非这里默认的功能无法满足你的需求保存状态 AutomaticKeepAliveClientMixin前面的组件会看到很多都继承了 AutomaticKeepAliveClientMixin,其就是一个保存状态的多继承 mixin 基类,由于默认使用 tabbar 会丢失子组件状态,因此需要 AutomaticKeepAliveClientMixin 来进行保存状态,避免重新初始化,至于为什么会丢失,跟系统实现有关系了使用如下所示,继承后需要重写 wantKeepAlive 并且在 build 调用父类方法class _TabbarItemInAppBarViewState extends State with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { //需要重写该方法,返回为 true @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { //还需要调用super.build方法 super.build(context); return container(); } } 最后这边文章能新手带来一些方便,老手忘了也可以参考一下,大家一起学习进步哈以上文章来自[掘金]-[剪刀石头布啊]本程序使用github开源项目RSSHub提取聚合!
2022年10月20日
2 阅读
0 评论
0 点赞
2022-10-19
LVS负载均衡群集-掘金
1. 群集的含义 Cluster,集群、群集 由多台主机构成,但对外只表现为一个整体,只提供一个访问入口(域名或IP地址), 相当于一台大型计算机。 问题:互联网应用中,随着站点对硬件性能、响应速度、服务稳定性、数据可靠性等要求越来越高,单台服务器已经无法满足负载均衡及高可用的要求。解决方法: 使用价格昂贵的小型机、大型机。(纵向扩容) 使用多台相对廉价的普通服务器构建服务群集。(横向扩容) 通过整合多台服务器,使用LVS来达到服务器的高可用和负载均衡,并以同一个IP地址对外提供相同的服务在企业中常用的一种群集技术——LVS(Linux Virtual Server,Linux虚拟服务器) 纵向扩展 对服务器的CPU 内存 硬盘 等硬件进行升级或者扩容来实现 性能上限会有瓶颈,成本昂贵,收效比不高等问题横向扩展 通过增加服务器主机数量来应对高并发的场景2. 群集分类2.1 根据群集针对的目标差异,可以分三个类型 负载均衡群集 高可用群集 高性能运算群集 2.2 负载均衡群集(Load Balance Cluster)(LB)(阿里云SLB) 提高应用系统的响应能力、尽可能处理更多的访问请求、减少延迟为目标,获得高并发、高负载(LB)的整体性能 LB的负载分配依赖于主节点的分流算法,将来自客户机的访问请求分担给多个服务器节点,从而缓解整个系统的负载压力,例如,“DNS轮询”“反向代理”等 2.3 高可用群集(High Availability Cluster)(HA) 提高应用系统的可靠性、尽可能地减少中断时间为目标,确保服务的连续性,达到高可用(HA) 的容错效果。 HA的工作方式包括双工和主从两种模式,双工即所有节点同时在线;主从则只有主节点在线,但当出现故障时从节点能自动切换为主节点。例如,“故障切换” 、“双机热备” 等。 2.3 高性能运算群集(High Performance Computer Cluster)(HPC) 以提高应用系统的CPU运算速度、扩展硬件资源和分析能力为目标,获得相当于大型、超级计算机的高性能运算(HPC)能力。 高性能依赖于"分布式运算”、 “并行计算”,通过专用硬件和软件将多个服务器的CPU、内存等资源整合在一起,实现只有大型、超级计算机才具备的计算能力。例如,'云计算”、 “网格计算”等。 不同类型的群集可以根据实际需求进行合并,如高可用的负载均衡群集。3. 负载均衡群集3.1 负载均衡群集架构第一层,负载调度器(Load Balancer或Director) 负载均衡层访问整个群集系统的唯一入口,对外使用所有服务器共有的VIP地址,也称为群集 IP地址。通常会配置主、备两台调度器实现热备份,当主调度器失效以后能够平滑 替换至备用调度器,确保高可用性。第二层,服务器池(Server Pool) WEB应用层群集所提供的应用服务、由服务器池承担,其中每个节点具有独立的RIP地址(真 实IP),只处理调度器分发过来的客户机请求。当某个节点暂时失效时,负载调度 器的容错机制会将其隔离,等待错误排除以后再重新纳入服务器池。第三层,共享存储(Share Storage) 确保多台服务器使用的是相同的资源为服务器池中的所有节点提供稳定、一致的文件存取服务,确保整个群集的统一性 共享存储可以使用NAS设备,或者提供NFS共享服务的专用服务器。(因为节点服务器的资源都是由NAS或NFS提供,所以NAS或NFS需要做主备、或分布式,从而实现高可用。) 3.2 负载均衡群集的工作模式负载均衡群集是目前企业用得最多的群集类型。群集的负载调度技术有三种工作模式: 地址转换(NAT模式) IP隧道(IP-TUN) 直接路由(DR模式) 3.3 NAT模式(地址转换)地址转换 Network Address Translation,简称NAT模式 类似于防火墙的私有网络结构,负载调度器作为所有服务器节点的网关,即作为客户机 的访问入口,也是各节点回应客户机的访问出口。 服务器节点使用私有IP地址,与负载调度器位于同一个物理网络,安全性要优于其他两 种方式。 缺点:由于NAT的负载均衡器既作为用户的访问请求入口,也作为节点服务器响应请求的出口,承载两个方向的压力,调度器的性能会成为整个集群的瓶颈。 3.4 TUN模式(IP隧道)IP隧道: IP Tunnel ,简称TUN模式。 采用开放式的网络结构,负载调度器仅作为客户机的访问入口,各节点通过各自的Internet连接直接回应客户机,而不再经过负载调度器。承载的压力比NAT小。 服务器节点分散在互联网中的不同位置,具有独立的公网IP地址,通过专用IP隧道与负载调度器相互通信。 缺点:成本很高。 这种模式一般应用于特殊场景,比如将节点服务器分布在全国各地,防止被物理攻击(如地震、战争等),做灾备。 3.5 DR模式(直接路由) 直接路由: Direct Routing,简称DR模式。 采用半开放式的网络结构,与TUN模式的结构类似,负载调度器仅作为客户机的访问入口,各节点通过各自的Internet连接直接回应客户机,而不再经过负载调度器。承载的压力比NAT小。 但各节点并不是分散在各地,而是与调度器位于同一个物理网络。 负载调度器与各节点服务器通过本地网络连接,不需要建立专用的IP隧道。 群集类型 3类 负载均衡(LB) 提高应用系统的响应效率,处理更多的访问请求,减少延迟,提高并发和负载能力 高可用(HA) 提高应用系统的可靠性,减少服务中断的时间,确保服务的连续性 高性能运算(HPC) 将多台主机的硬件计算资源整合到一起实现分布式运行,比如 云计算 负载均衡群集的架构 负载调度器 通过VIP通过用户的请求,再通过调度算法确定要转发的节点服务器 服务器池中的节点服务器 通过RIP接受调度器转发来的请求,并处理请求进行响应 共享存储 为各个节点服务器提供稳定,一致的文件存取服务,比如NAS+NFS,文件服务器+NFS,分布式对象存储等存储设备 LVS模式 3种 **NAT(地址转换) ** 调度器作为所有节点服务器的网关,既做客户端的访问入口,也做节点服务器响应的访问出口,也就意味着调度器将成为整个集群系统的瓶颈 优点:由于在转发过程中做了地址转发,对于节点服务器安全性较比其它模式好 调度器至少要有2张网卡,一个承载VIP用于接受客户端的请求,另一个用于使用私有IP在同一局域网中连接节点服务器相互通讯 DR(直接路由) 调度器只负责接收客户端的请求,并根据调度算法转发给节点服务器,节点服务器在处理完请求后是直接响应返回给客户端,响应的数据包不经过调度器 调度器和节点服务器使用私有IP在同一个局域网中与连接节点服务器相互通讯 YUN(IP隧道) 结构与DR模式相类似,但是节点服务器分散在互联网各个位置,都具有独立的公网IP,通过专用的IP隧道与调度器相互通讯 4. LVS虚拟服务器Linux Virtual Server: 针对Linux内核开发的负载均衡 1998年5月,由我国的章文嵩博士创建 官方网站:www.linuxvirtualserver.org/ LVS实际上相当于基于IP地址的虚拟化应用,为基于IP地址和内容请求分发的负载均衡提出来一种高效的解决方法 4.1 LVS相关术语DS:Director Server。指的是前端负载均衡器。RS:Real Server。节点服务器,后端真实的工作服务器。VIP:向外部直接面向用户请求,作为用户请求的目标的IP地址。DIP:Director Server IP,主要用于和内部主机通讯的IP地址。RIP:Real Server IP,后端服务器的IP地址。CIP:Client IP,访问客户端的IP地址。5. LVS的负载调度算法5.1 固定调度算法:rr, wrr, dh,shrr:轮询算法(Round Robin) 将请求依次分配给不同的RS节点,即RS节点中均摊分配。适合于RS所有节点处理性能接近的情况。 将收到的访问请求安装顺序轮流分配给群集指定各节点(真实服务器),均等地对待每一台服务器,而不管服务器实际的连接数和系统负载。 wrr:加权轮询调度(Weighted Round Robin) 依据不同RS的权重值分配任务。权重值较高的RS将优先获得任务,并且分配到的连接数将比权值低的RS更多。相同权值的RS得到相同数目的连接数。 保证性能强的服务器承担更多的访问流量。 dh:目的地址哈希调度(destination hashing) 以目的地址为关键字查找一个静态hash表来获得所需RS。 sh:源地址哈希调度(source hashing) 以源地址为关键字查找--个静态hash表来获得需要的RS。 5.2 动态调度算法: wlc,lc,1blcwlc:加权最小连接数调度(Weighted Least Connections) 假设各台RS的权值依次为Wi,当前tcp连接数依次为Ti,依次取Ti/Wi为最小的RS作为下一个分配的RS。 在服务器节点的性能差异较大时,可以为真实服务器自动调整权重。 性能较高的节点将承担更大比例的活动连接负载。 lc:最小连接数调度( Least Connections) ipvs表存储了所有活动的连接。LB会比较将连接请求发送到当前连接最少的RS。 根据真实服务器已建立的连接数进行分配,将收到的访问请求优先分配给连接数最少的节点。 lblc:基于地址的最小连接数调度(locality-based least-connection) 将来自同一个目的地址的请求分配给同一-台RS,此时这台服务器是尚未满负荷的。否则就将这个请求分配给连接数最小的RS,并以它作为下一次分配的首先考虑。 6 ipvsadm工具选项 选项 含义 -A 添加虚拟服务器 -D 删除整个虚拟服务器 -s 指定负载调度算法(轮询: rr、加权轮询: wrr、最少连接: 1c、加权最少连接: wlc) -a 表示添加真实服务器(节点服务器) -d 删除某一个节点 -t 指定VIP地址及TCP端口 -r 指定RIP地址及TCP端口 -m 表示使用NAT群集模式 -g 表示使用DR模式 -i 表示使用TUN模式 -w 设置权重(权重为0时表示暂停节点) -p 60 表示保持长连接60秒; -l 列表查看LVS虚拟服务器(默认为查看所有) -n 以数字形式显示地址、端口等信息,常与“-l”选项组合使用。ipvsadm -1n 6.2 加载 ip_vs 通用模块 LVS现在已成为 Linux 内核的一部分,默认编译为 ip_vs 模块,必要时能够自动调动。在CentOS 7 系统中,手动加载 ip_vs 模块的命令如下: modprobe ip_vs //手动加载 ip_vs 模块 cat /proc/net/ip_vs //查看当前系统中ip_vs模块的版本信息 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn 加载 LVS 所有的负载调度算法[
[email protected]
ipvs]#pwd /usr/lib/modules/3.10.0-693.el7.x86_64/kernel/net/netfilter/ipvs [
[email protected]
ipvs]#ls ip_vs_dh.ko.xz ip_vs_lblcr.ko.xz ip_vs_rr.ko.xz ip_vs_wrr.ko.xz ip_vs_ftp.ko.xz ip_vs_lc.ko.xz ip_vs_sed.ko.xz ip_vs.ko.xz ip_vs_nq.ko.xz ip_vs_sh.ko.xz ip_vs_lblc.ko.xz ip_vs_pe_sip.ko.xz ip_vs_wlc.ko.xz 过滤所有调度算法的名称 [
[email protected]
ipvs]#ls | grep -o "^[^.]*" ip_vs_dh ip_vs_ftp ip_vs ip_vs_lblc ip_vs_lblcr ip_vs_lc ip_vs_nq ip_vs_pe_sip ip_vs_rr ip_vs_sed ip_vs_sh ip_vs_wlc ip_vs_wrr 加载所有调度算法 for i in $(ls /usr/lib/modules/$(uname -r)l/net/netfilter/ipvs|grep -o "^[^.]*");do echo $i; /sbin/modF filename $i >/dev/null 2>&1 && /sbin/modprobe $i;done NAT模式 LVS负载均衡群集部署部署NFS共享存储服务关闭防火墙,SELinux [
[email protected]
~]#systemctl disable --now firewalld.service Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service. Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service. [
[email protected]
~]#setenforce 0 安装nfs-utils,rpcbind软件 [ro
[email protected]
~]#yum install rpcbind nfs-utlis -y 新建目录,并创建站点文件 [
[email protected]
~]#cd /opt [
[email protected]
opt]#mkdir nfs [
[email protected]
opt]#cd nfs/ [
[email protected]
nfs]#mkdir w l [
[email protected]
nfs]#echo 'w is web 1!' > w/index.html [
[email protected]
nfs]#echo ' is web 1!' > l/index.html 修改共享配置文件,设置共享策略 [
[email protected]
nfs]#vim /etc/exports /opt/nfs/w 192.168.85.0/24 /opt/nfs/l 192.168.85.0/24 启动服务,并查看本机的NFS共享目录 [
[email protected]
~]#systemctl start rpcbind [
[email protected]
~]#systemctl start nfs [
[email protected]
nfs]#showmount -e //查看本机发布的NFS共享目录 Export list for localhost.localdomain: /opt/nfs/l 192.168.85.0/24 /opt/nfs/w 192.168.85.0/24 部署两台web节点服务器第一台web关闭防火墙selinux [
[email protected]
~]# systemctl stop firewalld [
[email protected]
~]# setenforce 0 安装httpd [
[email protected]
~]# yum install -y httpd 查看NFS共享目录信息 [
[email protected]
~]#showmount -e 192.168.85.50 Export list for 192.168.85.50: /opt/nfs/l 192.168.85.0/24 /opt/nfs/w 192.168.85.0/24 挂载站点 [
[email protected]
~]#mount 192.168.85.50:/opt/nfs/w /var/www/html/ [
[email protected]
~]#df 192.168.85.50:/opt/nfs/w 73364480 3735040 69629440 6% /var/www/html [
[email protected]
~]#cat /var/www/html/index.html w is web 1! 永久挂载 vim /etc/fstab 192.168.85.50:/opt/nfs/wa /var/www/html/ nfs defaults,_netdev 0 0 指定网关,网关地址设置为负载调度器的内网地址 [
[email protected]
~]#vim /etc/sysconfig/network-scripts/ifcfg-ens33 GATEWAY=192.168.85.80 //网关地址写负载调度器的内网网卡地址 #DNS1=8.8.8.8 重启网络服务,开启httpd服务 [
[email protected]
~]# systemctl restart network [
[email protected]
~]# systemctl start httpd 第二台web服务器关闭防火墙 [
[email protected]
~]#systemctl stop firewalld.service [
[email protected]
~]#setenforce 0 修改网卡,网关设置为负载调度服务的内网地址 [
[email protected]
~]#vim /etc/sysconfig/network-scripts/ifcfg-ens33 GATEWAY=192.168.85.80 #DNS1=8.8.8.8 [
[email protected]
~]#systemctl restart network 安装httpd [
[email protected]
~]#yum -y install httpd 查看nfs共享信息 [
[email protected]
~]#showmount -e 192.168.85.50 Export list for 192.168.85.50: /opt/nfs/l 192.168.85.0/24 /opt/nfs/w 192.168.85.0/24 挂载NFS共享目录 [
[email protected]
~]#mount 192.168.85.50:/opt/nfs/l /var/www/html/ [
[email protected]
~]#df 192.168.85.50:/opt/nfs/l 73364480 3740160 69624320 6% /var/www/html [
[email protected]
~]#cat /var/www/html/index.html l is web 1! 永久挂载 vim /etc/fstab 192.168.85.50:/opt/nfs/la /var/www/html nfs defaults,_netdev 0 0 mount -a 重启httpd服务 [
[email protected]
~]#systemctl start httpd 部署LVS负载调度服务器添加网卡 [
[email protected]
~]#cd /etc/sysconfig/network-scripts/ [
[email protected]
network-scripts]#vim ifcfg-ens33 #GATEWAY=192.168.85.2 //将网卡和DNS服务器地址注释掉 #DNS1=8.8.8.8 [
[email protected]
network-scripts]#cp ifcfg-ens33 ifcfg-ens36 [
[email protected]
network-scripts]#vim ifcfg-ens36 IPADDR=12.0.0.12 //设置IP地址 NETMASK=255.255.255.0 #GATEWAY=192.168.85.2 #DNS1=8.8.8.8 [
[email protected]
network-scripts]#systemctl restart network 开启路由转发功能 [
[email protected]
network-scripts]#vim /etc/sysctl.conf net.ipv4.ip_forward = 1 [
[email protected]
network-scripts]#sysctl -p net.ipv4.ip_forward = 1 配置SNAT策略(用作内网连接外网) [
[email protected]
network-scripts]#iptables -F -t nat [
[email protected]
network-scripts]#iptables -t nat -A POSTROUTING -s 192.168.-o ens36 -j SNAT --to 12.0.0.12 [
[email protected]
network-scripts]#iptables -nL POSTROUTING -t nat Chain POSTROUTING (policy ACCEPT) target prot opt source destination SNAT all -- 192.168.85.0/24 0.0.0.0/0 to:12.0.0.12 加载LVS内核模块 [
[email protected]
network-scripts]#modprobe ip_vs [
[email protected]
network-scripts]#cat /proc/net/ip_vs IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn 加载LVS所有调度算法 [
[email protected]
network-scripts]#for i in $(ls /usr/lib/modules/$(uname -r)/kernel/net/netfilter/ipvs|grep -o "^[^.]*");do echo $i; /sbin/modinfo -F filename $i >/dev/null 2>&1 && /sbin/modprobe $i;done ip_vs_dh ip_vs_ftp ip_vs ip_vs_lblc ip_vs_lblcr ip_vs_lc ip_vs_nq ip_vs_pe_sip ip_vs_rr ip_vs_sed ip_vs_sh ip_vs_wlc ip_vs_wrr 安装ipvsadm管理工具,开启服务 [
[email protected]
~]#mount /dev/sr0 /mnt [
[email protected]
~]#yum -y install ipvsadm.x86_64 开启服务前必须保存负载分配策略,生成/etc/sysconfig/ipvsadm文件。如果该文件不存在,服务无法启动。 [
[email protected]
~]#ipvsadm-save >/etc/sysconfig//ipvsadm [
[email protected]
~]#systemctl start ipvsadm.service 配置负载分配策略(NAT模式只需要在负载器上配置,节点服务器不需要特殊配置) [
[email protected]
~]#ipvsadm -C //清空原有规则 指定指定VIP地址及TCP端口,-s rr 指定负载调度策略为轮询 [
[email protected]
~]#ipvsadm -A -t 12.0.0.12:80 -s rr 先指定虚拟服务器再添加真实服务器地址,-r指定真实服务器地址,-m指定nat模式。-w指定权重值,权重为1时可省略不写 [
[email protected]
~]#ipvsadm -a -t 12.0.0.12:80 -r 192.168.85.60:80 -m [
[email protected]
~]#ipvsadm -a -t 12.0.0.12:80 -r 192.168.85.70:80 -m [
[email protected]
~]#ipvsadm //查看策略 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP linux7-8:http rr -> 192.168.85.60:http Masq 1 0 0 -> 192.168.85.70:http Masq 1 0 0 [
[email protected]
~]#ipvsadm-save >/etc/sysconfig/ipvsadm //保存负载分配策略 [
[email protected]
~]#ipvsadm -ln //以数字形式查看策略,Masq表示NAT模式 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 12.0.0.12:80 rr -> 192.168.85.60:80 Masq 1 0 0 -> 192.168.85.70:80 Masq 1 0 0 以上文章来自[掘金]-[命令加载中]本程序使用github开源项目RSSHub提取聚合!
2022年10月19日
0 阅读
0 评论
0 点赞
2022-10-19
[Android开发学iOS系列] 快速上手UIKit-掘金
快速上手iOS UIKitUIKit是苹果官方的framework, 其中包含了各种UI组件, window和view, 事件处理, 交互, 动画, 资源管理等基础设施支持.按照前面的介绍, 用UIKit写UI可以用storyboard(Interface Builder)和代码两种方式.大体的思路都是添加组件后, 设置属性, 设置尺寸位置约束, 处理响应事件.这里主要介绍用代码写的情形. 希望这篇文章, 可以帮你快速上手UIKit, 熟悉常用的组件, 完成一些简单的UI界面相关任务.在代码中写UI的基本步骤在代码中写UI的步骤大致是: 初始化. addSubview添加到当前view, 或hierarchy中的其他可达view. 设置约束. 比如:class ViewController: UIViewController { var myLabel: UILabel! override func loadView() { view = UIView() view.backgroundColor = .white // 创建实例 myLabel = UILabel() myLabel.translatesAutoresizingMaskIntoConstraints = false myLabel.text = "Hello" // 添加到view中 view.addSubview(myLabel) // 设置约束 NSLayoutConstraint.activate([ myLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), myLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), ]) } } 这里有几点说明: var** myLabel: UILabel! 组件字段这样声明有lateinit的作用, 如果不带!会报错, 说controller没有init方法. 如果在代码中设置UI组件的constraints, 那么这个属性经常要设置为false: translatesAutoresizingMaskIntoConstraints = **false**. 如果组件的位置是通过frame来设置的, 则不用设置这个属性. 约束有多种写法, 这里只是其中一种, 用anchor的方式. 常用组件文字: UILabel设置文字等属性:myLabel = UILabel() myLabel.translatesAutoresizingMaskIntoConstraints = false myLabel.font = UIFont.systemFont(ofSize: 24) myLabel.text = "Hello" myLabel.numberOfLines = 0 myLabel.textAlignment = .right 给UILabel设置点击事件:myLabel.isUserInteractionEnabled = true let tapGesture = UITapGestureRecognizer(target: self, action: #selector(userDidTapLabel(tapGestureRecognizer:))) myLabel.addGestureRecognizer(tapGesture) 点击事件处理方法:@objc func userDidTapLabel(tapGestureRecognizer _: UITapGestureRecognizer) { print("label clicked!") } 这里有#selector, 对应的userDidTapLabel方法要加上@objc. 便于OC的代码调用能找到swift的方法.给UILabel设置点击事件和UIButton不同, 这点我们后面说继承关系的时候解释一下.按钮: UIButton设置文字:submitButton = UIButton(type: .system) submitButton.translatesAutoresizingMaskIntoConstraints = false submitButton.titleLabel?.font = UIFont.systemFont(ofSize: 36) submitButton.setTitle("SUBMIT", for: .normal) submitButton.setTitleColor(.black, for: .normal) 设置点击事件:submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside) @objc func submitTapped(_ sender: UIButton) { } 这里使用@objc的理由同上.基本上我们在iOS代码中用到#的时候, 对应的方法都要加上@objc.输入框: UITextFieldmyTextField = UITextField() myTextField.translatesAutoresizingMaskIntoConstraints = false myTextField.placeholder = "What's your name?" myTextField.textAlignment = .center myTextField.font = UIFont.systemFont(ofSize: 44) 想要禁用输入框可以这样:myTextField.isUserInteractionEnabled = false 弹框在app里简单的交互我们经常需要弹出一个对话框:let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Ok", style: .default)) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) present(alert, animated: true) 其中preferredStyle有.alert和.actionSheet两种..alert是中心的对话框, 一般用于信息提示或者确认操作; .actionSheet是底部的bottom sheet, 一般用来在几个选项中做选择.其他 view中比较常用的属性isHidden, 控制view是否需要隐藏. 所有的UIView都有一个layer属性. 设置border的宽度和颜色就在layer上设置. CALayer在UIView之下. 所以不知道UIColor, 只知道CGColor. 本文仅列出几个常用组件, 更多的请看官方示例.这里可以下载继承关系NSObject是所有Cocoa Touch class的基类. 所有UIKit中的类都是它的子类.这里有一个类关系的图: 我们这里不展开讲述所有了, 只解答一下前面提出的关于UILabel点击事件的问题.这里可以看到UILabel和UIButton虽然都继承了UIView, 但是UIButton的继承层次更深一些, 它还继承了了UIControl.可以看到和UIButton平级的还有好几个子类.Controls使用的是target-action机制, 所有的action都通过方法: addTarget(_:action:for:) 添加.约束Constraints当在代码中设置约束时, 有三种选择: 使用layout anchors. 使用NSLayoutConstraint类. 使用Visual Format Language. 上面我们提到过的就是其中Layout Anchors的写法:初级单个写法:buttonsView.topAnchor.constraint(equalTo: view.centerYAnchor).isActive = true buttonsView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true buttonsView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true buttonsView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 放进数组里批量激活写法:NSLayoutConstraint.activate([ buttonsView.topAnchor.constraint(equalTo: view.centerYAnchor), buttonsView.bottomAnchor.constraint(equalTo: view.bottomAnchor), buttonsView.leadingAnchor.constraint(equalTo: view.leadingAnchor), buttonsView.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) 感觉是对新手比较直观的一种写法.其他写法文末有参考文档.PS: 项目中更流行用 SnapKit.区域限制 safeAreaLayoutGuide : 去掉圆角和刘海. layoutMarginsGuide : safe area的内部再加上一些额外的margin. Bonus 友情提示: 在xcode里就可以看官方文档, 快捷键是Cmd + Shift + 0. References UIKit Documentation UIKit Catalog https://codewithchris.com/swift-tutorial-complete/#uikit Programmatically Creating Constraints 以上文章来自[掘金]-[圣骑士Wind]本程序使用github开源项目RSSHub提取聚合!
2022年10月19日
0 阅读
0 评论
0 点赞
2022-10-19
iOS小技能:集成下拉刷新控件 & 实现无感知上拉加载更多-掘金
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情引言需求:由于消息列表,数据量比较大,为了提升用户体验,需采用分页加载显示数据案例:iOS零售版ERP APP增加支付奖励消息通知通知信息(定时xx点;历史消息可查)2021-04-29 尊敬的商家,您参与的xxx激励金活动,昨日参与成功10笔,共获得激励金1元! I 集成下/上拉刷新控件1.1 定义相关分页属性 分页属性 @property (nonatomic , assign) NSInteger pageNum;//当前页码 @property (nonatomic , assign) NSInteger pageCount;// 总页数 @property (nonatomic , assign) BOOL isfooterRereshing; // 每页显示数... VM中的事件和数据属性 @property (nonatomic,strong) NSMutableArray *datas; @property (nonatomic,strong) RACSubject *reloadSubject; @property (nonatomic,strong) RACSubject *ShowNoviewSubject; @property (nonatomic,strong) RACSubject *hidenNoviewSubject; 1.2 监听下拉和上拉事件 VC 监听和处理下拉和上拉事件 _tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(footerRereshing)]; _tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(headerRereshing)]; 处理上拉加载数据事件 /** 用于标志下拉动作*/ @property (nonatomic , assign) BOOL isfooterRereshing; - (void)footerRereshing { self.isfooterRereshing = YES; if ((_pageNum + 1) > _pageCount) { [self.tableView.mj_footer endRefreshingWithNoMoreData]; return; } _pageNum = _pageNum + 1; [self doorRequest]; } 处理下拉刷新数据事件 - (void)headerRereshing { self.isfooterRereshing = NO; [_doorArr removeAllObjects];// 移除数据,可请求成功之后,再移除 _pageNum = 1; [self doorRequest]; } 1.3 请求数据的处理请求成功和失败都要关闭刷新视图 [weakSelf.vcView.tableView.mj_footer endRefreshing]; [weakSelf.vcView.tableView.mj_header endRefreshing]; 完成处理的代码- (void)doorRequest { //暂无数据 if (self.viewModel.datas.count == 0) { [self.viewModel.ShowNoviewSubject sendNext:QCTLocal(@"no_data")]; }else{ [self.viewModel.hidenNoviewSubject sendNext:QCTLocal(@"")]; } if(![UserInfoModel.shareUserInfoModel ispayStoreId]){ // [self showHUDMessage:@"请先进件"]; // 显示暂无数据 return; } NSString *post = [NSString stringWithFormat:@"%@%@",currentPayHost,@""]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; //111850 // [params setValue:@"" forKey:@"sid"]; [params setValue:UserInfoModel.shareUserInfoModel.store.payStoreId forKey:@"sid"]; [params setValue:[[NSNumber numberWithInteger:self.pageNum]description] forKey:@"page"]; [params setValue:kPageSize forKey:@"pageSize"]; __weak __typeof__(self) weakSelf = self; [QCTNetworkHelper Post:post parameters:params success:^(NSDictionary* responseObj) { NSDictionary *data = nil; if([responseObj.allKeys containsObject:@"data"]){ data = responseObj[@"data"]; }else{ [self showHUDMessage:@"数据异常!"]; // [SVProgressHUD showInfoWithStatus:@"数据异常!"]; return;// 获取数据失败 } if([data.allKeys containsObject:@"data"]){ data = responseObj[@"data"]; }else{ // [SVProgressHUD showInfoWithStatus:@"数据异常!"]; [self showHUDMessage:@"数据异常!"]; return;// 获取数据失败 } NSMutableArray* tmparrresult = [ERPTradeRewardReportDto mj_objectArrayWithKeyValuesArray:data[@"data"]]; if(self.isfooterRereshing){ [weakSelf.viewModel.datas addObjectsFromArray:tmparrresult]; }else{ weakSelf.viewModel.datas = tmparrresult ; } [weakSelf.vcView.tableView reloadData]; weakSelf.pageCount = [responseObj[@"data"][@"pageCount"] integerValue]; weakSelf.pageNum = [responseObj[@"data"][@"page"] integerValue]; [weakSelf.vcView.tableView.mj_footer endRefreshing]; [weakSelf.vcView.tableView.mj_header endRefreshing]; if (weakSelf.viewModel.datas.count == 0) { [weakSelf.viewModel.ShowNoviewSubject sendNext:QCTLocal(@"no_data")]; }else{ [weakSelf.viewModel.hidenNoviewSubject sendNext:QCTLocal(@"no_data")]; } } failure:^(NSError * _Nonnull error) { [QCTNetworkHelper showLoading_failed_please_try_again_laterBlock]; [self.vcView.tableView.mj_footer endRefreshing]; [self.vcView.tableView.mj_header endRefreshing]; } bizFailure:^(id _Nonnull responseObj) { [self.vcView.tableView.mj_footer endRefreshing]; [self.vcView.tableView.mj_header endRefreshing]; [QCTNetworkHelper showresponseObjmessage:responseObj]; } isShowLoadingDataGif:YES]; } II iOS实现无感知上拉加载更多2.1 思路1:UITableViewDataSourcePrefetching// this protocol can provide information about cells before they are displayed on screen. @protocol UITableViewDataSourcePrefetching @required // indexPaths are ordered ascending by geometric distance from the table view - (void)tableView:(UITableView *)tableView prefetchRowsAtIndexPaths:(NSArray *)indexPaths; @optional // indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -tableView:prefetchRowsAtIndexPaths: - (void)tableView:(UITableView *)tableView cancelPrefetchingForRowsAtIndexPaths:(NSArray *)indexPaths; @end 2.2 实现思路2:通过 KVO 去监听 scrollView 的 contentOffset 变化MJRefreshAutoFooter 有个专门的属性triggerAutomaticallyRefreshPercent 去做自动刷新 #import "MJRefreshFooter.h" NS_ASSUME_NONNULL_BEGIN @interface MJRefreshAutoFooter : MJRefreshFooter /** 是否自动刷新(默认为YES) */ @property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh; /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ @property (assign, nonatomic) CGFloat appearencePercentTriggerAutoRefresh MJRefreshDeprecated("请使用triggerAutomaticallyRefreshPercent属性"); /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ @property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent; /** 自动触发次数, 默认为 1, 仅在拖拽 ScrollView 时才生效, 如果为 -1, 则为无限触发 */ @property (nonatomic) NSInteger autoTriggerTimes; @end III 刷新控件的适配 上拉加载:安全区域距离适配 #define k_safeAreaInsetsBottom [UIApplication sharedApplication].delegate.window.safeAreaInsets.bottom #define isIphoneX isHasSafeAreaInsets #define k_ignoredScrollViewContentInsetBottom (isIphoneX?k_safeAreaInsetsBottom:0) _vcView.tableView.mj_footer.ignoredScrollViewContentInsetBottom = k_ignoredScrollViewContentInsetBottom; 下拉刷新适配:present 半屏适配、设置下拉样式 https://blog.csdn.net/z929118967/article/details/104477314 分页并发适配: 方式1. 升级MJRefresh到3.7.5版本 Fix/duplicated async method -> Installing MJRefresh 3.7.5 (was 3.3.1) 方式2. 使用自动刷新控件MJRefreshNormalHeader->MJRefreshAutoNormalFooter see also案例:新浪微博API(获取用户微博数据) https://download.csdn.net/download/u011018979/20689654 集成下拉刷新控件:下拉刷新 HWHomeTableViewController 获取未读消息数: HWHomeTableViewController 封装标题按钮:HWTitleButton 更多内容请关注公众号:iOS逆向以上文章来自[掘金]-[公众号iOS逆向]本程序使用github开源项目RSSHub提取聚合!
2022年10月19日
0 阅读
0 评论
0 点赞
2022-10-19
21、 Flutter Widgets 之 标签类控件大全Chip-掘金
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情概述:Flutter 标签类控件大全ChipFlutter内置了多个标签类控件,但本质上它们都是同一个控件,只不过是属性参数不同而已,在学习的过程中可以将其放在放在一起学习,方便记忆。RawChipMaterial风格标签控件,此控件是其他标签控件的基类,通常情况下,不会直接创建此控件,而是使用如下控件: Chip InputChip ChoiceChip FilterChip ActionChip 如果你想自定义标签类控件,通常使用此控件。RawChip可以通过设置onSelected被选中,设置onDeleted被删除,也可以通过设置onPressed而像一个按钮,它有一个label属性,有一个前置(avatar)和后置图标(deleteIcon)。 RawChip(label: Text('RawChip')), 效果:设置左侧控件,一般是图标: RawChip( avatar: CircleAvatar(child: Text('R'),), label: Text('RawChip'), isEnabled: false,//禁止点选状态 ), 设置label的样式和内边距: RawChip( avatar: CircleAvatar(child: Text('R'),), label: Text('RawChip'), // isEnabled: false,//禁止点选状态 labelPadding: EdgeInsets.symmetric(horizontal: 20), padding: EdgeInsets.only(left: 10,right: 10,top: 5), ), 设置删除相关属性: RawChip( label: Text('RawChip'), onDeleted: (){ print('onDeleted'); }, deleteIcon: Icon(Icons.delete), deleteIconColor: Colors.red, deleteButtonTooltipMessage: "删除", // isEnabled: false,//禁止点选状态 labelPadding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.only(left: 10,right: 10,top: 5,bottom: 5), ), 设置形状、背景颜色及内边距,阴影: RawChip( label: Text('RawChip'), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), backgroundColor: Colors.blue, padding: EdgeInsets.symmetric(vertical: 10), elevation: 8, shadowColor: Colors.grey, ) materialTapTargetSize是配置组件点击区域大小的属性,很多组件都有此属性,比如:[FloatingActionButton], only the mini tap target size is increased. * [MaterialButton] * [OutlineButton] * [FlatButton] * [RaisedButton] * [TimePicker] * [SnackBar] * [Chip] * [RawChip] * [InputChip] * [ChoiceChip] * [FilterChip] * [ActionChip] * [Radio] * [Switch] * [Checkbox] MaterialTapTargetSize有2个值,分别为: padded:最小点击区域为48*48。 shrinkWrap:子组件的实际大小。 设置选中状态、颜色: RawChip( label: Text('RawChip'), selected: _selected, onSelected: (v){ setState(() { _selected =v; }); }, selectedColor: Colors.blue, selectedShadowColor: Colors.red, ) ChipChip是一个简单的标签控件,仅显示信息和删除相关属性,是一个简化版的RawChip,用法和RawChip一样。源代码如下:@override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); return RawChip( avatar: avatar, label: label, labelStyle: labelStyle, labelPadding: labelPadding, deleteIcon: deleteIcon, onDeleted: onDeleted, deleteIconColor: deleteIconColor, deleteButtonTooltipMessage: deleteButtonTooltipMessage, tapEnabled: false, shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, autofocus: autofocus, backgroundColor: backgroundColor, padding: padding, materialTapTargetSize: materialTapTargetSize, elevation: elevation, shadowColor: shadowColor, isEnabled: true, ); } InputChip以紧凑的形式表示一条复杂的信息,例如实体(人,地方或事物)或对话文本。InputChip 本质上也是RawChip,用法和RawChip一样。源代码如下:override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); return RawChip( avatar: avatar, label: label, labelStyle: labelStyle, labelPadding: labelPadding, deleteIcon: deleteIcon, onDeleted: onDeleted, deleteIconColor: deleteIconColor, deleteButtonTooltipMessage: deleteButtonTooltipMessage, onSelected: onSelected, onPressed: onPressed, pressElevation: pressElevation, selected: selected, tapEnabled: true, disabledColor: disabledColor, selectedColor: selectedColor, tooltip: tooltip, shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, autofocus: autofocus, backgroundColor: backgroundColor, padding: padding, materialTapTargetSize: materialTapTargetSize, elevation: elevation, shadowColor: shadowColor, selectedShadowColor: selectedShadowColor, showCheckmark: showCheckmark, checkmarkColor: checkmarkColor, isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null), avatarBorder: avatarBorder, ); } 基本用法: InputChip( avatar: CircleAvatar( radius: 12.0, ), label: Text( 'InputChip', style: TextStyle(fontSize: 12.0), ), shadowColor: Colors.grey, deleteIcon: Icon( Icons.close, color: Colors.black54, size: 14.0, ), onDeleted: () { print('onDeleted'); }, onSelected: (bool selected) { setState(() { _selected = selected; }); }, selectedColor: Colors.orange, disabledColor: Colors.grey, selected: _selected, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, labelStyle: TextStyle(color: Colors.black54), ), ChoiceChip允许从一组选项中进行单个选择,创建一个类似于单选按钮的标签,本质上ChoiceChip也是一个RawChip,ChoiceChip本身不具备单选属性。 int _selectedIndex = 0; Wrap( spacing: 5, children: List.generate(20, (index){ return ChoiceChip( label: Text('测试 $index'), selected: _selectedIndex==index, onSelected: (v){ setState(() { _selectedIndex =index; }); }, ); }).toList(), ) FilterChipFilterChip可以作为过滤标签,本质上也是一个RawChip,用法如下: List _filters = []; _buildFilterChip(){ return Column( children: [ Wrap( spacing: 15, children: List.generate(10, (index) { return FilterChip( label: Text('测试 $index'), selected: _filters.contains('$index'), onSelected: (v) { setState(() { if(v){ _filters.add('$index'); }else{ _filters.removeWhere((f){ return f == '$index'; }); } }); }, ); }).toList(), ), Text('选中:${_filters.join(',')}'), ], ); } 运行效果:总结:本篇主要讲了以下几种chip组件的用法案例: RawChip:是Material风格标签控件,此控件是其他标签控件的基类,通常情况下,不会直接创建此控件,而是使用其他的标签控件。 InputChip:以紧凑的形式表示一条复杂的信息,例如实体(人,地方或事物)或对话文本。InputChip 本质上也是RawChip,用法和RawChip一样 ChoiceChip:允许从一组选项中进行单个选择,创建一个类似于单选按钮的标签,本质上ChoiceChip也是一个RawChip,ChoiceChip本身不具备单选属性。 FilterChip:可以作为过滤标签,本质上也是一个RawChip ActionChip:显示与主要内容有关的一组动作,本质上也是一个RawChip Chip:一个简单的标签控件,仅显示信息和删除相关属性,是一个简化版的RawChip,用法和RawChip一样 以上文章来自[掘金]-[风雨_83]本程序使用github开源项目RSSHub提取聚合!
2022年10月19日
0 阅读
0 评论
0 点赞
2022-10-19
Flutter 解放双手-Shell自动化打包之APK-掘金
构建app系统类型,分apk,ios等等类型. eg: flutter build apk --release(默认类型,可以不用写)--no-shrink 不使用混淆 如果你想使用混淆,请手动删除即可.--darf-define(重点要讲) 可以用于和项目传参数,这样就可以让我们,最起码实现多渠道分发的功能 eg: --darf-define=name=params eg: 多个参数就是--darf-define=name=params --darf-define=name=params 即可下面我们来讲一下和 --darf-define 相互配合的就是android项目配置android 项目配置--dart-define=CHANNEL="$1" --dart-define=DEBUG="$build_type"那我们怎么让安卓项目接收这些参数呢,下面我们先看一下,不配置后面参数的时候,我们来看下,打包成功后,显示的原始apk名字. flutter build apk 执行脚本如下 如图所示,默认情况下我们打包的环境的release,所以flutter自己导出的apk,默认就是app+budiltype.apk,即app-release.apk.那我们怎么修改打包出的apk名字呢. 修改android原生中gradle中的配置,添加修改apk名字的代码.如下所示 对应的代码如下所示applicationVariants.all { variant -> variant.outputs.all { output -> // 设置新名称 println(dartEnvironmentVariables.DEBUG) println(dartEnvironmentVariables) println(variant.buildType.name) def newApkName ="APP_${dartEnvironmentVariables.CHANNEL}_${dartEnvironmentVariables.DEBUG}_${flutterVersionName}_${flutterVersionCode.toInteger()}.apk" outputFileName = new File(newApkName) } } 这样我们就可以去定义apk的名字了.细心的你会发现,上面的代码中有几个和dart有关的参数. dartEnvironmentVariables flutterVersionName flutterVersionCode dartEnvironmentVariables 这个是我在buidl.gradle中,定义的一个属性.来我们也看一下,它具体做了些什么呢 从代码中,我们终于找到了和 --dart--define有关的代码了.通过上面的方式,我们就可以获取到 --dart-define=CHANNEL="$1" --dart-define=DEBUG="$build_type" 中的CHANNEL和DEBUG,对应的参数了. flutterVersionName flutter项目pubspec.yaml中的name: 字段 flutterVersionCode flutter项目pubspec.yaml中的version: 字段中 + 前面版本 此时我们设置的apk命名规范就变成如下所示:APP_+ {app渠道}_ + {buildType}_ + {flutterVersionName}_ + {flutterVersionCode}.apk到此.apk名字修改就配置完成了.剩下的就是打包APK时,代码的配置了.android 最终打包最终还是到了这一步,终于经过前面的配置.我们还是来到了这里.经过前面的项目配置,以及pack_apk.sh,文件的编写.我们的APK打包终于完成. 并且我们的android项目也适配了,多渠道打包的功能.就是通过 --dart--define 参数的形式,去重新编写不同渠道的apk的名字.也可以根据业务需求,做不同渠道的事件处理,这里我就不一一讲解了.那我们就再看一下,脚本是怎么实现多渠道打包.如下所示# 构建渠道包 apkBuild() { echomsg "开始打包" if [[ $apk_chanhels_length == 0 || $upload_type == 2 ]]; then flutterBuild "Normal" elif [[ $pack_apk_channel == 0 && $apk_chanhels_length != 0 ]]; then echomsg "开始构建: 全部渠道包" for ((i = 0; i < "$apk_chanhels_length"; i++)); do echomsg "正在构建: ${apk_channels[$i]}渠道包" flutterBuild apk_channels["$i"] done else flutterBuild apk_channels["$pack_apk_channe"] fi } 从代码上,我这边做了三个逻辑处理,分别如下: 构建默认渠道. 构建全渠道的 构建指定渠道的 这样基本上就满足了我们正常的打包需求.至此.APK自动化打包完成.系列文章 Flutter 解放双手-Shell自动化打包之概要 Flutter 解放双手-Shell自动化打包之配置 Flutter 解放双手-Shell自动化打包之IPA(未发布) flutter 解放双手 --dart--define(未发布) flutter 解放双手-flutter build(未发布) 以上文章来自[掘金]-[TT_Close]本程序使用github开源项目RSSHub提取聚合!
2022年10月19日
0 阅读
0 评论
0 点赞
2022-10-19
iOS嵌入虚拟引擎unity3d-掘金
我正在参加「掘金·启航计划」前言最近虚拟引擎还是很火的,QQ超级秀,淘宝人生,抖音仔仔,玩的都是虚拟偶像,那如果我们 APP 如果也想做类似的功能,那我们好做吗,有没有什么优缺点,用什么方案比较好,这些都值得我们去探讨一下。因为没有接触过 UE4 ,本文仅讨论 unity 方案如何嵌入使用,如何协议交互,以及带来的问题。Unity导入iOS工程其实 unity 导出的包,也是一个 Target,那我们这里采用的方案是把 Target 接入到我们项目工程,具体看业务决定,有些是用iOS SDK 嵌入到 unity 的 iOS 包,这个不在本文讨论范围内。本来是一个 Target ,我们工程也是一个 Target ,这时候我们就可以通过 workspace 来添加到一起。首先我们把 unity 包放到我们工程下面。然后,我们在项目工程中添加 Unity-iPhone.xcodepro。把 unity 工程导入到项目中。我们还需要更改一些项目配置。首先,我们需要对 unity 工程的 bitcode 设置为 NO。 然后 Data 文件夹勾上 UnityFramework 。最后,我们需要把 NativeCallProxy.h文件更改unityFramework权限为Public。这样,我们项目配置就完成了。配置Unity导入工程成功后,我们就要对 unity 进行代码配置使用。首先,我们创建一个叫UnityManager类的单例工具,专门来处理 unity 配置信息,以及交互使用。我们优先导入头文件#include ,配置一下信息参数,如下: @property (nonatomic, assign) int gArgc; @property (nonatomic, assign) char** gArgv; @property (nonatomic, strong) UnityFramework *unityFramework; @property (nonatomic, strong, readonly ) UIView *unityView; 然后我们在 main 赋值一下gArgc,gArgv。int main(int argc, char * argv[]) { @autoreleasepool { UnityManager.shareInstance.gArgc = argc; UnityManager.shareInstance.gArgv = argv; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 接着我们在 UnityManager 添加一个初始化 Unity 加载方法。- (UnityFramework *)loadUnityFramework { NSString* bundlePath = nil; bundlePath = [[NSBundle mainBundle] bundlePath]; bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"]; NSBundle* bundle = [NSBundle bundleWithPath: bundlePath]; if ([bundle isLoaded] == false) { [bundle load]; } UnityFramework* ufw = [bundle.principalClass getInstance]; if (![ufw appController]) { // unity is not initialized [ufw setExecuteHeader: &_mh_execute_header]; } return ufw; } 然后我们添加一个启动引擎的方法。- (void)loadUnityWithComplete:(void(^)(void))complete { if (!self.unityView) { [self setUnityFramework: [self loadUnityFramework]]; [[self unityFramework] setDataBundleId: "com.unity3d.framework"]; [[self unityFramework] registerFrameworkListener: self]; // 用于桥接使用 [NSClassFromString(@"FrameworkLibAPI") registerAPIforNativeCalls:self]; [[self unityFramework] runEmbeddedWithArgc:self.gArgc argv: self.gArgv appLaunchOpts: self.launchOptions]; self.unityView = [[[self unityFramework] appController] rootView]; self.unityFramework.appController.window.hidden = YES; } // 等unity回调信息用到 self.loadUnityComplete = complete; } 最后我们把 unity 加入到我们想要展示的视图当中即可。[self.view addSubview:OPRUnityManager.shareInstance.unityView]; 视图的大小可以自定义哦。unity 协议对接NativeCallProxy.h文件里面包含了我们获取 unity 信息的桥接协议。所以我们的 UnityManager 需要遵守NativeCallsProtocol。这样我们就可以接收到 unity 的信息。另外我们着重关注UnityFramework,这里面赋予了我们好多可以已使用功能。那这里我们想发消息给 unity,就可以利用下面的方法,名字和 unity 一起命令即可。- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg { UnitySendMessage(goName, name, msg); } 就这样,我们就完成了双方的通信功能了。unity遇到的问题问题1:unityFramework 的 rootView 问题我们获取unityView视图是通过 unityFramework 的 rootView获取的,它本身就有自己的 window。self.unityView = [[[self unityFramework] appController] rootView]; 如果我们一个A视图添加了 unityView ,然后去到另外一个B视图,也添加一个 unityView,这时候之前的A视图就不会有unityView,我们只能回到A视图的时候,再重新布局一次。问题2:unityView 手势问题首先我们得保证,unityView这个视图层级,没有被其它view挡住,就算这个view设置了clearColor,也会影响unityView的触摸手势问题。第二种就是滑动,刚好我们unityView加入到我们的 scrollView 里面,这时候左右触摸 unityView,也会影响我们 scrollView 的抖动。这时候我们需要加入一个手势判断,通过45度来决定,现在触发的是 unityView 的事件,还是 scrollView 的手势事件。- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if ([gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) { UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer; CGPoint translation = [pan translationInView:self]; CGFloat absX = fabs(translation.x); CGFloat absY = fabs(translation.y); if (absX > absY ) { return NO; } else if (absY > absX) { return YES; } } return YES; } 问题3:unity 排查问题iOS 和 unity 交互方面,如果只是提供一个入口,那里面的排查处理就比较简单,那如果很多页面都可能用到unity,而且又有原生的,那交互起来就比较多了,如果出了问题,那该如何排查呢? 首先 unity 尽量写全一些日志信息,这样我们可以通过 Xcode 的控制台去看有没有报错信息。 我们可以让 unity 开启本地服务器,在我们交互协议的时候,把信息发送出去,这样只要有手机就可以看调用流程。 问题4:unity 内存暴增问题unity 引擎加进来,自然会增加内存,而且要渲染各种资源,绘制各种东西,这时候如果想排查为什么会增量很多,就可以通过 xcode -> Debug -> Capture GPU Workload 来查看内存问题。问题5: 电量消耗问题自从引入了 unity 引擎后,电量消耗也明显加快了,这块的问题也是 unity 团队非常关注的问题。严重的时候,电量消耗方面,GPU占用 45%。我们 APP 端能做了,就是及时给数据进行反馈。所以我们在每个页面都给一个数据反馈,显示实时 CPU 使用率,内存大小。内存大小:+ (int64_t)memoryUsage { int64_t memoryUsageInByte = 0; task_vm_info_data_t vmInfo; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count); if(kernelReturn == KERN_SUCCESS) { memoryUsageInByte = (int64_t) vmInfo.phys_footprint; } else { } return memoryUsageInByte; } CPU 使用率:+ (double)getCpuUsage { kern_return_t kr; thread_array_t threadList; mach_msg_type_number_t threadCount; thread_info_data_t threadInfo; mach_msg_type_number_t threadInfoCount; thread_basic_info_t threadBasicInfo; kr = task_threads(mach_task_self(), &threadList, &threadCount); if (kr != KERN_SUCCESS) { return -1; } double cpuUsage = 0; for (int i = 0; i < threadCount; i++) { threadInfoCount = THREAD_INFO_MAX; kr = thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount); if (kr != KERN_SUCCESS) { return -1; } threadBasicInfo = (thread_basic_info_t)threadInfo; if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) { cpuUsage += threadBasicInfo->cpu_usage; } } // 回收内存,防止内存泄漏 vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t)); return cpuUsage / (double)TH_USAGE_SCALE * 100.0; } 最后说实话,一路走来也遇到各种各样的坑,很多时候拿出来的方案也不一定是最优方案,后面会在写一篇关于 unity3d 联调之间产生的有趣事情。秉着一起学习的心态,也希望有专业的同学能提出更好的意见,万分感谢!!!参考iOS开发入门-unity手册以上文章来自[掘金]-[可爱亲宝宝]本程序使用github开源项目RSSHub提取聚合!
2022年10月19日
0 阅读
0 评论
0 点赞
2022-10-18
老司机 iOS 周报 #219 | 2022-10-17-掘金
老司机 iOS 周报,只为你呈现有价值的信息。你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。 Developer - 设计开发加速器Apple 面向开发者推出 Ask AppleApple 宣布推出 Ask Apple,这是一项全新的互动式 Q & A 和一对一咨询(Office Hour)系列活动,为开发者提供更多机会与 Apple 专家直接交流沟通,获取洞见、支持与反馈。开发者需要加入 Developer 计划,并且下载 Slack 参与交流互动(与 WWDC22 的 Digital Lounges 模式相似,当时参与过的同学应该对讨论中的丰富信息记忆犹新)。欢迎大家注册参加 Q & A 的大量话题讨论,例如灵动岛、SwiftUI、新平台和硬件的体验优化等。同时还有中文开发者的 Office Hour 专场(共 4 场),时间也充分考虑到了国内开发者。 咨询设计布道团队(大中华区) 10 月 21 日上午 10:00 - 12:00 10 月 21 日下午 14:00 - 16:00 咨询技术布道团队(大中华区) 10 月 21 日上午 10:00 - 12:00 10 月 21 日下午 14:00 - 16:00 新手推荐🐎 Dynamic Island (and Live Activities): Quick start tutorial@极速男孩:苹果在 Xcode 14.1 Beta 中带来了灵动岛的相关 API ,本文主要介绍了如何简单的利用新的 Live Activities 的 API 来创建一个自己的灵动岛。文章🐕 CPU 是如何与内存交互的@老驴:今年上半年 M1 Pro/Max/Ultra 在 GPU 上实际表现没有达成预期之后引发了很多人对 M1 系列芯片的进一步探究,后来 TLB 容量不足的问题也被发现。那什么是 TLB?CPU 又是如何去映射内存的?本文就从当今主流 CPU 架构出发,科普当今的 CPU 是如何管理使用 L1/2/3 缓存和内存的。🐕 Apple’s use of Swift and SwiftUI in iOS 16@含笑饮砒霜:iOS 16 刚刚发布,本文分析了苹果内置的应用程序使用 Objective-C、Swift、SwiftUI 以及 C 和 C++ 的占比。通过对比 iPhone OS 1 到 iOS 16 的各个系统,可以得出一些结论: Objective-C 是 iOS 的核心,被大多数应用程序直接或间接使用 Swift 的使用在过去的 iOS 版本中迅速增加。今年 Swift 终于超越了 C++ 多年来C++的使用稳定 SwiftUI 开始引人注目,这是一个很好的迹象 C正在缓慢但肯定地消失 🐕 一文让你理清
[email protected]
: 对苹果用户来说,大家基本都知道,iOS 手机应用有一个比较常见的功能:点击状态栏,列表就会滚动到顶部. 但是在 Flutter 的各种列表组件中并未带有该特性,本文教大家如何使用 PrimaryScrollController 来实现这一特性.Where View.task gets its main-actor isolation
[email protected]
: Swift 5.5 引入了 async/await 特性,苹果也为 SwiftUI 添加了 task 视图修饰器,以方便开发者在视图中使用基于 async/await 的异步代码,但是你知道 task 是如何获取自己所执行的线程吗?以及它和 MainActor 修饰词有什么关系?如果感兴趣不妨一读。🐕 Xcode 14.0 generates wrong concurrency code for macOS
[email protected]
:Mac 开发者在 Xcode 14.0 和 14.0.1 进行构建工程的时候可能会出现并发错误(concurrency bugs),因为 Swift 5.7 编译器在针对 macOS 12.3 SDK 时会生成无效代码。解决方案是在 Xcode 14.1 发布之前,使用 Xcode 13.4.1 构建工程。产生错误的原因如下: 因为实现了 SE-0338 ,Swift 5.7 编译器的执行器跳跃点(executor hops)与 Swift 5.6 不同 Swift 5.7 新增非官方属性 @_unsafeInheritExecutor,用于注释一些需要退出新规则的标准库函数,当编译器编译到该属性时,它会生成不同的执行器跃点 产生编译错误的原因是在 Mac 开发中,Xcode14 使用了 Swift 5.7 的编译器和 Swift 5.6 的标准库,后者不包含 @_unsafeInheritExecutor 属性 Xcode14.1 和 macOS 13 SDK 发布后将会修复这个问题 🐢 基于自建 VTree 的全链路埋点方案@Barney: 本文详细介绍了网易云音乐技术团队自研的一套全链路埋点方案,从埋点设计、到客户端三端(iOS、Android、H5)开发、以及埋点校验&稽查、再到埋点数据使用。从设计到背后的思考和上线实践都有详细的说明,适合仔细阅读思考。🐢 虚拟内存 & I/O & 零拷贝@老峰:内存管理也是操作系统的核心功能之一,本文主要讲解了 Linux 系统下内存管理技术,是一篇不错的科普文,主要包含以下四部分: 虚拟内存: 地址翻译、高速缓存、加速翻译&优化页表 内核空间 & 用户空间:内核态与用户态 IO:(同步)阻塞 IO、(同步)非阻塞 IO、IO 多路复用、网络 IO 模型、异步 IO 零拷贝:传统 IO 流程、mmap + write、写时复制 工具🐕 妙言 - 更适合工程师用的 markdown 笔记应用@水水:一个简洁好看的开源的 Mac Markdown 编辑器,没有任何多余的功能。使用原生 Swift 开发,轻量性能高,安全纯本地使用,具备语法高亮、黑暗模式、自动格式化、单独编辑、演示模式、图床等功能。也可以做为备忘录、归档使用,我个人还是比较喜欢的。 下载链接:http://miaoyan.app/ 工具源码:https://github.com/tw93/MiaoYan 代码
[email protected]
:Semaphore 是一种信号量管理类,它和 DispatchSemaphore 区别是不会阻塞线程,而是在 Swift Concurrency 框架下提供更细颗粒度的资源管理能力,例如支持对 Task 的挂起和恢复等。响应式编程 Demo - RxStudy、
[email protected]
: RxStudy、GetXStudy 分别是基于 RxSwift/RxCocoa、GetX 框架的 Demo,可以结合两个项目,对比编程思路。
[email protected]
:package-benchmark 是一种用于轻松创建 “macOS 和 Linux 的 Swift 性能测试报告” 的工具。它既适用于主要关注运行时的临时小型代码片段(本着 Google swift-benchmark 的精神),也适用于更广泛的指标,例如内存分配、系统调用、线程使用等。package-benchmark 支持本地使用和基准比较,用于单个开发人员的迭代工作流,但更重要的是,它很好地支持 GitHub CI 的集成。其提供的示例工作流用于在主分支和 PR 分支之间进行自动比较,以支持 PR 的强制性能验证(具有可定制的阈值),这也是该工具的主要预期用例。摸鱼周报@摸鱼周报 #70:第 70 期摸鱼周报,一起来看下本期概要: 本期话题:苹果 iOS / iPadOS 16.1 公测版 Beta 3 发布,为老款 iPad 支持台前调度 本周学习:排查修复 App Store 上架项目闪退问题 内容推荐:iOS 开发技巧及计算机基础内容学习 摸一下鱼:计算麦当劳套餐卡路里的营养计算器,可以模拟木鱼声音的软件,以及插图绘制软件 @摸鱼周报 #71:第 71 期摸鱼周报,一起来看下本期概要: 本期话题:十月份还有 One More Thing?Apple 新增 QA 活动;Swift 和 SwiftUI 在 iOS 系统中的使用情况。 本周学习:在 iOS 16 中更改文本编辑器背景;解决 Cocoapods 导致的 Showing Recent Messages 问题 内容推荐:SwiftUI 好文推荐 摸一下鱼:推荐一款图片在线处理网站以及老地图查询网站;供摸鱼的迷宫生成器。 关注我们我们是「老司机技术周报」,一个持续追求精品 iOS 内容的技术公众号,欢迎关注。关注有礼,关注【老司机技术周报】,回复「2021」,领取 2017/2018/2019/2020 内参同时也支持了 RSS 订阅:https://github.com/SwiftOldDriver/iOS-Weekly/releases.atom 。说明🚧 表示需某工具,🌟 表示编辑推荐预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)以上文章来自[掘金]-[老司机技术]本程序使用github开源项目RSSHub提取聚合!
2022年10月18日
0 阅读
0 评论
0 点赞
2022-10-18
Objective-C基础(四)-掘金
这是OC基础的最后一个章节啦,这节主要给大家讲讲响应者链条。1. 响应者链条关于响应者链条,相信大家可能听说过这么一句话:事件由上往下传递,响应由下往上传递,那么这句话是什么意思呢?我们知道,在写UI时,每个UI控件,或是UI视图,都是从最初的一个UIView上,不断调用addSubview方法,叠加在父view上,进行展示的。例如,假设我们有下面这么一段代码:UIView *view1, *view2; UIButton *btn1; view1 = [UIView new]; view2 = [UIView new]; btn1 = [UIButton new]; [view1 addSubview:view2]; [view2 addSubview:btn1]; 显而易见,view2的父视图为view1,btn1的父视图为view2。如果我们现在在btn1上有一个点击事件,那么这个点击事件会直接传递给btn1吗? 答案是否定的,因为事件是由上往下传递的,这个事件会先传递给view1,再传递给view2,最后传递给btn1。事件传递主要依靠下面这个函数来实现:- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // 3种状态无法响应事件,1.用户交互被禁用;2.当前视图被隐藏;3.当前视图透明度小于0.01(跟被隐藏了差不多) if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha = 0; i--) { // 获取子视图 UIView *childView = self.subviews[i]; // 坐标系的转换,把触摸点在当前视图上坐标转换为在子视图上的坐标 CGPoint childP = [self convertPoint:point toView:childView]; // 询问子视图层级中的最佳响应视图 UIView *fitView = [childView hitTest:childP withEvent:event]; if (fitView){ // 如果子视图中有更合适的就返回 return fitView; } } // 没有在子视图中找到更合适的响应视图,那么自身就是最合适的 return self; } 从函数中可以看出,事件传递的流程为: 如果当前视图无法响应事件,则返回nil 如果当前点击处在当前视图可响应范围之外,则返回nil 从后往前遍历子视图,如果子视图能够处理当前事件,则返回子视图 否则返回自身视图 其中,第3步中,从后往前而不是从前往后遍历子视图的原因是:后加入的子视图会覆盖在先前加入的子视图之上,从用户角度来说,用户希望得到响应的视图应该是能够被看见的视图,而后加入的子视图因为会覆盖在最顶层所以更容易被用户看见,因此应该从后往前遍历。此外,我们也应该注意pointInside: withEvent:这个方法,是很多面试官爱考的考点。 我们可以通过overwrite这个方法,来改变一个视图能够响应的范围(默认能够响应的范围是这个视图包含的屏幕区域)。关于响应从下往上传递:我们在将事件从上往下传递后,利用- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法找到的最合适的响应视图,并不一定能够处理当前事件,仍按照上面的例子来说,假设我们有如下代码:UIView *view1, *view2; UIButton *btn1; view1 = [UIView new]; view2 = [UIView new]; view2.userInteractionEnabled = YES; btn1 = [UIButton new]; [view1 addSubview:btn1]; [btn1 addSubview:view2]; 注意与之前的区别,现在view2的父视图为btn1,而btn1的父视图为view1,view2和btn1的父子关系互相调换了。 此外,我们还开启了view2的用户交互属性。如果我们现在在btn1上有一个点击事件,按照事件从上往下传递的流程,我们会找到view2。 然而,我们会发现view2并不能够处理这个点击事件,因此,这个点击事件便由下往上传递给了btn1,并交由btn1处理(btn1能够处理,则调用相应的响应方法进行处理)。 假设btn1仍然不能处理,则继续往上传递给view1,直至事件被处理或者最后被丢弃。2. UIButton的继承关系UIButton的继承关系为:UIButton --> UIControl --> UIView --> UIResponder --> NSObject我们要注意UIResponder和UIControl的区别: UIResponder可以响应某个事件,利用touchesBegan: withEvent:方法(自己的事情自己做) UIControl不仅本身可以响应某个事件,还可以利用addTarget: forSelector: withEvent:为指定的某个对象添加事件(交给别人来做) 例如,普通的UIView想要响应事件,只能依靠自身实现touchesBegan: withEvent:方法; 而UIButton想要响应事件,不仅可以依靠自身,还可以将这个事件绑定到一个目标对象上,依靠目标对象的某个方法来处理事件。好啦,OC基础到这里就讲完啦,下期开始讲Runtime,欢迎继续关注! :)个人公众号:iOS开发学习未经作者允许,禁止转载!以上文章来自[掘金]-[dayulejia1224]本程序使用github开源项目RSSHub提取聚合!
2022年10月18日
2 阅读
0 评论
0 点赞
1
2
...
140