首先明确Compose TextField的底层依赖是:
TextField BasicTextField CoreTextField
相较于Text,TextField需要提供可输入的能力以及渲染效果动态更新的能力。
// CompositionLocals
// If the text field is disabled or read-only, we should not deal with the input service
// !!!用于请求焦点的对象。通过调用 focusRequester.requestFocus(),可以请求将焦点设置到与之关联的 TextField 上,但如何设置的???
val textInputService = LocalTextInputService.current
// 输入框显示的内容的密度
val density = LocalDensity.current
// 字体的管理
val fontFamilyResolver = LocalFontFamilyResolver.current
val selectionBackgroundColor = LocalTextSelectionColors.current.backgroundColor
// 管理焦点的对象
val focusManager = LocalFocusManager.current
// 获取当前窗口的信息。窗口是什么???
val windowInfo = LocalWindowInfo.current
// 用于控制键盘的对象
val keyboardController = LocalSoftwareKeyboardController.current
针对所谓的文本内容超过TextField的显示区域,会出现Scroller滚动条来动态查看所输入的内容;默认情况下,次效果不显示;且Scroller不算TextField的核心的内容。
// Scroll state
val singleLine = maxLines == 1 && !softWrap && imeOptions.singleLine
val orientation = if (singleLine) Orientation.Horizontal else Orientation.Vertical
val scrollerPosition = textScrollerPosition ?: rememberSaveable(
orientation,
saver = TextFieldScrollerPosition.Saver
) { TextFieldScrollerPosition(orientation) }
if (scrollerPosition.orientation != orientation) {
throw IllegalArgumentException(
"Mismatching scroller orientation; " + (
if (orientation == Orientation.Vertical)
"only single-line, non-wrap text fields can scroll horizontally"
else
"single-line, non-wrap text fields can only scroll horizontally"
)
)
}
代码的作用是创建和初始化一个 TextFieldState
对象,并更新其属性。首先通过currentRecomposeScope获取当前重新组合的作用域(recompose scope)。
使用 remember
函数创建一个 TextFieldState
对象,并将其赋值给变量 state;其中keyboardController
是一个键盘控制器对象,它被传递给 remember
函数作为键,以确保在重新组合时 TextFieldState
对象可与输入内容保持一致。
state.update
方法,更新 TextFieldState
对象的属性。这些属性包括文本内容、样式、软换行、密度等,以及一些回调函数和其他对象。通过更新这些属性,可以确保 TextFieldState
对象的状态与传递的参数保持一致。
val scope = currentRecomposeScope
// 产生一个关键的对象 state
val state = remember(keyboardController) {
TextFieldState(
TextDelegate(
text = visualText,
style = textStyle,
softWrap = softWrap,
density = density,
fontFamilyResolver = fontFamilyResolver
),
recomposeScope = scope,
keyboardController = keyboardController
)
}
state.update(
value.annotatedString,
visualText,
textStyle,
softWrap,
density,
fontFamilyResolver,
onValueChange,
keyboardActions,
focusManager,
selectionBackgroundColor
)
TextFieldState是CoreTextField完成构建各类组件的对象以及声明剩余输入框样式及内容的状态state之后开始构建支持TextField样式渲染的各种Modifier。以此可以理解,Compose布局的过程是先测量到布局到渲染,所以构建各类对象再构建各种Modifier的顺序理应如此。
// Focus
val focusModifier = Modifier.textFieldFocusModifier(
enabled = enabled,
focusRequester = focusRequester,
interactionSource = interactionSource
) {
if (state.hasFocus == it.isFocused) {
return@textFieldFocusModifier
}
state.hasFocus = it.isFocused
if (textInputService != null) {
if (state.hasFocus && enabled && !readOnly) {
startInputSession(
textInputService,
state,
value,
imeOptions,
offsetMapping
)
} else {
endInputSession(state)
}
// The focusable modifier itself will request the entire focusable be brought into view
// when it gains focus – in this case, that's the decoration box. However, since text
// fields may have their own internal scrolling, and the decoration box can do anything,
// we also need to specifically request that the cursor itself be brought into view.
// TODO(b/216790855) If this request happens after the focusable's request, the field
// will only be scrolled far enough to show the cursor, _not_ the entire decoration
// box.
if (it.isFocused) {
state.layoutResult?.let { layoutResult ->
coroutineScope.launch {
bringIntoViewRequester.bringSelectionEndIntoView(
value,
state.textDelegate,
layoutResult.value,
offsetMapping
)
}
}
}
}
if (!it.isFocused) manager.deselect()
}
1️⃣ 为 TextField
添加一个修饰符(modifier):focusModifier,用于处理焦点相关的逻辑。
接下来,继续执行多个if语句,旨在判断是否提供可交互的TextField输入框组件。如果存在 textInputService
(文本输入服务),并且 TextFieldState
对象具有焦点、启用且不是只读模式,那么会调用 startInputSession
函数来启动输入会话。这个函数会与文本输入服务进行交互,以处理文本输入和键盘事件。如果 TextFieldState
对象失去焦点,会调用 endInputSession
函数来结束输入会话。
coroutineScope.launch
和 startInputSession
函数分别用于在协程中异步执行将光标位置带入视图的请求和启动输入会话的操作。
因此focusModifier可以总结出必须具备这样的参数条件,页面中的TextField组件才是可输入进行交互的:
- 存在
textInputService
(文本输入服务)。 TextFieldState
对象具有焦点(state.hasFocus
为true
)。TextField
启用(enabled
为true
)。TextField
不是只读模式(readOnly
为false
)。
val pointerModifier = Modifier.textFieldPointer(
manager,
enabled,
interactionSource,
state,
focusRequester,
readOnly,
offsetMapping
)
2️⃣ pointerModifier
是一个修饰符,用于处理与指针(例如鼠标)交互相关的逻辑。
它接受多个参数,包括 manager
、enabled
、interactionSource
、state
、focusRequester
、readOnly
和 offsetMapping
。
这个修饰符的作用是为 TextField
添加指针交互的功能。它处理与指针相关的事件,例如鼠标点击、拖动等。通过使用这个修饰符,可以实现对 TextField
的指针交互进行控制和处理。
3️⃣ drawModifier
是一个修饰符,用于在 TextField
的绘制过程中进行自定义绘制操作。
这个修饰符使用了 drawBehind
函数,允许在组件的背后进行绘制操作。调用drawIntoCanvas
函数,获取 Canvas
对象,然后在这个 Canvas
上进行绘制操作。canvas
、value
、offsetMapping
、layoutResult.value
和 state.selectionPaint
。这些参数用于确定绘制的位置、内容和样式。
所以我们可以明确TextField不仅是会基于ResumableComposeNode作为渲染的基础,而且还是基于canvas进行渲染的。