当前位置:   article > 正文

【HarmonyOS实战开发】ArkTS仿写微信-通讯录_基于鸿蒙系统开发的通讯录软件

基于鸿蒙系统开发的通讯录软件

技术点拆分

1、List列表;

2、AlphabetIndexer侧栏索引联动;

3、swipeAction侧滑删除;

4、Badge消息提示;

效果展示

图片

 

图片

List列表

通讯录中数据不是很多,因此List使用ForEach即可,使用ListItemGroup对列表进行分组,然后设置下划线等样式。

  1. private listScroller: Scroller = new Scroller();
  2. List({ scroller: this.listScroller }){
  3. ListItemGroup() {
  4. ListItem(){
  5. ContactsRow({imagePath:'/images/contact_0.png',titleString:'新的朋友'})
  6. }
  7. ListItem(){
  8. ContactsRow({imagePath:'/images/contact_1.png',titleString:'仅聊天的朋友'})
  9. }
  10. ListItem(){
  11. ContactsRow({imagePath:'/images/contact_2.png',titleString:'群聊'})
  12. }
  13. ListItem(){
  14. ContactsRow({imagePath:'/images/contact_3.png',titleString:'标签'})
  15. }
  16. ListItem(){
  17. ContactsRow({imagePath:'/images/contact_4.png',titleString:'公众号'})
  18. }
  19. }
  20. .padding({left:15,right:15})
  21. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
  22. ListItemGroup() {
  23. ListItem(){
  24. Column() {
  25. Column() {
  26. Text('我的企业及企业联系人').fontSize(14).fontColor(Color.Gray)
  27. }
  28. .backgroundColor('#E6E6E6')
  29. .width('100%')
  30. .height(36)
  31. .padding({
  32. left: 15
  33. })
  34. .justifyContent(FlexAlign.Center)
  35. .alignItems(HorizontalAlign.Start)
  36. ContactsRow({
  37. imagePath:'/images/contact_5.png',
  38. titleString:'企业微信联系人'
  39. }).padding({left:15,right:15})
  40. }
  41. }
  42. ListItem(){
  43. ContactsRow({
  44. imagePath:'/images/contact_5.png',
  45. titleString:'企业微信通知',
  46. count: 36
  47. }).padding({left:15,right:15})
  48. }
  49. }
  50. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
  51. }

AlphabetIndexer侧栏索引联动

图片

1、通过onSelect获取索引值,然后通过scrollToIndex(index)滚动到对应位置;

2、list中通过onScrollIndex,判断索引值;

  1. onScrollIndex((firstIndex: number) => {
  2. this.selectedIndex = firstIndex
  3. console.log('**************', firstIndex);
  4. if(firstIndex > 2) {
  5. this.selectedIndex = this.letters.findIndex(i => i === this.fList[firstIndex - 3]['letter'])
  6. }
  7. // 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
  8. })

3、配置索引侧栏

  1. @State letters: string[] = ['→', '※', 'A','B','C','D','E','F','H','I','G','K','L','M','N', 'O','P','Q','R','S','T','U','V','W','X','Y','Z','#']
  2. private listScroller: Scroller = new Scroller();
  3. @State selectedIndex: number = 0;
  4. build() {
  5. AlphabetIndexer({ arrayValue: this.letters, selected: 0 })
  6. .color(Color.Black)
  7. .selectedColor(0xFFFFFF) // 选中项文本颜色
  8. .popupColor(0xFFFAF0) // 弹出框文本颜色
  9. .selectedBackgroundColor('rgb(1,196,194)') // 选中项背景颜色
  10. .popupBackground(0xD2B48C) // 弹出框背景颜色
  11. .usingPopup(true) // 是否显示弹出框
  12. .selectedFont({ size: 13, weight: FontWeight.Bolder }) // 选中项字体样式
  13. .popupFont({ size: 30, weight: FontWeight.Bolder }) // 弹出框内容的字体样式
  14. .itemSize(20) // 每一项的尺寸大小
  15. .alignStyle(IndexerAlign.Left) // 弹出框在索引条左侧弹出
  16. .onSelect((index: number) => {
  17. console.info(this.letters[index] + ' Selected!', index)
  18. this.listScroller.scrollToIndex(index)
  19. })
  20. .onPopupSelect((index: number) => {
  21. console.info('onPopupSelected:' + index)
  22. })
  23. .selected(this.selectedIndex)
  24. }

List实现swipeAction侧滑删除

