当前位置: 首页 > news >正文

制作电子相册

本案例介绍了如何实现一个简单的电子相册应用,主要功能包括:

  1. 实现首页顶部的轮播效果。
  2. 实现页面多种布局方式。
  3. 实现通过手势控制图片的放大、缩小、左右滑动查看细节等效果。

一、案例效果截图

二、案例运用到的知识点

  1. 核心知识点
  • 组合手势:手势识别组,多种手势组合为复合手势,支持连续识别、并行识别和互斥识别。
  • Swiper:滑块视图容器,提供子组件滑动轮播显示的能力。
  • Grid:网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。
  • Navigation:Navigation组件一般作为Page页面的根容器,通过属性设置来展示页面的标题、工具栏、菜单。
  • List:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
  1. 其他知识点
  • ArkTS 语言基础
  • V2版状态管理:@ComponentV2/@Local/@Param/AppStorageV2
  • 渲染控制:ForEach
  • 自定义组件和组件生命周期
  • 自定义构建函数@Builder
  • @Extend:定义扩展样式
  • Navigation导航组件与router路由导航
  • 内置组件:Swiper/Stack/Column/Row/Image/Grid/List
  • 常量与资源分类的访问
  • MVVM模式

三、代码结构

├──entry/src/main/ets                // 代码区
│  ├──common
│  │  ├──constansts
│  │  │  └──Constants.ets            // 常量类
│  │  └──utils
│  │     └──Logger.ets               // Logger公共类
│  ├──entryability
│  │  └──EntryAbility.ets            // 程序入口类
│  ├──pages
│  │  ├──DetailListPage.ets          // 图片详情页面
│  │  ├──DetailPage.ets              // 查看大图页面
│  │  ├──IndexPage.ets               // 电子相册主页面
│  │  └──ListPage.ets                // 图片列表页面
│  └──view
│     └──PhotoItem.ets               // 首页相册Item组件
└──entry/src/main/resources          // 资源文件

四、公共文件与资源

本案例涉及到的常量类和工具类代码如下:

  1. 通用常量类
// entry/src/main/ets/common/constants/Constants.ets
export default class Constants {/*** banner list*/static readonly BANNER_IMG_LIST: Array<Resource> = [$r('app.media.ic_scene_1'),$r('app.media.ic_food_0'),$r('app.media.ic_life_0'),$r('app.media.ic_men_0')]/*** scene list*/static readonly SCENE_LIST: Array<Resource> = [$r('app.media.ic_scene_1'),$r('app.media.ic_scene_2'),$r('app.media.ic_scene_0')]/*** men list*/static readonly MEN_LIST: Array<Resource> = [$r('app.media.ic_men_0'),$r('app.media.ic_men_2'),$r('app.media.ic_men_3')]/*** food list*/static readonly FOOD_LIST: Array<Resource> = [$r('app.media.ic_food_1'),$r('app.media.ic_food_0'),]/*** life list*/static readonly LIFE_LIST: Array<Resource> = [$r('app.media.ic_life_1'),$r('app.media.ic_life_0'),$r('app.media.ic_life_2'),$r('app.media.ic_life_3'),$r('app.media.ic_life_4'),$r('app.media.ic_life_5')]/*** index page img arr*/static readonly IMG_ARR: Resource[][] = [new Array<Resource>().concat(Constants.SCENE_LIST, Constants.LIFE_LIST, Constants.MEN_LIST),new Array<Resource>().concat(Constants.MEN_LIST, Constants.LIFE_LIST, Constants.SCENE_LIST),new Array<Resource>().concat(Constants.FOOD_LIST, Constants.SCENE_LIST, Constants.SCENE_LIST),new Array<Resource>().concat(Constants.LIFE_LIST, Constants.FOOD_LIST, Constants.MEN_LIST)]/*** title font weight*/static readonly TITLE_FONT_WEIGHT: number = 500/*** aspect ratio*/static readonly BANNER_ASPECT_RATIO: number = 1.5/*** animate duration*/static readonly BANNER_ANIMATE_DURATION: number = 300/*** share delay*/static readonly SHARE_TRANSITION_DELAY: number = 100/*** aspect ratio*/static readonly STACK_IMG_RATIO: number = 0.7/*** item space*/static readonly LIST_ITEM_SPACE: number = 2/*** cache size*/static readonly CACHE_IMG_SIZE: number = 4/*** cache list*/static readonly CACHE_IMG_LIST: string[] = ['', '', '', '']/*** title*/static readonly PAGE_TITLE: string = '电子相册'/***  router param*/static readonly PARAM_PHOTO_ARR_KEY: string = 'photoArr'/***  selected index*/static readonly SELECTED_INDEX_KEY: string = 'selectedIndex'/*** grid column template*/static readonly GRID_COLUMNS_TEMPLATE: string = '1fr 1fr 1fr 1fr'/*** index page columns template*/static readonly INDEX_COLUMNS_TEMPLATE: string = '1fr 1fr'/***  percent*/static readonly FULL_PERCENT: string = '100%'/*** photo item percent*/static readonly PHOTO_ITEM_PERCENT: string = '90%'/*** show count*/static readonly SHOW_COUNT: number = 8/*** default width*/static readonly DEFAULT_WIDTH: number = 360/*** padding*/static readonly PHOTO_ITEM_PADDING: number = 8/*** offset*/static readonly PHOTO_ITEM_OFFSET: number = 16/*** item opacity offset*/static readonly ITEM_OPACITY_OFFSET: number = 0.2/*** double number*/static readonly DOUBLE_NUMBER: number = 2/*** list page url*/static readonly URL_LIST_PAGE: string = 'pages/ListPage'/*** detail list page url*/static readonly URL_DETAIL_LIST_PAGE: string = 'pages/ListDetailPage'/*** detail page url*/static readonly URL_DETAIL_PAGE: string = 'pages/DetailPage'/*** index page tag*/static readonly TAG_INDEX_PAGE: string = 'IndexPage push error '/*** list page tag*/static readonly TAG_LIST_PAGE: string = 'ListPage push error '/*** detail list page tag*/static readonly TAG_DETAIL_PAGE: string = 'DetailListPage push error '}
  1. 公共日志类
