安卓开发中的Jetpack Compose

news2024/11/26 0:40:11

本文内容总结自郭神的jetpack compose专栏 ,整理自学,未完持续...

一、了解Jetpack Compose

        Compose是一个由Google Android团队官方推出的声明式UI框架,Compose也是属于AndroidX的一部分,它的本质就是用来编写界面以及处理与用户交互相关的逻辑的,来代替传统的View用xml来定义UI的方法。

        传统的View肯定不是声明式编程,它更多是一种过程式的思维,们在描述一个View的时候是不会描述它的状态的,或者只会描述它的初始状态,想要更新这个View的状态就是先调用findViewById()方法来获取到这个View的实例,然后再通过setXXX来更改它的状态,这就是过程式思维。

        而声明式思维我们仍然正常地去描述一个控件,但这次要附带上它的状态。然后当有任何状态需要发生改变时,只需要像刷新网页一样,让整个界面上的所有有变化的元素刷新一遍,那么自然所有状态都能得到更新了。重新刷新界面以此来更新界面内容的这个过程我们称之为重组。Compose会保证,每次重组永远都只会去更新那些必要的控件,状态没有发生变化的控件是不会更新的,以此来保证运行效率。

 


二、Compose工程的基础知识

1、创建一个新的Compose工程

创建一个新的Compose工程之后(会自动添加相关依赖)生成的代码如下:

package com.example.composeapplication

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.composeapplication.ui.theme.ComposeApplicationTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ComposeApplicationTheme {
                Scaffold( modifier = Modifier.fillMaxSize() ) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    ComposeApplicationTheme {
        Greeting("Android")
    }
}

2、Composable函数

        如果一个函数的上方,使用了@Composable进行声明,那么它就是一个Composable函数。

        Composable函数只能在Composable作用域中才能调用,所以如果你尝试在setContent函数的闭包外面调用Greeting函数,编译会无法通过。

        所有的Composable函数还有一个约定俗成的习惯,就是函数的命名首字母需要大写。 这样我们就能够更加直观地通过函数名称来快速地判断一个函数是不是Composable函数,不然的话还需要找到这个函数的定义位置,看看它有没有@Composable注解才行。

3、setContent函数

     setContent 函数是 Jetpack Compose 中用于设置 Activity 或 Fragment 的 UI 内容的一个关键函数。setContent 函数负责接收这些Composable函数并将其设置为 Activity 或 Fragment 的内容。setContent函数会提供一个Composable作用域,所以在它的闭包中我们就可以随意地调用Composable函数了。

4、GreetingPreview函数

GreetingPreview函数。它也是一个Composable函数,但是它比普通的Composable函数多了一个@Preview注解,这个注解是在Jetpack Compose中用于预览UI组件的一个重要特性。它允许开发者在不运行整个应用的情况下查看Composable函数的输出(用来快速预览UI样式的),这对于快速迭代UI设计非常有用。

 


 

三、常见基础控件

1、Text(TextView)

Text的用法只需要给它指定一个text参数,里面传入要显示的内容即可

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTestTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Greeting()
                }
            }
        }
    }
}

@Composable
fun Greeting() {
    Column {
        Text(text = "This is Text")
    }
}

通过增加以下代码可以更改text的设置:

@Composable
fun Greeting() {
    Column {
        Text(
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )
    }
}

 

2、Button

添加Button按钮

fun Greeting() {
    Column {
        ...
        
        Button(onClick = { /*TODO*/ }) {
            
        }
    }
}

        如果要去给Button设置一个text属性来指定文字内容,Compose中的Button没有这个text属性。在Compose当中,Button和Text之间并没有什么关系。它们是两个独立的控件,并且通常它们还需要配合在一起使用才行。

给Button添加文本:将Text放到了Button的闭包当中,这样就可以为Button指定显示的内容了。

@Composable
fun Greeting() {
    Column {
        ...
        
        Button(onClick = { /*TODO*/ }) {
            Text(
                text = "This is Button",
                color = Color.White,
                fontSize = 26.sp
            )
        }
    }
}

给Button添加Toast点击事件:

注意:要想弹出Toast需要有Context参数才行。

       在Composable函数当中获取Context对象,可以调用LocalContext.current获得。

