006-Jetpack Compose for Android之传感器数据

在这里插入图片描述
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需求分析

想要看看手机的传感器数据,看看滤波一下能玩点什么无聊的。先搞个最简单的,手机本身的姿态。

需求:采集手机姿态数据,显示在界面上。

那么我们需要:

  • 一个文本标签类似的控件,显示手机姿态数据,三个角度:pitch, roll, yaw
  • 是不是需要做一个图标?显示姿态的变化?
  • 这样就提出了需要一个时间标签,显示采集数据的时间(间隔)
  • 开始/停止采集数据的按钮是否需要?在这个场景,单一功能,不需要,把软件打开和软件关闭作为采集数据的开始和停止。
  • 数据如何导出?肯定是需要的,那么我们考虑导出csv文件。

核心数据

  • 时间序列,(t, pitch, roll, yaw)
  • 采集间隔, d t dt dt,由硬件确定?

用户交互

  • 打开程序
  • 关闭程序
  • 导出数据

界面设计

大概我们可以在上方设置一个标签,显示实时得到的最新数据,下方主体部分一个图标,动态更新,显示姿态的变化。

实现流程

建立工程

打开Androi的Studio,新建一个项目,选择Jetpack Compose模板。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

记得要认准这个中间的Compose图标。

然后否就是一顿修改镜像地址。首先是gradle下载地址,修改gradle/wrapper/gradle-wrapper.properties文件:

#Fri Dec 13 22:34:09 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v8.9.0/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

接下来就是修改settings.gradle.kts文件,增加下载地址:

