当前位置: 首页 > news >正文

Jetpack Compose开发一个Android WiFi导航应用

在以前的一篇文章构建一个WIFI室内定位系统_wifi定位系统-CSDN博客中,我介绍了如何用Android来测量WiFi信号,上传到服务器进行分析后,生成室内不同地方的WiFi指纹,从而帮助进行室内导航。当时我是用的HTML5+的技术来快速开发一个Android的应用,可以看到HTML5+能很便利的用我们熟悉的Web技术来进行开发,而不需要了解原生Android应用繁琐的开发知识。但是Android原生应用也有其优势,尤其在性能上以及一些Android核心功能的调用上。尤其是Google推出了新的Jetpack Compose用于构建原生 Android 界面的新工具包,它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助简化并加快 Android 界面开发,打造生动而精彩的应用,让我们能更快速、更轻松地构建 Android 界面以及更加便利进行原生应用的开发。因此这次我也用Jetpack Compose来重构了我之前写的Wifi信号测量的应用。

WiFi测量主界面

界面UI设计

Jepack Compose的精髓在于用可组合函数来声明一个UI界面。界面是不可变的,在绘制后无法进行更新。您可以控制的是界面的状态。每当界面的状态发生变化时,Compose 都会重新创建界面数更新的部分。

在Android studio里面新建一个项目,选择Empty activity类型,在这种类型的项目,res资源文件夹没有layout这个子文件夹,因为这种类型已经是用新的Compose方式来进行布局了,不再采用以前的XML方式来定义布局。

新建一个名为WifiMeasure的class,在里面定义一个MeasureScreen的Composable函数,用来声明我们的主界面,代码如下:

@Composable
fun MeasureScreen() {Column(modifier = Modifier.padding(all = 8.dp)) {Text(text = stringResource(R.string.screen_title),style = MaterialTheme.typography.titleLarge,)Spacer(modifier = Modifier.height(8.dp))val imageModifier = Modifier.height(150.dp).fillMaxWidth().border(BorderStroke(1.dp, Color.Black))Image(painter = painterResource(id = R.drawable.indooratlas),contentDescription = null,contentScale = ContentScale.FillWidth,modifier = imageModifier)Spacer(modifier = Modifier.height(8.dp))OutlinedTextField(value = "",onValueChange = { },label = { Text(text = stringResource(R.string.label_position_name), style = MaterialTheme.typography.bodyMedium)},modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(8.dp))TextField(value = "0.0",onValueChange = { },label = { Text(text = stringResource(R.string.label_current_angle), style = MaterialTheme.typography.bodyMedium)},readOnly = true,modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(16.dp))TextButton(onClick = { },shape = RectangleShape,contentPadding = PaddingValues(16.dp),modifier = Modifier.fillMaxWidth().border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)) {Texttext = "Measure",style = MaterialTheme.typography.bodyMedium)}}
}

这里采用了Column来作为一个垂直布局,在里面放置了Text, Image等组件来显示界面。我们可以添加一个Preview的函数,这样在进行代码改动的时候,我们就可以马上在Android studio的Design里面看到UI的改动了,非常方便,代码如下:

@Preview(showBackground = true)
@Composable
fun PreviewMeasureScreen() {MeasureScreen()
}

这个界面的效果如下图:

在要进行测量的时候,我们需要首先输入当前位置的名字,同时手机会实时显示当前的朝向,因为不同的朝向对Wifi信号的测量也有影响。然后当我们点击Measure这个按钮的时候,就会把当前这个位置的Wifi信号信息测量出来。

在MainActivity的onCreate方法的setContent中直接调用刚才我们定义的函数MeasureScreen()即可在APP中显示我们的界面。

定义ViewModel保存UI状态

现在在输入框中输入位置的名字,可以看到输入无法显示,这是因为在OutlinedTextField里面我们没有定义value是一个可观测状态,因此Compose组件无法进行重组更新。为此我们需要定义一个ViewModel来保存UI的状态。新建一个WifiMeasureViewModel的class,代码如下:

