大师学SwiftUI第12章 - 手势 Part 1

手势识别器

手势是用户在屏幕上执行的动作,如点击、滑动或捏合。这些手势很难识别,因为屏幕上只能返回手指的位置。为此,Apple提供了手势识别器。手势识别器完成所有识别手势所需的计算。所以我们不用处理众多的事件和值,只需在等待系统监测到复杂手势时发送通知并进行相应处理即可。

手势修饰符

手机设备上最常用的手势是点击,在用户手指触碰屏幕时得到识别。因这种手势使用频繁,SwiftUI定义了两个非常方便的修饰符来完成处理。

  • onTapGesture(count: Int, perform: Closure):此修饰符识别一次或多次点击。​​count​​​参数指定要多少次点击才能做手势识别(默认值为1),​​perform​​​参数是在监测到手势时执行的闭包。闭包接收一个表示视图坐标中点击位置的​​CGPoint​​值。
  • onLongPressGesture(minimumDuration: Double, maximumDistance: CGFloat, perform: Closure, onPressingChanged: Closure):此修饰符识别长按姿势(用户用手指在屏幕上长按)。​​minimumDuration​​​参数是用户长按屏幕直到识别手势的秒数。​​maximumDistance​​​参数表示手指移动距原始位置不再识别手势的点数距离。​​perform​​​参数是在确认手势时执行的闭包。最后,​​onPressingChanged​​参数是用户和结束按压视图时执行的闭包。闭包接收一个表示用户是否在按压的布尔值。

我们经常使用​​onTapGesture()​​修饰符来监测点击并执行操作(参见示例7-36)。在之前的示例中我们没有使用点击时手指的位置。这通过闭包所接收的​​CGPoint​​值实现,其中包含视图中手指的x和y坐标。在下例中,我们在点击图片时打开弹窗并展示如何访问其值。

示例12-1:监测图片上的点击手势

struct ContentView: View {
    @State private var expand: Bool = false
    
    var body: some View {
        Image(.spot1)
            .resizable()
            .scaledToFit()
            .frame(width: 160, height: 200)
            .onTapGesture { location in
                expand = true
                print("Location: \(location)")
            }
            .sheet(isPresented: $expand) {
                ShowImage()
            }
    }
}

示例12-1的代码中,定义了160乘200点的​​Image​​​视图。将​​onTapGesture()​​​和​​sheet()​​​修饰符应用于视图来监测点击并展示弹窗。以下是由弹窗所打开的​​ShowImage​​视图。

示例12-2:展开图片

import SwiftUI

struct ShowImage: View {
    var body: some View {
        Image(.spot1)
            .resizable()
            .scaledToFill()
            .edgesIgnoringSafeArea(.all)
    }
}

此视图创建一个​​Image​​视图并展开图片填满弹窗,包含安全区。结果是界面在屏幕上展示一张小图,用户点击后,会以全尺寸在弹窗中显示图片。

图12-2:响应点击手势的图片

图12-2:响应点击手势的图片

✍️跟我一起做:创建一个多平台项目。下载spot1.jpg,添加到资源目录中。使用示例12-1中的代码更新​​ContentView​​视图。创建一个SwiftUI文件ShowImage.swift,使用示例12-2中的代码更新视图。此时会在界面看到图12-1(左)中所示的界面。点击图片打开弹窗,在控制台中会打印出点击的位置。

长按手势类似于点击手势,但系统会等待一段时间来确定该手势、执行任务。通过​​onLongPressGesture()​​修饰符,我们可以设置等待时长、执行用户点击时的任务以及等手势完成,如下例所示。

示例12-3:监测长按手势

struct ContentView: View {
    @State private var expand: Bool = false
    @State private var pressing: Bool = false
    
    var body: some View {
        Image(.spot1)
            .resizable()
            .scaledToFit()
            .frame(width: 160, height: 200)
            .opacity(pressing ? 0 : 1)
            .onLongPressGesture(minimumDuration: 1, maximumDistance: 10,perform: {
                expand = true
            }, onPressingChanged: { value in
                withAnimation(.easeInOut(duration: 1.5)) {
                    pressing = value
                }
            })
            .sheet(isPresented: $expand) {
                ShowImage()
            }
    }
}