pluginManagement {repositories {maven { url = uri("https://maven.aliyun.com/repository/public/") }google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.google.*")includeGroupByRegex("androidx.*")}}mavenCentral()gradlePluginPortal()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {maven { url = uri("https://maven.aliyun.com/repository/public/") }google()mavenCentral()maven { url = uri("https://jitpack.io") }}
}rootProject.name = "YawPitchRoll"
include(":app")

只有经过了上面两步,才能什么同步Gradle 工程之类的,然后build一下,确认所有的依赖都下载完了。可以稍微运行一下也没问题。

建立界面

建立界面在Jetpack中间很简单很直观。

package org.cardc.fdii.qc.Instrumentsimport android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.MotionEvent
import android.widget.EditText
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.listener.ChartTouchListener
import com.github.mikephil.charting.listener.OnChartGestureListener
import com.github.mikephil.charting.utils.ColorTemplate
import org.cardc.fdii.qc.Instruments.ui.theme.FirstApplicationTheme
import java.io.File
import java.io.FileWriter@Composable
fun SensorChart(yawData: List<Entry>,pitchData: List<Entry>,rollData: List<Entry>,modifier: Modifier = Modifier
) {val context = LocalContext.currentval chart = remember { LineChart(context) }val yawDataSet = LineDataSet(yawData, "Yaw").apply {lineWidth = 2fcolor = ColorTemplate.COLORFUL_COLORS[0]axisDependency = YAxis.AxisDependency.LEFT}val pitchDataSet = LineDataSet(pitchData, "Pitch").apply {lineWidth = 2fcolor = ColorTemplate.COLORFUL_COLORS[1]axisDependency = YAxis.AxisDependency.LEFT}val rollDataSet = LineDataSet(rollData, "Roll").apply {lineWidth = 2fcolor = ColorTemplate.COLORFUL_COLORS[2]axisDependency = YAxis.AxisDependency.LEFT}val lineData = LineData(yawDataSet, pitchDataSet, rollDataSet)chart.data = lineDatachart.xAxis.position = XAxis.XAxisPosition.BOTTOMchart.axisRight.isEnabled = falsechart.description.isEnabled = false// Set gesture listenerchart.onChartGestureListener = object : OnChartGestureListener {override fun onChartGestureStart(me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?) {}override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?) {}override fun onChartLongPressed(me: MotionEvent?) {}@RequiresApi(Build.VERSION_CODES.O)override fun onChartDoubleTapped(me: MotionEvent?) {showFileNameDialog(context, yawData, pitchData, rollData)}override fun onChartSingleTapped(me: MotionEvent?) {}override fun onChartFling(me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float) {}override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) {}override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {}}chart.invalidate()// Enable auto-scalingchart.isAutoScaleMinMaxEnabled = trueAndroidView({ chart }, modifier = modifier.padding(16.dp).border(1.dp, Color.Gray))
}class MainActivity : ComponentActivity(), SensorEventListener {private lateinit var sensorManager: SensorManagerprivate var rotationVectorSensor: Sensor? = nullprivate var _yaw by mutableFloatStateOf(0f)private var _pitch by mutableFloatStateOf(0f)private var _roll by mutableFloatStateOf(0f)// add a variable to store the high resolution timeprivate val _time0 = System.nanoTime()private var _time by mutableLongStateOf(0L)override fun onResume() {super.onResume()rotationVectorSensor?.also { sensor ->sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)}}override fun onPause() {super.onPause()sensorManager.unregisterListener(this)}override fun onSensorChanged(event: SensorEvent?) {event?.let {if (it.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {val rotationMatrix = FloatArray(9)SensorManager.getRotationMatrixFromVector(rotationMatrix, it.values)val orientation = FloatArray(3)SensorManager.getOrientation(rotationMatrix, orientation)_yaw = Math.toDegrees(orientation[0].toDouble()).toFloat()_pitch = Math.toDegrees(orientation[1].toDouble()).toFloat()_roll = Math.toDegrees(orientation[2].toDouble()).toFloat()// update the time_time = System.nanoTime() - _time0}}}override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {// Do nothing}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()sensorManager = getSystemService(SENSOR_SERVICE) as SensorManagerrotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)setContent {FirstApplicationTheme {Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->SensorDataDisplay(yaw = _yaw,pitch = _pitch,roll = _roll,t = _time,modifier = Modifier.padding(innerPadding))}}}}
}@Composable
fun SensorDataDisplay(yaw: Float, pitch: Float, roll: Float, t: Long, modifier: Modifier = Modifier
) {val yawData = remember { mutableStateListOf<Entry>() }val pitchData = remember { mutableStateListOf<Entry>() }val rollData = remember { mutableStateListOf<Entry>() }if (t > 0) {yawData.add(Entry(t * 1e-9f, yaw))pitchData.add(Entry(t * 1e-9f, pitch))rollData.add(Entry(t * 1e-9f, roll))}Column(modifier = modifier) {val context = LocalContext.currentText(text = "qchen2015@hotmail.com © 2024",modifier = Modifier.padding(6.dp).fillMaxWidth(),textAlign = TextAlign.Center)// add a hyperlink to the author's websiteText(text = "https://www.windtunnel.cn",modifier = Modifier.padding(6.dp).fillMaxWidth().clickable {val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.windtunnel.cn/categories/jetpack/"))context.startActivity(intent)},textAlign = TextAlign.Center,color = Color.Blue,style = TextStyle(textDecoration = TextDecoration.Underline))Text(text = "Yaw  : %16.4f°\nPitch: %16.4f°\nRoll  : %16.4f°\nTime: %16.6fs".format(yaw, pitch, roll, t * 1e-9),modifier = Modifier.padding(16.dp))SensorChart(yawData, pitchData, rollData, modifier = Modifier.fillMaxSize())// add an about button to show author information}
}@RequiresApi(Build.VERSION_CODES.O)
fun showFileNameDialog(context: Context, yawData: List<Entry>, pitchData: List<Entry>, rollData: List<Entry>
) {val editText = EditText(context).apply {setHint("Enter file name")// get date and timeval currentDateTime = java.time.LocalDateTime.now()val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")setText(currentDateTime.format(formatter))}val dialog = AlertDialog.Builder(context).setTitle("Enter file name").setView(editText).setPositiveButton("Save") { _, _ ->val fileName = editText.text.toString()if (fileName.isNotEmpty()) {saveDataToCsv(context, fileName, yawData, pitchData, rollData)} else {Toast.makeText(context, "File name cannot be empty", Toast.LENGTH_SHORT).show()}}.setNegativeButton("Cancel", null).create()dialog.show()
}fun saveDataToCsv(context: Context,fileName: String,yawData: List<Entry>,pitchData: List<Entry>,rollData: List<Entry>
) {val file = File(context.getExternalFilesDir(null), "${fileName.trim()}.csv")FileWriter(file).use { writer ->writer.append("Time,Yaw,Pitch,Roll\n")for (i in yawData.indices) {writer.append("${yawData[i].x},${yawData[i].y},${pitchData[i].y},${rollData[i].y}\n")}}Toast.makeText(context, "Data saved to ${file.absolutePath}", Toast.LENGTH_SHORT).show()
}

这里面自己写的代码几乎没有,就是把MainActivity增加了一个继承SensorEventListener的接口,然后增加了一个SensorManager的实例,传感器Sensor实例,还有三个角度的数据、时间零点和当前时间。

SensorEventListener的接口要求实现几个方法:

  • onResume,注册传感器监听器
  • onPause,取消注册传感器监听器
  • onSensorChanged,传感器数据变化时调用
  • onAccuracyChanged,传感器精度变化时调用,这里我们不关心

MainActivityonCreate方法中,我们初始化了传感器管理和传感器实例。在setContent中,我们在Scaffold中增加了一个SensorDataDisplay的组件,这个组件是我们自己写的,用来显示传感器数据。

在这个SensorDataDisplay组件中,我们组织了一个Column,整个都是简单直观。

对于组件的输入变量,我们采用了remember的方式,这样可以在组件内部保存状态。当更新组件角度时,奖结果存入mutableStateListOf<Entry>中,这个EntryMPAndroidChart库中的数据结构,用来存储图表数据。

第一行是一个版权信息,第二行稍微有一点意思,是一个可以点击的Text,会访问本站。

    // add a hyperlink to the author's websiteText(text = "https://www.windtunnel.cn",modifier = Modifier.padding(6.dp).fillMaxWidth().clickable {val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.windtunnel.cn/categories/jetpack/"))context.startActivity(intent)},textAlign = TextAlign.Center,color = Color.Blue,style = TextStyle(textDecoration = TextDecoration.Underline))

Android这一点就挺好,只要用Intent就可以打开浏览器,不用自己写什么复杂的东西。

第三行就是角度标签:

    Text(text = "Yaw  : %16.4f°\nPitch: %16.4f°\nRoll  : %16.4f°\nTime: %16.6fs".format(yaw, pitch, roll, t * 1e-9),modifier = Modifier.padding(16.dp))

第四行,是一个采用开源图标库MPAndroidChartLineChart来实现的SensorChart,用来显示角度变化。

    SensorChart(yawData, pitchData, rollData, modifier = Modifier.fillMaxSize())
@Composable
fun SensorChart(yawData: List<Entry>,pitchData: List<Entry>,rollData: List<Entry>,modifier: Modifier = Modifier
) {val context = LocalContext.currentval chart = remember { LineChart(context) }val yawDataSet = LineDataSet(yawData, "Yaw").apply {lineWidth = 2fcolor = ColorTemplate.COLORFUL_COLORS[0]axisDependency = YAxis.AxisDependency.LEFT}val pitchDataSet = LineDataSet(pitchData, "Pitch").apply {lineWidth = 2fcolor = ColorTemplate.COLORFUL_COLORS[1]axisDependency = YAxis.AxisDependency.LEFT}val rollDataSet = LineDataSet(rollData, "Roll").apply {lineWidth = 2fcolor = ColorTemplate.COLORFUL_COLORS[2]axisDependency = YAxis.AxisDependency.LEFT}val lineData = LineData(yawDataSet, pitchDataSet, rollDataSet)chart.data = lineDatachart.xAxis.position = XAxis.XAxisPosition.BOTTOMchart.axisRight.isEnabled = falsechart.description.isEnabled = false// Set gesture listenerchart.onChartGestureListener = object : OnChartGestureListener {override fun onChartGestureStart(me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?) {}override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?) {}override fun onChartLongPressed(me: MotionEvent?) {}@RequiresApi(Build.VERSION_CODES.O)override fun onChartDoubleTapped(me: MotionEvent?) {showFileNameDialog(context, yawData, pitchData, rollData)}override fun onChartSingleTapped(me: MotionEvent?) {}override fun onChartFling(me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float) {}override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) {}override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {}}chart.invalidate()// Enable auto-scalingchart.isAutoScaleMinMaxEnabled = trueAndroidView({ chart }, modifier = modifier.padding(16.dp).border(1.dp, Color.Gray))
}

这里调用的是一个AndroidView,这个是Compose中的一个组件,用来显示Android原生的View。

这里实现一个动作,双击图表,会弹出一个对话框,让用户输入文件名,然后导出数据。

@RequiresApi(Build.VERSION_CODES.O)
fun showFileNameDialog(context: Context, yawData: List<Entry>, pitchData: List<Entry>, rollData: List<Entry>
) {val editText = EditText(context).apply {setHint("Enter file name")// get date and timeval currentDateTime = java.time.LocalDateTime.now()val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")setText(currentDateTime.format(formatter))}val dialog = AlertDialog.Builder(context).setTitle("Enter file name").setView(editText).setPositiveButton("Save") { _, _ ->val fileName = editText.text.toString()if (fileName.isNotEmpty()) {saveDataToCsv(context, fileName, yawData, pitchData, rollData)} else {Toast.makeText(context, "File name cannot be empty", Toast.LENGTH_SHORT).show()}}.setNegativeButton("Cancel", null).create()dialog.show()
}fun saveDataToCsv(context: Context,fileName: String,yawData: List<Entry>,pitchData: List<Entry>,rollData: List<Entry>
) {val file = File(context.getExternalFilesDir(null), "${fileName.trim()}.csv")FileWriter(file).use { writer ->writer.append("Time,Yaw,Pitch,Roll\n")for (i in yawData.indices) {writer.append("${yawData[i].x},${yawData[i].y},${pitchData[i].y},${rollData[i].y}\n")}}Toast.makeText(context, "Data saved to ${file.absolutePath}", Toast.LENGTH_SHORT).show()
}

结论

导出的数据很容易用Matlab或者Python画出来。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总的来说,这个过程非常丝滑,最终编译的apk文件大小不到10MB,非常适合用来搞一些无聊的事情。

  • 代码
  • 数据
  • apk不推荐下载

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单元测试入门和mockup

Java 新手入门&#xff1a;Java单元测试利器&#xff0c;Mock详解_java mock-CSDN博客 这个是典型的before when assert三段式&#xff0c;学一下单测思路 这个没有动态代理&#xff0c;所以是直接class(对比下面) Jmockit使用笔记_增加代码覆盖率_覆盖try catch_使用new Mock…

抢先体验:人大金仓数据库管理系统KingbaseES V9 最新版本 CentOS 7.9 部署体验

一、简介 KingbaseES 是中国人大金仓信息技术股份有限公司自主研发的一款通用关系型数据库管理系统&#xff08;RDBMS&#xff09;。 作为国产数据库的杰出代表&#xff0c;它专为中国市场设计&#xff0c;广泛应用于政府、金融、能源、电信等关键行业&#xff0c;以高安全性…

HT-HaiBOX边缘计算盒 智慧工厂方案,智慧医疗方案,智慧加油站方案,智慧安防方案,智慧城市方案;方案定制开发

背景介绍 在当今数字化时代&#xff0c;各个行业对于智能化视频监控设备的需求日益增长。无论是安防监控&#xff0c;还是智慧工厂、智慧城市等领域&#xff0c;都需要高效、智能的设备来保障安全和提高生产效率。然而&#xff0c;传统的视频监控设备存在诸多痛点&#xff1a;…

Unity中实现转盘抽奖效果(一)

实现思路&#xff1a; 旋转转盘的z轴&#xff0c;开始以角加速度加速到角速度最大值&#xff0c;结束的时候&#xff0c;以角加速度减速使角速度减少到0&#xff0c;然后转盘z轴旋转的角度就是加上每秒以角速度数值大小&#xff0c;为了使角度不能一直增大&#xff0c;对360度…

LockSupport的源码实现原理(一)

目录 底层源码分析 线程状态变化 许可证机制 中断处理 底层源码分析 public class LockSupport {// Unsafe实例private static final Unsafe U Unsafe.getUnsafe();// Thread对象中parkBlocker字段的偏移量private static final long PARKBLOCKER U.objectFieldOffset(Thre…

电子应用设计方案80:智能桌椅系统设计

智能桌椅系统设计 一、引言 智能桌椅系统旨在为用户提供更加舒适、便捷和个性化的使用体验&#xff0c;适应不同的工作和学习场景。 二、系统概述 1. 系统目标 - 实现桌椅高度、角度的自动调节&#xff0c;适应不同用户的身体尺寸和使用需求。 - 具备坐姿监测和提醒功能&…

【SpringBoot】深度解析 Spring Boot 拦截器:实现统一功能处理的关键路径

前言 &#x1f31f;&#x1f31f;本期讲解关于拦截器的详细介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话不多说…

网络渗透测试实验四:CTF实践

1.实验目的和要求 实验目的:通过对目标靶机的渗透过程,了解CTF竞赛模式,理解CTF涵盖的知识范围,如MISC、PPC、WEB等,通过实践,加强团队协作能力,掌握初步CTF实战能力及信息收集能力。熟悉网络扫描、探测HTTP web服务、目录枚举、提权、图像信息提取、密码破解等相关工具…

计算机毕业设计Python+Spark考研预测系统 考研推荐系统 考研数据分析 考研大数据 大数据毕业设计 大数据毕设

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

2024年度总结:保持正念 延迟满足

总结&#xff1a; 时光荏苒&#xff0c;很快就到了年底&#xff0c;2024年也就悄悄的过去了&#xff0c;回顾这一年&#xff0c;有很多的感触&#xff0c;在此做一个总结&#xff0c;留下自己的脚印 CTF&#xff1a; 要总结的第一件事那当然是CTF&#xff0c;回顾这一年&#…

LeetCode - 初级算法 数组(存在重复元素)

存在重复元素 这篇文章讨论如何判断一个数组中是否存在重复元素。 免责声明:本文来源于个人知识与公开资料,仅用于学术交流。 描述 给定一个整数数组 nums,如果任一值在数组中出现至少两次,返回 true;如果数组中每个元素互不相同,返回 false。 示例: 输入: nums =…

03-spring-理-DefaultListableBeanFactory

DefaultListableBeanFactory 是 beanFactory的实现 重要 有一些类被注解标注了。但是没有加入到容器。 怎么将标注的类加入到容器&#xff1f; 这个时候使用bean的后置处理器 主要是通过bean后置处理器来处理 被注解标注的类 org.springframework.context.annotation.inter…

绘制三元图、颜色空间图:R语言代码

本文介绍基于R语言中的Ternary包&#xff0c;绘制三元图&#xff08;Ternary Plot&#xff09;的详细方法&#xff1b;其中&#xff0c;我们就以RGB三色分布图为例来具体介绍。 三元图可以从三个不同的角度反映数据的特征&#xff0c;因此在很多领域都得以广泛应用&#xff1b;…

30天开发操作系统 第 10 天 -- 叠加处理

前言 得益于昨天的努力&#xff0c;我们终于可以进行内存管理了。不过仔细一看会注意到&#xff0c;bootpack.c都已经有254行了。笔者感觉这段程序太长了&#xff0c;决定整理一下&#xff0c;分出一部分到memory.c中去。(整理中)…好了&#xff0c;整理完了。现在bootpack.c变…

2024年RAG:回顾与展望

2024年&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff09;技术经历了从狂热到理性的蜕变&#xff0c;成为大模型应用领域不可忽视的关键力量。年初&#xff0c;AI的“无所不能”让市场充满乐观情绪&#xff0c;RAG被视为解决复杂问题的万能钥匙&#xff1…

鸿蒙项目云捐助第三十一讲云捐助项目云前台显示商品列表

鸿蒙项目云捐助第三十一讲云捐助项目云前台显示商品列表 前面完成了云数据库后台的商品批量添加&#xff0c;这里需要把数据放在分类导航页面中显示。 一、云前台显示商品列表 这里需要把商品列表显示在MyNavSliderBar的组件中,MyNavSliderBar组件是通过首页路由实现的,在项…

【brew安装失败】DNS 查询 raw.githubusercontent.com 返回的是 0.0.0.0

从你提供的 nslookup 输出看&#xff0c;DNS 查询 raw.githubusercontent.com 返回的是 0.0.0.0&#xff0c;这通常意味着无法解析该域名或该域名被某些 DNS 屏蔽了。这种情况通常有几个可能的原因&#xff1a; 可能的原因和解决方法 本地 DNS 问题&#xff1a; 有可能是你的本…

【SpringMVC】拦截器

拦截器&#xff08;Interceptor&#xff09;是一种用于动态拦截方法调用的机制。在 Spring MVC 中&#xff0c;拦截器能够动态地拦截控制器方法的执行过程。以下是请求发送与接收的基本流程&#xff1a; 当浏览器发出请求时&#xff0c;请求首先到达 Tomcat 服务器。Tomcat 根…

python使用PyQt5,整套,桌面应用

安装 安装 pip install PyQt55.7.1 pip install PyQtWebEngine1、创建窗口&#xff0c;按百分比划分 from PyQt5.QtGui import QGuiApplication from PyQt5.QtWidgets import QApplication, QWidget # 创建应用程序实例 app QApplication([]) # 创建主窗口 window QWidget(…

018-spring-基于aop的事务控制

1 先配置平台事务管理器 2 在配置 spring提供的advice 3 事务增强的aop 总结&#xff1a; 事务就是要做2个配置&#xff1a; <!-- 1 开启事务管理器 不同的框架对应不同的事务管理器 --> <bean id"transactionManager" class"org.springframework.j…