鸿蒙(HarmonyOS)应用开发——健康生活应用(3)

发现页与个人信息页的UI设计:打造用户中心化的交互体验

一、引言

在移动应用设计中,用户体验是至关重要的。本文将详细介绍如何设计发现页和个人信息页的UI,包括使用tabs组件和list遍历,以及集成自定义弹窗和页面跳转功能。

二、发现页UI设计

1、设计目标

  • 提供清晰的导航和直观的用户体验。
  • 实现响应式设计,适配不同设备和屏幕尺寸。

2、设计实现

使用两层嵌套的tabs组件,通过list遍历定义的数组,实现内容的动态分布。

三、信息页UI设计

1、设计目标

  • 展示用户的个人信息,提供便捷的编辑功能。
  • 集成点击事件,实现页面跳转和信息更新。

2、设计实现

采用list遍历定义的数组,设置个人头像的点击事件,实现页面跳转。

四、修改个人信息页面

1、设计目标

  • 提供一个直观的界面,让用户轻松修改个人信息。
  • 集成数据库操作,实现信息的持久化存储。

2、设计实现

根据预设布局排列组件,每个文本项都有点击事件。例如,在昵称修改处,使用自定义组件弹出弹窗,允许用户输入新的昵称,并更新数据库中的数据。

五、结构一览

67253416c4f6481f9c8859c89a0452ec.png22ce83fe05814b9db97f4101e6b780aa.pngafeec60e9b184900bb94afe48c87174a.png

六、技术实现

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设计。这不仅增强了用户的交互体验,还提供了清晰的信息架构和直观的操作流程。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值