这还是前面的示例,但现在是对​​Image​​​视图进行长按,所以用户需要用手指按压一段时间才能打开弹窗。本例中,我们将等待时间设置为1秒,最大移动距离设置为10点,一旦用户手指移动距原始位置超过10点,手势就会取消。​​onPressingChanged​​​参数所指定的闭包在用户开始触碰图片时执行,离开时会再次执行。在闭包中,我们修改了​​@State​​​属性​​pressing​​​的值,这个值用于设置视图的透明度。在手势开始时,闭包接收到的值是​​true​​​,因此透明度设置为0。但在用户将手指移开、抬起手指或结束手势时,闭包接收到值​​false​​​,因此透明度设置为1。透明度的变化关联了​​easeInOut​​动画,持续1.5秒,所以弹窗会在图片完全消失前打开,给用户一些必要的反馈来知晓他们要等待处理完成。

✍️跟我一起做:使用示例12-3中的代码更新​​ContentView​​视图。在图像上按压手指(长按)。会看到图像逐渐隐去,并在1秒后打开弹窗。

命中测试

因视图有时会重叠,系统必须确定某个视图是处理手势还是将其传递给其它视图。这种查找用户交互的视图并确定是否响应手势的过程称为命中测试(hit testing)。​​View​​协议定义了如下的修饰符来控制这一处理。

  • allowsHitTesting(Bool):此修饰符决定是否对指定视图启用命中检测。
  • contentShape(Shape, eoFill: Bool):此修饰符定义命中区的形状。第一个参数是确定用户可交互的形状视图,​​eoFill​​参数决定用于监控命中热点的算法。

​allowsHitTesting()​​​修饰符可用于禁用某个手势。比如,我们可以对前例中的​​Image​​视图启用或禁用点击手势。

示例12-4:禁用点击手势

struct ContentView: View {
    @State private var expand: Bool = false
    @State private var allowExpansion: Bool = false
    
    var body: some View {
        VStack(spacing: 20) {
            Image(.spot1)
                .resizable()
                .scaledToFit()
                .frame(width: 160, height: 200)
                .onTapGesture {
                    expand = true
                }
                .allowsHitTesting(allowExpansion)
                .sheet(isPresented: $expand) {
                    ShowImage()
            }
            Toggle("", isOn: $allowExpansion)
                .labelsHidden()
        }
    }
}

示例12-4中的视图在图像下方添加了一个​​Toggle​​​视图,控制​​@State​​​属性的值。该属性决定是否允许对​​Image​​​视图添加命中测试。其初始值为​​false​​​,因此用户法通过点击图像打开弹窗,但在切换开关为打开时,就会将​​true​​​赋值给该属性,因此​​Image​​视图就可以识别手势了。

✍️跟我一起做:使用示例12-4中的代码更新​​ContentView​​视图。点击图像。什么都不会发生。打开图像下方的开关。此时点击图像时就会打开弹窗。

​contentShape()​​​修饰符在对于手势识别也具有重要的作用。在对​​Image​​​视图或​​Text​​​视图应用手势识别器时,在用户触碰视图所占据的任意区域时会识别手势。但并非总是如此。容器视图,如​​VStack​​​和​​HStack​​,仅对其内容所占据的区域执行手势识别。要确保视图的每个部分都能识别手势,我们需要强制内容占据整个区域。前面我们碰到过这种问题(示例7-36)。这时,我们需要使用​​Color​​​视图定义背景来定义识别点击手势的区域。这可以满足我们的要求,但它创建了界面中不需要有的内容。更好的方案时应用​​contentShape()​​修饰符。这一修饰符允许我们定义了手势命中区,而又不要对视图添加真实的内容。

下例中,我们重建之前项目中的视图,创建一个行列表,但这次我们不使用​​Color​​​视图来响应手势,而是使用​​Rectangle​​​视图和​​contentShape()​​修饰符来定义了内容行。这让用户可以点击行的任意区域来进行选中。

示例12-5:定义内容区

struct ContentView: View {
    @State private var selected: Bool = false
    
    var body: some View {
        VStack {
            HStack(alignment: .top) {
                Image(.spot1)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 80, height: 100)
                    .border(selected ? Color.yellow : Color.clear, width: 5)
                VStack(alignment: .leading, spacing: 2) {
                    Text("Balmy Beach").bold()
                    Text("Toronto")
                    Text("2020").font(.caption)
                    Spacer()
                }
                Spacer()
            }.frame(height: 100)
                .padding(5)
                .border(.gray, width: 1)
                .contentShape(Rectangle())
                .onTapGesture {
                    selected.toggle()
                }
            Spacer()
        }
    }
}

