Foreword
this article is based on Api13
on the right is the list of letters, and on the left is the list corresponding to the letters. This effect is common in address books, such as WeChat address books, and also common in mobile phone contacts, as shown in the following figure:
the letters in the vertical column on the right can be clicked or slid with gestures, while the list on the left needs to automatically switch the information under the corresponding letters with gestures. How can this effect be realized? I remember when I was working in Android, the letter List on the right was drawn with Canvas, which is convenient to measure the current sliding letters according to gestures. In fact, Hongmeng can also follow this idea, but we can also directly use the List component to do it.
Let's look at the final implementation effect first, which is basically satisfied. The list on the left can absorb the ceiling, the list on the right can slide and click, and the list can be switched in linkage.
The dynamic effects are as follows:
the main contents of this paper are as follows:
1, realize the letter list display
2. Slide and click on the letter list
3. Display the left contact list
4. Realize the linkage between letters and contact list
5, source code
6. Relevant summary
first, to achieve the letter list display
to show the list of letters, we must first have letter Data. We can integrate the letters from A to Z into an array of letters. Here is A simple method. Using the String.fromCharCode method, we can easily traverse the letters and save them one by one. The code is as follows:
for (let i = 65; i <= 90; i++) {
this.letterList.push(String.fromCharCode(i))
}
With the data source, we can use the List component to display. First of all, we need to know that the effect to be achieved is to display the letter List on the right and vertically centered. Therefore, here, we do not need to set the height of the List component to make it Adaptive. On the outer layer, we can use the RelativeContainer relative layout, so that we can use the attribute alignRules to place the position.
Right and centered vertically:
alignRules({
right: { anchor: "__container__", align: HorizontalAlign.End },
center: { anchor: "__container__", align: VerticalAlign.Center },
})
the List is loaded as follows:
List({ space: 5 }) {
ForEach(this.letterList, (item: string, index: number) => {
ListItem() {
Text(item)
.fontColor("#666666")
.fontSize(14)
}
})
}
.width(30)
.backgroundColor("#f2f6f9")
.margin({ right: 10 })
.padding({ top: 10, bottom: 10 })
.borderRadius(30)
.alignListItem(ListItemAlign.Center)
.alignRules({
right: { anchor: "__container__", align: HorizontalAlign.End },
center: { anchor: "__container__", align: VerticalAlign.Center },
})
through the above code, we have realized that the letter list is displayed on the right and vertically centered.
Second, the realization of the letter list sliding and clicking
after the letter list is displayed, we need to implement the following functions:
Each letter can be triggered by clicking.
Each letter can trigger sliding.
When touching its own letters, change its own color and text size.
Touch the current letter and display the current letter in the middle of the screen.
The above functions are the basic settings of the letter list, and the subsequent linkage with the Left List also needs to be driven by the above events. Therefore, these steps are very important.
At the beginning, the idea was to set click and move for each letter component, that is, Text component. Later, it was found that a single setting could not affect other components. Therefore, it was still necessary to set the List component on the outer layer. As for click and slide, gestures could actually be used to handle it.
How to determine the currently touched letter based on the gesture? In fact, it is very simple, because we are A vertical list and only judge the Y coordinate. First, we can obtain the Y coordinate of each letter. When the Y value of the gesture touch is greater than or less than A certain value, it is proved that we touch this letter, for example, the letter B, and the Y coordinate obtained is 20, then the Y coordinate of the touch is greater than the Y coordinate of the letter A and less than the Y coordinate of the letter B, is this the B letter area?
The pseudocode is as follows:
let A字母Y坐标 = 10
let B字母Y坐标 = 20
if(moveY>A字母Y坐标&&moveY<B字母Y坐标){
//B字母
}
step 1, get the Y coordinate of each letter
first define a map collection that stores the positions of all letters:
private letterPositionMap: HashMap<string, number> = new HashMap()
then, store the Y coordinate of each letter, which can be obtained by onAreaChange method. Here, you can directly set each item, that is, ListItem.
.onAreaChange((_: Area, newValue: Area) => {
this.letterPositionMap.set(item, Number(newValue.position.y) + Number(newValue.height))
})
The second step is to implement the onTouch method, listening for gestures to press and move
.onTouch((event) => {
switch (event.type) {
case TouchType.Down://手势按下
case TouchType.Move://手势抬起
this.changeStatus(event.touches[0].y);
break
}
})
the third step is to calculate and obtain the letter index.
changeStatus(moveY: number) {
const positions = this.letterList.map(letter => this.letterPositionMap.get(letter))
let index = 0
for (let i = 0; i < positions.length; i++) {
if (moveY < positions[i]) {
break
}
index = i + 1
}
//index就是触摸的每个字母的索引
}
}
Through the above steps, we have obtained the index of each letter. Next, we can change the color and size of the letter. Of course, we need to use decorators to modify the color and size of the letter, so we cannot write it to death. The processing method can be either an object or an array. Here we simply use an array to represent it.
Change Style
define an array of text colors and sizes:
@State textFontSizeArray: Length[] = []
@State textFontColorArray: ResourceColor[] = []
set default values:
aboutToAppear(): void {
for (let i = 65; i <= 90; i++) {
this.letterList.push(String.fromCharCode(i))
this.textFontSizeArray.push(14)
this.textFontColorArray.push("#666666")
}
}
the UI component is modified to define an array:
Text(item)
.fontColor(this.textFontColorArray[index])
.fontSize(this.textFontSizeArray[index])
define the style change method:
the purpose of this method is to clear all styles and then set the new style for the entry at the specified index.
latterChange(index: number) {
this.textFontSizeArray.forEach((_, index) => {
this.textFontSizeArray[index] = 14
this.textFontColorArray[index] = "#666666"
})
this.textFontSizeArray[index] = 16
this.textFontColorArray[index] = "#222222"
}
After calling in the changeStatus method, we can see that the text style has changed, and then we can achieve the effect of displaying letters in the middle.
First, define a component used to display letters, make it in the middle position, and hide it by default. letterSelect is the defined letter used for display and needs to be decorated with decorators.
Text(this.letterSelect)
.visibility(this.letterSelect == undefined ? Visibility.None : Visibility.Visible)
.backgroundColor("#f2f6f9")
.fontColor("#222222")
.fontWeight(FontWeight.Bold)
.fontSize(18)
.textAlign(TextAlign.Center)
.width(50)
.height(50)
.borderRadius(50)
.alignRules({
middle: { anchor: "__container__", align: HorizontalAlign.Center },
center: { anchor: "__container__", align: VerticalAlign.Center },
})
In the changeStatus method, dynamically change the value of letterSelect.
this.letterSelect = this.letterList[index]
It should be noted that when the gesture is lifted, it needs to be hidden. This point needs to be noted. In order to be able to hide without appearing so abrupt, it can be delayed.
case TouchType.Up: //手势抬起
clearTimeout(this.timeoutId)
this.timeoutId = setTimeout(() => {
this.letterSelect = undefined
}, 500)
break
We are looking at the effect achieved:
three, to achieve the left Contact List Display
the contact person on the left is relatively simple, is it a list that can be used for the top operation, or is it to prepare the data first, because the grouping operation is involved, so the definition of the data is slightly different from that of the common one.
1 Definition of data
define an interface for grouping display:
interface ListData {
title: string;
projects: string[];
}
define the test data, which needs to be set according to the interface.
private listData: ListData[] = [
{
title: 'A',
projects: ['安大同学', '安少同学', '安1同学', '安2同学', '安3同学']
},
{
title: 'B',
projects: ['包1同学', '包2同学', '包3同学', '包4同学', '包5同学', '包6同学', '包7同学', '包8同学', '包9同学']
},
{
title: 'C',
projects: ['蔡1同学', '蔡2同学', '蔡3同学', '蔡4同学', '蔡5同学', '蔡6同学', '蔡7同学', '蔡8同学', '蔡9同学']
},
{
title: 'D',
projects: ['杜1同学', '杜2同学', '杜3同学', '杜4同学', '杜5同学', '杜6同学']
},
{
title: 'F',
projects: ['范1同学', '范2同学', '范3同学', '范4同学', '范5同学', '范6同学', '范7同学']
},
{
title: 'L',
projects: ['李大同学', '李2同学', '李3同学', '李4同学', '李5同学', '李6同学']
},
{
title: 'M',
projects: ['马大同学', '马2同学', '马3同学', '马4同学', '马5同学', '马6同学']
}
]
2. Set the List component
because it is a group display, the listtemgroup component is used here.
List({ space: 10 }) {
ForEach(this.listData, (item: ListData) => {
ListItemGroup({ header: this.itemHead(item.title) }) {
ForEach(item.projects, (project: string) => {
ListItem() {
Text(project)
.width('100%')
.height(50)
.fontSize(18)
.textAlign(TextAlign.Center)
}
}, (item: string) => item)
}
.divider({ strokeWidth: 1, color: "#f2f6f9" }) // 每行之间的分界线
})
}
.width('100%')
.height("100%")
.sticky(StickyStyle.Header)
.scrollBar(BarState.Off)
itemHead component:
@Builder
itemHead(text: string) {
Text(text)
.fontSize(16)
.backgroundColor("#f2f6f9")
.width('100%')
.height(40)
.fontWeight(FontWeight.Bold)
.padding({ left: 10 })
}
four, to achieve the letter and contact list linkage
in the front, we have basically realized all the functions. At present, there is only one linkage. What we need to know is that the linkage is two-way, that is to say, sliding the letter list, the information list on the left also needs to be scrolled, sliding the information list, and the letter list on the right also needs to be changed in style.
1. Setting up the scroller
here we use scroller to locate the List component. First, we define a scroller.
private scroller: Scroller = new Scroller()
Then set the information list on the left:
List({ space: 10, scroller: this.scroller })
2. Right linkage
also in the previous gesture method, the scroller performs positioning.
const scrollIndex = this.listData.findIndex(item => item.title === this.letterSelect)
if (scrollIndex !== -1) {
this.scroller.scrollToIndex(scrollIndex)
}
3. Left linkage
the left-side linkage is relatively simple. It is nothing more than listening to the sliding events of the left-side list and then changing the style of the right-side list according to the index.
.onScrollIndex((start: number) => {
let title = this.listData[start].title
let latterPosition = this.letterList.indexOf(title)
this.latterChange(latterPosition)
})
V. Source Code
the function is very simple, all the code is as follows:
import { HashMap } from '@kit.ArkTS'
interface ListData {
title: string;
projects: string[];
}
@Entry
@Component
struct Index {
private letterList: string[] = []
private letterPositionMap: HashMap<string, number> = new HashMap()
@State textFontSizeArray: Length[] = []
@State textFontColorArray: ResourceColor[] = []
@State letterSelect: string | undefined = undefined //当前选中的字母
private timeoutId: number = 0
private scroller: Scroller = new Scroller()
private listData: ListData[] = [
{
title: 'A',
projects: ['安大同学', '安少同学', '安1同学', '安2同学', '安3同学']
},
{
title: 'B',
projects: ['包1同学', '包2同学', '包3同学', '包4同学', '包5同学', '包6同学', '包7同学', '包8同学', '包9同学']
},
{
title: 'C',
projects: ['蔡1同学', '蔡2同学', '蔡3同学', '蔡4同学', '蔡5同学', '蔡6同学', '蔡7同学', '蔡8同学', '蔡9同学']
},
{
title: 'D',
projects: ['杜1同学', '杜2同学', '杜3同学', '杜4同学', '杜5同学', '杜6同学']
},
{
title: 'F',
projects: ['范1同学', '范2同学', '范3同学', '范4同学', '范5同学', '范6同学', '范7同学']
},
{
title: 'L',
projects: ['李大同学', '李2同学', '李3同学', '李4同学', '李5同学', '李6同学']
},
{
title: 'M',
projects: ['马大同学', '马2同学', '马3同学', '马4同学', '马5同学', '马6同学']
}
]
aboutToAppear(): void {
for (let i = 65; i <= 90; i++) {
this.letterList.push(String.fromCharCode(i))
this.textFontSizeArray.push(14)
this.textFontColorArray.push("#666666")
}
}
@Builder
itemHead(text: string) {
Text(text)
.fontSize(16)
.backgroundColor("#f2f6f9")
.width('100%')
.height(40)
.fontWeight(FontWeight.Bold)
.padding({ left: 10 })
}
build() {
RelativeContainer() {
List({ space: 10, scroller: this.scroller }) {
ForEach(this.listData, (item: ListData) => {
ListItemGroup({ header: this.itemHead(item.title) }) {
ForEach(item.projects, (project: string) => {
ListItem() {
Text(project)
.width('100%')
.height(50)
.fontSize(18)
.textAlign(TextAlign.Center)
}
}, (item: string) => item)
}
.divider({ strokeWidth: 1, color: "#f2f6f9" })
})
}
.width('100%')
.height("100%")
.sticky(StickyStyle.Header)
.scrollBar(BarState.Off)
.onScrollIndex((start: number) => {
let title = this.listData[start].title
let latterPosition = this.letterList.indexOf(title)
this.latterChange(latterPosition)
})
List({ space: 5 }) {
ForEach(this.letterList, (item: string, index: number) => {
ListItem() {
Text(item)
.fontColor(this.textFontColorArray[index])
.fontSize(this.textFontSizeArray[index])
}.onAreaChange((_: Area, newValue: Area) => {
this.letterPositionMap.set(item, Number(newValue.position.y) + Number(newValue.height))
})
})
}
.width(30)
.backgroundColor("#f2f6f9")
.margin({ right: 10 })
.padding({ top: 10, bottom: 10 })
.borderRadius(30)
.alignListItem(ListItemAlign.Center)
.alignRules({
right: { anchor: "__container__", align: HorizontalAlign.End },
center: { anchor: "__container__", align: VerticalAlign.Center },
})
.onTouch((event) => {
switch (event.type) {
case TouchType.Down: //手势按下
case TouchType.Move: //手势抬起
this.changeStatus(event.touches[0].y);
break
case TouchType.Up: //手势抬起
clearTimeout(this.timeoutId)
this.timeoutId = setTimeout(() => {
this.letterSelect = undefined
}, 500)
break
}
})
Text(this.letterSelect)
.visibility(this.letterSelect == undefined ? Visibility.None : Visibility.Visible)
.backgroundColor("#f2f6f9")
.fontColor("#222222")
.fontWeight(FontWeight.Bold)
.fontSize(18)
.textAlign(TextAlign.Center)
.width(50)
.height(50)
.borderRadius(50)
.alignRules({
middle: { anchor: "__container__", align: HorizontalAlign.Center },
center: { anchor: "__container__", align: VerticalAlign.Center },
})
}
.width("100%")
.height("100%")
}
changeStatus(moveY: number) {
const positions = this.letterList.map(letter => this.letterPositionMap.get(letter))
let index = 0
for (let i = 0; i < positions.length; i++) {
if (moveY < positions[i]) {
break
}
index = i + 1
}
//index就是触摸的每个字母的索引
this.letterSelect = this.letterList[index]
const scrollIndex = this.listData.findIndex(item => item.title === this.letterSelect)
if (scrollIndex !== -1) {
this.scroller.scrollToIndex(scrollIndex)
}
this.latterChange(index)
}
latterChange(index: number) {
this.textFontSizeArray.forEach((_, index) => {
this.textFontSizeArray[index] = 14
this.textFontColorArray[index] = "#666666"
})
this.textFontSizeArray[index] = 16
this.textFontColorArray[index] = "#222222"
}
}
VI. Relevant Summary
the way of implementation is not static. You can also implement it through Canvas custom drawing. It is basically the same. It is necessary to confirm the position of the current touch letter, then change the style and link the left and right lists.
This article label: HarmonyOS/ArkUI/contact list actual combat
Top comments (0)