// entry/src/main/ets/common/utils/Logger.ets
import { hilog } from '@kit.PerformanceAnalysisKit'const LOGGER_PREFIX: string = 'Electronic Album'class Logger {private domain: numberprivate prefix: stringprivate format: string = '%{public}s, %{public}s'constructor(prefix: string = '', domain: number = 0xFF00) {this.prefix = prefixthis.domain = domain}debug(...args: string[]): void {hilog.debug(this.domain, this.prefix, this.format, args)}info(...args: string[]): void {hilog.info(this.domain, this.prefix, this.format, args)}warn(...args: string[]): void {hilog.warn(this.domain, this.prefix, this.format, args)}error(...args: string[]): void {hilog.error(this.domain, this.prefix, this.format, args)}
}export default new Logger(LOGGER_PREFIX)

本案例涉及到的资源文件如下:

  1. string.json
// entry/src/main/resources/base/element/string.json
{"string": [{"name": "module_desc","value": "模块描述"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "电子相册"}]
}
  1. color.json
// entry/src/main/resources/base/element/color.json
{"color": [{"name": "start_window_background","value": "#FFFFFF"},{"name": "detail_background","value": "#000000"}]
}
  1. float.json
// entry/src/main/resources/base/element/float.json
{"float": [{"name": "detail_list_margin","value": "20vp"},{"name": "img_border_radius","value": "12vp"},{"name": "grid_padding","value": "12vp"},{"name": "title_padding","value": "24vp"},{"name": "title_font_size","value": "30fp"},{"name": "navi_bar_height","value": "56vp"}]
}

其他资源请到源码中获取。

五、应用首页

应用首页用Column组件来实现纵向布局,从上到下依次是标题组件Text、轮播图Swiper、相册列表Grid。标题和轮播图均设置固定高度,底部相册列表通过layoutWeight属性实现自适应布局占满剩余空间。