示例12-5中的视图中显示一行带位置的信息。用户点击行中任意位置时,手势识别器切换​​@State​​​属性​​selected​​​的值,我们使用它来定义图像边框的颜色。值为​​true​​​(选中)时边框为黄色,为​​false​​(取消选中)时为透明。

图12-2:响应行

图12-2:响应行

Gesture结构体

​onTapGesture()​​​和​​onLongPressGesture()​​​修饰符所处理的手势由结构体定义,遵循​​Gesture​​协议。以下是一些最常用。

  • TapGesture(count: Int):这一初始化方法创建一个手势识别器监测点击手势。​​count​​参数决定识别手势所需要的点击次数。
  • LongPressGesture(minimumDuration: Double, maximumDistance: CGFloat):此初始化方法创建一个手势识别器监测长按手势。​​minimumDuration​​​参数用户手指按压屏幕被识别为手势的秒数。​​maximumDistance​​参数是用户手指移动距原始位置的最大位移,超过被判定为不识别手势。
  • MagnificationGesture(minimumScaleDelta: CGFloat):此初始化方法创建一个手势识别器监测放大手势。​​minimumScaleDelta​​参数为识别为手势所需的最小递增或增减比例。
  • RotationGesture(minimumAngleDelta: Angle):该初始化方法创建一个手势识别器监测旋转手势。​​minimumAngleDelta​​参数是识别为手势所需的视图最小递增或递减角度。

这些初始化方法配置手势识别器,但要响应手势的不同状态,结构体需要实现如下方法。

  • onChanged(Closure):该方法在手势状态发生改变时执行传入的闭包。闭包接收有关手势状态的信息值。
  • onEnded(Closure):该方法在手势结束时执行传入的闭包。闭包接收有关手势状态的信息值。
  • updating(GestureState, body: Closure):该方法在手势状态更新时执行传入的闭包,可能是由于值发生改变或是取消了手势。第一个参数是存储手势状态值的绑定属性,​​body​​​参数是在每次状态发生更新时所执行的闭包。闭包接收有关手势状态的信息值、绑定属性的指针以及包含动画信息的​​Transaction​​类型的值。

由于​​updating()​​​方法折调用频率,我们无法使用常规的​​@State​​属性追踪手势的状态。在更新闭包中任何对状态的修改尝试都会导致错误。因此,SwiftUI定义了如下属性封装来配合该方法使用。

  • @GestureState:这个属性封装存储了手势的状态并在手势结束将其重置为初始值。

获取到妥当的配置的手势识别器实例之后,我们必须将其应用于视图。为此​​View​​协议定义了如下的修饰符。

  • gesture(Gesture):该修饰符将手势识别器赋给视图,优先级低于已赋值给视图的手势识别器。
  • highPriorityGesture(Gesture):该修饰符将手势识别器赋给视图,优先级高于已赋值给视图的手势识别器。
  • simultaneousGesture(Gesture):该修饰符将手势识别器赋给视图,与已赋值给视图的手势识别器同时处理。

过程很简单。需要初始化​​Gesture​​​结构体来定义手势识别器,根据希望处理的内容来对结构体应用​​onChanged()​​​、​​onEnded()​​​或​​updating()​​​方法,并使用​​gesture()​​等修饰符来将实例赋值给视图。应用哪个方法取决于手势和希望完成的任务,而这些方法所接收到值取决我们所使用的手势识别器的类型。因此有多种选项,稍后我们就会知道。

点击手势

因为点击手势的简单性,它和应用​​onTapGesture()​​​修饰符和实现​​TapGesture​​​结构体并没有多大的不同。结柳体和修饰符有同样的功能,并且能定义识别为手势的点击次数,因为没有即时的变化上报,仅能使用​​onEnded()​​​。以下示例重现了之前的项目,但这次我们用​​TapGesture​​结构体定义了手势识别器。

示例12-6:定义一个​​TapGesture​​识别器

struct ContentView: View {
    @State private var expand: Bool = false
    
