发现页与个人信息页的UI设计:打造用户中心化的交互体验
一、引言
在移动应用设计中,用户体验是至关重要的。本文将详细介绍如何设计发现页和个人信息页的UI,包括使用tabs组件和list遍历,以及集成自定义弹窗和页面跳转功能。
二、发现页UI设计
1、设计目标
- 提供清晰的导航和直观的用户体验。
- 实现响应式设计,适配不同设备和屏幕尺寸。
2、设计实现
使用两层嵌套的tabs组件,通过list遍历定义的数组,实现内容的动态分布。
三、信息页UI设计
1、设计目标
- 展示用户的个人信息,提供便捷的编辑功能。
- 集成点击事件,实现页面跳转和信息更新。
2、设计实现
采用list遍历定义的数组,设置个人头像的点击事件,实现页面跳转。
四、修改个人信息页面
1、设计目标
- 提供一个直观的界面,让用户轻松修改个人信息。
- 集成数据库操作,实现信息的持久化存储。
2、设计实现
根据预设布局排列组件,每个文本项都有点击事件。例如,在昵称修改处,使用自定义组件弹出弹窗,允许用户输入新的昵称,并更新数据库中的数据。
五、结构一览
六、技术实现
1、使用Tabs和List组件
通过tabs和list组件,实现页面内容的动态展示和数据遍历。
import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordService from '../../service/RecordService'
import GroupInfo from '../../viewmodel/GroupInfo'
import RecordType from '../../viewmodel/RecordType'
import RecordVO from '../../viewmodel/RecordVO'
import ItemData from '../item/ItemData'
import SearchHeader from './SearchHeader'
@Component
export default struct FindPage{
Item: ItemData[] = [
new ItemData('健康打卡vlog Day1...|', $r('app.media.bb')),
new ItemData('健康打卡vlog Day2...|', $r('app.media.tt2')),
new ItemData('健康打卡vlog Day3...|', $r('app.media.back3')),
new ItemData('健康打卡vlog Day4...|', $r('app.media.tt1')),
new ItemData('健康打卡vlog Day1...|', $r('app.media.bb')),
new ItemData('健康打卡vlog Day1...|', $r('app.media.bb')),
new ItemData('健康打卡vlog Day1...|', $r('app.media.bb')),
new ItemData('健康打卡vlog Day1...|', $r('app.media.bb')),
new ItemData('健康打卡vlog Day1...|', $r('app.media.bb'))
];
@Builder TabBarBuilder(title: ResourceStr, index: number) {
Column({ space: CommonConstants.SPACE_8 }) {
Text(title)
.fontSize(20)
.margin({top:10,bottom:-10})
}
}
@Builder TabBarBuilder1(title: ResourceStr, index: number) {
Column({ space: CommonConstants.SPACE_8 }) {
Text(title)
.fontSize(15)
.fontColor('#cccccc')
}
}
build() {
Column() {
// 1.头部搜索栏
// SearchHeader()
Tabs(){
TabContent(){
Column() {
Tabs() {
TabContent() {
Column(){
List() {
ForEach(this.Item, (item) => {
ListItem() {
Column() {
Image(item.img)
.width('100%')
.height(200)
.borderRadius(30)
Text(item.title)
.fontSize(25)
.fontColor('#cccccc')
}
.margin({bottom:40})
}
})
}
}
.height(700)
}.tabBar(this.TabBarBuilder1('推荐', 1))
TabContent() {
Text('页面2')
}.tabBar(this.TabBarBuilder1('视频', 2))
TabContent() {
Text('页面3')
}.tabBar(this.TabBarBuilder1('直播', 3))
TabContent() {
Text('页面4')
}.tabBar(this.TabBarBuilder1('音乐', 4))
TabContent() {
Text('页面5')
}.tabBar(this.TabBarBuilder1('生活', 5))
}
.width('100%')
}
}
.tabBar(this.TabBarBuilder('发现', 1))
TabContent(){
Column() {
Row() {
Image($r('app.media.xiongmao'))
.width('70%')
.height(79)
}
.height(100)
Row() {
Text('没有任何动态')
.fontSize(20)
.fontWeight(10)
}
Row() {
Text('关注她/他,查看他们的最新消息')
.fontSize(15)
.fontWeight(10)
.fontColor('#cccccc')
}
}
}.tabBar(this.TabBarBuilder('动态', 2))
TabContent(){
Text('开启定位,查看附近的人...')
.fontSize(20)
.fontWeight(20)
}.tabBar(this.TabBarBuilder('附近', 3))
}
.width('100%')
}
}
}
2、自定义弹窗
使用@CustomDialog装饰器声明弹窗组件,实现自定义弹窗的展示和事件处理。
@CustomDialog
struct CustomDialogExample {
@Link textValue: string
@Link inputValue: string
@Link name:string
controller: CustomDialogController
cancel: () => void
confirm: () => void
build() {
Column() {
Text('更改名称').fontSize(20).margin({ top: 10, bottom: 10 })
TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
.onChange((value: string) => {
this.name = value
})
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('取消')
.onClick(() => {
this.controller.close()
this.cancel()
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button('确认')
.onClick(() => {
this.inputValue = this.textValue
this.controller.close()
this.confirm()
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
}
}
}
3、页面跳转
利用@ohos.router实现页面间的跳转。
Image($r('app.media.pp'))
.width('80%')
.height('80%')
.margin({top:10})
.borderRadius(50)
.onClick(()=>{
router.pushUrl({
url: `pages/IdentifyPage`,
}, router.RouterMode.Single, (err) => {
if (err) {
console.error(`路由失败,errCode: ${err.message}`);
}
});
})
七、代码示例
// 引入所需的组件和工具类 import router from '@ohos.router'; // ...引入其他所需组件和工具类 @Entry @Component struct IdentifyPage { // 状态和属性定义 @State nickname: string = '默认昵称'; // ...其他状态和属性 // 构建方法 build() { Column() { // 个人信息展示和编辑 Row() { Text('昵称') .fontSize(25) .fontColor('#ff676666') Blank() Text(this.nickname) .fontSize(25) .margin({right:20}) .onClick(() => { // 弹出昵称编辑弹窗 }) } // ...其他UI组件和样式设置 } } } // 自定义弹窗示例 @CustomDialog struct CustomDialogExample { // 弹窗构建逻辑 build() { Column() { // 弹窗内容 } } } // 其他页面和组件实现...
import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordService from '../../service/RecordService'
import GroupInfo from '../../viewmodel/GroupInfo'
import RecordType from '../../viewmodel/RecordType'
import RecordVO from '../../viewmodel/RecordVO'
import ItemData from '../item/ItemData'
@Component
export default struct MyHomePage{
Item: ItemData[] = [
new ItemData('推送通知', $r('app.media.message')),
new ItemData('数据管理', $r('app.media.record')),
new ItemData('菜单设置', $r('app.media.message')),
new ItemData('关于', $r('app.media.about')),
new ItemData('清除缓存', $r('app.media.target')),
new ItemData('隐私协议', $r('app.media.privacy')),
];
build() {
Column(){
Row(){
Text('我的')
.fontSize(35)
.fontWeight(10)
.margin({left:-160})
}
Row(){
Column(){
Image($r('app.media.pp'))
.width('80%')
.height('80%')
.margin({top:10})
.borderRadius(50)
.onClick(()=>{
router.pushUrl({
url: `pages/IdentifyPage`,
}, router.RouterMode.Single, (err) => {
if (err) {
console.error(`路由失败,errCode: ${err.message}`);
}
});
})
}
.width('40%')
.height(120)
// .margin({left:10})
Column(){
//个人信息:
Row(){
Text('lalala')
.fontSize(20)
.fontWeight(20)
.margin({top:20,bottom:15})
}
Row(){
Text('www.lala.com')
.fontSize(15)
}
}
.width('55%')
.height(100)
.margin({left:10})
}
.backgroundColor('#f7f7f7')
.width('90%')
.height(150)
.borderRadius(20)
.margin({top:10})
//list集合:
Row() {
List() {
ForEach(this.Item, (item) => {
ListItem() {
Row({ space: 3 }) {
Image(item.img)
.width(30)
.height(30)
Text(item.title)
.fontSize(20)
.margin({left:10})
}
.width('100%')
.height(60)
.margin({left:2})
}
})
}
}
.backgroundColor('#f7f7f7')
.width('100%')
.height(70)
.backgroundColor('#f7f7f7')
.width('90%')
.height(350)
.borderRadius(20)
.margin({top:30})
Button('退出登录')
.width('60%')
.height(40)
.margin({top:50})
.backgroundColor(Color.Orange)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.index_page_background'))
}
// navigateToNewItemPage() {
// const url = `pages/IdentifyPage`; // 假设NewItemPage是你的目标页面
//
// router.pushUrl({
// url: url,
// }, router.RouterMode.Single, (err) => {
// if (err) {
// console.error(`路由失败,errCode: ${err.message}`);
// }
// });
// }
}
修改页面:
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import DateUtil from '../common/utils/DateUtil';
import DatePickDialog from '../view/record/DatePickDialog';
@Entry
@Component
struct IdentifyPage{
@StorageProp('selectedDate') selectedDate: number = DateUtil.beginTimeOfDay(new Date())
@State img1:Resource=$r('app.media.pp')
@State selectedGenderIndex: number = 0; // 假设初始选中项为0
@State select: number = 0; // 假设初始选中项为0
@State nickname: string = '睡觉'; // 假设初始选中项为0
@State gender: string = '女'; // 假设初始选中项为0
@State province: string = '山东'; // 假设初始选中项为0
@State date: string = new Date().getFullYear().toString()+'-'+(new Date().getMonth() + 1).toString().padStart(2, '0')+'-'
+ new Date().getDate().toString().padStart(2, '0') ; // 假设初始选中项为0
@State genders: string[] = ['男', '女', '其他']; // 性别选项@State gentle1:string='男'
@State provinces: string[] = [
"黑龙江", "吉林", "辽宁", "河北", "山西", "陕西", "山东",
"河南", "江苏", "安徽", "浙江", "福建", "江西", "湖南",
"湖北", "广东", "海南", "四川", "贵州", "云南", "甘肃",
"青海", "内蒙古", "广西", "西藏", "宁夏", "新疆", "北京", "天津", "上海", "重庆",
"香港", "澳门"
];
@State textValue: string = ''
@State inputValue: string = 'click me'
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
cancel: this.onCancel,
confirm: this.onAccept,
textValue: $textValue,
inputValue: $inputValue,
name:this.nickname
}),
cancel: this.existApp,
autoCancel: true,
alignment: DialogAlignment.Default,
offset: { dx: 0, dy: -20 },
gridCount: 4,
customStyle: false
})
controller: CustomDialogController = new CustomDialogController({
builder: DatePickDialog({selectedDate: new Date(this.selectedDate)})
})
aboutToDisappear() {
delete this.dialogController,
this.dialogController = undefined
}
onCancel() {
console.info('Callback when the first button is clicked')
}
onAccept() {
console.info('Callback when the second button is clicked')
}
existApp() {
console.info('Click the callback in the blank area')
}
build() {
Column() {
this.Header();
Column() {
Row() {
Text('个人资料')
.fontColor("#cccccc")
.fontSize(18)
}
.width('90%')
.margin({ top: 20 })
Row() {
Row() {
Text('头像')
.fontSize(25)
.fontColor('#ff676666')
Blank();
Image(this.img1)
.width(80)
.height('80%')
.onClick(() => {
// this.showTaskInfoDialog();
})
}
.width('90%')
.margin({ left: 20 })
}
.width('90%')
.height(100)
.backgroundColor('#fffdf4ea')
.borderRadius(30)
.margin({ top: 10, left: 10 })
Row() {
Row() {
Text('昵称')
.fontSize(25)
.fontColor('#ff676666')
Blank()
Text(this.nickname)
.fontSize(25)
.margin({right:20})
.onClick(()=>{
this.dialogController.open();
})
}
.width('90%')
.margin({ left: 20 })
}
.width('90%')
.height(100)
.backgroundColor('#fffdf4ea')
.borderRadius(30)
.margin({ top: 10, left: 10 })
Row() {
Row() {
Text('性别')
.fontSize(25)
.fontColor('#ff676666')
Blank()
Text(this.gender)
.fontSize(25)
.margin({right:20})
.onClick(()=>{
TextPickerDialog.show({
range: this.genders, // 设置文本选择器的选择范围
selected: this.selectedGenderIndex, // 设置初始选中项的索引值。
onAccept: (value: TextPickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调。
// 设置select为按下确定按钮时候的选中项index,这样当弹窗再次弹出时显示选中的是上一次确定的选项
this.select = value.index;
this.gender=this.genders[value.index]
console.info("TextPickerDialog:onAccept()" + JSON.stringify(value));
},
onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调。
console.info("TextPickerDialog:onCancel()");
},
onChange: (value: TextPickerResult) => { // 滑动弹窗中的选择器使当前选中项改变时触发该回调。
console.info("TextPickerDialog:onChange()" + JSON.stringify(value));
}
})
})
}
.width('90%')
.margin({ left: 20 })
}
.width('90%')
.height(100)
.backgroundColor('#fffdf4ea')
.borderRadius(30)
.margin({ top: 10, left: 10 })
Row() {
Row() {
Text('生日')
.fontSize(25)
.fontColor('#ff676666')
Blank()
Text(DateUtil.formatDate(this.selectedDate))
.fontSize(20)
.margin({ top: 10, left: 10 })
.onClick(()=>{
this.controller.open();
})
}
.width('90%')
.margin({ left: 20 })
}
.width('90%')
.height(100)
.backgroundColor('#fffdf4ea')
.borderRadius(30)
.margin({ top: 10, left: 10 })
Row() {
Text('其他信息')
.fontColor("#cccccc")
.fontSize(18)
}
.width('90%')
.margin({ top: 20 })
Row() {
Row() {
Text('ip属地')
.fontSize(25)
.fontColor('#ff676666')
Blank()
Text(this.province)
.fontSize(20)
.margin({right:20})
.onClick(()=>{
TextPickerDialog.show({
range: this.provinces, // 设置文本选择器的选择范围
selected: this.selectedGenderIndex, // 设置初始选中项的索引值。
onAccept: (value: TextPickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调。
// 设置select为按下确定按钮时候的选中项index,这样当弹窗再次弹出时显示选中的是上一次确定的选项
this.select = value.index;
this.province=this.provinces[value.index]
console.info("TextPickerDialog:onAccept()" + JSON.stringify(value));
},
onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调。
console.info("TextPickerDialog:onCancel()");
},
onChange: (value: TextPickerResult) => { // 滑动弹窗中的选择器使当前选中项改变时触发该回调。
console.info("TextPickerDialog:onChange()" + JSON.stringify(value));
}
})
})
}
.width('90%')
.margin({ left: 20 })
}
.width('90%')
.height(100)
.backgroundColor('#fffdf4ea')
.borderRadius(30)
.margin({ top: 10, left: 10 })
}
}
}
@Builder Header() {
Row() {
Image($r('app.media.ic_public_back'))
.width(30)
.margin({right:100})
.onClick(() => router.back())
// Blank()
Text('编辑资料').fontSize(30).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
.height(32)
.margin({top:20,left:-5})
}
}
@CustomDialog
struct CustomDialogExample {
@Link textValue: string
@Link inputValue: string
@Link name:string
controller: CustomDialogController
cancel: () => void
confirm: () => void
build() {
Column() {
Text('更改名称').fontSize(20).margin({ top: 10, bottom: 10 })
TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
.onChange((value: string) => {
this.name = value
})
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('取消')
.onClick(() => {
this.controller.close()
this.cancel()
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button('确认')
.onClick(() => {
this.inputValue = this.textValue
this.controller.close()
this.confirm()
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
}
}
}
八、结论
通过上述步骤和代码示例,我们实现了一个功能丰富、用户友好的发现页和个人信息页UI设计。这不仅增强了用户的交互体验,还提供了清晰的信息架构和直观的操作流程。