// entry/src/main/ets/pages/Index.ets
import { router } from '@kit.ArkUI'
import Constants from '../common/constants/Constants'
import PhotoItem from '../view/PhotoItem'@Entry
@ComponentV2
struct IndexPage {swiperController: SwiperController = new SwiperController()scroller: Scroller = new Scroller()@Local currentIndex: number = 0@Local angle: number = 0build() {Column() {Row() {Text($r('app.string.EntryAbility_label')).fontSize($r('app.float.title_font_size')).fontWeight(Constants.TITLE_FONT_WEIGHT)}.height($r('app.float.navi_bar_height')).alignItems(VerticalAlign.Center).justifyContent(FlexAlign.Start).margin({ top: $r('app.float.grid_padding') }).padding({ left: $r('app.float.title_padding') }).width(Constants.FULL_PERCENT)Swiper(this.swiperController) {ForEach(Constants.BANNER_IMG_LIST, (item: Resource) => {Row() {Image(item).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}.width(Constants.FULL_PERCENT).aspectRatio(Constants.BANNER_ASPECT_RATIO)}, (item: Resource, index?: number) => JSON.stringify(item) + index)}.autoPlay(true).loop(true).margin($r('app.float.grid_padding')).borderRadius($r('app.float.img_border_radius')).clip(true).duration(Constants.BANNER_ANIMATE_DURATION).indicator(false)Grid() {ForEach(Constants.IMG_ARR, (photoArr: Array<Resource>) => {GridItem() {PhotoItem({ photoArr })}.width(Constants.FULL_PERCENT).aspectRatio(Constants.STACK_IMG_RATIO).onClick(() => {router.pushUrl({url: Constants.URL_LIST_PAGE,params: { photoArr: photoArr }})})}, (item: Resource, index?: number) => JSON.stringify(item) + index)}.scrollBar(BarState.Off).columnsTemplate(Constants.INDEX_COLUMNS_TEMPLATE).columnsGap($r('app.float.grid_padding')).rowsGap($r('app.float.grid_padding')).padding({ left: $r('app.float.grid_padding'), right: $r('app.float.grid_padding')}).width(Constants.FULL_PERCENT).layoutWeight(1)}.width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}
}

PhotoItem视图用来渲染相册图片:

// entry/src/main/ets/view/PhotoItem.ets
import Constants from '../common/constants/Constants'@ComponentV2
export default struct PhotoItem {@Param photoArr: Array<Resource> = []@Local currentIndex: number = 0private showCount: number = Constants.SHOW_COUNT / Constants.DOUBLE_NUMBER@Builder albumPicBuilder(img: Resource, index: number) {Column() {Image(img).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT).borderRadius($r('app.float.img_border_radius')).opacity(1 - (this.showCount - index - 1) * Constants.ITEM_OPACITY_OFFSET)}.padding((this.showCount - index - 1) * Constants.PHOTO_ITEM_PADDING).offset({ y: (this.showCount - index - 1) * Constants.PHOTO_ITEM_OFFSET }).height(Constants.PHOTO_ITEM_PERCENT).width(Constants.FULL_PERCENT)}build() {Stack({ alignContent: Alignment.Top }) {ForEach(Constants.CACHE_IMG_LIST, (image: string, index?: number) => {if (index) {this.albumPicBuilder(this.photoArr[this.showCount - index - 1], index)}}, (item: string, index?: number) => JSON.stringify(item) + index)}.width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}
}

六、图片列表页

图片列表页是网格状展开的图片列表,主要使用Grid组件和GridItem组件,GridItem高度通过aspectRatio属性设置为跟宽度一致。