@Composable
fun Greeting() {
    Column {
        ...
        
        val context = LocalContext.current
        Button(onClick = {
            Toast.makeText(context, "This is Toast", Toast.LENGTH_SHORT).show()
        }) {
            Text(
                text = "This is Button",
                color = Color.White,
                fontSize = 26.sp
            )
        }
    }
}

3、TextField(EditText)

        TextField对应的是View当中的EditText,也就是一个输入框,TextField参数列表上有两个必填参数,其中value参数用于指定当前输入框中显示的文字内容,onValueChange参数用于监听输入框中的文字内容变化。

        这里通过placeholder参数来指定一个占位符,其实就是和hint差不多的功能,用户没有在输入框里输入任何内容时就显示placeholder中的内容,一旦用户输入了任何内容,placeholder就会消失。

@Composable
fun Greeting() {
    Column {
        ...

        TextField(
            value = "",
            onValueChange = {},
            placeholder = {
                Text(text = "Type something here")
            }
        )
    }
}

        这样编写代码当尝试在输入框里输入内容时不管你在键盘上敲了什么东西,输入框上都不会显示出来。这是和EditText最大的不同点,因为EditText一定是可以显示你输入的内容的。

        这就是声明式UI的工作流程,有点像是刷新网页一样。即我们去描述一个控件时要附带上它的状态。然后当有任何状态需要发生改变时,只需要像刷新网页一样,让界面上的元素刷新一遍,那么自然状态就能得到更新了。

        而TextField中显示的内容就是一种状态,因为随着你的输入,界面上显示的内容也需要跟着更新才行。

        当在TextField中输入内容时,首先我们并没有去做刷新页面这个操作。其次,就算是做了刷新操作,TextField刷新后发现value参数指定的内容仍然是一个空字符串,因此我们输入的内容还是无法上屏。想要显示输入内容需要借助Compose的State组件。

4、Image(ImageView)

        Image对应的是View当中的ImageView,图片通常有两种比较常见的形式,一种是drawable资源,另一种是bitmap对象。Image对这两种形式的图片都提供了支持。

        Image参数列表上有两个必填参数,其中painter参数用于指定要展示的drawable资源,contentDescription参数用于指定对于这个资源的文字描述。

        这个文字描述主要是在accessibility模式下,为有视觉障碍的群体提供发音辅助的。ImageView上也有类型的功能,但只是作为一项可选的属性提供。而到了Compose的Image上,则变成了一个强制性的参数。

        当然,如果不想要为图片指定contentDescription,也可以直接传null。

drawable资源图片:

@Composable
fun Greeting() {
    Column {
        ...

        Image(
            painter = painterResource(id = R.drawable.dog),
            contentDescription = "A dog image"
        )
    }
}

bitmap资源:

        先借助ImageBitmap.imageResource函数将drawable资源转换成了一个ImageBitmap对象,然后再将它转给Image控件即可。

@Composable
fun Greeting() {
    Column {
        ...

        val bitmap: ImageBitmap = ImageBitmap.imageResource(id = R.drawable.dog)
        Image(
            bitmap = bitmap,
            contentDescription = "A dog image"
        )
    }
}

        需要注意的是,Image接收的是Compose中专有的ImageBitmap对象,而不是传统的Bitmap对象。如果你这里要传入的是一个传统的Bitmap对象,那么还得再额外调用asImageBitmap函数转换一下,如下所示:

@Composable
fun Greeting(bitmap: Bitmap) {
    Column {
        ...

        Image(
            bitmap = bitmap.asImageBitmap(),
            contentDescription = "A dog image"
        )
    }
}

5、ProgressIndicator(ProgressBar)

ProgressIndicator对应的是View当中的ProgressBar,也就是用于展示进度条的。

ProgressIndicator也有这两种形态:

对应的控件分别是CircularProgressIndicatorLinearProgressIndicator

它们的用法非常简单,只需要放置一个CircularProgressIndicator控件即可,我们甚至都不需要指定任何的参数就可以直接使用。除了默认的效果外,我们也可以轻松定制进度条的样式,比如通过如下代码就可以修改进度条的颜色和线条粗细:

@Composable
fun Greeting() {
    Column {
        ...

        CircularProgressIndicator(
            color = Color.Green,
            strokeWidth = 6.dp
        )
    }
}

