• 實現了一個功能完整的鴻蒙圖片瀏覽器應用,全面展示了ArkTS在圖片顯示、手勢操作、文件管理和用户界面設計等方面的核心能力。主要功能包括:
  • 圖片瀏覽:支持圖片的縮放、平移、旋轉等手勢操作
  • 圖片管理:添加、刪除、收藏圖片
  • 分類查看:按相冊、時間、收藏等分類瀏覽圖片
  • 圖片信息:顯示圖片的詳細信息(大小、分辨率、拍攝時間等)
  • 幻燈片播放:自動輪播圖片,支持播放控制
  • 圖片編輯:簡單的圖片裁剪、濾鏡效果
  • 分享功能:模擬圖片分享到其他應用

通過這個示例,可以深入理解ArkTS如何實現複雜的圖片處理和手勢交互。

2. 代碼邏輯分析

應用採用"圖片數據驅動UI"的架構設計:

  1. 初始化階段:應用啓動時,加載模擬的圖片數據和用户設置
  2. 狀態管理:使用多個@State裝飾器管理圖片列表、當前圖片、瀏覽模式和編輯狀態
  3. 圖片瀏覽流程
  • 選擇圖片 → 進入詳情視圖 → 顯示大圖
  • 手勢操作 → 縮放/平移圖片 → 實時更新顯示
  • 切換圖片 → 滑動切換 → 更新當前圖片
  1. 圖片管理操作
  • 添加圖片 → 選擇圖片 → 添加到相冊
  • 刪除圖片 → 確認刪除 → 從列表中移除
  • 收藏圖片 → 標記收藏 → 更新收藏狀態
  1. 幻燈片播放
  • 開始播放 → 自動切換圖片 → 顯示播放控制
  • 暫停播放 → 停止自動切換 → 保持當前圖片
  1. 圖片編輯
  • 選擇編輯功能 → 應用效果 → 預覽並保存
  1. 界面更新:所有操作實時反映在UI上,提供流暢的用户體驗

完整代碼

@Entry
@Component
struct ImageViewerTutorial {
  @State imageList: ImageItem[] = [];
  @State currentImage: ImageItem = new ImageItem();
  @State currentIndex: number = 0;
  @State viewMode: string = 'grid';
  @State scale: number = 1.0;
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State isPlaying: boolean = false;
  @State selectedAlbum: string = 'all';

  aboutToAppear() {
    this.loadImages();
  }

