更新记录

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-AndroidAPP-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 起)、totalscrollTopscale
scrollChange UTSJSONObject 滚动距离或缩放变化:scrollTopscale
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.5pan-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

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。