Compose - 修饰符 Modifier

一、概念

四大使用场景:

  • 修改外观(尺寸、样式、布局、行为)。
  • 添加额外信息(如无障碍标签)。
  • 添加交互功能(点击、滚动、拖拽、缩放)。
  • 处理用户输入。

1.1 为组合函数添加 Modifier 参数

  • 任何一个组合函数都应该有一个 Modifier 参数:出于灵活性考虑,例如 Image() 的作用是显示头像,它可以控制形状边框等,但不应该控制对齐,否则总是居左显示,该对齐方式应该让父控件调用时决定,此时为 Image() 增加 Modifier 参数,就可以在调用时指定对齐方式了。
  • 放在第一个非强制性参数位置:由于是可选参数,放在所有强制性参数后面,这样调用方就可以选择是否指定,否则就必须被强制性的先指定Modifier,使用起来不变。
@Composable
fun ParentLayout(modifier: Modifier = Modifier) {//调用时指定对齐方式Avatar(Modifier.align(Alignment.CenterHorizontally))
}@Composable
fun Avatar(modifier: Modifier = Modifier) {Image(painter = painterResource(id = R.drawable.icon),contentDescription = "Icon Image",//使用时,用传入的modifiermodifier = modifier.wrapContentSize().background(Color.Gray).padding(18.dp).border(5.dp, Color.Magenta, CircleShape).clip(CircleShape))
}
@Composable
fun TestComposable(a: Int, b: String, modifier: Modifier = Modifier) {...}

二、修改外观

通过链式调用串接API,因此顺序会影响最终结果(例如边距padding)。

2.1 尺寸

2.1.1 指定具体值

设置首选值(如果指定的大小不满足父布局的约束,则尺寸将会无效。如果强制设置而不考虑父控件约束使用 requiredSize)。

.width(width: Dp)

.width(intrinsicSize: IntrinsicSize)        //参数为 IntrinsicSize.Min 或 IntrinsicSize.Max

.height(height: Dp)

.height(intrinsicSize: IntrinsicSize)

.size(size: Dp)        //同时设置宽高

.size(width: Dp, height: Dp)        //分开设置宽高

Modifier.width(5.dp).height(5.dp)
Modifier.size(5.dp)    //同时设置宽高

@Composable
fun Demo() {Box(Modifier.background(Color.Blue).width(50.dp).height(IntrinsicSize.Min)  //高度){Column{//不管这里是Column还是Row,不管上面是MIn还是Max,蓝色高度上都是包裹效果Box(Modifier.background(Color.Red).size(25.dp))Box(Modifier.background(Color.Green).size(10.dp))}}
}

2.1.2 强制使用指定值

.requiredWidth(width: Dp)
.requiredHeight(height: Dp)
.requiredSize(size: Dp)

 

@Preview(showBackground = true)
@Composable
fun Demo1() {Column(modifier = Modifier.size(100.dp)) {Image(painter = painterResource(id = R.drawable.logo_wechat_rectangle),contentDescription = null,modifier = Modifier.size(150.dp)    //大于父控件尺寸,无效)}
}@Preview(showBackground = true)
@Composable
fun Demo2() {Column(modifier = Modifier.size(100.dp)) {Image(painter = painterResource(id = R.drawable.logo_wechat_rectangle),contentDescription = null,modifier = Modifier.requiredSize(150.dp)    //强制使用指定值)}
}

2.1.3 占可用空间百分比

取值范围0.0~1.0。

.fillMaxWidth(fraction: Float = 1f)

.fillMaxHeight(fraction: Float = 1f)

.fillMaxSize(fraction: Float = 1f)

Modifier.fillMaxWidth(0.5f).fillMaxHeight(0.5f)
Modifier.fillMaxSize(0.5f)    //同时设置宽高

2.1.4 设置范围

限定在最大值和最小值之间。

.widthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)

.heightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)

.sizetIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)

父容器和子元素width一致,只看height
父heightIn(min=10、max=40)子 < 父:子显示3,父显示10
子 = 父:高度显示一致
子 > 度:都为父最大值40
父heightIn(min=50)子 < 父:子显示30,父显示50
子 = 父:高度显示一致
子 > 度:都为子高度100
父height(max=50)子 < 父:都为子高度30
子 = 父:高度显示一致
子 > 度:都为父最大值50
@Composable
fun Demo() {Box(Modifier.background(Color.Blue).width(50.dp).heightIn(min = 10.dp,max = 40.dp)){Box(Modifier.background(Color.Red).width(25.dp)
//            .height(30.dp)  //子元素在范围内,蓝色和红色一样高
//            .height(900.dp)  //子元素高于最大值,红色蓝色都显示40dp.height(3.dp)  //子元素低于最小值,红色显示3dp,蓝色显示10dp)}
}

2.1.5 权重 weight

设置 weight 时,fill = true/false 的区别。

2.1.6 根据自身内容决定大小

例如可以让 Image 根据自身内容来决定控件的大小。

.wrapContentWidth(
    align: Alignment.Horizontal = Alignment.CenterHorizontally,        //对齐方式
    unbounded: Boolean = false
)
.wrapContentHeight(
    align: Alignment.Vertical = Alignment.CenterVertically,
    unbounded: Boolean = false
)
.wrapContentSize(
    align: Alignment = Alignment.Center,
    unbounded: Boolean = false
)
@Composable
fun Demo() {Column(modifier = Modifier.width(50.dp)) {Image(painter = painterResource(id = R.drawable.logo_baidu),contentDescription = null,modifier = Modifier.wrapContentSize())}
}

2.2 样式

2.2.1 边距 padding

由于调整链式调用的顺序就能实现内外边距,因此是没有margin的。

分别设置:上下左右.padding(
    start = 0.dp,
    top = 0.dp,
    end: Dp = 0.dp,
    bottom: Dp = 0.dp
)
分别设置:水平、垂直.padding(
    horizontal: Dp = 0.dp,
    vertical: Dp = 0.dp
)
同时设置.padding(all: Dp)

val paddingValues = PaddingValues(10.dp,20.dp,30.dp,40.dp)@Composable
fun Demo() {//传入一个PaddingValues对象Box(Modifier.padding(paddingValues)) {}
}

2.2.3 背景 background

.background(
    color: Color,        //颜色
    shape: Shape = RectangleShape        //形状
)

2.2.4 剪裁 clip

.clip(shape: Shape)        //CircleShape、RectangleShape

 

@Composable
fun Demo() {Image(painter = painterResource(id = R.drawable.logo_wechat_square),contentDescription = null,modifier = Modifier.clip(CircleShape))
}

2.2.5 边框 border

.border(width: Dp, brush: Brush, shape: Shape)

 

@Composable
fun Demo() {Image(painter = painterResource(id = R.drawable.logo_wechat_square),contentDescription = null,modifier = Modifier.border(width = 2.dp,color = Color.Blue,shape = CircleShape))
}

2.2.6 阴影 shadow

.shadow(
    elevation: Dp,
    shape: Shape = RectangleShape,
    clip: Boolean = elevation > 0.dp,
    ambientColor: Color = DefaultShadowColor,
    spotColor: Color = DefaultShadowColor,
)

2.3 布局

具体选项和效果详见 

Compose能理解当前代码所处作用域,例如给一个纵向布局设置子元素对齐,IDE给出的选项自动变成 Alignment.Horizontal,说明只能在水平方向上指定对齐方式。

2.3.1 子元素对齐 align

.align(alignment: Alignment)

2.4 行为

2.4.1 偏移

.offset(x: Dp = 0.dp, y: Dp = 0.dp)

  

@Composable
fun Demo() {Image(painter = painterResource(id = R.drawable.logo_wechat_square),contentDescription = null,modifier = Modifier.offset(x = 10.dp, y = 30.dp))
}

2.4.2 旋转

.rotate(degrees: Float)

@Composable
fun Demo() {Image(painter = painterResource(id = R.drawable.logo_wechat_square),contentDescription = null,modifier = Modifier.rotate(180F))
}

三、添加额外信息

        在Compose的内部,是用树型结构来存储一次重组过程中每个Composable函数节点的。一颗就是我们现在看到的重组树,另外一颗则是我们看不到的语义树。

        语义树完全不参与绘制和渲染工作,因此是完全不可见的,它只为 Accessibility 和 Test 服务。Accessibility需要根据语义树的节点内容进行发音,Test则需要根据语义树找到想要测试的节点来执行测试逻辑。

        绝大部分情况下不需要专门为语义树去做什么事情,标准的组合项已经在内部处理好了这些工作(Button嵌套一个Text,它俩是独立控件,Talkback会单独发声,但只要控件可点击,就会自动将所有子节点合并)。若使用了一些底层API自行绘制界面(日历选中8号,只会发音选中日历),这些工作就得自己来做了。

.semantics(
    mergeDescendants: Boolean = false,
    properties: (SemanticsPropertyReceiver.() -> Unit)
)

允许向当前Compose控件添加键值对形式的额外信息,但是不能覆写。

.clearAndSetSemantics(
    properties: (SemanticsPropertyReceiver.() -> Unit)
)

相对用得更多一些,它会把Compsoe控件之前携带的一些额外信息都清除掉。

四、添加交互功能

4.1 点击 clickable

允许应用检测对该元素的点击。

.clickable(
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onClick: () -> Unit
)

 

@Composable
fun ClickableSample() {val count = remember { mutableStateOf(0) }Text(text = count.value.toString(),modifier = Modifier.clickable { count.value += 1 })
}

4.2 滚动

4.2.1 verticalScroll、horizontalScroll

类似与 ScrollView,可以让内容边界大于最大尺寸约束时滚动里面的元素,借助 ScrollState 还可以更改滚动位置或获取当前状态。

 

@Composable
fun ScrollBoxes() {val scrollState = rememberScrollState()LaunchedEffect(Unit) { scrollState.animateScrollTo(100) }Column(modifier = Modifier.background(Color.LightGray).size(100.dp)
//            .verticalScroll(rememberScrollState())  //使用默认参数.verticalScroll(scrollState)    //一显示就会自动滚动100px) {repeat(10) {Text("Item $it", modifier = Modifier.padding(2.dp))}}
}

4.2.2  scrollable

只检测手势不偏移内容。构造时需要提供一个 consumeScrollDelta( ) 函数,该函数在每个滚动步骤都会调用,以像素为单位,返回所消耗的距离。

 

@Composable
fun ScrollableSample() {var offset by remember { mutableStateOf(0f) }Box(Modifier.size(150.dp).scrollable(orientation = Orientation.Vertical,state = rememberScrollableState { delta ->//拿到每次滑动的偏移量deltaoffset += deltadelta}).background(Color.LightGray),contentAlignment = Alignment.Center) {Text(offset.toString())}
}

4.2.3 嵌套滚动

4.2.3.1 自动嵌套滚动

简单的嵌套滚动无需额外操作,当子元素无法进一步滚动时手势会由父元素处理,手势会自动从子元素传播到父元素。

 

//父Box嵌套10个子Box,子Box滚动到边界会滚动父Box
@Composable
fun ScrollableSample() {//设置渐变色方便观察子Box滚动(蓝→黄1000级)val gradient = Brush.verticalGradient(0f to Color.Blue, 1000f to Color.Yellow)Box(modifier = Modifier.background(Color.LightGray).verticalScroll(rememberScrollState()).padding(32.dp)) {Column {repeat(10) {Box(modifier = Modifier.height(128.dp).verticalScroll(rememberScrollState())) {Text(text = "Scroll here",color = Color.Red,modifier = Modifier.border(12.dp, Color.DarkGray).background(brush = gradient).padding(24.dp).height(150.dp))}}}}
}

4.2.3.2 nestedScroll

4.2.3.3 嵌套滚动互操作性(v1.2.0)

4.3 拖拽

只检测手势不偏移内容(需要保存状态并在屏幕上表示,例如通过 offset 修饰符移动元素),以像素为单位。

4.3.1 线性拖拽(一维) draggable

使元素在水平或垂直方向上拖拽,并可以监听拖拽距离。

.draggable(
    state: DraggableState,
    orientation: Orientation,        //拖动方向
    enabled: Boolean = true,        //是否启用
    interactionSource: MutableInteractionSource? = null,
    startDragImmediately: Boolean = false,
    onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},        //拖拽开始时的回调
    onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},        //拖拽结束时的回调
    reverseDirection: Boolean = false        //反转方向
)
var offsetX by remember { mutableStateOf(0f) }
Text(modifier = Modifier.offset { IntOffset(offsetX.roundToInt(), 0) }.draggable(orientation = Orientation.Horizontal,state = rememberDraggableState { delta ->offsetX += delta}),text = "Drag me!"
)

在这里插入图片描述

4.3.2 平面拖动(二维)pointerInput

改为使用更加底层的 pointerInput( ) 。

//父Box中拖动蓝色子Box
@Composable
fun ScrollableSample() {Box(modifier = Modifier.fillMaxSize()) {var offsetX by remember { mutableStateOf(0f) }var offsetY by remember { mutableStateOf(0f) }Box(Modifier.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }.background(Color.Blue).size(50.dp).pointerInput(Unit) {//监听用户拖拽手势detectDragGestures { change, dragAmount ->change.consume()    //由于是底层API很多事情需要自己做,这里要消费掉offsetX += dragAmount.x    //水平距离offsetY += dragAmount.y    //垂直距离}})}
}

4.4 滑动 swipeable

只检测手势不偏移内容(需要保存状态并在屏幕上表示,例如通过 offset 修饰符移动元素)。具有惯性,释放后会朝着锚点呈现动画效果,常见用途是滑动关闭。

 

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipeableSample() {val squareSize = 48.dp    //子Box的大小val swipeableState = rememberSwipeableState(0)val sizePx = with(LocalDensity.current) { squareSize.toPx() }   //DP转PX//设置锚点(key是像素,value是索引)val anchors = mapOf(0f to 0, sizePx to 1)Box(modifier = Modifier.width(96.dp).swipeable(state = swipeableState,anchors = anchors,//阈值(超过就会自己滑到底,达不到就会滑回来)thresholds = { _, _ -> FractionalThreshold(0.3f) },orientation = Orientation.Horizontal).background(Color.LightGray)) {Box(Modifier.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }.size(squareSize).background(Color.DarkGray))}
}

4.5 多点触控 transformable

只检测手势不转换元素。平移、缩放、旋转。

 

@Composable
fun TransformableSample() {var scale by remember { mutableStateOf(1f) }    //缩放var rotation by remember { mutableStateOf(0f) }    //旋转var offset by remember { mutableStateOf(Offset.Zero) }    //平移val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->scale *= zoomChangerotation += rotationChangeoffset += offsetChange}Box(Modifier.graphicsLayer(scaleX = scale,    //等比缩放scaleY = scale,    //等比缩放rotationZ = rotation,translationX = offset.x,translationY = offset.y).transformable(state = state).background(Color.Blue).fillMaxSize())
}

五、处理用户输入 pointerInput

这里的输入不是文本,而是用户手指在屏幕上滑动点击(手势)。当上层API无法满足时(第四部分系统提供的交互功能),就需要自己调用底层API定制了。

.pointerInput(
    key1: Any?,        //至少要传一个key(其它重载传更多key),变化时函数会重新执行(不需要就传Unit)。
    block: suspend PointerInputScope.() -> Unit
)

5.1 点击、拖拽

两个函数都是阻塞的,不能同时写在同一个 pointerInput( ) 中。

监听点击事件

detectTapGestures(
    onDoubleTap: ((Offset) -> Unit)? = null,        //双击
    onLongPress: ((Offset) -> Unit)? = null,        //长按
    onPress: suspend PressGestureScope.(Offset) -> Unit = NoPressGesture, //短按(其它三个都会触发有一次)
    onTap: ((Offset) -> Unit)? = null        //单击
)

监听拖拽事件

detectDragGestures(
    onDragStart: (Offset) -> Unit = { },
    onDragEnd: () -> Unit = { },
    onDragCancel: () -> Unit = { },
    onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit        //通过 dragAmount.x 和 dragAmount.y 拿到各方向上的拖动距离。
)

Box(modifier = Modifier.size(200.dp).background(Color.Blue).pointerInput(Unit) {detectTapGestures { offset ->Log.d("PointerInputEvent", "Tap")}//注意:检测函数是阻塞的,此处代码不可达}.pointerInput(Unit) {detectDragGestures { change, dragAmount ->Log.d("PointerInputEvent", "Dragging")}//注意:检测函数是阻塞的,此处代码不可达}
)

在这里插入图片描述

5.2 更底层处理(自定义)

写法过于底层,基本没有太多场景我们需要使用。

启动协程作用域

suspend fun <R> awaitPointerEventScope(

        block: suspend AwaitPointerEventScope.() -> R

): R

等待用户输入事件

suspend fun awaitPointerEvent(
        pass: PointerEventPass = PointerEventPass.Main

): PointerEvent

在这里插入图片描述

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

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

相关文章

kafka的位移

文章目录 概要消费位移__consumer_offsets主题位移提交 概要 本文主要总结kafka的位移是如何管理的&#xff0c;在broker端如何通过命令行查看到位移信息&#xff0c;并从代码层面总结了位移的提交方式。 消费位移 对于 Kafka 中的分区而言&#xff0c;它的每条消息都有唯一…

LVS-DR的RS进行ARP抑制的原因和LVS持久连接配置

一.RS的ARP抑制 1.为什么要抑制 2.如何抑制 &#xff08;1&#xff09;修改/etc/sysctl.conf文件&#xff0c;增加以下内容 &#xff08;2&#xff09;命令行临时设置 二.LVS持久连接 1.客户端持久连接 2.端口持久连接 3.防火墙标记持久连接 一.RS的ARP抑制 1.为什么要…

C++--红黑树

1.什么是红黑树 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出俩倍&#xff0c;因…

解决c/c++ Error: redefinition of ‘xxx’ 的问题

错误信息 两个类/文件同时引用定义ReplyInfo的头文件&#xff0c;会造成头文件中定义重复定义 如两个类/文件重复引用massage文件报错 message.h:36:16: error: redefinition of struct MSG_SERVOCTRL message.h:40:2: error: conflicting types for servoctrl解决 一般是目…

计算机组成与设计 Patterson Hennessy 笔记(一)MIPS 指令集

计算机的语言&#xff1a;汇编指令集 也就是指令集。本书主要介绍 MIPS 指令集。 汇编指令 算数运算&#xff1a; add a,b,c # abc sub a,b,c # ab-cMIPS 汇编的注释是 # 号。 由于MIPS中寄存器大小32位&#xff0c;是基本访问单位&#xff0c;因此也被称为一个字 word。M…

YOLOv5改进系列(21)——替换主干网络之RepViT(清华 ICCV 2023|最新开源移动端ViT)

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制 YOLOv5改进系列(2

React 之 Suspense和lazy

一. Suspense 参考链接&#xff1a;https://react.docschina.org/reference/react/Suspense suspense&#xff1a;n. 焦虑、悬念 <Suspense> 允许你显示一个退路方案&#xff08;fallback&#xff09;直到它的所有子组件完成加载。 <Suspense fallback{<Loadin…

vue项目根据word模版导出word文件

一、安装依赖 //1、docxtemplaternpm install docxtemplater pizzip -S//2、jszip-utilsnpm install jszip-utils -S//3、pizzipnpm install pizzip -S//4、FileSaver npm install file-saver --save二、创建word模版 也就是编辑一个word文档&#xff0c;文档中需要动态取值的…

基于chatgpt动手实现一个ai_translator

动手实现一个ai翻译 前言 最近在极客时间学习《AI 大模型应用开发实战营》&#xff0c;自己一边跟着学一边开发了一个进阶版本的 OpenAI-Translator&#xff0c;在这里简单记录下开发过程和心得体会&#xff0c;供有兴趣的同学参考&#xff1b; ai翻译程序 版本迭代 在学习…

Spring-MVC的数据响应-19

在访问服务端MVC的时候&#xff0c;这个controller层进行相应操作之后 他要做两件事&#xff1a;页面跳转和返回字符串&#xff0c;在做完这些操作之后&#xff0c;我们一般进行页面展示:排除页面展示之外&#xff0c;有些需求可能直接回写给我们一些数据&#xff1a; 页面跳…

spring bean创建总览 1

1 开始 这是一个总图 下边慢慢看 我们最基础的写的方式就是xml的方式去写 像这样&#xff0c; 而我们会通过applicationContext的方式去获得我们的bean &#xff0c;我其中一篇博客就写到了applicationContext他的父类就是beanFactory 但是中间的是怎么样处理的呢&#xff1f…

springboot引入druid解析sql

一、前言 在开发中&#xff0c;有时我们可能会需要获取SQL中的表名&#xff0c;那么因为不同的数据源类型SQL会存在部分差异&#xff0c;那么我们就可以使用alibaba 的druid包实现不同的数据源类型的sql解析。 二、引入相关maven依赖 <dependency><groupId>com.a…

MySQL表的操作

MySQL表的操作 创建表查看表结构的详细信息修改表结构增加表结构属性删除表结构表结构的修改 删除表结构 创建表 语法: create table table_name( field1 datatype [comment xxxxx], field2 datatype [comment xxxxx], field3 datatype [comment xxxxx]) [charsetxxx][collatey…

idea 转换为 Maven Project 的方法

选项&#xff1a; Add as Maven Project

一、进入sql环境,以及sql的查询、新建、删除、使用

1、进入sql环境 》》》mysql -u root -p 》》》输入密码 2、sql语言的分类 3、注意事项&#xff1a; 4、基础操作&#xff1a; &#xff08;1&#xff09;查询所有数据库&#xff1a; show databases; 运行结果&#xff1a; &#xff08;2&#xff09;创建一个新的数据库&…

ceph数据分布

ceph的存储是无主结构&#xff0c;数据分布依赖client来计算&#xff0c;有两个条主要路径。 1、数据到PG 2、PG 到OSD 有两个假设&#xff1a; 第一&#xff0c;pg的数量稳定&#xff0c;可以认为保持不变&#xff1b; 第二&#xff0c; OSD的数量可以增减&#xff0c;OSD的…

我能“C”——实用的调试技巧

什么是bug&#xff1f; 调试是什么&#xff1f;有多重要&#xff1f; debug和release的介绍。 windows环境调试介绍。 一些调试的实例。 如何写出好&#xff08;易于调试&#xff09;的代码。 编程常见的错误。 1.什么是bug&#xff1f; 世界上第一个bug是程序员赫柏发现的。 …

中大许少辉博士《乡村振兴战略下传统村落文化旅游设计》中国建筑工业出版社八一付梓。

中大许少辉博士《乡村振兴战略下传统村落文化旅游设计》中国建筑工业出版社八一付梓。

Django之定时任务--apscheduler

Django--定时任务apscheduler的使用 apscheduler定时任务的使用1、安装包2、配置settings.py3、在manage.py的文件同级目录下创建文件scheduler.py4、在项目的urls.py中调用这个定时计划5、然后启动项目 python manage.py runserver,在admin中查看就能看到你的定时任务及执行的…

【编程二三事】ES究竟是个啥?

在最近的项目中&#xff0c;总是或多或少接触到了搜索的能力。而在这些项目之中&#xff0c;或多或少都离不开一个中间件 - ElasticSearch。 今天忙里偷闲&#xff0c;就来好好了解下这个中间件是用来干什么的。 ES是什么? ​ ES全称ElasticSearch&#xff0c;是个基于Lucen…