LinearProgressIndicator使用方法也是一样的,不再赘述。

四、常见基础布局

1、Column (LinearLayout vertical)

        在 Jetpack Compose 中,Column 是一个布局修饰符,它可以将一组 Composable 函数按垂直方向排列。它类似于 XML 布局中的 LinearLayout

可以看到Column 接受以下参数:

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}
  1. modifier: 用于修改 Column 的布局行为,例如设置宽度、高度等。
  2. horizontalAlignment: 设置 Column 中元素的水平对齐方式,默认为 Alignment.Start,即左对齐。
  3. verticalArrangement: 设置 Column 中元素的垂直间距,默认没有间距

verticalArrangement参数可指定的分布方式非常丰富,LinearLayout想要进行完全类似的模拟还是相当困难的,看Google官方的动图示例就能快速了解每种分布方式的效果。

 

2、Row(LinearLayout horizontal)

Row和Column基本是就是完全一样的东西,只是方向上有所区别。

关于horizontalArrangement参数可以指定哪些分布方式及其效果如何,看图:

3、Box(FrameLayout)

        Box对应的是View当中的FrameLayout,所有的控件都会默认摆放在布局的左上角。并且后添加的控件是会压在先添加的控件上面的。也可以通过修改子控件的modifier参数来指定控件在布局中的对齐方式,这和Column中的用法是相似的。

 


 

五、Modifier

只要使用了Compose,那么就一定绕不开Modifier。

Modifier主要负责以下4个大类的功能:

  1. 修改Compose控件的尺寸、布局、行为和样式。
  2. 为Compose控件增加额外的信息,如无障碍标签。
  3. 处理用户的输入(在屏幕上进行滑动、点击各种操作时,会认为这是用户的一种输入)
  4. 添加上层交互功能,如让控件变得可点击、可滚动、可拖拽。 

为什么一个参数可以做这么多事情呢?

        因为Modifier是一个非常特殊的参数,它可以通过链式调用的方式串接无限多的API,从而实现各种效果。

        而Modifier的链式调用模式对于串接的顺序是有要求的,不同的串接顺序可能实现的是不同的效果。这点和xml的区别非常大,因为xml对于属性的指定是没有顺序要求的,每个属性写在上面还是写在下面都无所谓。


六、State

1、作用

        在Jetpack Compose中,State 是一个非常重要的概念,它允许你在 Composable 函数中维护和更新数据

        State 通常用于跟踪 Composable 函数的内部状态,例如按钮的点击次数、文本输入字段的内容、列表的滚动位置等。State 的更新会触发 Composable 函数重新绘制,从而反映最新的状态变化。

        State是一种基于观察者机制的组件,它的用法和LiveData类似,State可以让Compose感知到界面上有状态发生了变化,从而对界面上相关联的Composable函数进行重组。不仅如此,State还可以让Compose能够精准只更新那些状态有变化的控件,而那些状态没有变化的控件在重组的时候则会跳过执行。

2、用法

基础用法:

@Composable
fun Counter(modifier: Modifier = Modifier) {
    val count = remember { mutableStateOf(0) }
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "${count.value}",
            fontSize = 50.sp
        )
        Button(
            onClick = { count.value++ }
        ) {
            Text(
                text = "Click me",
                fontSize = 26.sp
            )
        }
    }
}

使用步骤:

  1. 把count变量从原来的整型变成了一个State类型。mutableStateOf()函数就是用于创建一个可变的State对象,参数中传入的是初始值。
  2. 接下来如果想要访问这个State对象中的值,那么就调用它的getValue()函数,想要更新State对象中的值,就调用它的setValue()函数
  3. 而使用了State来追踪某个数据之后,当这个数据的值发生变化,Compose就会自动触发重组来更新所有读取这个值的地方,从而界面就会得到更新了。
  4. remember函数的作用是让其包裹住的变量在重组的过程中得到保留,从而就不会出现变量被重新初始化的情况了。

3、使用By关键字

更加普遍的写法是借助Kotlin的委托语法对来State的用法进一步精简:

