
回复
支持横向、纵向双向滑动的列表控件。实现思路是:
1、在封装的控件内实现首行布局,第一列为固定列,不可滑动,其它列通过Scroll组件包裹,实现横向滑动。
2、数据列通过List组件包裹,实现纵向滑动。数据通过LazyForEach实现懒加载,行布局第一列为固定列,不可滑动,其它列通过Scroll组件包裹,实现横向滑动。为了APP可以自定义数据列样式,因此通过定义Builder参数,由调用者动态传入,在调用方实现数据列的布局。
技术细节
1、定义表头单元格布局
/**
* 表头组件
*/
@Component
struct itemRank {
//当前列对应的下标
private thisColumnIndex: number = 0;
//当前列名称
private thisColumnName: string = '';
//图标宽度
private iconSize = 15;
//排序状态图标
private rankIcon: ResourceStr[] = [$r('app.media.icon_multi_list_rank_down'), $r('app.media.icon_multi_list_rank_up'),
$r('app.media.icon_multi_list_rank_nor')]
//点击列对应的下标
@Link clickedColumnIndex: number;
//排序状态下标
@Link rankSelect: number;
//列宽
@Prop itemWidth: ResourceStr | number;
@Prop itemHeight: ResourceStr | number;
// 字号
@Prop fontSize: ResourceStr | number
build() {
Row() {
Text(`${this.thisColumnName}`)
.fontSize(this.fontSize)
.textAlign(TextAlign.End)
.layoutWeight(1)
.height(this.itemHeight)
if (this.thisColumnIndex === this.clickedColumnIndex) {
//点击的为自己,自己开始切换图片
Image(this.rankIcon[this.rankSelect]).height(this.iconSize).margin({ left: 3 })
} else {
//点击的不是自己,显示默认图片
Image(this.rankIcon[2]).height(this.iconSize).margin({ left: 3 })
}
}
.width(this.itemWidth)
.height(this.itemHeight)
.justifyContent(FlexAlign.End)
.alignItems(VerticalAlign.Center)
.onClick(() => {
//点击的为自己,自己开始切换图片
if (this.thisColumnIndex === this.clickedColumnIndex) {
this.rankSelect = (this.rankSelect + 1) % this.rankIcon.length
} else {
this.rankSelect = 0 // 点击按钮的时候,其他状态的按钮在恢复默认图片的同时,rankSelect 也要恢复为0
this.clickedColumnIndex = this.thisColumnIndex //点击按钮,更换子组件选中的父组件序号
}
})
}
}
2、定义表头行
//表头布局
@Builder
headerScroll() {
Row() {
Text(this.fixColumn.name)
.width(100)
.height(this.itemHeight)
.fontSize(this.fontSize)
.textAlign(TextAlign.Start)
.margin({left: $r('app.float.multi_list_margin_left')})
// 右侧可横向滚动列
Scroll(this.headerRowScroller) {
Row() {
ForEach(this.scrollColumns, (item: ColumnField, index: number) => {
if (index == this.scrollColumns.length - 1) {
itemRank({
thisColumnName: item.name,
thisColumnIndex: index + 1,
itemWidth: this.itemWidth,
itemHeight: this.itemHeight,
fontSize: this.fontSize,
clickedColumnIndex: this.clickedColumnIndex,
rankSelect: this.rankSelect
})
.margin({ right: $r('app.float.multi_list_margin_right') })
.height(this.itemHeight)
} else {
itemRank({
thisColumnName: item.name,
thisColumnIndex: index + 1,
itemWidth: this.itemWidth,
itemHeight: this.itemHeight,
fontSize: this.fontSize,
clickedColumnIndex: this.clickedColumnIndex,
rankSelect: this.rankSelect
})
.height(this.itemHeight)
}
}, (item: ColumnField) => JSON.stringify(item))
}
.height(this.itemHeight)
}
.layoutWeight(1)
.height(this.itemHeight)
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.None)
.onWillScroll((xOffset: number, yOffset: number, scrollState: ScrollState, scrollSource: ScrollSource) => {
if (scrollSource == ScrollSource.SCROLLER || scrollSource == ScrollSource.SCROLLER_ANIMATION) {
return
}
this.listRowScroller.forEach((scroller) => {
scroller.scrollBy(xOffset, 0)
})
})
}
.width('100%')
.height(this.itemHeight)
.backgroundColor(Color.White)
}
3、构造数据列表
build() {
Column() {
this.headerScroll()
List({ scroller: this.listScroller }) {
LazyForEach(this.dataSource, (item: IMultiItem, position: number) => {
ListItem() {
if (item.itemType == 'data') {
Row() {
this.fixColumnBuilder(item)
// 右侧可横向滚动列
Scroll(this.listRowScroller[position]) {
Column() {
this.scrollColumnBuilder(item, position, this.scrollColumns)
}
.height('100%')
}
.layoutWeight(1)
.height('100%')
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.None)
.onAttach(() => {
// 每次刷新数据时,右侧每一行的Scroll要和头部的Scroller同步
for (let index = this.startRowIndex, length = this.endRowIndex; index <= length; index++) {
this.listRowScroller[index]?.scrollTo({
xOffset: this.headerRowScroller.currentOffset().xOffset,
yOffset: 0
})
}
})
.onWillScroll((x: number, y: number, state: ScrollState, source: ScrollSource) => {
if (source == ScrollSource.SCROLLER || source == ScrollSource.SCROLLER_ANIMATION) {
return
}
this.headerRowScroller.scrollBy(x, 0)
for (let index = this.startRowIndex, length = this.endRowIndex; index <= length; index++) {
if (index != position) {
this.listRowScroller[index]?.scrollBy(x, 0)
}
}
this.scrollX = this.listRowScroller[position]?.currentOffset().xOffset
})
}
.width('100%')
.height(this.itemHeight)
.onClick((event) => {
if (this.onItemClick) {
this.onItemClick(position)
}
})
} else {
this.scrollColumnBuilder(item, position, this.scrollColumns)
}
}
}, (item: object, index: number) => `${index}_${JSON.stringify(item)}`)
}
.divider({
strokeWidth: 0.5,
startMargin: 0,
endMargin: 0,
color: $r('app.color.multi_list_divider')
})
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST,
})
.onScrollIndex((start: number, end: number) => {
this.startRowIndex = start;
this.endRowIndex = end;
})
.scrollBar(BarState.Off)
.width('100%')
.height('100%')
.edgeEffect(EdgeEffect.None)
.onDidScroll(() => {
for (let index = this.startRowIndex, length = this.endRowIndex; index <= length; index++) {
this.listRowScroller[index].scrollTo({ xOffset: this.scrollX, yOffset: 0 })
}
})
}
}
双向滑动列表的实现,需要使用复杂的手势处理。感兴趣的朋友可以参考@sunshine/multilist · git_zhaoyang/MultiList - 码云 - 开源中国