更新记录
0.1.1(2026-09-23)
更新下文档
0.1.0(2026-09-23)
初版发布
平台兼容性
uni-app(5.23)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | √ | - | - | - | √ | √ | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(5.23)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | √ | - | - | - |
mx-reader-native
Android 原生漫画/图片阅读器组件(uni-app x / UTS 原生插件),基于 RecyclerView 渲染纵向连续图片列表。
- 原生惯性滚动,长列表回收复用,滑动不卡顿
- 双指缩放、放大后单指平移(带惯性、边界限制)
- 支持图片间距、内容内边距(适配工具栏/安全区)、页码角标
- 支持低清预览(省流模式)与占位模糊图
- 提供页码变化、滚动/缩放状态、居中单击等事件
仅支持 APP-Android(APP-Android 条件编译内生效,其他平台渲染为空容器)。
环境要求
| 项 | 要求 |
|---|---|
| HBuilderX | ^5.24 |
| uni-app / uni-app x | ^3.1.0 |
| 平台 | Android(minSdkVersion 24) |
| 原生依赖 | androidx.recyclerview:recyclerview:1.3.2(插件 config.json 已声明) |
安装
将 uni_modules/mx-reader-native 放入项目 uni_modules 目录即可。
导入时必须使用插件根入口
@/uni_modules/mx-reader-native,禁止直接导入utssdk/app-android/index.uts。
快速开始
<template>
<view class="page">
<!-- 仅 Android 生效,建议套 #ifdef APP-ANDROID -->
<!-- #ifdef APP-ANDROID -->
<mx-reader-native
ref="readerRef"
class="reader"
:images="images"
:initial-page="initialPage"
:image-spacing="spacingPx"
:zoom-enabled="true"
:max-scale="3"
:original-min-scale="1"
:pan-sensitivity="1.5"
:content-top-inset="0"
:content-bottom-inset="bottomInsetPx"
:show-index="false"
:low-quality="false"
:low-quality-placeholder-scale="4"
:low-quality-viewport-scale="2"
:preview-width="480"
@ready="onReady"
@page-change="onPageChange"
@scroll-change="Change"
@center-tap="onCenterTap"
/>
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import type { ComponentPublicInstance } from 'vue'
import { ShenshiReaderImage } from '@/uni_modules/mx-reader-native'
const readerRef = ref<ComponentPublicInstance | null>(null)
const initialPage = ref(0)
const spacingPx = ref(8) // 单位 px
const bottomInsetPx = ref(0)
const images = ref<ShenshiReaderImage[]>([
{ src: 'https://example.com/1.jpg', width: 750, height: 1000 },
{ src: 'https://example.com/2.jpg', width: 750, height: 1200 }
])
function onReady(): void {
// 原生视图就绪后才能调用方法
}
function onPageChange(detail: UTSJSONObject): void {
// index: 从 0 起的下标;page: 从 1 起的页码;total: 总页数;scrollTop; scale
const index = detail['index'] as number
}
function Change(detail: UTSJSONObject): void {
// scrollTop、scale 变化时触发
}
function onCenterTap(): void {
// 单击屏幕中央区域
}
</script>
<style>
.page { flex: 1; }
.reader { flex: 1; width: 100%; }
</style>
Props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
images |
ShenshiReaderImage[] |
[] |
图片列表,见下表类型说明 |
initialPage |
number |
0 |
首次就绪时跳转的下标(从 0 起) |
imageSpacing |
number |
0 |
图片间距,单位 px(rpx 需自行换算:rpx * 窗口宽 / 750) |
zoomEnabled |
boolean |
true |
是否开启双指缩放 |
maxScale |
number |
3 |
最大缩放倍率 |
originalMinScale |
number |
1 |
原图查看的最小缩放基准 |
panSensitivity |
number |
1.5 |
放大后单指平移灵敏度(增益) |
contentTopInset |
number |
0 |
内容顶部内边距 px(避开顶部覆盖层) |
contentBottomInset |
number |
0 |
内容底部内边距 px(避开工具栏/安全区) |
showIndex |
boolean |
false |
是否显示页码角标 |
lowQuality |
boolean |
false |
省流模式:进入视口先显示低清占位 |
lowQualityPlaceholderScale |
number |
4 |
占位图相对原图的压缩比例 |
lowQualityViewportScale |
number |
2 |
视口放大多少倍后切换高清图 |
previewWidth |
number |
480 |
低清预览图生成宽度(px) |
所有 props 均响应式变更;images 变更时组件会自动 diff(尾部追加 / 头部裁剪 / 局部更新 / 整表刷新),无需手动调用 setImages。
ShenshiReaderImage
export type ShenshiReaderImage = {
src: string // 图片地址:http(s)、file:// 或本地绝对路径
width: number // 图片宽度(用于等比算高)
height: number // 图片高度
}
高度按容器宽度等比计算。建议提供真实宽高,避免初次布局后跳动;本地图片可传高度 0 交由原生按真实比例测高,远程图请给占位高度。
事件
| 事件名 | 回调参数 | 说明 |
|---|---|---|
ready |
- | 原生视图初始化完成,此后可调用方法 |
pageChange |
UTSJSONObject |
当前页变化:index(0 起)、page(1 起)、total、scrollTop、scale |
scrollChange |
UTSJSONObject |
滚动距离或缩放变化:scrollTop、scale |
center-tap |
- | 单击屏幕中央(用于呼出/隐藏工具栏) |
方法(通过 ref 调用)
组件 defineExpose 暴露以下方法,用 ref.$callMethod('方法名', ...) 调用:
const reader = readerRef.value
reader?.$callMethod('scrollToPage', 10) // 跳转到下标 10
reader?.$callMethod('getCurrentPage') // 返回 0 起下标
reader?.$callMethod('getTotalPages') // 总页数
reader?.$callMethod('updateReaderPage', 3, src, w, h) // 更新单页
reader?.$callMethod('updateReaderPages', items) // 批量更新,items 为 { index, src, width, height }[]
reader?.$callMethod('preloadPreview', src) // 预热低清占位图
reader?.$callMethod('resetZoom') // 重置缩放
reader?.$callMethod('resetBitmaps') // 释放位图缓存
reader?.$callMethod('setMaxScale', 4)
reader?.$callMethod('setZoomEnabled', false)
reader?.$callMethod('setContentInsets', topPx, bottomPx)
reader?.$callMethod('destroy') // 组件卸载时自动调用
| 方法 | 参数 | 说明 |
|---|---|---|
scrollToPage |
index: number |
滚动到指定下标;ready 前调用会被缓存,就绪后自动执行 |
getCurrentPage / getTotalPages |
- | 当前页下标 / 总页数 |
updateReaderPage |
index, src, width, height |
更新单页图片(下载完成回填本地路径时用) |
updateReaderPages |
items: UTSJSONObject[] |
批量更新,减少桥接次数;适合节流后一次下发 |
preloadPreview |
src: string |
低清模式下提前缓存占位图 |
setMaxScale / setZoomEnabled / setPanSensitivity |
数值/布尔 | 运行时调整缩放行为(也可直接改 props) |
setContentInsets |
topPx, bottomPx |
设置内容内边距(也可直接改 props) |
setShowIndex |
show: boolean |
显示/隐藏页码角标 |
resetZoom / resetBitmaps |
- | 重置缩放 / 释放位图 |
destroy |
- | 销毁原生视图(组件 onUnmounted 自动调用) |
完整示例(图片列表滚动页)
实际项目中的用法(参考 pages/manhua/reader/index.uvue):渐进式加载图片 → 用 pageChange 更新页码与预取 → 下载完成后 updateReaderPages 回填本地地址与真实尺寸。
<template>
<view class="page">
<!-- #ifdef APP-ANDROID -->
<mx-reader-native
ref="readerRef"
class="reader"
:images="readerImages"
:initial-page="0"
:image-spacing="spacingPx()"
:zoom-enabled="zoomMax > 1"
:max-scale="zoomMax"
:original-min-scale="1"
:pan-sensitivity="1.5"
:content-bottom-inset="bottomInset()"
:show-index="showIndex"
:low-quality="lowQuality"
:low-quality-placeholder-scale="4"
:low-quality-viewport-scale="2"
:preview-width="480"
@ready="onReady"
@page-change="onPageChange"
@scroll-change="Change"
@center-tap="toggleControls"
/>
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import type { ComponentPublicInstance } from 'vue'
import { ShenshiReaderImage } from '@/uni_modules/mx-reader-native'
const win = uni.getWindowInfo()
const readerRef = ref<ComponentPublicInstance | null>(null)
const readerImages = ref<ShenshiReaderImage[]>([])
const currentPage = ref(1)
const controlsVisible = ref(false)
const zoomMax = ref(3)
const showIndex = ref(false)
const lowQuality = ref(false)
let ready = false
function spacingPx(): number { return 8 * win.windowWidth / 750 }
function bottomInset(): number { return controlsVisible.value ? 200 * win.windowWidth / 750 : 0 }
function onReady(): void {
ready = true
readerRef.value?.$callMethod('scrollToPage', 0)
}
function onPageChange(detail: UTSJSONObject): void {
const index = detail['index'] as number
const total = detail['total'] as number
currentPage.value = Math.min(index + 1, total > 0 ? total : index + 1)
prefetchAround(index)
}
function Change(detail: UTSJSONObject): void {
// 需要时读取 scale 判断是否放大
const scale = detail['scale'] as number
}
function toggleControls(): void {
controlsVisible.value = !controlsVisible.value
}
function goChapter(images: string[]): void {
const next: ShenshiReaderImage[] = []
const w = Math.round(win.windowWidth)
for (let i = 0; i < images.length; i++) {
next.push({ src: images[i], width: w, height: Math.round(w * 0.75) })
}
readerImages.value = next
}
// 图片解析出真实尺寸/本地路径后,节流批量回填
function applyResolved(index: number, localSrc: string, w: number, h: number): void {
if (!ready) return
const ratio = win.windowWidth / w
const items: UTSJSONObject[] = [
{ index: index, src: localSrc, width: Math.round(win.windowWidth), height: Math.round(h * ratio) }
]
readerRef.value?.$callMethod('updateReaderPages', items)
}
function prefetchAround(center: number): void {
// 按业务预取 center 前后若干页
}
</script>
</style>
手势说明(Android)
原生视图由两层组成:
GestureContainer:不做缩放,仅接收和分发触摸事件,保证双指间距坐标稳定。RecyclerView:只负责图片渲染、缩放与平移。
未放大时,单指事件交给 RecyclerView 执行正常纵向阅读;双指缩放与放大后的单指拖动由 GestureContainer 处理,防止列表滚动抢占手势。
- 双指缩放使用增量
scaleFactor,当前增益1.4;单指平移增益默认1.5(pan-sensitivity) - 达到最大倍率后反向捏合会立即缩小
- 放大状态下单指松手启动惯性平移,速度衰减到阈值或触及边界时停止;平移范围受视口边界限制,避免拖出空白
维护
- Android 原生实现:
utssdk/app-android/ReaderNativeView.kt - UTS 桥接类:
utssdk/app-android/index.uts - 组件封装(props/事件/方法):
components/mx-reader-native/mx-reader-native.uvue - 类型定义:
utssdk/interface.uts

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 6
赞赏 0
下载 12634998
赞赏 1950
赞赏
京公网安备:11010802035340号