@Composable
fun Counter(modifier: Modifier = Modifier) {
    //var count by remember { mutableStateOf(0) }
    val count = rememberSaveable { mutableStateOf(0) }
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "$count",
            fontSize = 50.sp
        )
        Button(
            onClick = { count++ }
        ) {
            Text(
                text = "Click me",
                fontSize = 26.sp
            )
        }
    }
}

使用by关键字替代了之前的等号,用委托的方式来为count变量赋值,改变了一下赋值方式,count变量的类型都会因此而发生变化。

  • 之前用等号赋值的时候,count的类型是MutableState
  • 而改用by关键字赋值之后,count的类型就变成了Int。

补充:防止旋转屏幕时丢失count,可以使用rememberSaveable函数

rememberSaveable函数是remember函数的一个增强版,它唯一和remember不同的地方就是在于其包裹的数据在手机横竖屏旋转时会被保留下来。

 

4、State hoisting(状态提升)

观察下面两个分别是有状态的Composable函数和无状态的Composable函数:

@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
    val count by remember { mutableStateOf(0) }
    Text(
        text = "$count",
        fontSize = 50.sp
    )
}

@Composable
fun StatelessCounter(count: Int, modifier: Modifier = Modifier) {
    Text(
        text = "$count",
        fontSize = 50.sp
    )
}

        根据Google给出的最佳实践准则,有状态的Composable函数通常在复用性和可测试性方面都会表现得比较差。因此,当我们编写Composable函数时,最理想的情况就是尽可能地让它成为一个无状态的Composable函数

        Compose提供了一种编程模式,叫State hoisting,中文译作状态提升。在编写代码时要尽可能地把State提到更上一层的Composable函数当中,这样偏底层的Composable函数就可以成为无状态的函数,从而提高它们的复用性。

而实现状态提升最核心的步骤只有两个。

  1. 将原来声明State对象的写法改成用参数传递的写法,就像上面的示例一样。
  2. 将写入State数据的地方改成用回调的方式来通知到上一层。(借助Kotlin中的高阶函数实现回调编写)

对之前的代码进行状态提升后的结果是下面这样的:

@Composable
fun CallCounter(modifier: Modifier = Modifier) {
    var count by rememberSaveable { mutableStateOf(0) }
    Counter(
        count = count,
        onIncrement = { count++ },
        modifier
    )
}

@Composable
fun Counter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "$count",
            fontSize = 50.sp
        )
        Button(
            onClick = { onIncrement() }
        ) {
            Text(
                text = "Click me",
                fontSize = 26.sp
            )
        }
    }
}

状态提升要点:

  1. Counter函数中移除了count变量的声明,改成了使用参数传递的模式。
  2. 同时,当按钮被点击时,因为Counter函数已经无法对State变量进行写入,这里改用了回调的方式将点击事件通知到上一层。最后由上一层来完成对count变量的修改操作。

复用Counter函数:

现在Counter函数已经无状态了(状态提升),在状态提升之后,就可以使用如下写法轻松复用同一套逻辑实现两种不同的计数器了:(状态提升之前,Counter函数是无论如何都无法实现两种不同逻辑的计数器的)

实现两个计数器

第一个计数器和之前保持一致即可

第二个计数器则是双倍计数器,每点击一次按钮,让计数器的数值加2。

@Composable
fun CallCounter(modifier: Modifier = Modifier) {
    var count by rememberSaveable { mutableStateOf(0) }
    var doubleCount by rememberSaveable { mutableStateOf(0) }
    Column {
        Counter(
            count = count,
            onIncrement = { count++ },
            modifier.fillMaxWidth()
        )
        Counter(
            count = doubleCount,
            onIncrement = { doubleCount += 2 },
            modifier.fillMaxWidth()
        )
    }
}

@Composable
fun Counter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
    ...
}

 

  1. 这里调用了两次Counter函数并让它们纵向排列。
  2. 然后通过参数传递的方式给两次Counter函数调用传入了不同的State对象,并通过回调的方式对两个计时器的点击事件进行了不同的逻辑处理。

5、在compose中使用Viewmodel

使用LiveData时使用:

将count和doubleCount变量封装到了ViewModel当中,并且提供了incrementCount()和incrementDoubleCount()这两个接口来允许增加这两个变量的值。