class WifiMeasureViewModel : ViewModel() {var positionName by mutableStateOf("")private setfun updatePositionName(name: String) {positionName = name}

这个类里面定义了一个positionName的mutableStateOf的State容器,通过一个update方法来更新数值。

修改WifiMeasure这个函数,传入这个ViewModel进行绑定,这里的viewModel()是一个生命周期的组件,可以使得ViewModel与Compose UI生命周期同步存在。

@Composable
fun MeasureScreen(measureViewModel: WifiMeasureViewModel = viewModel()
) 

修改OutlinedTextField,现在可以正常输入文字了,如果我们旋转手机,可以看到之前输入的文字能保留下来。

        OutlinedTextField(value = measureViewModel.positionName,onValueChange = { measureViewModel.updatePositionName(it) },label = { Text(text = stringResource(R.string.label_position_name), style = MaterialTheme.typography.bodyMedium)},modifier = Modifier.fillMaxWidth())

获取手机朝向

因为手机的朝向对于Wifi测量会有影响,因此通常我们会在同一个地点测试不同朝向的WiFi信号并记录下来。我们需要在APP上实时显示当前的朝向,这就需要用到手机提供的传感器数据。

传统的Android应用的方法是,在Activity类里面继承SensorEventListener并重写相应的方法来实现。但是在Composable function里面如何实现,在官网上并没有介绍。我的做法是先定义一个新的类继承SensorEventListener,例如我们新建一个SensorDataManager的类,代码如下:

class SensorDataManager(context: Context): SensorEventListener {private val sensorManager by lazy {context.getSystemService(Context.SENSOR_SERVICE) as SensorManager}private var accelerometerReading = FloatArray(3)private var magnetometerReading = FloatArray(3)private var rotationMatrix = FloatArray(9)private var orientationAngles = FloatArray(3)val data: Channel<Float> = Channel(Channel.UNLIMITED)fun init() {Log.d("SensorDataManager", "init")val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)val magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI)}override fun onSensorChanged(event: SensorEvent) {if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)} else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)}SensorManager.getRotationMatrix(rotationMatrix,null,accelerometerReading,magnetometerReading)SensorManager.getOrientation(rotationMatrix, orientationAngles)data.trySend(orientationAngles[0])}override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}fun cancel() {Log.d("SensorDataManager", "cancel")sensorManager.unregisterListener(this)}
}

解释一下代码,在init函数中获取accelerometer和maganetic这两个传感器并注册listener,根据官网的介绍,推荐用这两个传感器数据来获取准确的朝向。在重写的onSensorChanged方法中,根据这两个传感器的数据计算朝向,并通过Channel把协程的数据发送出去。最后在cancel中取消listener注册。

现在修改一下MeasureScreen这个函数,增加以下代码:

    val context = LocalContext.currentval scope = rememberCoroutineScope()var angle by remember { mutableStateOf<Float>(0f) }DisposableEffect(Unit) {val dataManager = SensorDataManager(context)dataManager.init()val job = scope.launch {dataManager.data.receiveAsFlow().onEach { angle = it }.collect()}onDispose {dataManager.cancel()job.cancel()}}

这里用到了Compose里面的附带效应Side effects,按照官网的解释附带效应是指发生在可组合函数作用域之外的应用状态的变化。DisposableEffect可以在键发生变化或可组合项退出组合后进行清理。因此我采用DiposableEffect(Unit)来监控这个MeasureScreen函数,完成初始化和清除Sensor Listener的工作。

修改一下显示朝向角度的TextField,设置其Value

        TextField(value = angle.toString(),onValueChange = { },label = { Text(text = stringResource(R.string.label_current_angle), style = MaterialTheme.typography.bodyMedium)},readOnly = true,modifier = Modifier.fillMaxWidth())

现在这个测量页面可以正常工作了。

WiFi测量报告页面

增加导航

当点击测量页面的Measure按钮的时候,应该能跳转到另一个页面,显示WiFi的测量结果。要实现导航的功能,我们需要用到Navigation组件。新增一个名为Navigation的class,代码如下:

object Destinations {const val MEASURE_ROUTE = "measure"const val REPORT_ROUTE = "report/{positionName}"
}@Composable
fun AppNavHost(modifier: Modifier = Modifier,navController: NavHostController = rememberNavController(),startDestination: String = MEASURE_ROUTE
) {NavHost(navController = navController,startDestination = startDestination) {composable(MEASURE_ROUTE) {MeasureScreen(navController = navController)}composable(REPORT_ROUTE,arguments = listOf(navArgument("positionName") {type = NavType.StringType},)) { backStackEntry ->val positionName = backStackEntry.arguments?.getString("positionName")WifiMeasureReport(positionName)}}
}