通过ListItem上面的swipeAction属性进行配置

  1. @Builder itemEnd(index: number) {
  2. // 侧滑后尾端出现的组件
  3. Button({ type: ButtonType.Circle }) {
  4. Image($r('app.media.ic_public_delete_filled'))
  5. .width(20)
  6. .height(20)
  7. .fillColor(Color.Red)
  8. }
  9. .backgroundColor(Color.Transparent)
  10. .padding(12)
  11. .margin({
  12. right: 18
  13. })
  14. .onClick(() => {
  15. // 操作数据/请求后台删除数据;
  16. })
  17. }
  18. build() {
  19. List() {
  20. ForEach(this.list, (item, index) => {
  21. ListItem(){
  22. ContactsRow({imagePath: $rawfile('start1.jpg'),titleString:'老婆'})
  23. .padding({left:15,right:15})
  24. }
  25. .swipeAction({ end: this.itemEnd.bind(this, index) })
  26. })
  27. }
  28. }

图标实现Badge消息提示

图片

通过Badge组件实现,或者通过Stack配合postion定位自定义实现

  1. @Component
  2. struct ContactsRow {
  3. imagePath:string | Resource
  4. titleString:string
  5. count?: number
  6. build(){
  7. Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
  8. Badge({
  9. count: this.count,
  10. position: BadgePosition.RightTop,
  11. style: { badgeSize: 16, badgeColor: '#FA2A2D' }
  12. }) {
  13. Image(this.imagePath)
  14. .width(41)
  15. .height(41)
  16. .borderRadius(3)
  17. }
  18. Text(this.titleString)
  19. .fontSize(18)
  20. .margin({left:10})
  21. }
  22. .height(55)
  23. }
  24. }