class MainViewModel : ViewModel() {
    private val _count = MutableLiveData<Int>()
    private val _doubleCount = MutableLiveData<Int>()

    val count: LiveData<Int> = _count
    val doubleCount: LiveData<Int> = _doubleCount

    fun incrementCount() {
        _count.value = (_count.value ?: 0).plus(1)
    }

    fun incrementDoubleCount() {
        _doubleCount.value = (_doubleCount.value ?: 0).plus(2)
    }
}

 接下来就是如何在Compose中监听和修改这两个变量的值:

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun CallCounter(modifier: Modifier = Modifier, viewModel: MainViewModel = viewModel()) {
    val count by viewModel.count.observeAsState(0)
    val doubleCount by viewModel.doubleCount.observeAsState(0)
    Column {
        Counter(
            count = count,
            onIncrement = { viewModel.incrementCount() },
            modifier.fillMaxWidth()
        )
        Counter(
            count = doubleCount,
            onIncrement = { viewModel.incrementDoubleCount() },
            modifier.fillMaxWidth()
        )
    }
}
  1. 在CallCounter函数的参数列表当中增加了一个viewModel参数,并且通过默认赋值的方式对它进行初始化,这样就可以调用MainViewModel中定义的对象和函数了。
  2. 需要将LiveData转换成State,observeAsState()函数就是用来做这个事情的,参数中传入的0表示它的初始值。
  3. 最后,当按钮被点击的时候,我们调用ViewMode的incrementCount()incrementDoubleCount()函数来对计数器进行修改即可。


七、Lazy Layout

Jetpack Compose 中的 Lazy Layout 类似于 Android View 系统中的 ListViewRecyclerView。它们都是用来高效地展示大量数据集合的组件,并且都采用了类似的懒加载机制,只渲染当前屏幕上可见的部分项,这样可以极大地提高性能并减少内存消耗。

在 Jetpack Compose 中,Lazy Layout只是一个可复用列表的统称,事实上并没有这样的一个控件。Lazy Layout 是通过 LazyColumnLazyRow 来实现的,它们分别对应于垂直方向水平方向的布局。这些布局会根据需要动态地加载和卸载视图,使得即使在数据量很大的情况下也能流畅地滚动。

1、基本Lazy Layout

当创建了纵向/纵向滚动的字母表,基本用法如下:

LazyColumn的用法:

@Composable
fun ScrollableList() {
    val list = ('A'..'Z').map { it.toString() }
    LazyColumn {
        items(list) { letter ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(120.dp)
            ) {
                Text(
                    text = letter,
                    textAlign = TextAlign.Center,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .fillMaxSize()
                        .wrapContentHeight(Alignment.CenterVertically)
                )
            }
        }
    }
}

LazyRow的用法:

@Composable
fun ScrollableList() {
    val list = ('A'..'Z').map { it.toString() }
    LazyRow {
        items(list) { letter ->
            Card(
                modifier = Modifier
                    .width(120.dp)
                    .height(200.dp)
            ) {
                Text(
                    text = letter,
                    textAlign = TextAlign.Center,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .fillMaxSize()
                        .wrapContentHeight(Alignment.CenterVertically)
                )
            }
        }
    }
}

Lazy Layout用法的核心:基本就是在LazyColumn或LazyRow的闭包中添加一个items函数,并且将我们的列表数据源传递给这个函数即可。

2、带下标的Lazy Layout

只需要将刚才的items函数替换成itemsIndexed函数就可以了,使用itemsIndexed函数之后,我们将会在函数闭包的参数列表上同时得到下标和字母表内容,代码如下所示:

@Composable
fun ScrollableList() {
    val list = ('A'..'Z').map { it.toString() }
    LazyRow {
        itemsIndexed(list) { index, letter ->
            Card(
                modifier = Modifier
                    .width(120.dp)
                    .height(200.dp)
            ) {
                Text(
                    text = "$index $letter",
                    textAlign = TextAlign.Center,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .fillMaxSize()
                        .wrapContentHeight(Alignment.CenterVertically)
                )
            }
        }
    }
}

3、Lazy Layout的边距设置

[1]Modifier.padding()

可以在Card控件上通过Modifier.padding()设置一些边距,让每个子项之间都留有一些空隙

[2]contentPadding