// entry/src/main/ets/pages/ListPage.ets
import { AppStorageV2, router } from '@kit.ArkUI'
import Constants from '../common/constants/Constants'
import { ListPageStorage } from '../model/ListPageModel'@Entry
@ComponentV2
struct ListPage {@Local storage: ListPageStorage = AppStorageV2.connect(ListPageStorage, 'storage', () => new ListPageStorage())!photoArr: Array<Resource> = (router.getParams() asRecord<string, Array<Resource>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`]build() {Navigation() {Grid() {ForEach(this.photoArr, (img: Resource, index?: number) => {GridItem() {Image(img).height(Constants.FULL_PERCENT).width(Constants.FULL_PERCENT).objectFit(ImageFit.Cover).onClick(() => {if (!index) {index = 0}this.storage.selectedIndex = indexrouter.pushUrl({url: Constants.URL_DETAIL_LIST_PAGE,params: {photoArr: this.photoArr,}})})}.width(Constants.FULL_PERCENT).aspectRatio(1)}, (item: Resource) => JSON.stringify(item))}.scrollBar(BarState.Off).columnsTemplate(Constants.GRID_COLUMNS_TEMPLATE).rowsGap(Constants.LIST_ITEM_SPACE).columnsGap(Constants.LIST_ITEM_SPACE).layoutWeight(1)}.title(Constants.PAGE_TITLE).hideBackButton(false).titleMode(NavigationTitleMode.Mini)}
}

上述代码中storage需要构建一个AppStorageV2的数据:

// entry/src/main/ets/model/ListPageModel.ets
@ObservedV2
export class ListPageStorage {@Trace selectedIndex: number = 0
}

七、图片详情页

图片详情页由上下两个横向滚动的List组件完成整体布局,两个组件之间有联动的效果。上边展示的大图始终是底部List处于屏幕中间位置的图片。滚动或者点击底部的List,上边展示的大图会随着改变,同样左右滑动上边的图片时,底部List组件也会随之进行滚动。

界面跳转动画是通过共享元素实现的,通过给Image添加sharedTransition属性来实现共享元素转场动画。两个页面的组件配置为同一个shareId,当shareId被配置为空字符串时不会有共享元素转场效果。

// entry/src/main/ets/pages/ListDetailPage.ets
import { router,display, AppStorageV2 } from '@kit.ArkUI'
import Constants from '../common/constants/Constants'
import { ListPageStorage } from '../model/ListPageModel'enum scrollTypeEnum {STOP = 'onScrollStop',SCROLL = 'onScroll'
}@Entry
@ComponentV2
struct DetailListPage {private smallScroller: Scroller = new Scroller()private bigScroller: Scroller = new Scroller()@Local deviceWidth: number = Constants.DEFAULT_WIDTH@Local smallImgWidth: number = (this.deviceWidth - Constants.LIST_ITEM_SPACE * (Constants.SHOW_COUNT - 1)) / Constants.SHOW_COUNT@Local imageWidth: number = this.deviceWidth + this.smallImgWidthprivate photoArr: Array<Resource | string> = (router.getParams() as Record<string, Array<Resource | string>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`]private smallPhotoArr: Array<Resource | string> = new Array<Resource | string>().concat(Constants.CACHE_IMG_LIST,(router.getParams() as Record<string, Array<Resource | string>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`],Constants.CACHE_IMG_LIST)@Local storage: ListPageStorage = AppStorageV2.connect(ListPageStorage, 'storage', () => new ListPageStorage())!@Builder SmallImgItemBuilder(img: Resource, index?: number) {if (index && index > (Constants.CACHE_IMG_SIZE - 1) && index < (this.smallPhotoArr.length - Constants.CACHE_IMG_SIZE)) {Image(img).onClick(() => this.smallImgClickAction(index))}}aboutToAppear() {let displayClass: display.Display = display.getDefaultDisplaySync()let width = displayClass?.width / displayClass.densityPixels ?? Constants.DEFAULT_WIDTHthis.deviceWidth = widththis.smallImgWidth = (width - Constants.LIST_ITEM_SPACE * (Constants.SHOW_COUNT - 1)) / Constants.SHOW_COUNTthis.imageWidth = this.deviceWidth + this.smallImgWidth}onPageShow() {this.smallScroller.scrollToIndex(this.storage.selectedIndex)this.bigScroller.scrollToIndex(this.storage.selectedIndex)}goDetailPage(): void {router.pushUrl({url: Constants.URL_DETAIL_PAGE,params: { photoArr: this.photoArr }})}smallImgClickAction(index: number): void {this.storage.selectedIndex = index - Constants.CACHE_IMG_SIZEthis.smallScroller.scrollToIndex(this.storage.selectedIndex)this.bigScroller.scrollToIndex(this.storage.selectedIndex)}smallScrollAction(type: scrollTypeEnum): void {this.storage.selectedIndex = Math.round(((this.smallScroller.currentOffset().xOffset as number) + this.smallImgWidth / Constants.DOUBLE_NUMBER) / (this.smallImgWidth + Constants.LIST_ITEM_SPACE))if (type === scrollTypeEnum.SCROLL) {this.bigScroller.scrollTo({xOffset: this.storage.selectedIndex * this.imageWidth, yOffset: 0})} else {this.smallScroller.scrollTo({xOffset: this.storage.selectedIndex * this.smallImgWidth, yOffset: 0})}}bigScrollAction(type: scrollTypeEnum): void {let smallWidth = this.smallImgWidth + Constants.LIST_ITEM_SPACEthis.storage.selectedIndex = Math.round(((this.bigScroller.currentOffset().xOffset as number) +smallWidth / Constants.DOUBLE_NUMBER) / this.imageWidth)if (type === scrollTypeEnum.SCROLL) {this.smallScroller.scrollTo({xOffset: this.storage.selectedIndex * smallWidth, yOffset: 0 })} else {this.bigScroller.scrollTo({xOffset: this.storage.selectedIndex * this.imageWidth, yOffset: 0 })}}build() {Navigation() {Stack({ alignContent: Alignment.Bottom }) {List({scroller: this.bigScroller, initialIndex: this.storage.selectedIndex}) {ForEach(this.photoArr, (img: Resource) => {ListItem() {Image(img).height(Constants.FULL_PERCENT).width(Constants.FULL_PERCENT).objectFit(ImageFit.Contain).gesture(PinchGesture({ fingers: Constants.DOUBLE_NUMBER }).onActionStart(() => this.goDetailPage())).onClick(() => this.goDetailPage())}.padding({left: this.smallImgWidth / Constants.DOUBLE_NUMBER,right: this.smallImgWidth / Constants.DOUBLE_NUMBER}).width(this.imageWidth)}, (item: Resource) => JSON.stringify(item))}.onDidScroll((scrollOffset, scrollState) => {if (scrollState === ScrollState.Fling) {this.bigScrollAction(scrollTypeEnum.SCROLL)}}).scrollBar(BarState.Off).onScrollStop(() => this.bigScrollAction(scrollTypeEnum.STOP)).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT).padding({ bottom: this.smallImgWidth * Constants.DOUBLE_NUMBER }).listDirection(Axis.Horizontal)List({scroller: this.smallScroller,space: Constants.LIST_ITEM_SPACE,initialIndex: this.storage.selectedIndex}) {ForEach(this.smallPhotoArr, (img: Resource, index?: number) => {ListItem() {this.SmallImgItemBuilder(img, index)}.width(this.smallImgWidth).aspectRatio(1)}, (item: Resource) => JSON.stringify(item))}.listDirection(Axis.Horizontal).onDidScroll((scrollOffset, scrollState) => {if (scrollState === ScrollState.Fling) {this.smallScrollAction(scrollTypeEnum.SCROLL)}}).scrollBar(BarState.Off).onScrollStop(() => this.smallScrollAction(scrollTypeEnum.STOP)).margin({top: $r('app.float.detail_list_margin'), bottom: $r('app.float.detail_list_margin')}).height(this.smallImgWidth).width(Constants.FULL_PERCENT)}.width(this.imageWidth).height(Constants.FULL_PERCENT)}.title(Constants.PAGE_TITLE).hideBackButton(false).titleMode(NavigationTitleMode.Mini)}
}

八、查看大图页

查看大图页面由一个横向滚动的List组件来实现图片左右滑动时切换图片的功能,和一个Row组件实现图片的缩放和拖动查看细节功能。对图片进行缩放时会从List组件切换成Row组件来实现对单张图片的操作,对单张图片进行滑动操作时,也会由Row组件转换为List组件来实现图片的切换功能。

界面跳转动画是通过共享元素实现的,通过给Image添加sharedTransition属性来实现共享元素转场动画。两个页面的组件配置为同一个shareId,当shareId被配置为空字符串时不会有共享元素转场效果。

大图浏览界面双指捏合时通过改变Image组件的scale来控制图片的缩放,单手拖动时通过改变Image的偏移量来控制图片的位置,手势操作调用组合手势GestureGroup实现。其中PinchGesture实现双指缩放手势,PanGesture实现单指拖动手势。

// entry/src/main/ets/pages/DetailPage.ets
import { router, display, AppStorageV2 } from '@kit.ArkUI'
import Constants from '../common/constants/Constants'
import { ListPageStorage } from '../model/ListPageModel'@Entry
@ComponentV2
struct DetailPage {scroller: Scroller = new Scroller()photoArr: Array<Resource> = (router.getParams() as Record<string, Array<Resource>>)[`${Constants.PARAM_PHOTO_ARR_KEY}`]preOffsetX: number = 0preOffsetY: number = 0currentScale: number = 1@Local deviceWidth: number = Constants.DEFAULT_WIDTH@Local smallImgWidth: number = (Constants.DEFAULT_WIDTH - Constants.LIST_ITEM_SPACE * (Constants.SHOW_COUNT - 1)) /Constants.SHOW_COUNT@Local imageWidth: number = this.deviceWidth + this.smallImgWidth@Local isScaling: boolean = true@Local imgScale: number = 1@Local imgOffSetX: number = 0@Local imgOffSetY: number = 0@Local bgOpacity: number = 0@Local storage: ListPageStorage = AppStorageV2.connect(ListPageStorage, 'storage', () => new ListPageStorage())!aboutToAppear() {let displayClass: display.Display = display.getDefaultDisplaySync()let width = displayClass?.width / displayClass.densityPixels ?? Constants.DEFAULT_WIDTHthis.deviceWidth = widththis.smallImgWidth = (width - Constants.LIST_ITEM_SPACE * (Constants.SHOW_COUNT - 1)) / Constants.SHOW_COUNTthis.imageWidth = this.deviceWidth + this.smallImgWidth}resetImg(): void {this.imgScale = 1this.currentScale = 1this.preOffsetX = 0this.preOffsetY = 0}handlePanEnd(): void {let initOffsetX = (this.imgScale - 1) * this.imageWidth + this.smallImgWidthif (Math.abs(this.imgOffSetX) > initOffsetX) {if (this.imgOffSetX > initOffsetX && this.storage.selectedIndex > 0) {this.storage.selectedIndex -= 1} else if (this.imgOffSetX < -initOffsetX && this.storage.selectedIndex < (this.photoArr.length - 1)) {this.storage.selectedIndex += 1}this.isScaling = falsethis.resetImg()this.scroller.scrollTo({ xOffset: this.storage.selectedIndex * this.imageWidth, yOffset: 0 })}}build() {Stack() {List({scroller: this.scroller, initialIndex: this.storage.selectedIndex}) {ForEach(this.photoArr, (img: Resource) => {ListItem() {Image(img).objectFit(ImageFit.Contain).onClick(() => router.back())}.gesture(GestureGroup(GestureMode.Exclusive,PinchGesture({ fingers: Constants.DOUBLE_NUMBER }).onActionStart(() => {this.resetImg()this.isScaling = truethis.imgOffSetX = 0this.imgOffSetY = 0}).onActionUpdate((event?: GestureEvent) => {if (event) {this.imgScale = this.currentScale * event.scale}}).onActionEnd(() => {if (this.imgScale < 1) {this.resetImg()this.imgOffSetX = 0this.imgOffSetY = 0} else {this.currentScale = this.imgScale}}), PanGesture().onActionStart(() => {this.resetImg()this.isScaling = true}).onActionUpdate((event?: GestureEvent) => {if (event) {this.imgOffSetX = this.preOffsetX + event.offsetXthis.imgOffSetY = this.preOffsetY + event.offsetY}}))).padding({left: this.smallImgWidth / Constants.DOUBLE_NUMBER,right: this.smallImgWidth / Constants.DOUBLE_NUMBER}).width(this.imageWidth)}, (item: Resource) => JSON.stringify(item))}.onScrollStop(() => {let currentIndex = Math.round(((this.scroller.currentOffset().xOffset as number) +(this.imageWidth / Constants.DOUBLE_NUMBER)) / this.imageWidth)this.storage.selectedIndex = currentIndexthis.scroller.scrollTo({ xOffset: currentIndex * this.imageWidth, yOffset: 0 })}).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT).listDirection(Axis.Horizontal).visibility(this.isScaling ? Visibility.Hidden : Visibility.Visible)Row() {Image(this.photoArr[this.storage.selectedIndex]).position({ x: this.imgOffSetX, y: this.imgOffSetY }).scale({ x: this.imgScale, y: this.imgScale }).objectFit(ImageFit.Contain).onClick(() => router.back())}.gesture(GestureGroup(GestureMode.Exclusive,PinchGesture({ fingers: Constants.DOUBLE_NUMBER }).onActionUpdate((event?: GestureEvent) => {if (event) {this.imgScale = this.currentScale * event.scale}}).onActionEnd(() => {if (this.imgScale < 1) {this.resetImg()this.imgOffSetX = 0this.imgOffSetY = 0} else {this.currentScale = this.imgScale}}),PanGesture().onActionStart(() => {this.preOffsetX = this.imgOffSetXthis.preOffsetY = this.imgOffSetY}).onActionUpdate((event?: GestureEvent) => {if (event) {this.imgOffSetX = this.preOffsetX + event.offsetXthis.imgOffSetY = this.preOffsetY + event.offsetY}}).onActionEnd(() => this.handlePanEnd()))).padding({left: this.smallImgWidth / Constants.DOUBLE_NUMBER,right: this.smallImgWidth / Constants.DOUBLE_NUMBER}).width(this.imageWidth).height(Constants.FULL_PERCENT).visibility(this.isScaling ? Visibility.Visible : Visibility.Hidden)}.offset({ x: -(this.smallImgWidth / Constants.DOUBLE_NUMBER) }).width(this.imageWidth).height(Constants.FULL_PERCENT).backgroundColor($r('app.color.detail_background'))}
}

九、路由页面配置

页面间通过路由跳转时,需要配置每个页面的源路径。

{"src": ["pages/IndexPage","pages/DetailPage","pages/ListPage","pages/ListDetailPage"]
}
http://www.xdnf.cn/news/910351.html

相关文章:

  • 【深度学习新浪潮】RoPE对大模型的外推性有什么影响?
  • Gojs渲染实线、虚线
  • 单周期cpu和多周期cpu、单周期数据通路和多周期数据通路与总线结构数据通路和专用数据通路的关系
  • JAVA学习 DAY2 java程序运行、注意事项、转义字符
  • 实现echarts全屏的放大/缩小最优解
  • Kyosan K5BMC ELECTRONIC INTERLOCKING MANUAL 电子联锁
  • 【PmHub面试篇】性能监控与分布式追踪利器Skywalking面试专题分析
  • pp-ocrv5改进
  • 核弹级漏洞深度解析:Log4j2 JNDI注入攻击原理与防御实战
  • [IMX][UBoot] 01.UBoot 常用命令
  • 【八股消消乐】MySQL参数优化大汇总
  • 使用 Python 和 HuggingFace Transformers 进行对象检测
  • xpath表达式的常用知识点
  • K7 系列各种PCIE IP核的对比
  • 每日算法 -【Swift 算法】电话号码字母组合
  • Keil调试模式下,排查程序崩溃简述
  • 六、【ESP32开发全栈指南:深入解析ESP32 IDF中的WiFi AP模式开发】
  • 读《创新者的窘境》二分 - 破坏性创新与延续性创新
  • 飞牛使用Docker部署Tailscale 内网穿透教程
  • KL散度计算示例:用户画像 vs. 专辑播放分布的性别偏好分析
  • MySQL查询语句
  • 02 nginx 的环境搭建
  • 禅道5月更新速览 | 新增交付物配置功能,支持建立跨执行任务依赖关系,研发效能平台上线
  • 6个可提升社媒投资回报率的Facebook KPI
  • 基于tensorflow实现的猫狗识别
  • 配置git命令缩写
  • 学习记录aigc
  • 智能制造数字孪生全要素交付一张网:智造中枢,孪生领航,共建智造生态共同体
  • Verilog编程技巧01——如何编写三段式状态机
  • 数论——同余问题全家桶3 __int128和同余方程组