这里定义了两个route,分别对应APP的两个页面。在跳转到测量报告页面的时候,route会带上positionName这个参数。

修改MainActivity,把setContent的内容替换为调用AppNavHost(),如以下代码:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {WifiPositionTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {AppNavHost()}}}}
}

WiFi扫描服务

增加一个Wifi扫描的服务,实现对Wifi信号的测量。新增一个WifiScanService的class,代码如下:

class WifiScanService(context: Context) {private val wifiManager by lazy {context.getSystemService(Context.WIFI_SERVICE) as WifiManager}private val context: Context = contextval data: Channel<List<WifiMeasureData>> = Channel(Channel.UNLIMITED)private val wifiScanReceiver = object : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)if (success) {scanSuccess()} else {scanFailure()}}}fun init() {val intentFilter = IntentFilter()intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)context.registerReceiver(wifiScanReceiver, intentFilter)val success = wifiManager.startScan()if (!success) {scanFailure()}}private fun scanFailure() {Log.d("WIFI", "Scan failure")}@SuppressLint("MissingPermission")private fun scanSuccess() {val results = wifiManager.scanResultsif (!results.isNullOrEmpty()) {val wifiMeasureData = results.map {WifiMeasureData(it.BSSID,it.level)}data.trySend(wifiMeasureData)}}fun cancel() {context.unregisterReceiver(wifiScanReceiver)}
}

定义一个WifiMeasureData的数据class,保存测量数据

data class WifiMeasureData (val bssId: String,val signalStrength: Int
)

另外,要开启WiFi测量,还需要申请相应的权限,在AndroidManifest.xml里面,增加以下权限申请

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

报告页面设计

同样是采用Compose的方式来设计一个页面展示Wifi测量数据的结果,新建一个WifiMeasureReport的class,代码如下:

