HarmonyOS 鸿蒙Next自然壁纸实战教程-首页

HarmonyOS 鸿蒙Next自然壁纸实战教程-首页

05-自然壁纸实战教程-首页

前言

这一章节需要完成的功能主要有沉浸式、首页搜索框、轮播大图、分类、精选主题、壁纸列表还有 tab容器结构

图像

tab容器结构

图像

元服务的tab结构需要使用 AtomicServiceNavigation来实现

基本示例

import { AtomicServiceNavigation, MixMode, GradientAlpha, BackgroundTheme } from '@kit.ArkUI';
import { AtomicServiceTabs, TabBarOptions, TabBarPosition } from '@kit.ArkUI';

@Component
struct Index {
  @State message: string = '主题';
  childNavStack: NavPathStack = new NavPathStack();
  
  @Builder
  tabContent1() {
    Text('first page')
      .onClick(() => {
        this.childNavStack.pushPath({ name: 'page one' })
      })
  }

  @Builder
  tabContent2() {
    Text('second page')
  }

  @Builder
  tabContent3() {
    Text('third page')
  }

  @Builder
  navigationContent() {
    AtomicServiceTabs({
      tabContents: [
        () => {
          this.tabContent1()
        },
        () => {
          this.tabContent2()
        },
        () => {
          this.tabContent3()
        }
      ],
      tabBarOptionsArray: [
        new TabBarOptions($r('sys.media.ohos_ic_public_phone'), '功能1'),
        new TabBarOptions($r('sys.media.ohos_ic_public_location'), '功能2', Color.Green, Color.Red),
        new TabBarOptions($r('sys.media.ohos_ic_public_more'), '功能3')
      ],
      tabBarPosition: TabBarPosition.BOTTOM,
      barBackgroundColor: $r('sys.color.ohos_id_color_bottom_tab_bg'),
      onTabBarClick: (index: Number) => {
        if (index == 0) {
          this.message = '功能1';
        } else if (index == 1) {
          this.message = '功能2';
        } else {
          this.message = '功能3';
        }
      }
    })
  }

  @Builder
  pageMap(name: string) {
    if (name === 'page one') {
      PageOne()
    } else if (name === 'page two') {
      PageTwo()
    }
  }