    var body: some View {
        Image(.spot1)
            .resizable()
            .scaledToFit()
            .frame(width: 160, height: 200)
            .gesture(
                TapGesture(count: 1)
                    .onEnded {
                        expand = true
                    }
            )
            .sheet(isPresented: $expand) {
                ShowImage()
            }
    }
}

​TapGesture​​​结构体定义了手势识别器,但要将其关联到视图,我们必须应用​​gesture()​​​修饰符。结果和之前相同。在点击图片时,执行赋给​​onEnded()​​​方法的闭包,将​​true​​​赋值给​​expand​​​属性,打开弹窗。注意​​onEnded()​​​方法是​​TapGesture​​结构体的一个方法,因此是在结构体的实例而不是在视图中调用。

c本例需要用到示例12-2中定义的​​ShowImage​​视图。使用示例12-6中的代码更新​​ContentView​​视图。会在屏幕上看到一张小图,点击该图会打开弹窗。

长按手势

类似​​TapGesture​​​结构体,​​LongPressGesture​​​结构体创建一个简单手势识别器,但它在执行手势时会有一些活动,因此除​​onEnded()​​​方法外,如果希望在按压视图时执行任务的话还可以实现​​updating()​​方法。

在实现​​updating()​​​方法时,我们需要注意几点。第一,如前所述,该方法需要​​@GestureState​​​属性而不是​​@State​​​属性。​​@GestureState​​​属性存储当前状态,也会在手势结束时重置为其初始值,因此应确保初始值为属性应当具备的初始值。第二,我们需要通过传给方法的闭包自己更新该状态,但不是直接更新,而是通过方法所接收到指针(通过名为​​state​​​)。第三,因为我们是在​​updating()​​​方法内处理修改,系统无法添加处理的动画。为此,我们需要将​​Animation​​​结构体赋值给手势所创建的​​Transaction​​​结构体中的​​animation​​属性,如下所示。

示例12-7:定义​​LongPressGesture​​识别器

struct ContentView: View {
    @GestureState private var pressing: Bool = false
    @State private var expand: Bool = false
    
    var body: some View {
        Image(.spot1)
            .resizable()
            .scaledToFit()
            .frame(width: 160, height: 200)
            .opacity(pressing ? 0 : 1)
            .gesture(
                LongPressGesture(minimumDuration: 1)
                    .updating($pressing) { value, state, transaction in
                        state = value
                        transaction.animation = Animation.easeInOut(duration: 1.5)
                    }
                    .onEnded { value in
                        expand = true
                    }
            )
            .sheet(isPresented: $expand) {
                ShowImage()
            }
    }
}

它和​​onLongPressGesture()​​修饰符创建的是相同的应用(参见示例12-3)。在用户按住图片一秒时,透明度发生改变,到时间后会打开弹窗。值和之前的处理方法相同,但没有直接通过​​@State​​​属性进行处理,而是将闭包接收到值赋值给​​pressing​​​属性的指针。这里,我们通过​​value​​​和​​state​​​这两个名称标识值和指针,但这些名称可任选。一旦将新值赋给​​state​​​,​​pressing​​​属性的值会发生改变,透明度也发生相应的调整。一秒后,执行​​onEnded()​​​方法,​​true​​​值会赋给​​expand​​属性,进而打开弹窗。

虽然我们可以你示例12-7中那样直接处理​​updating()​​​方法所生成的值,这个方法设计是用于通过枚举处理状态的。我们没有将方法所接收到值直接赋给​​@GestureState​​属性,而是将枚举值赋给该属性,然后通过枚举获取该状态,如下例所示。

示例12-8:通过枚举控制手势的状态

import SwiftUI

enum PressingState {
    case active
    case inactive
    
    var isActive: Bool {
        switch self {
        case .active:
            return true
        case .inactive:
            return false
        }
    }
}

struct ContentView: View {
    @GestureState private var pressingState = PressingState.inactive
    @State private var expand: Bool = false
    
    var body: some View {
        Image(.spot1)
            .resizable()
            .scaledToFit()
            .frame(width: 160, height: 200)
            .opacity(pressingState.isActive ? 0 : 1)
            .gesture(
                LongPressGesture(minimumDuration: 1)
                    .updating($pressingState) { value, state, transaction in
                        state = value ? .active : .inactive
                        transaction.animation = Animation.easeInOut(duration: 1.5)
                    }
                    .onEnded { value in
                        expand = true
                    }
            )
            .sheet(isPresented: $expand) {
                ShowImage()
            }
    }
}