@Composable
fun WifiMeasureReport (positionName: String?) {val context = LocalContext.currentval scope = rememberCoroutineScope()var wifiScanResult by remember { mutableStateOf<List<WifiMeasureData>>(listOf(WifiMeasureData("", 0))) }DisposableEffect(Unit) {val wifiScanService = WifiScanService(context)wifiScanService.init()val job = scope.launch {wifiScanService.data.receiveAsFlow().onEach { wifiScanResult = it }.collect()}onDispose {wifiScanService.cancel()job.cancel()}}Column() {Text(text = stringResource(id = R.string.report_title),style = MaterialTheme.typography.titleLarge,textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(8.dp))Text(text = stringResource(id = R.string.report_position_name)+": "+positionName,style = MaterialTheme.typography.bodyLarge,modifier = Modifier.padding(10.dp))LazyColumn(Modifier.fillMaxWidth(),contentPadding = PaddingValues(horizontal = 4.dp)){item {ItemHeader()}itemsIndexed(wifiScanResult) { index: Int, item: WifiMeasureData ->ItemRow(index, item)}}}
}@Composable
fun ItemHeader() {Row(Modifier.fillMaxWidth()//.border(BorderStroke(0.5.dp, Color.Black))) {Text(text = stringResource(R.string.report_header_bssid), fontWeight = FontWeight.Bold, modifier = Modifier.weight(5f).padding(10.dp))Text(text = stringResource(R.string.report_header_strength), fontWeight = FontWeight.Bold, modifier = Modifier.weight(5f).padding(10.dp))}Divider(color = Color.LightGray,modifier = Modifier.height(1.dp).fillMaxHeight().fillMaxWidth())
}@Composable
fun ItemRow(index: Int, item: WifiMeasureData) {val modifier: Modifier = Modifier.fillMaxWidth()Row(modifier = if (index%2 == 0) modifier.background(Color.LightGray) else modifier) {Text(text = item.bssId, modifier = Modifier.weight(5f).padding(10.dp))Text(text = item.signalStrength.toString(), modifier = Modifier.weight(5f).padding(10.dp))}Divider(color = Color.LightGray,modifier = Modifier.height(1.dp).fillMaxHeight().fillMaxWidth())
}@Preview(showBackground = true)
@Composable
fun PreviewMeasureReportScreen() {WifiMeasureReport("grid_1")
}

这里同样采用了Column垂直布局,其中用了一个LazyColumn来展示Wifi测量结果的列表。这个LazyColumn类似于以前的RecyclerView。

测试结果上报

WiFi测试的结果要上报到服务器来进行汇总分析,最后生成Wifi指纹,这部分的内容可以参考我之前提到的博客内容,这里不再重复。我们只需要修改一下WifiMeasureReport,把拿到的结果通过REST API上传即可。改动如下:

待补充。。。

运行效果

最后把项目打包为APK后上传到手机运行,实际效果如下:

待补充。。。

相关文章:

Jetpack Compose开发一个Android WiFi导航应用

在以前的一篇文章构建一个WIFI室内定位系统_wifi定位系统-CSDN博客中&#xff0c;我介绍了如何用Android来测量WiFi信号&#xff0c;上传到服务器进行分析后&#xff0c;生成室内不同地方的WiFi指纹&#xff0c;从而帮助进行室内导航。当时我是用的HTML5的技术来快速开发一个An…...

【Mode Management】ComM详细介绍

目录 1. Introduction and functional overview 2.Dependencies to other modules 3.Functional specification 3.1 Partial Network Cluster Management 3.2 ComM channel state machine 3.2.1 Behaviour in state COMM_NO_COMMUNICATION 3.2.1.1 COMM_NO_COM_NO_PENDI…...

【C++多线程编程】(二)之详解锁(lock)和解锁(unlock)

在C多线程编程中&#xff0c;锁&#xff08;lock&#xff09;和解锁&#xff08;unlock&#xff09;通常用于管理共享资源的访问&#xff0c;以防止多个线程同时对资源进行修改&#xff0c;从而避免竞态条件&#xff08;Race Condition&#xff09;和数据不一致性问题。C标准库…...

【Mypy】超级实用的python高级库!

今天&#xff0c;我很兴奋地向大家介绍一个神奇的Python库&#xff1a;Mypy。这个库是Python世界中的一颗璀璨明星&#xff0c;提供了静态类型检查的强大功能&#xff0c;极大地增强了Python这门动态类型语言的健壮性和可维护性。我们将深入探索Mypy的多个方面&#xff0c;并通…...

【Python基础】循环语句

文章目录 [toc]什么是循环Python中的循环方式while循环格式示例 什么是循环 程序中需要重复执行的代码&#xff0c;可以通过循环实现比如和女朋友道歉&#xff0c;或一万遍“宝宝&#xff0c;我错了”&#xff0c;在没有学习循环之前&#xff0c;我们只能通过如下方式实现 pr…...

【面试】广告优化

a1&#xff1a;点击率公式是什么&#xff1f;点击率低的原因是什么&#xff1f; 点击率点击/曝光&#xff0c;点击率低的原因主要有两点&#xff1a;一是创意不吸引人&#xff1b;二是目标受众不准确/定向过宽不精确&#xff0c;广告曝光给了对产品不感兴趣用户 a2&#xff1a;…...

RabbitMQ插件详解:rabbitmq_message_timestamp【Rabbitmq 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 RabbitMQ时空之旅&#xff1a;rabbitmq_message_timestamp的奇妙世界 前言什么是rabbitmq_message_timestamprabbitmq_message_timestamp 的定义与作用&#xff1a;如何在 RabbitMQ 中启用消息时间戳&…...

AD9361 Evaluation Software配置脚本转换工具

最近在玩一个开源的AD9361项目&#xff0c;AD9361采用纯逻辑配置&#xff0c;不需要ARM或者MicroBlaze。其中&#xff0c;先是用AD9361 Evaluation Software生成配置脚本&#xff0c;再转换成ad9361_lut.v。 在网上查了一圈&#xff0c;有个转换工具叫bit_converter&#xff0…...

Centos7 配置Git

随笔记录 目录 1&#xff0c; 新建用户 2. 给用户设置密码相关操作 3. 为新用户添加sudo 权限 4. 配置Git 4.1 配置Git 4.2 查看id_ras.pub 5, 登录Git 配置SSH 秘钥 6. Centos7 登录Git 7. clone 指定branch到本地 8. 将新代码复制到指定路径 9. 上传指定代码 …...

python工具方法 44 数据仿真生成(粘贴目标切片到背景图像上,数据标签校验)

在深度学习训练中数据是一个很重要的因素,在数据不够时需要我们基于现有的数据进行增强生成新的数据。此外,在某特殊情况,如对某些目标切片数据(例如:石块分割切片)预测效果较差,需要增强其在训练数据中的频率。故此,我们可以将先有数据标注中的目标裁剪出来,作为样本…...

Llama 架构分析

从代码角度进行Llama 架构分析 Llama 架构分析前言Llama 架构分析分词网络主干DecoderLayerAttentionMLP 下游任务因果推理文本分类 Llama 架构分析 前言 Meta 开发并公开发布了 Llama系列大型语言模型 (LLM)&#xff0c;这是一组经过预训练和微调的生成文本模型&#xff0c;参…...

vue3前端 md5工具类

工具类 /*** Namespace for hashing and other cryptographic functions* Copyright (c) Andrew Valums* Licensed under the MIT license, http://valums.com/mit-license/*/var V V || {}; V.Security V.Security || {};(function () {// for faster accessvar S V.Secur…...

Unity触摸 射线穿透UI解决

unity API 之EventSystem.current.IsPointerOverGameObject() 命名空间 &#xff1a;UnityEngine.EventSystems 官方描述&#xff1a; public bool IsPointerOverGameObject(); public bool IsPointerOverGameObject(int pointerId); //触摸屏时需要的参数&#xff…...

基于QTreeWidget实现带Checkbox的多级组织结构选择树

基于QTreeWidget实现带Checkbox的多级组织结构选择树 采用基于QWidgetMingw实现的原生的组织结构树 通过QTreeWidget控件实现的带Checkbox多级组织结构树。 Qt相关系列文章&#xff1a; 一、Qt实现的聊天画面消息气泡 二、基于QTreeWidget实现多级组织结构 三、基于QTreeWidget…...

探索 Vim:一个强大的文本编辑器

引言&#xff1a; Vim&#xff08;Vi IMproved&#xff09;是一款备受推崇的文本编辑器&#xff0c;拥有强大的功能和高度可定制性&#xff0c;提供丰富的编辑和编程体验。本文将探讨 Vim 的基本概念、使用技巧以及为用户带来的独特优势。 简介和发展 1. Vim 的简介和历史 V…...

K8S(十)—容器探针

这里写目录标题 容器探针&#xff08;probe&#xff09;检查机制探测结果探测类型何时该使用存活态探针?何时该使用就绪态探针?何时该使用启动探针&#xff1f; 使用exechttptcpgrpc使用命名端口 使用启动探针保护慢启动容器定义就绪探针配置探针HTTP 探测TCP 探测探针层面的…...

[C错题本]

1.int,short,long都是signed的 但是char可能是signed 也可能是unsigned的——《C Primer》 2.在16位的PC中 char类型占1个字节 int占2个字节 long int占4个字节 float占四个字节 double占八个字节 3.自增运算符和自减运算符即使是在判断条件中使用也会实际生效 int i 1; int…...

tomcat启动异常:子容器启动失败(a child container failed during start)

最近在使用eclipse启动Tomcat时&#xff0c;发现一个问题&#xff0c;启动以前的项目突然报子容器启动异常。 异常信息如下&#xff1a; 严重: 子容器启动失败 java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: 无法启动组件[org.apache.…...

JAVA序列化(创建可复用的 Java 对象)

JAVA 序列化(创建可复用的 Java 对象) 保存(持久化)对象及其状态到内存或者磁盘 Java 平台允许我们在内存中创建可复用的 Java 对象&#xff0c;但一般情况下&#xff0c;只有当 JVM 处于运行时&#xff0c;这些对象才可能存在&#xff0c;即&#xff0c;这些对象的生命周期不…...

如何使用自动化工具编写测试用例?

以下为作者观点&#xff0c;仅供参考&#xff1a; 在快速变化的软件开发领域&#xff0c;保证应用程序的可靠性和质量至关重要。随着应用程序复杂性和规模的不断增加&#xff0c;仅手动测试无法满足行业需求。 这就是测试自动化发挥作用的地方&#xff0c;它使软件测试人员能…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...