plain-ui 指南:PlainApp 的 Compose Multiplatform 共享 UI 组件库

发布时间:2026/10/8 1:32:16
plain-ui 指南:PlainApp 的 Compose Multiplatform 共享 UI 组件库
移动开发后端即时通讯音视频【免费下载链接】plain-app PlainApp is an open-source app that lets you securely manage your phone from a web browser. Access files, media, contacts, SMS, calls, and more through a simple, easy-to-use interface on your desktop.项目地址https://gitcode.com/gh_mirrors/pl/plain-app点击查看免费下载导读plain-ui 是 PlainApp 系列应用Android、iOS 与 Web共同复用的 Compose Multiplatform UI 组件库它把PScaffold、PTopAppBar、快速滚动条、下拉刷新、拖拽选择等高频界面能力收敛到com.ismartcoding.plain.ui.base并内置了可处理大文件的CodeEditor编辑器及其文档、语法高亮、搜索、编辑历史引擎以及com.ismartcoding.plain.ui.scanner下的QrCodeScanner扫码控件。阅读本文后你将掌握如何通过 Maven 坐标引入该库、按包名使用各组件、理解扫码控件与宿主应用之间的回调契约以及编辑器引擎的底层工作原理。一、库定位跨平台复用的 UI 原语plain-ui 被定义为 Reusable Compose Multiplatform UI primitives shared by Plain applications即 Plain 系列应用共享的可复用 Compose Multiplatform UI 原语。它在不承载具体业务逻辑的前提下把 Plain 应用日常页面中最常见、最容易重复书写的界面结构提炼为统一组件让各端保持一致的视觉与交互体验。从目录结构plain-ui/src/commonMain/kotlin/com/ismartcoding/plain/ui可以清楚看到库的分层组织base通用基础组件PScaffold、PTopAppBar等以及fastscroll、pullrefresh、dragselect三个能力子包components/codeeditor可复用的大文件代码编辑器含独立的engine引擎scannerQrCodeScanner扫码控件及配套组件theme主题、颜色、形状、排版定义。这套结构把页面骨架与重型能力分离轻量组件随取随用重量级编辑器与扫码器则独立成包按需引入。二、Maven 坐标与版本获取依赖坐标plain-ui 的 Maven 坐标是com.ismartcoding:plain-ui。在 Gradle 项目中按如下方式声明依赖示例版本为 0.4.0dependencies { implementation(com.ismartcoding:plain-ui:0.4.0) }发布仓库与版本来源版本发布到专用 Maven 仓库https://plainhub.github.io/plain-app/maven该仓库托管在独立的maven分支上该分支同时承载 policy 与 terms 页面。每次发布通过两种方式触发打上plain-ui-vversion形式的 git tag或运行仓库中的Publish plain-ui工作流即 .github/workflows/publish-plain-ui.yml。README 还指出PlainRouter Android 客户端已经在使用与plain-common相同的 Maven 仓库地址也就是说同一发布通道同时服务于 plain-common 与 plain-ui 两个库接入方只需配置一次仓库地址即可消费全部 Plain 共享库。使用前提该仓库地址是发布 plain-ui 的专用仓库实际引入时需在repositories中声明这一 Maven 仓库并选取当前已发布的具体版本号README 示例为 0.4.0。三、从 base 包使用共享组件README 建议从com.ismartcoding.plain.ui.base使用共享组件并从fastscroll、pullrefresh、dragselect三个子包取用能力组件。3.1 PScaffold页面骨架与系统栏内边距处理PScaffold是 Material3Scaffold的轻量封装位于 PScaffold.kt签名如下Composable fun PScaffold( modifier: Modifier Modifier, containerColor: Color MaterialTheme.colorScheme.background, topBar: Composable () - Unit {}, bottomBar: (Composable () - Unit)? null, floatingActionButton: (Composable () - Unit)? null, content: Composable (PaddingValues) - Unit {}, )其核心价值在于源码注释所描述的细节它在内容区域居中应用水平方向的系统栏 insetcalculateStartPadding/calculateEndPadding这样各页面在横屏、三键导航栏遮挡内容时无需各自处理水平边距只需通过传入的PaddingValues处理顶部和底部的 inset 即可水平方向已归零。这是把易踩坑的系统栏适配统一收敛到骨架层的典型设计。3.2 PTopAppBar导航回调与主题切换适配PTopAppBar位于 PTopAppBar.kt接受onNavigateBack与navigationIcon两个回调Composable fun PTopAppBar( onNavigateBack: (() - Unit)? null, modifier: Modifier Modifier, navigationIcon: (Composable () - Unit)? null, title: String, subtitle: String , titleTrailing: (Composable () - Unit)? null, containerColor: Color? null, subtitleColor: Color? null, actions: (Composable RowScope.() - Unit)? null, scrollBehavior: TopAppBarScrollBehavior? null, )值得注意的工程细节导航图标策略navigationIcon ! null时优先使用自定义图标否则若提供了onNavigateBack自动渲染内置的返回箭头BackIcon使用资源中的arrow_left图标与back文案见 PTopAppBar.kt。两个都为空则不显示导航区。副标题模式subtitle非空时标题切换为主标题 副标题的两行排版为空则单行标题。主题切换适配源码注释明确解释了为何在外层套一个即时着色的Surface、而内部TopAppBar保持透明——Material3 的TopAppBar会内部动画化容器颜色导致主题切换时比页面背景慢半拍用即时Surface兜底可让两者同步变化。另外 README 特别提到下拉刷新的文案随库资源发布Pull refresh strings ship with the library resources即pullrefresh子包内的EllipseRefreshContent、LoadMoreRefreshContent等组件所需的提示文案已内置于库的 Compose 资源中使用方无需再自行翻译维护。3.3 fastscroll懒加载列表/网格的快速滚动条fastscroll子包为LazyColumn、LazyVerticalGrid与普通滚动状态提供滚动条。以 LazyColumnScrollbar.kt 为例Composable fun LazyColumnScrollbar( state: LazyListState, modifier: Modifier Modifier, settings: ScrollbarSettings ScrollbarSettings.Default, indicatorContent: (Composable (index: Int, isThumbSelected: Boolean) - Unit)? null, content: Composable () - Unit, )通过ScrollbarSettings控制启用状态enabled、滑块最小长度thumbMinLength、是否常显alwaysShowScrollbar与选择模式selectionMode底层controller包中提供了rememberLazyListStateController、rememberLazyGridStateController、rememberScrollStateController三类控制器见 controller 目录分别适配列表、网格与普通滚动滚动条布局逻辑封装在foundation包ScrollbarLayoutSettings、VerticalScrollbarLayout等可搭配 Material3 的Scaffold的scrollBehavior使用实现滚动即隐藏/淡出的现代移动端交互。3.4 pullrefresh下拉刷新与加载更多PullToRefresh是入口封装PullToRefresh.kt核心参数包括refreshLayoutState: RefreshLayoutState刷新状态对象userEnable: Boolean true是否允许用户手势触发refreshContent刷新指示器内容默认为PullToRefreshContentcontent被包裹的列表内容。同一子包还提供EllipseRefreshContent椭圆动画刷新指示、LoadMoreRefreshContent加载更多指示、ComposePosition等RefreshLayoutNestedScrollConnection负责把刷新手势接入 Compose 的嵌套滚动体系保证与LazyColumn等可滚动容器的协同。3.5 dragselect拖拽框选与全选管理DragSelectStateDragSelectState.kt是拖拽选择的状态核心它持有selectedIds、selectMode、dragState并基于 README 提到的Identifiable契约工作——该契约来自plain-common定义极其精简interface Identifiable { val id: String }见 Identifiable.kt。任意数据模型只需实现id属性即可接入toggleSelectAll(allItems: ListIdentifiable)、isAllSelected等批量操作。子包内还提供GridDragSelect、ListDragSelect两种实现分别适配网格与列表布局并支持拖拽过程中的自动滚动autoScrollSpeed。四、QrCodeScanner扫码控件与宿主回调契约QrCodeScanner位于com.ismartcoding.plain.ui.scanner是可复用的扫码 UI 宿主业务回调的典型组合。README 明确指出应用方保留扫码页的 TopBar 与结果弹层result sheet扫码控件接收宿主的回调负责图像选择、扫码结果与特定应用逻辑的处理。4.1 完整参数清单Composable fun QrCodeScanner( cameraPermissionGranted: Boolean, multipleCodesHint: String, imagePickerDescription: String, closeRequest: Int, onCloseActionVisibilityChanged: (Boolean) - Unit, pickImage: ((String) - Unit) - Unit, onScanResult: (String, () - Unit) - Unit, handleSpecialCode: (String, () - Unit) - Boolean, showNoCodeFound: () - Unit, showImageLoading: () - Unit, hideImageLoading: () - Unit, modifier: Modifier Modifier, )各回调的职责参数类型职责cameraPermissionGrantedBoolean相机权限是否已授予决定是否渲染相机预览multipleCodesHintString同时识别到多个码时展示的提示文案imagePickerDescriptionString相册取图按钮的无障碍描述closeRequestInt宿主发来的关闭/重置请求信号增量计数触发onCloseActionVisibilityChanged(Boolean) - Unit通知宿主关闭按钮何时该显示/隐藏如冻结多码或展示图片选择器时pickImage((String) - Unit) - Unit宿主提供的取图入口回调内将图片 URI 回传给控件onScanResult(String, () - Unit) - Unit扫码结果回调第二个参数是控件提供的完成/继续函数handleSpecialCode(String, () - Unit) - Boolean处理应用特有码如配对码返回 true 表示已消费不再走通用结果流程showNoCodeFound/showImageLoading/hideImageLoading无参回调图片识别无结果、加载中、加载完成的状态通知4.2 识别流程与防误报机制扫码逻辑在 QrCodeScanner.kt 的onFrameCodes中体现为逐帧确认流程每帧的识别结果先交给ScanCodeTracker去重确认见 ScanCodeTracker.kt同一码需在CONFIRM_FRAMES 2帧内连续出现才被确认允许MISSED_FRAMES_TOLERANCE 1帧的丢失从而保证单帧误识别永远不会直接冒给用户标签顺序按首次出现排序保持稳定。确认结果若同时出现 ≥2 个码则冻结画面并渲染可点击的码标签ScanCodeTags配合半透明遮罩与底部提示用户点选某个码后进入对应处理。若确认出恰好 1 个码先经ScanAutoOpenPolicyScanAutoOpenPolicy.kt裁决是否立即自动弹出结果当画面中还有其他未确认的码时最多等待PENDING_FRAME_CAP 3帧避免多码场景中先确认的那个码抢先弹出结果弹层。结果弹出后通过onScanResult(text) { resumeIfIdle() }与宿主协作宿主处理完业务后调用回调函数恢复扫描。4.3 相册取图识别右下角的圆形取图按钮触发pickImage宿主回调拿到 URI 后控件内部用rememberQrImageDecoder()一个expect/actual平台解码器见 Scan.kt解码图片无结果时通知showNoCodeFound单个码直接进入结果处理多个码则切换到ScanImageCodePicker图片内点选界面。4.4 expect/actual 平台抽象ScanCameraView与rememberQrImageDecoder都是expect声明由各平台提供actual实现相机预览、图像解码分别依赖各平台的相机与图像框架。这意味着扫码控件的交互逻辑、状态机、UI 全部跨平台共享只有相机/解码这类平台能力需要分端实现。五、CodeEditor大文件代码编辑器引擎CodeEditor位于com.ismartcoding.plain.ui.components.codeeditor专为大文件场景设计其能力由独立的engine包支撑。5.1 组件与控制器架构CodeEditorCodeEditor.kt是公开入口接收EditorController渲染搜索栏按需、编辑视口 输入层 选择工具栏、状态栏三部分纵向组合EditorControllerEditorController.kt是状态中枢持有文档、撤销历史、搜索会话与滚动/光标/选区状态并通过mutableStateOf暴露loadState、docVersion、wrapContent、readOnly、fontSizeSp、isDirty、canUndo/canRedo、searchVisible等可观察状态。从EditorController的注释可以看到线程策略重活索引构建、搜索、高亮跑在Dispatchers.Default编辑应用在主线程从而保证大文件打开与编辑时界面不卡顿。5.2 engine 引擎组件engine包各模块分工engine 目录模块职责LineVectorDocument按行组织的向量文档模型支撑大文件LineIndexBuilder行索引构建器大文件加载阶段EditHistory编辑历史撤销/重做通过injectClock(fileIO::nowMillis)注入时钟HighlightEngineRegexLexer语法高亮引擎按正则词法规则着色支持多种语言LanguagesSearchEngine搜索会话支持大小写与正则模式产出SearchMatch列表EncodingProbe/DetectedEncoding文件编码探测ByteSource字节源抽象HorizontalPan/IntList/VisualLineMapper水平平移、性能数据结构、可视行映射5.3 视口渲染与异步高亮视口EditorViewport见 CodeEditor.kt的关键实现点等宽字体网格排版charWidthPx、lineHeightPx依据字号动态计算行高为字号的 1.5 倍行号槽宽度按controller.gutterDigits() * 9 16.dp动态计算异步高亮snapshotFlow监听当前可见区间的firstVisibleItemIndex与可见行数切到Dispatchers.Default计算first..(first n 8)范围的高亮——只对可见区域加少量预取行做高亮配合LazyColumn虚拟化是大文件流畅滚动的关键水平平移HorizontalPan以视口宽度减行号槽宽度为边界保证完全平移后的行不会越过屏幕边缘syncPan逻辑见 EditorController.kt。5.4 打开流程EditorController.open(filePath, gotoEnd)EditorController.kt的流程为按路径经Languages.pathToLanguageId推断语言 → 在Dispatchers.Default上loadDocument→ 状态流转为Loading(progress) → Ready失败则进入Error(message)。EditorLoadState密封类Idle/Loading/Ready/Error将打开进度暴露给 UI见 EditorController.kt。六、在应用中组合使用综合 README 的包路径指引与上述源码结构接入 plain-ui 的推荐方式是声明依赖并配置 Maven 仓库见第二节页面骨架用PScaffoldPTopAppBar传onNavigateBack或自定义navigationIcon搭出统一框架列表页LazyColumnScrollbar/LazyVerticalGridScrollbar提供快速滚动PullToRefresh提供下拉刷新DragSelectStateListDragSelect/GridDragSelect提供长按拖拽多选重型能力按需取用QrCodeScannercom.ismartcoding.plain.ui.scanner负责扫码 UI 与回调协作宿主自持 TopBar 与结果弹层CodeEditorcom.ismartcoding.plain.ui.components.codeeditor负责大文件查看/编辑主题统一theme包提供PlainTheme、ColorHelper、Shapes、Type等主题原语保证各端观感一致。七、总结plain-ui 的定位决定了它在 PlainApp 技术栈中的角色界面层的高复用底座 两个重型能力模块。base 包的组件把骨架、系统栏适配、快速滚动、下拉刷新、拖拽选择等共性能力标准化scanner 与 codeeditor 则通过宿主回调 独立引擎的架构把扫码与大文件编辑的复杂状态机留在库内业务方只需实现少量契约即可获得完整能力。无论是复用其 UI 原语还是学习 Compose Multiplatform 组件库的工程化组织方式plain-ui 源码 都是值得精读的参考实现。赞分享移动开发后端即时通讯音视频【免费下载链接】plain-app PlainApp is an open-source app that lets you securely manage your phone from a web browser. Access files, media, contacts, SMS, calls, and more through a simple, easy-to-use interface on your desktop.项目地址https://gitcode.com/gh_mirrors/pl/plain-app点击查看免费下载相关推荐Turbolinks组件库开发共享UI组件Turbolinks组件库开发共享UI组件 组件架构概述 Turbolinks通过模块化设计实现页面导航加速核心组件分布在 src/ https://lin前端plain-common从 PlainApp 中抽取的可复用 Kotlin Multiplatform 工具库与发布指南plain common从 PlainApp 中抽取的可复用 Kotlin Multiplatform 工具库与发布指南 导读 本文以 plain comm移动开发后端即时通讯音视频GitHub_Trending/core97/coreReact组件库共享UI组件开发GitHub_Trending/core97/coreReact组件库共享UI组件开发 项目概述 GitHub_Trending/core97/core项目基上一篇harelba/q大数据集成与Hadoop、Spark的消息传递方案下一篇AlphaFold预测结果解读指南pLDDT、PAE与五模型判定方法一次讲清创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考