示例功能和之前相同,但这里使用枚举来捕获手势状态。枚举名为​​PressingState​​​,包含两个分支,​​active​​​和​​inactive​​​,以及一个返回布尔值的计算属性,响应实例的当前值(​​true​​​为​​active​​​,​​false​​​为​​inactive​​​)。此时,不再定义​​Bool​​​类型的​​@GestureState​​​属性来存储​​updating()​​​方法所接收的值,我们可以定义一个​​PressingState​​​类型的属性来存储一个枚举值。我们调用这个属性​​pressingState​​​并将其赋值给​​updating()​​​方法。调用方法时,根据方法接收到值对这个属性赋值​​active​​​和​​inactive​​​。在读取​​opacity()​​​修饰符的状态时,我们通过将​​@GestureState​​​属性换成​​isActive​​​属性来获取布尔值。如果​​pressingState​​​属性的当前值为​​active​​​,​​isActive​​​属性返回​​true​​​,而​​opacity​​​被设置为0。否则返回​​false​​​,​​opacity​​被设置为1。

结果和之前相同,但在处理复杂手势或在合并多个手势时使用枚举类型就非常必要了。

其它相关内容请见​​虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记​​

代码请见:​​GitHub仓库​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/254650.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

公共字段自动填充——后端

场景:当处理一些请求时,会重复的对数据库的某些字段进行赋值(如:在插入和更新某个物品时,需要更新该物品的更新时间和更新者的信息),这样会导致代码冗余。 如: 思路: 自…

避免付费搜索广告系列与有机搜索广告系列之间出现关键词蚕食现象