通讯录完整代码

  1. import router from '@ohos.router'
  2. @Entry
  3. @Component
  4. export default struct Contacts {
  5. @State message: string = 'Hello World'
  6. @State fList: object[] = [
  7. {'letter':"A",'data':[
  8. {'name':'A-老张','avtar': $rawfile('icon4.jpg')},
  9. {'name':'A-老张1','avtar':$rawfile('icon4.jpg')},
  10. {'name':'A-老张2','avtar': $rawfile('icon4.jpg')},
  11. {'name':'A-老张3','avtar': $rawfile('icon4.jpg')},
  12. {'name':'A-老张4','avtar': $rawfile('icon4.jpg')},
  13. {'name':'A-老张5','avtar': $rawfile('icon4.jpg')},
  14. ]},
  15. {'letter':"L",'data':[
  16. {'name':'老张','avtar':'/images/icon2.png'},
  17. {'name':'老张1','avtar':'/images/icon2.png'},
  18. {'name':'老张2','avtar':'/images/icon2.png'},
  19. {'name':'老张3','avtar':'/images/icon2.png'},
  20. {'name':'老张4','avtar':'/images/icon2.png'},
  21. {'name':'老张5','avtar':'/images/icon2.png'},
  22. ]},
  23. {'letter':"W",'data':[
  24. {'name':'王富贵','avtar':'/images/icon3.png'},
  25. {'name':'王富贵1','avtar':'/images/icon3.png'},
  26. {'name':'王富贵2','avtar':'/images/icon3.png'},
  27. {'name':'王富贵3','avtar':'/images/icon3.png'},
  28. {'name':'王富贵4','avtar':'/images/icon3.png'},
  29. {'name':'王富贵5','avtar':'/images/icon3.png'},
  30. {'name':'王富贵6','avtar':'/images/icon3.png'},
  31. ]},
  32. {'letter':"Z",'data':[
  33. {'name':'猪头','avtar':'/images/icon1.png'},
  34. {'name':'猪头1','avtar':'/images/icon1.png'},
  35. {'name':'猪头2','avtar':'/images/icon1.png'},
  36. {'name':'猪头3','avtar':'/images/icon1.png'},
  37. {'name':'猪头4','avtar':'/images/icon1.png'},
  38. {'name':'猪头5','avtar':'/images/icon1.png'},
  39. {'name':'猪头6','avtar':'/images/icon1.png'},
  40. {'name':'猪头7','avtar':'/images/icon1.png'},
  41. {'name':'猪头8','avtar':'/images/icon1.png'},
  42. {'name':'猪头9','avtar':'/images/icon1.png'},
  43. {'name':'猪头10','avtar':'/images/icon1.png'},
  44. {'name':'猪头11','avtar':'/images/icon1.png'},
  45. {'name':'猪头12','avtar':'/images/icon1.png'},
  46. ]},
  47. ]
  48. @State letters: string[] = ['→', '※', 'A','B','C','D','E','F','H','I','G','K','L','M','N', 'O','P','Q','R','S','T','U','V','W','X','Y','Z','#']
  49. @Builder NavigationMenus() { // CustomBuilder类型的菜单栏
  50. Row() {
  51. Image('/images/contact_add.png')
  52. .size({ width: 24, height: 24 })
  53. .margin({ left: 5, top: 16 })
  54. .onClick(()=>{
  55. })
  56. }.justifyContent(FlexAlign.End)
  57. }
  58. @Builder NavigationTitle() {
  59. Row(){
  60. Text("通讯录")
  61. .width('100')
  62. }
  63. .width('100%')
  64. .justifyContent(FlexAlign.Center)
  65. }
  66. @Builder itemHead(text:string) {
  67. Text(text)
  68. .fontSize(14)
  69. .backgroundColor('#E6E6E6')
  70. .width("120%")
  71. .padding({
  72. left: 15,
  73. top: 8,
  74. bottom: 8
  75. })
  76. .fontColor(Color.Gray)
  77. .margin({
  78. left: -15,
  79. })
  80. }
  81. private listScroller: Scroller = new Scroller();
  82. @State selectedIndex: number = 0;
  83. @Builder itemEnd(index: number) {
  84. // 侧滑后尾端出现的组件
  85. Button({ type: ButtonType.Circle }) {
  86. Image($r('app.media.ic_public_delete_filled'))
  87. .width(20)
  88. .height(20)
  89. .fillColor(Color.Red)
  90. }
  91. .backgroundColor(Color.Transparent)
  92. .padding(12)
  93. .margin({
  94. right: 18
  95. })
  96. .onClick(() => {
  97. // 操作数据/请求后台删除数据;
  98. })
  99. }
  100. build() {
  101. Column() {
  102. Navigation(){
  103. Stack({ alignContent: Alignment.End }){
  104. Column(){
  105. List({ scroller: this.listScroller }){
  106. ListItemGroup() {
  107. ListItem(){
  108. ContactsRow({imagePath:'/images/contact_0.png',titleString:'新的朋友'})
  109. }
  110. ListItem(){
  111. ContactsRow({imagePath:'/images/contact_1.png',titleString:'仅聊天的朋友'})
  112. }
  113. ListItem(){
  114. ContactsRow({imagePath:'/images/contact_2.png',titleString:'群聊'})
  115. }
  116. ListItem(){
  117. ContactsRow({imagePath:'/images/contact_3.png',titleString:'标签'})
  118. }
  119. ListItem(){
  120. ContactsRow({imagePath:'/images/contact_4.png',titleString:'公众号'})
  121. }
  122. }
  123. .padding({left:15,right:15})
  124. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
  125. ListItemGroup() {
  126. ListItem(){
  127. Column() {
  128. Column() {
  129. Text('我的企业及企业联系人').fontSize(14).fontColor(Color.Gray)
  130. }
  131. .backgroundColor('#E6E6E6')
  132. .width('100%')
  133. .height(36)
  134. .padding({
  135. left: 15
  136. })
  137. .justifyContent(FlexAlign.Center)
  138. .alignItems(HorizontalAlign.Start)
  139. ContactsRow({
  140. imagePath:'/images/contact_5.png',
  141. titleString:'企业微信联系人'
  142. }).padding({left:15,right:15})
  143. }
  144. }
  145. ListItem(){
  146. ContactsRow({
  147. imagePath:'/images/contact_5.png',
  148. titleString:'企业微信通知',
  149. count: 36
  150. }).padding({left:15,right:15})
  151. }
  152. }
  153. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
  154. ListItemGroup() {
  155. ListItem(){
  156. Column() {
  157. Column() {
  158. Text('星标朋友').fontSize(14).fontColor(Color.Gray)
  159. }
  160. .backgroundColor('#E6E6E6')
  161. .width('100%')
  162. .height(36)
  163. .padding({
  164. left: 15
  165. })
  166. .justifyContent(FlexAlign.Center)
  167. .alignItems(HorizontalAlign.Start)
  168. }
  169. }
  170. ListItem(){
  171. ContactsRow({imagePath: $rawfile('start1.jpg'),titleString:'老婆'})
  172. .padding({left:15,right:15})
  173. }
  174. .swipeAction({ end: this.itemEnd.bind(this, 0) })
  175. ListItem(){
  176. ContactsRow({imagePath:$rawfile('start2.jpg'),titleString:'大甜'})
  177. .padding({left:15,right:15})
  178. }.swipeAction({ end: this.itemEnd.bind(this, 1) })
  179. ListItem(){
  180. ContactsRow({imagePath:$rawfile('start3.jpg'),titleString:'大甜'})
  181. .padding({left:15,right:15})
  182. }.swipeAction({ end: this.itemEnd.bind(this, 2) })
  183. }
  184. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
  185. ForEach(this.fList,(item:object,index:number)=>{
  186. ListItemGroup({header:this.itemHead(item['letter'])}){
  187. ForEach(item['data'],(p:object,i:number)=>{
  188. ListItem(){
  189. Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
  190. Image(p['avtar'])
  191. .width(45)
  192. .height(45)
  193. .borderRadius(5)
  194. Text(p['name'])
  195. .fontSize(18)
  196. .margin({left:10})
  197. }
  198. .height(55)
  199. }
  200. .onClick(()=>{
  201. router.pushUrl({url:'pages/Contact/PersonalPage',params: {
  202. person: p
  203. }})
  204. })
  205. })
  206. }
  207. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
  208. .padding({left:15,right:15})
  209. .margin({top:10})
  210. })
  211. }
  212. .divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 }) // 每行之间的分界线
  213. .margin({top:17})
  214. .backgroundColor(Color.White)
  215. .height('100%')
  216. .onScrollIndex((firstIndex: number) => {
  217. this.selectedIndex = firstIndex
  218. console.log('**************', firstIndex);
  219. if(firstIndex > 2) {
  220. this.selectedIndex = this.letters.findIndex(i => i === this.fList[firstIndex - 3]['letter'])
  221. }
  222. // 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
  223. })
  224. }
  225. AlphabetIndexer({ arrayValue: this.letters, selected: 0 })
  226. .color(Color.Black)
  227. .selectedColor(0xFFFFFF) // 选中项文本颜色
  228. .popupColor(0xFFFAF0) // 弹出框文本颜色
  229. .selectedBackgroundColor('rgb(1,196,194)') // 选中项背景颜色
  230. .popupBackground(0xD2B48C) // 弹出框背景颜色
  231. .usingPopup(true) // 是否显示弹出框
  232. .selectedFont({ size: 13, weight: FontWeight.Bolder }) // 选中项字体样式
  233. .popupFont({ size: 30, weight: FontWeight.Bolder }) // 弹出框内容的字体样式
  234. .itemSize(20) // 每一项的尺寸大小
  235. .alignStyle(IndexerAlign.Left) // 弹出框在索引条左侧弹出
  236. .onSelect((index: number) => {
  237. console.info(this.letters[index] + ' Selected!', index)
  238. this.listScroller.scrollToIndex(index)
  239. })
  240. .onPopupSelect((index: number) => {
  241. console.info('onPopupSelected:' + index)
  242. })
  243. .selected(this.selectedIndex)
  244. }
  245. .width('100%')
  246. .height('100%')
  247. }
  248. .title(this.NavigationTitle())
  249. .mode(NavigationMode.Stack)
  250. .titleMode(NavigationTitleMode.Mini)
  251. .hideBackButton(true)
  252. .menus(this.NavigationMenus())
  253. .size({ width: '100%', height: '100%' })
  254. .backgroundColor('rgb(237,237,237)')
  255. }
  256. // .bindMenu(this.MyMenu())
  257. .width('100%')
  258. .height('95%')
  259. }
  260. }
  261. @Component
  262. struct ContactsRow {
  263. imagePath:string | Resource
  264. titleString:string
  265. count?: number
  266. build(){
  267. Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
  268. Badge({
  269. count: this.count,
  270. position: BadgePosition.RightTop,
  271. style: { badgeSize: 16, badgeColor: '#FA2A2D' }
  272. }) {
  273. Image(this.imagePath)
  274. .width(41)
  275. .height(41)
  276. .borderRadius(3)
  277. }
  278. Text(this.titleString)
  279. .fontSize(18)
  280. .margin({left:10})
  281. }
  282. .height(55)
  283. }
  284. }

扩展:LazyForEach,长列表处理

循环渲染适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用数据懒加载(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。

当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,只在懒加载LazyForEach中生效。

具体参考文档:https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/quick-start/arkts-rendering-control-lazyforeach.md/

  1. List() {
  2. LazyForEach(this.list, item => {
  3. ListItem() {
  4. ...
  5. }
  6. })
  7. }.cachedCount(3)

最后,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(Harmony NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(Harmony NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

 获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(Harmony NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

 有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/773918
推荐阅读
相关标签
  

闽ICP备14008679号