使用专门为Lazy Layout打造的边距设置属性contentPadding,就能保证给Lazy Layout整体的左右两边设置边距的同时,还不会在滚动中出现切割现象了

[3]Arrangement.spacedBy()

可以不用借助Modifier.padding()来设置边距,Lazy Layout提供了专门给子项之间设置边距的属性,使用Arrangement.spacedBy()即可,得出的效果就是,每个子项之间都会有20dp的间隔,

使用Arrangement.spacedBy()之后,第一个子项的左侧和最后一个子项的右侧是不会有边距的。

这个可以继续叠加我们刚才学到的contentPadding属性,给Lazy Layout整体设置边距,从而完成想要的效果。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】二叉树的深度理解

&#x1f36c;个人主页&#xff1a;Yanni.— &#x1f308;数据结构&#xff1a;Data Structure.​​​​​​ &#x1f382;C语言笔记&#xff1a;C Language Notes 前言 在之前学习了二叉树的基本概念&#xff0c;但二叉树有着更深入知识理解&#xff0c;这篇文章可以帮助大…

Java语言程序设计——篇十六

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 欢迎大家&#xff1a;这里是我的学习笔记、总结知识的地方&#xff0c;喜欢的话请三连&#xff0c;有问题可以私信&#x1f333;&#x1f333;&…

ROBOT--空心杯电机是什么

空心杯电机是什么 1. 简介2. 结构3. 主要特点4. 应用场合5. 优点6. 缺点 1. 简介 空心杯电机是一种微型伺服直流电机&#xff0c;由定子和转子两大核心部分组成&#xff0c;通常来讲尺寸一般较小通常不超过40mm。在结构上突破了传统直流电机的结构形式&#xff0c;采用的是无铁…

如何理解递归

在二叉树的题目中&#xff0c;我们难免会用到递归方法&#xff0c;递归思想很简单&#xff0c;但运用起来却因为抽象而难以理解。 理解递归的关键在于认识到它是一种解决问题的方法&#xff0c;允许函数直接或间接地调用自身。以下是对递归的概述以及如何理解它的几个要点&…

【算法】一文带你搞懂完全背包!(附背包问题总结)

理论基础 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。这就是完全背包问题。 完全背包…

【计算机组成原理】三、存储系统:1.存储器的分类、层次化结构、性能指标、基本组成(半导体、存储芯片基本原理)

三、存储系统&#xff08;存储器层次结构&#xff09; 文章目录 三、存储系统&#xff08;存储器层次结构&#xff09;1.存储器的分类1.1按在计算机中的作用&#xff08;层次&#xff09;❗多级存储结构&#xff08;层次化结构&#xff09;1.2按存储介质1.3按存取方式1.4按信息…

uniapp 小程序 设置按钮固定到页面的最下方

解决方案 我们在做小程序的时候&#xff0c;特别是页面是以列表的形式进行展示&#xff0c;并且页面必须还要新增数据时&#xff0c;这是就会在页面的底部加一个固定的新增按钮&#xff0c;点击新增按钮&#xff0c;弹出一个弹窗…然后进行下一步的业务逻辑操作&#xff0c;那…

Answer use of function tool by OpenAI assistant in Python

题意&#xff1a;“在 Python 中使用 OpenAI 助手的函数工具的用途” 问题背景&#xff1a; I am trying to answer to OpenAI assistants function tool. “我正在尝试回答 OpenAI 助手的函数工具。” See my code below. The custom function is called "funnyfunc&qu…

系统编程 网络 基于tcp协议

tcp的客户端&#xff1a; socket&#xff08;&#xff09;&#xff1b;用来开链接的端口 bind&#xff08;&#xff09;&#xff1b;绑定作用&#xff08;在客户端可选可不选&#xff09; connect&#xff08;&#xff09;&#xff1b;链接作用 tcp的服务端&#xff1a; s…

性能测试全解

世界上没有陌生人&#xff0c;只有还没认识的朋友 一&#xff0e;性能测试的意义 由于软件系统的性能问题而引起严重后果的事件比比皆是&#xff0c;下面列举几个案例 (1)2007年10月&#xff0c;北京奥组委实行2008年奥运会门票预售&#xff0c;一时间订票官网访问量激致系统…