自然搜索(搜索引擎优化)和付费搜索(或按点击付费)具有足够的技术复杂性和战略方面。 关键词蚕食是一个可能使它们变得更加困难的问题——如果你的搜索引擎优化 (SEO) 和按点击付费 (PPC&#x…

【Python】获取B站粉丝列表保存至数据库中

分析网络请求,获取到有粉丝接口的数据url 可以在响应信息处看到粉丝的信息 通过浏览器也可以直接请求到具体的JSON信息 通过独立的标签我们可以看到接口数据,但是要注意如果不是查看自己登录账户的接口那么就无法查询到所有粉丝的数据,默认只…

Python通过函数名调用函数的几种场景

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 除了执行系统命令外,我们有时还需要动态地执行一些python代码,有经验的朋友就会知道可以使用内置函数eval实现这一需求,如eval(“…

Appcelerator打包ipa有哪些优势

大家好,我是咕噜-凯撒,我们得先知道Appcelerator是啥,Appcelerator(现在更名为Axway Titanium)是一个跨平台的移动应用开发框架通过提供一种简化和加速移动应用开发的方式帮助你构建高质量的跨平台应用程序。那使用App…

【基础篇】YOLO系列训练环境(GPU)搭配篇

🚀Pytorch环境配置(Windows) 🔨 Anaconda安装 此处下载安装即可 ⭐温馨提示:安装路径不能含有中文,建议不要安在c盘(很占内存) 环境变量配置 编辑系统环境变量 -> 环境变量 -> 系统变量 新建并添加以下路径 打开cmd,输入conda&

设计模式—装饰模式

与其明天开始,不如现在行动! 文章目录 装饰模式—穿衣服💎总结 装饰模式—穿衣服 装饰模式(Decorator)可以动态的给对象添加一些额外的职责。 Component是定义一个对象接口,可以给这些对象动态地添加职责。…

【深度学习目标检测】十、基于yolov5的火灾烟雾识别(python,目标检测)

YOLOv5是目标检测领域一种非常优秀的模型,其具有以下几个优势: 1. 高精度:YOLOv5相比于其前身YOLOv4,在目标检测精度上有了显著的提升。YOLOv5使用了一系列的改进,如更深的网络结构、更多的特征层和更高分辨率的输入图…

Vditor - Markdown编辑器使用

1、安装 yarn add vditor2、代码 import vditor/dist/index.css; import React, { useEffect } from react; import Vditor from vditor; import ./index.less;const App ({ setVditorValue, vditorValue }: any) > {const [vd, setVd] React.useState<Vditor>();…

Redis-网络模型

参考资料 &#xff1a;极客时间 Redis&#xff08;亚风&#xff09; 前置知识 系统隔离 为了避免⽤户应⽤导致冲突甚⾄内核崩溃&#xff0c;⽤户应⽤与内核是分离的&#xff1a; 进程的寻址空间会划分为两部分&#xff1a;内核空间、⽤户空间 • ⽤户空间只能执⾏受限的命令&…

激活函数-SwiGLU

SwiGLU SiLU 函数是一种神经网络中的激活函数&#xff0c;全称是 Sigmoid Linear Unit, 也被称为 Swish 函数。它由 Google Brain 在 2017 年提出&#xff0c;是一种非线性激活函数&#xff0c;能够有效地对神经网络的输入进行非线性变换。 定义 f ( x ) x ∗ σ ( x ) f(x)…

vue3使用vue-router嵌套路由(多级路由)

文章目录 1、Vue3 嵌套路由2、项目结构3、编写相关页面代码3.1、编写route文件下 index.ts文件3.2、main.ts文件代码&#xff1a;3.3、App.vue文件代码&#xff1a;3.4、views文件夹下的Home文件夹下的index.vue文件代码&#xff1a;3.5、views文件夹下的Home文件夹下的Tigerhh…

生命在于学习——TV电视盒子渗透测试抓包设置

一、前言 封面图是示例图&#xff0c;因为涉及到保密&#xff0c;所以本次测试的电视盒子不放出外观和设置界面。 这一次要测试电视盒子&#xff0c;大家也都知道&#xff0c;市面上的电视盒子大部分都是Android&#xff0c;当然&#xff0c;要使用笔记本去抓电视盒子的数据包…

圣诞节来了,为大家送上专属圣诞树

Hello大家好&#xff0c;我是Dream。 今天给大家分享一下我很早之前做过的圣诞树&#xff0c;分享给大家&#xff0c;希望可以帮助到大家度过一个浪漫的圣诞节~ Python打造专属于你的圣诞树落叶雪花背景音乐浪漫弹窗 五合一版圣诞树 一、背景故事圣诞节风波❤️❤️❤️ 二、五…

针对海量数据的存储与访问瓶颈的解决方案

背景 在当今这个时代&#xff0c;人们对互联网的依赖程度非常高&#xff0c;也因此产生了大量的数据&#xff0c;企业视这些数据为瑰宝。而这些被视为瑰宝的数据为我们的系统带来了很大的烦恼。这些海量数据的存储与访问成为了系统设计与使用的瓶颈&#xff0c;而这些数据往往存…

内网BUG管理系统本地部署并结合内网穿透实现异地远程访问

文章目录 前言1. 本地安装配置BUG管理系统2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射本地服务3. 测试公网远程访问4. 配置固定二级子域名4.1 保留一个二级子域名5.1 配置二级子域名6. 使用固定二级子域名远程 前言 BUG管理软件,作为软件测试工程师的必备工具之一。在…

Windows如何安装使用TortoiseSVN客户端并实现公网访问本地SVN Server

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

泛型深入理解

泛型的概述 泛型&#xff1a;是JDK5中引入的特性&#xff0c;可以在编译阶段约束操作的数据类型&#xff0c;并进行检查。 泛型的格式&#xff1a;<数据类型>; 注意&#xff1a;泛型只能支持引用数据类型。 集合体系的全部接口和实现类都是支持泛型的使用的。 泛型的…

周周清(3)

周一&#xff1a; 写了一道算法题&#xff0c;然后由于身体原因&#xff0c;在宿舍休息了很久&#xff1b;然后还是完成了一道算法题&#xff1a;写了一道LRC缓存题&#xff0c;之前就开始写了&#xff0c;写了很久&#xff0c;每次都开始动笔&#xff0c;刚开始用的是队列加m…

太阳能电池效能IV测试PV检测太阳光模拟器

目录 概述 一、系统组成 产品特点&#xff1a; 技术参数 数字源表 本系统支持Keithley24xx系列源表 标准太阳能电池 低阻测试夹具 自动化测试软件 概述 太阳能光伏器件的所有性能表征手段中&#xff0c;IV特性测试无疑是最直观、最有效、最被广泛应用的一种…