  build() {
    Row() {
      Column() {
        AtomicServiceNavigation({
          navigationContent: () => {
            this.navigationContent()
          },
          title: this.message,
          titleOptions: {
            isBlurEnabled: false
          },
          gradientBackground: {
            primaryColor: '#FF0000',
            secondaryColor: '#00FF00',
            backgroundTheme: BackgroundTheme.LIGHT,
            mixMode: MixMode.AVERAGE,
            alpha: GradientAlpha.OPACITY_100
          },
          navDestinationBuilder: this.pageMap,
          navPathStack: this.childNavStack,
          mode: NavigationMode.Stack
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
export struct PageOne {
  pageInfo: NavPathStack = new NavPathStack();

  build() {
    NavDestination() {
      Button('Next')
        .onClick(() => {
          this.pageInfo.pushPath({ name: 'page two'})
        })
    }
    .title('PageOne')
    .onReady((context: NavDestinationContext) => {
      this.pageInfo = context.pathStack;
    })
  }
}

@Component
export struct PageTwo {
  pageInfo: NavPathStack = new NavPathStack();

  build() {
    NavDestination() {
      Button('End')
    }
    .title('PageTwo')
    .onReady((context: NavDestinationContext) => {
      this.pageInfo = context.pathStack;
    })
  }
}

项目中的 AtomicServiceNavigation 使用

AtomicServiceNavigation({
  navigationContent: () => {
    this.navigationContent()
  },
  navPathStack: NavigationUtils.getInstance().pageInfos,// 封装的常量,指定跳转的页面
  hideTitleBar: true,
  mode: NavigationMode.Stack,
})

沉浸式

沉浸式的代码已经提前封装起来了 src/main/ets/utils/fullScreenHelper.ets

import { window } from "@kit.ArkUI";

/**
 * 安全区域
 */
@ObservedV2
export class AppStatu {
  @Trace static vpBottomHeight: number = 0
  @Trace static vpTopHeight: number = 0
}

export class FullScreenHelper {
  static window: window.Window

  static setWindow(window: window.Window) {
    FullScreenHelper.window = window
  }

  static setFullScreen() {
    FullScreenHelper.window.setWindowLayoutFullScreen(true)
    const topAvoidArea = FullScreenHelper.window.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
    const topRectHeight = topAvoidArea.topRect.height;
    const vpTopHeight = px2vp(topRectHeight)
    const bottomAvoidArea = FullScreenHelper.window.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
    const bottomRectHeight = bottomAvoidArea.bottomRect.height;
    const vpBottomHeight = px2vp(bottomRectHeight)
    AppStatu.vpBottomHeight = vpBottomHeight
    AppStatu.vpTopHeight = vpTopHeight
  }

  static setWindowSystemBarProperties(color: string) {
    window.getLastWindow(getContext())
      .then(win => {
        win.setWindowSystemBarProperties({
          statusBarContentColor: color, // 默认浅色文字(可自定义)
        });
      })
  }
}

然后在EntryAbility中使用

onWindowStageCreate(windowStage: window.WindowStage): void {
  // Main window is created, set main page for this ability
  hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  windowStage.getMainWindow() // 沉浸式
    .then(win => {
      FullScreenHelper.setWindow(win)
      FullScreenHelper.setFullScreen()
    })
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
      return;
    }
    hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
  });
}

首页

这里的首页不是指 src/main/ets/pages/Index.ets,而是指使用了AtomicServiceNavigation 管理的 首页 src/main/ets/views/homeView/HomeView.ets

首页搜索框

这里的图片和文字内容做成了可以轮训的样子 图像

@Builder
PositiveQuotesBuilder() {
  Row() {
    Column() {
      Swiper() {
        ForEach(LocalData.PositiveQuotes, (quote: string) => {
          Row() {
            Image($r('app.media.app_icon'))
              .width(24)
              .height(24)
              .margin({ right: 10 })
            Text(quote)
              .fontSize(14)
              .fontColor('#666666')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
          }
          .width('100%')
          .height('100%')
          .backgroundColor($r('app.color.white'))
          .borderRadius(20)
          .padding({ left: 15, right: 15 })
          .justifyContent(FlexAlign.Start)
        })
      }
      .width('100%')
      .height(45)
      .autoPlay(true)
      .loop(true)
      .vertical(true)
      .interval(3000)
      .indicator(false)
    }
    .width('75%')
    .height(45)
    .borderRadius(20)
    .backgroundColor($r('app.color.white'))
    .shadow({
      radius: 4,
      color: 'rgba(0, 0, 0, 0.1)',
      offsetX: 0,
      offsetY: 2
    })
  }
  .width('100%')
  .justifyContent(FlexAlign.Start)
  .padding({ left: 15 })
  .margin({ top: 10, bottom: 15 })
}
/**
 * 正能量语句数组
 */
static readonly PositiveQuotes: string[] = [
  '每一天都是新的开始,充满无限可能',
  '坚持不懈,直到成功',
  '微笑面对生活,积极拥抱未来',
  '态度决定高度,行动创造价值',
  '今天的努力,是明天的收获',
  '相信自己,你比想象中更强大',
  '感恩所有,珍惜当下',
  '用心生活,用爱前行'
]

轮播大图

轮播图使用Swiper组件渲染即可 图像

// 精选壁纸轮播
Swiper() {
  ForEach(LocalData.BannerData, (item: string) => {
    Image(item)
      .width('100%')
      .height(200)
      .borderRadius(10)
  })
}
.width('90%')
.height(200)
.autoPlay(true)
.loop(true)
.interval(3000)
.borderRadius(10)
//导航点颜色
.indicator(
  Indicator.dot()
    .color('rgba(255, 255, 255, 0.3)')
    .selectedColor('#ff258075')
)
/**
 * 精选壁纸轮播数据
 */
static readonly BannerData: string[] = [
  "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/1.png",
  "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/2.png",
  "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/3.png"
]

分类

图像

可以滚动的结构,选择使用了 Scroll

// 分类图标-- 横向列表
Scroll() {
  Row() {
    ForEach(LocalData.CategoryData, (item: ICategory) => {
      CategoryItemView({ text: item.text, icon: item.icon, value: item.id })
        .margin({ right: 15 })
    })
  }
  .padding({ left: 15 })
}
.width("100%")
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
/**
 * 类型数据
 */
static readonly CategoryData: ICategory[] = [
  {
    "id": 0,
    "text": "背景",
    "value": "backgrounds",
    "icon": "🌅"
  },
  {
    "id": 1,
    "text": "时尚",
    "value": "fashion",
    "icon": "👔"
  },
  {
    "id": 2,
    "text": "自然",
    "value": "nature",
    "icon": "🌲"
  },
  {
    "id": 3,
    "text": "科学",
    "value": "science",
    "icon": "🔬"
  },
  {
    "id": 4,
    "text": "教育",
    "value": "education",
    "icon": "📚"
  },
  {
    "id": 5,
    "text": "感情",
    "value": "feelings",
    "icon": "❤️"
  },
  {
    "id": 6,
    "text": "健康",
    "value": "health",
    "icon": "🏥"
  },
  {
    "id": 7,
    "text": "人",
    "value": "people",
    "icon": "👥"
  },
  {
    "id": 8,
    "text": "宗教",
    "value": "religion",
    "icon": "🙏"
  },
  {
    "id": 9,
    "text": "地方",
    "value": "places",
    "icon": "🌆"
  },
  {
    "id": 10,
    "text": "动物",
    "value": "animals",
    "icon": "🐱"
  },
  {
    "id": 11,
    "text": "工业",
    "value": "industry",
    "icon": "🏭"
  },
  {
    "id": 12,
    "text": "计算机",
    "value": "computer",
    "icon": "💻"
  },
  {
    "id": 13,
    "text": "食品",
    "value": "food",
    "icon": "🍜"
  },
  {
    "id": 14,
    "text": "体育",
    "value": "sports",
    "icon": "🏃"
  },
  {
    "id": 15,
    "text": "交通",
    "value": "transportation",
    "icon": "🚗"
  },
  {
    "id": 16,
    "text": "旅行",
    "value": "travel",
    "icon": "✈️"
  },
  {
    "id": 17,
    "text": "建筑物",
    "value": "buildings",
    "icon": "🏢"
  },
  {
    "id": 18,
    "text": "商业",
    "value": "business",
    "icon": "💼"
  },
  {
    "id": 19,
    "text": "音乐",
    "value": "music",
    "icon": "🎵"
  }
]

精选主题

图像

精选主题比较简单,就是标题和对应的内容。 内容是使用Grid 布局实现

// 精选专题
Column() {
  // 标题
  Row({ space: 10 }) {
    Text('精选专题')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
    Text('探索精彩壁纸专题')
      .fontSize(13)
      .fontColor('#ff888991')
  }
  .width('100%')
  .justifyContent(FlexAlign.Start)
  .margin({ bottom: 16 })

  // 内容
  Grid() {
    ForEach(LocalData.SpecialTopicData, (item: SpecialTopicType) => {
      GridItem() {
        SpecialTopicItemView({
          tag: item.tag,
          tagColor: item.tagColor,
          title: item.title,
          desc: item.desc,
          text: item.text,
          img: item.img
        })
      }
    })
  }
  .columnsTemplate('1fr 1fr')
  .columnsGap(16)
  .rowsGap(16)
  .width('100%')
}
.padding({ left: 16, right: 16, top: 6 })
/**
 * 精选专题
 */
static readonly SpecialTopicData: SpecialTopicType[] = [
  {
    tag: '热门',
    tagColor: '#FF4081',
    title: '夏日清凉壁纸',
    desc: '精选清凉夏日壁纸',
    text: '夏日',
    img: "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/summer.jpg"
  },
  {
    tag: '精选',
    tagColor: '#2196F3',
    title: '梦幻星空系列',
    desc: '精选唯美星空壁纸',
    text: '星空',
    img: "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/starry.jpg"
  },
  {
    tag: '最新',
    tagColor: '#4CAF50',
    title: '城市夜景',
    desc: '精选城市&夜景壁纸',
    text: '城市夜景',
    img: "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/city.jpg"
  },
  {
    tag: '推荐',
    tagColor: '#FF9800',
    title: '自然风光精选',
    desc: '精选自然风光壁纸',
    text: '自然风光',
    img: "https://wsy997.obs.cn-east-3.myhuaweicloud.com/zrbz/generated.png"
  }
]

壁纸列表

最后就是壁纸列表了,考虑到壁纸列表存在比较多数据,因此这里使用 WaterFlowLazyForEach 实现页面渲染和分页加载 图像

//壁纸瀑布流
WaterFlow() {
  LazyForEach(this.homeViewModel.imgList, (item: PixabayImage, index: number) => {
    FlowItem() {
      if (index % 6 === 0) {
        NativeAdPage()
      } else {
        WallpaperCard({ image: item })
      }
    }
    .width('100%')
    .onAppear(() => {
      if (index == (this.homeViewModel.imgList.totalCount() - 5)) {
        this.homeViewModel.onGetListEnd()
      }
    })
  })
}
.cachedCount(10)
.columnsGap(16)
.rowsGap(16)
.width('100%')
.height('100%')
.columnsTemplate('1fr 1fr')
.padding({ left: 16, right: 16 })
.nestedScroll({
  scrollForward: NestedScrollMode.PARENT_FIRST,
  scrollBackward: NestedScrollMode.SELF_FIRST
})
import { getPhotoList } from "../services/photoServices";
import { LazyForEachDataSource } from "../utils/lazyForEachDataSource";
import { LocalData } from "../utils/localData";
import { PixabayImage, PixabayParams } from "../utils/types";
import json from "@ohos.util.json";

@ObservedV2
export class HomeViewModel {
  /**
   * 携带的参数
   */
  params: PixabayParams = {
    q: LocalData.CategoryData[0].value,
    page: 1,
    safesearch: true,
    per_page: 20,
  }
  /**
   * 瀑布流图片数据集合
   */
  @Trace
  imgList: LazyForEachDataSource<PixabayImage> = new LazyForEachDataSource

  /**
   * 获得瀑布流图片
   */
  async getList() {
    try {
      const res = await getPhotoList(this.params)
      if (res) {
        this.imgList.setArray(res.hits)
      }
    } catch (e) {
      console.log('瀑布流图片', json.stringify(e, null, 2))
    }
  }

  /**
   * 触底加载更多方法
   */
  async onGetListEnd() {
    this.params.page!++
    const res = await getPhotoList(this.params)
    if (res && res.hits.length > 0) {
      this.imgList.pushDataArr(res.hits)
    } else {
      this.params.page!--
    }
  }
}

如何获取资料

获取资料的途径,可以关注我们 官网的公众号 青蓝逐码 ,输入 项目名称 《自然壁纸》 即可获得以上资料。

图像

为什么需要关注公众号

如果我们的资源,网友连关注公众号的欲望都没有,说明我们的这个资料和资源也没有什么太大价值,那么不要也罢,可以让用户付出一些成本的,才是能证明有真正价值的东西。

关于我们

关于青蓝逐码组织

如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯甚至你想要做出一款属于自己的应用!欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。

图像


更多关于HarmonyOS 鸿蒙Next自然壁纸实战教程-首页的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next自然壁纸实现主要通过ArkUI框架完成。首页开发需使用ets文件,核心代码结构如下:

  1. 创建PageAbility作为入口
export default class WallpaperAbility extends Ability {
    onCreate() {
        super.onCreate();
        // 初始化壁纸服务
    }
}
  1. 首页UI使用自定义组件
@Component
struct HomePage {
    [@State](/user/State) currentWallpaper: Resource = $r('app.media.default_wallpaper')

    build() {
        Column() {
            WallpaperPreview({src: this.currentWallpaper})
            WallpaperCategory()
        }
    }
}
  1. 壁纸预览组件实现
@Component
struct WallpaperPreview {
    @Link src: Resource

    build() {
        Image(this.src)
            .width('100%')
            .height(360)
    }
}

关键点:

  • 使用Resource类型加载本地媒体资源
  • @State管理组件状态
  • 组件化开发模式

更多关于HarmonyOS 鸿蒙Next自然壁纸实战教程-首页的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这是一个非常详细的HarmonyOS Next自然壁纸应用开发教程,涵盖了首页的主要功能实现。以下是对关键技术的简要分析:

  1. 容器结构:
  • 使用AtomicServiceNavigation实现元服务tab导航结构
  • 通过AtomicServiceTabs管理底部导航栏
  • 示例代码展示了如何配置多个tab页和导航内容
  1. 沉浸式实现:
  • 封装了FullScreenHelper工具类处理沉浸式
  • 在EntryAbility中调用setFullScreen方法
  • 获取安全区域高度并适配状态栏
  1. 首页组件:
  • 搜索框使用Swiper实现文字轮播效果
  • 轮播图使用Swiper组件,配置自动播放和指示器
  • 分类区域采用横向Scroll布局
  • 精选主题使用Grid网格布局
  1. 壁纸列表:
  • 使用WaterFlow实现瀑布流布局
  • 结合LazyForEach实现懒加载
  • 封装HomeViewModel处理数据获取和分页
  1. 代码结构:
  • 将数据源封装在LocalData中
  • 组件化开发,拆分独立视图组件
  • 使用ObservedV2和Trace实现数据响应式

这个教程提供了完整的实现思路和代码示例,适合想要学习HarmonyOS应用开发的开发者参考。特别是对复杂UI布局、数据管理和性能优化有很好的示范作用。

回到顶部