「知识篇」UWB精确测距与定位技术优势的详细探讨

UWB650模块是思为无线新发布的一款双边双向测距&#xff0c;三点平面定位模块&#xff0c;WB650模块是在UWB3000F27基础上研发&#xff0c;并搭载单片机&#xff0c;用户无需配置可直接使用。 遵循IEEE 802.15.4-2020标准的UWB技术及其通信协议&#xff0c;提供高精度、低功耗…

第12章 网络 (1)

目录 12.1 互联的计算机 12.2 ISO/OSI 和TCP/IP 参考模型 12.3 通过套接字通信 12.3.1 创建套接字 12.3.2 使用套接字 12.3.3 UDP套接字 12.4 网络实现的分层模型 本专栏文章将有70篇左右&#xff0c;欢迎关注&#xff0c;查看后续文章。 网络相关的头文件数目巨大&…

两台电脑之间记事本内容如何转移?

记事本是我们日常生活中不可或缺的工具&#xff0c;它轻便、简单&#xff0c;方便我们随时记录生活中的点滴、工作中的灵感或重要的事务。比如&#xff0c;在会议中快速记下关键点&#xff0c;或者在阅读时捕捉一闪而过的想法。然而&#xff0c;随着数字化生活的推进&#xff0…

重塑“我店”平台:绿色积分引领的数字消费新纪元

在数字化转型的洪流中&#xff0c;“我店”平台凭借其创新的绿色积分体系异军突起&#xff0c;成为市场中的璀璨新星。本文将深度剖析“我店”的运营模式、市场效应及其如何通过绿色积分机制开创消费新潮流。 一、崛起之路与市场震撼力 自2021年盛夏在上海启航以来&#xff0c…

研讨会邀请函-Parasoft TÜV Rheinland|SOA架构下符合功能安全要求的软件自动化测试解决方案

尊敬的技术先锋&#xff0c; 在汽车行业的数字化转型浪潮中&#xff0c;软件安全已成为我们共同关注的焦点。Parasoft 联合 TV Rheinland&#xff0c;荣幸地邀请您参与我们即将举办的专业研讨会&#xff0c;与行业领袖一同探索SOA架构下的功能安全软件开发测试方案。 会议议程…

支付宝小程序websocket长连接(心跳版本)

注意点&#xff1a; 关闭连接一定要把那些开下来的监听全部关闭掉 1.开启连接 /*长连接*/ connectWebSocket() {let that this;my.connectSocket({url: ws://192.xx.8.xx:7780/charger-service-netty/websocket/${uni.getStorageSync(chargePointId)},header: {AccessType: a…

三种相机模型总结(针孔、鱼眼、全景)

相机标定 文章目录 相机标定前言 前言 我们最常见的投影模型Perspective Projection Model描述的就是针孔相机的成像原理。从上面的图根据相似三角形可以得出 参考链接 https://zhuanlan.zhihu.com/p/540969207 相机标定之张正友标定法数学原理详解&#xff08;含python源码&a…

楼宇智慧公厕系统实时卫生状况一目了然

在科技飞速发展的今日&#xff0c;楼宇智慧公厕系统如一颗璀璨的新星&#xff0c;悄然改变着我们的生活。它以先进的技术手段&#xff0c;让公厕的实时卫生状况一目了然&#xff0c;为人们带来了全新的如厕体验。 当我们步入一栋现代化的楼宇&#xff0c;对公厕的期待不再仅仅是…

JVM 内存结构了解吗,每个区域都存放什么数据?

Java 程序是运行在 JVM 之中的&#xff0c;所有对象的创建和分配都在 JVM 中。 内存结构&#xff1a; 方法区&#xff1a;各线程共享&#xff0c;主要存放类信息、常量、静态变量 虚拟机栈&#xff1a;线程私有&#xff0c;主要存放基本数据类型&#xff08;int、char、float……

Blazor开发框架Known-V2.0.9

V2.0.9 Known是基于Blazor的企业级快速开发框架&#xff0c;低代码&#xff0c;跨平台&#xff0c;开箱即用&#xff0c;一处代码&#xff0c;多处运行。本次版本主要是修复一些BUG和表格页面功能增强。 官网&#xff1a;http://known.pumantech.comGitee&#xff1a; https:…