
05-自然壁纸实战教程-首页 原创
05-自然壁纸实战教程-首页
前言
这一章节需要完成的功能主要有沉浸式、首页搜索框、轮播大图、分类、精选主题、壁纸列表还有 tab容器结构。
tab容器结构
元服务的tab结构需要使用 AtomicServiceNavigation来实现
基本示例
import { AtomicServiceNavigation, MixMode, GradientAlpha, BackgroundTheme } from '@kit.ArkUI';
import { AtomicServiceTabs, TabBarOptions, TabBarPosition } from '@kit.ArkUI';
@Entry
@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,
})
this.navigationContent()
@Builder
navigationContent() {
AtomicServiceTabs({
tabContents: [
() => {
this.tabContentHome()
},
() => {
this.tabContentCategory()
},
() => {
this.tabContentVideo()
},
() => {
this.tabContentMine()
}
],
tabBarOptionsArray: [
new TabBarOptions($r('app.media.index'), '首页', $r('app.color.common__bg'), "#53AA37"),
new TabBarOptions($r('app.media.category'), '分类', $r('app.color.common__bg'), "#53AA37"),
new TabBarOptions($r('app.media.video'), '视频', $r('app.color.common__bg'), "#53AA37"),
new TabBarOptions($r('app.media.mine'), '我的', $r('app.color.common__bg'), "#53AA37"),
],
tabBarPosition: TabBarPosition.BOTTOM,
barBackgroundColor: $r('app.color.white'),
})
.padding({
top: AppStatu.vpTopHeight,
bottom: AppStatu.vpBottomHeight,
})
}
沉浸式
沉浸式的代码已经提前封装起来了 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 })
}
LocalData.PositiveQuotes 提供了基本的数据资源
/**
* 正能量语句数组
*/
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')
)
LocalData.BannerData 负责提供轮播数据
/**
* 精选壁纸轮播数据
*/
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)
LocalData.CategoryData 对应的数据是
/**
* 类型数据
*/
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 })
LocalData.SpecialTopicData 对应的数据是
/**
* 精选专题
*/
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"
}
]
壁纸列表
最后就是壁纸列表了,考虑到壁纸列表存在比较多数据,因此这里使用 WaterFlow 和 LazyForEach 实现页面渲染和分页加载
//壁纸瀑布流
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
})
this.homeViewModel.imgList 来自于自己封装的 获取数据的类 entry/src/main/ets/viewModel/HomeViewModel.ets
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!--
}
}
}
如何获取资料
获取资料的途径,可以关注我们 官网的公众号 青蓝逐码 ,输入 项目名称 《自然壁纸》 即可获得以上资料。
关于我们
如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯,甚至你想要做出一款属于自己的应用!欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。