  build() {
    Column({ space: 0 }) {
      this.BuildHeader()
      
      if (this.viewMode === 'grid') {
        this.BuildGridView()
      } else if (this.viewMode === 'detail') {
        this.BuildDetailView()
      } else if (this.viewMode === 'slideshow') {
        this.BuildSlideshowView()
      }
      
      if (this.viewMode !== 'grid') {
        this.BuildControlBar()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
  }

  @Builder BuildHeader() {
    Row({ space: 15 }) {
      if (this.viewMode !== 'grid') {
        Button('返回')
          .onClick(() => {
            this.viewMode = 'grid';
            this.resetImageTransform();
          })
          .fontSize(16)
          .fontColor('#FFFFFF')
          .backgroundColor('#333333')
          .borderRadius(20)
          .padding(10)
      }
      
      Text(this.getHeaderTitle())
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor('#FFFFFF')
        .layoutWeight(1)
      
      if (this.viewMode === 'detail') {
        Button('編輯')
          .onClick(() => {
            this.showEditOptions();
          })
          .fontSize(16)
          .fontColor('#FFFFFF')
          .backgroundColor('#333333')
          .borderRadius(20)
          .padding(10)
      }
    }
    .width('100%')
    .padding({ left: 15, right: 15, top: 10, bottom: 10 })
    .backgroundColor('#1A1A1A')
  }

  @Builder BuildGridView() {
    Column({ space: 10 }) {
      this.BuildAlbumSelector()
      
      Grid() {
        ForEach(this.getFilteredImages(), (image: ImageItem, index: number) => {
          GridItem() {
            this.BuildGridItem(image, index)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .columnsGap(5)
      .rowsGap(5)
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .padding(10)
    .layoutWeight(1)
  }

  @Builder BuildAlbumSelector() {
    const albums = this.getAlbums();
    
    Scroll() {
      Row({ space: 10 }) {
        ForEach(albums, (album: AlbumInfo) => {
          Button(album.name + ` (${album.count})`)
            .onClick(() => {
              this.selectedAlbum = album.name;
            })
            .backgroundColor(this.selectedAlbum === album.name ? '#4A90E2' : '#333333')
            .fontColor('#FFFFFF')
            .borderRadius(15)
            .padding({ left: 12, right: 12 })
            .height(30)
        })
      }
      .padding(5)
    }
    .scrollable(ScrollDirection.Horizontal)
    .width('100%')
    .height(40)
  }

  @Builder BuildGridItem(image: ImageItem, index: number) {
    Column({ space: 0 }) {
      Image(image.path)
        .width('100%')
        .height(120)
        .objectFit(ImageFit.Cover)
        .borderRadius(8)
        .onClick(() => {
          this.currentImage = image;
          this.currentIndex = index;
          this.viewMode = 'detail';
        })
      
      if (image.isFavorite) {
        Image($r('app.media.favorite_icon'))
          .width(16)
          .height(16)
          .position({ x: '80%', y: '10%' })
      }
    }
    .width('100%')
    .height(130)
  }

  @Builder BuildDetailView() {
    Stack({ alignContent: Alignment.Center }) {
      this.BuildImageDisplay()
      this.BuildImageInfo()
      this.BuildActionButtons()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
  }

  @Builder BuildImageDisplay() {
    GestureGroup(GestureMode.Exclusive) {
      PinchGesture()
        .onActionStart(() => {})
        .onActionUpdate((event: GestureEvent) => {
          this.scale = Math.max(0.5, Math.min(3, this.scale * event.scale));
        })
        .onActionEnd(() => {})
      
      PanGesture()
        .onActionStart(() => {})
        .onActionUpdate((event: GestureEvent) => {
          this.offsetX += event.offsetX;
          this.offsetY += event.offsetY;
        })
        .onActionEnd(() => {})
    }
    
    Image(this.currentImage.path)
      .width('100%')
      .height('100%')
      .objectFit(ImageFit.Contain)
      .scale({ x: this.scale, y: this.scale })
      .translate({ x: this.offsetX, y: this.offsetY })
  }

  @Builder BuildImageInfo() {
    Column({ space: 8 }) {
      Text(this.currentImage.name)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#FFFFFF')
      
      Text(`${this.currentImage.width} × ${this.currentImage.height}`)
        .fontSize(14)
        .fontColor('#CCCCCC')
      
      Text(this.currentImage.createTime)
        .fontSize(12)
        .fontColor('#999999')
    }
    .width('90%')
    .padding(15)
    .backgroundColor('rgba(0, 0, 0, 0.7)')
    .borderRadius(10)
    .position({ x: '5%', y: '85%' })
  }

  @Builder BuildActionButtons() {
    Row({ space: 20 }) {
      Button('收藏')
        .onClick(() => {
          this.toggleFavorite();
        })
        .backgroundColor(this.currentImage.isFavorite ? '#FF6B6B' : '#333333')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 15, right: 15 })
      
      Button('分享')
        .onClick(() => {
          this.shareImage();
        })
        .backgroundColor('#333333')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 15, right: 15 })
      
      Button('刪除')
        .onClick(() => {
          this.deleteImage();
        })
        .backgroundColor('#333333')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 15, right: 15 })
    }
    .position({ x: '50%', y: '10%' })
  }

  @Builder BuildSlideshowView() {
    Stack({ alignContent: Alignment.Center }) {
      Image(this.currentImage.path)
        .width('100%')
        .height('100%')
        .objectFit(ImageFit.Contain)
      
      if (this.isPlaying) {
        this.BuildPlaybackControls()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
    .onClick(() => {
      this.isPlaying = !this.isPlaying;
    })
  }

  @Builder BuildPlaybackControls() {
    Column({ space: 15 }) {
      Text('幻燈片播放中...')
        .fontSize(16)
        .fontColor('#FFFFFF')
      
      Row({ space: 20 }) {
        Button('上一張')
          .onClick(() => {
            this.previousImage();
          })
          .backgroundColor('#333333')
          .fontColor('#FFFFFF')
          .borderRadius(15)
          .padding(10)
        
        Button('暫停')
          .onClick(() => {
            this.isPlaying = false;
          })
          .backgroundColor('#4A90E2')
          .fontColor('#FFFFFF')
          .borderRadius(15)
          .padding(10)
        
        Button('下一張')
          .onClick(() => {
            this.nextImage();
          })
          .backgroundColor('#333333')
          .fontColor('#FFFFFF')
          .borderRadius(15)
          .padding(10)
      }
    }
    .width('80%')
    .padding(20)
    .backgroundColor('rgba(0, 0, 0, 0.7)')
    .borderRadius(15)
    .position({ x: '10%', y: '80%' })
  }

  @Builder BuildControlBar() {
    Row({ space: 30 }) {
      Button('幻燈片')
        .onClick(() => {
          this.viewMode = 'slideshow';
          this.startSlideshow();
        })
        .backgroundColor(this.viewMode === 'slideshow' ? '#4A90E2' : '#333333')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 15, right: 15 })
      
      Button('上一張')
        .onClick(() => {
          this.previousImage();
        })
        .backgroundColor('#333333')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 15, right: 15 })
      
      Button('下一張')
        .onClick(() => {
          this.nextImage();
        })
        .backgroundColor('#333333')
        .fontColor('#FFFFFF')
        .borderRadius(20)
        .padding({ left: 15, right: 15 })
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#1A1A1A')
    .justifyContent(FlexAlign.Center)
  }

  private loadImages(): void {
    const mockImages: ImageItem[] = [
      {
        id: '1',
        name: '風景1.jpg',
        path: $r('app.media.landscape1'),
        album: '風景',
        size: 2048576,
        width: 1920,
        height: 1080,
        createTime: '2024-01-15 10:30',
        isFavorite: true
      },
      {
        id: '2',
        name: '人物1.jpg', 
        path: $r('app.media.portrait1'),
        album: '人物',
        size: 1572864,
        width: 1080,
        height: 1920,
        createTime: '2024-01-16 14:20',
        isFavorite: false
      }
    ];
    
    this.imageList = mockImages;
    if (this.imageList.length > 0) {
      this.currentImage = this.imageList[0];
    }
  }

  private getFilteredImages(): ImageItem[] {
    if (this.selectedAlbum === 'all') {
      return this.imageList;
    }
    return this.imageList.filter(image => image.album === this.selectedAlbum);
  }

  private getAlbums(): AlbumInfo[] {
    const albums: {[key: string]: AlbumInfo} = {};
    
    this.imageList.forEach(image => {
      if (!albums[image.album]) {
        albums[image.album] = {
          name: image.album,
          count: 0,
          cover: image.path
        };
      }
      albums[image.album].count++;
    });
    
    return Object.values(albums);
  }

  private getHeaderTitle(): string {
    switch (this.viewMode) {
      case 'grid':
        return '圖片庫';
      case 'detail':
        return this.currentImage.name;
      case 'slideshow':
        return '幻燈片';
      default:
        return '圖片瀏覽器';
    }
  }

  private toggleFavorite(): void {
    this.currentImage.isFavorite = !this.currentImage.isFavorite;
    const index = this.imageList.findIndex(img => img.id === this.currentImage.id);
    if (index >= 0) {
      this.imageList[index].isFavorite = this.currentImage.isFavorite;
    }
  }

  private previousImage(): void {
    if (this.currentIndex > 0) {
      this.currentIndex--;
      this.currentImage = this.imageList[this.currentIndex];
      this.resetImageTransform();
    }
  }

  private nextImage(): void {
    if (this.currentIndex < this.imageList.length - 1) {
      this.currentIndex++;
      this.currentImage = this.imageList[this.currentIndex];
      this.resetImageTransform();
    }
  }

  private resetImageTransform(): void {
    this.scale = 1.0;
    this.offsetX = 0;
    this.offsetY = 0;
  }

  private startSlideshow(): void {
    this.isPlaying = true;
    setInterval(() => {
      if (this.isPlaying) {
        this.nextImage();
      }
    }, 3000);
  }

  private shareImage(): void {
    console.log('分享圖片:', this.currentImage.name);
  }

  private deleteImage(): void {
    this.imageList = this.imageList.filter(img => img.id !== this.currentImage.id);
    
    if (this.imageList.length > 0) {
      this.currentIndex = Math.min(this.currentIndex, this.imageList.length - 1);
      this.currentImage = this.imageList[this.currentIndex];
    } else {
      this.viewMode = 'grid';
    }
  }

  private showEditOptions(): void {
    console.log('顯示編輯選項');
  }
}

class ImageItem {
  id: string = '';
  name: string = '';
  path: string = '';
  album: string = '默認相冊';
  size: number = 0;
  width: number = 0;
  height: number = 0;
  createTime: string = '';
  isFavorite: boolean = false;
}

class AlbumInfo {
  name: string = '';
  count: number = 0;
  cover: string = '';
}

想入門鴻蒙開發又怕花冤枉錢?別錯過!現在能免費系統學 -- 從 ArkTS 面向對象核心的類和對象、繼承多態,到吃透鴻蒙開發關鍵技能,還能衝刺鴻蒙基礎 +高級開發者證書,更驚喜的是考證成功還送好禮!快加入我的鴻蒙班,一起從入門到精通,班級鏈接:點擊免費進入