使用ArkTS语言开发下拉菜单,需要考虑一下内容:
1、List的edgeEffect、LazyForEach
2、opacity属性可以设置整个组件的透明度
3、容器的.markAnchor可以和offset搭配使用
半成品代码实例、仿京东首页差一部分内容
import display from '@ohos.display';
import media from '@ohos.multimedia.media';
import { MyDataSource } from '../widget/list/datasource';
@Entry
@Component
struct TBListPage {
@State firstListData : string[] = ['item value: 0', 'item value: 1', 'item value: 2', 'item value: 3', 'item value: 4', 'item value: 5']
private data: MyDataSource = new MyDataSource(this.firstListData)
private scrollerForScroll: Scroller = new Scroller()
private scrollerForList: Scroller = new Scroller()
@State offsetY : number = 0
@State startIndex : number = 0
@State endIndex : number = 0
// 下拉刷新的布局高度
private pullRefreshHeight = 70
// 下拉刷新文字:下拉刷新、松开刷新、正在刷新、刷新成功
@State pullRefreshText: string= '下拉刷新'
// 是否可以刷新:未达到刷新条件,收缩回去
private isCanRefresh = false
// 是否正在刷新:刷新中不进入触摸逻辑
private isRefreshing: boolean = false
// 是否已经进入了下拉刷新操作
private isPullRefreshOperation = false
private lastMoveY = 0
// 上拉加载的布局默认高度
private loadMoreHeight = 70
// 上拉加载的布局是否显示
@State isVisibleLoadMore: boolean = false
// 是否可以加载更多
private isCanLoadMore = false
// 是否加载中:加载中不进入触摸逻辑
private isLoading: boolean = false
private downY = 0
private showAd : boolean = false;
private topbarHeight : number = 30
private adHeight : number = 60
build() {
Stack(){
Column(){
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.flo"))
.width(18)
.height(18)
Text(this.pullRefreshText)
.margin({ left: 7, bottom: 1 })
.fontSize(17)
}
.backgroundColor(Color.Red)
.width('100%')
.height(vp2px(this.adHeight))
.offset({ x: 0, y: `${this.offsetY}px` })
//列表
List({scroller : this.scrollerForList}){
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(`${item}`)
.width('100%')
.height(100)
.fontSize(20)
.margin({top : 10})
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0x007DFF)
}
}
}, item => item)
}
.edgeEffect(EdgeEffect.None)
.onScrollIndex((start, end) => {
this.startIndex = start
this.endIndex = end
})
.offset({ x: 0, y: `${this.offsetY}px` })
.onReachEnd(()=>{
if (this.data.totalCount() < 15) {
this.data.pushData('item value: ' + this.data.totalCount())
this.data.pushData('item value: ' + this.data.totalCount())
this.data.pushData('item value: ' + this.data.totalCount())
this.data.pushData('item value: ' + this.data.totalCount())
this.data.pushData('item value: ' + this.data.totalCount())
}
}).onTouch((event) => this.listTouchEvent(event))
.height('100%')
.width('100%')
}.markAnchor({x:0, y:`${vp2px(this.adHeight + this.topbarHeight)}px`})
Column(){
Text('首页')
.fontColor(Color.White)
.fontSize(24)
.margin(6)
TextInput()
.backgroundColor(Color.White)
}.opacity(this.getTopBarOpacity())
.backgroundColor(Color.Blue)
.padding({left: 10, right: 10})
.width('100%')
.height(vp2px(this.topbarHeight))
}
.height('100%')
.alignContent(Alignment.TopStart)
}
getTopBarOpacity() : number{
if (this.offsetY == 0) {
return 1;
}
if (this.offsetY > 100) {
return 0
}
return ((100 - this.offsetY) * 0.01)
}
listTouchEvent(event: TouchEvent){
switch (event.type) {
case TouchType.Down: // 手指按下
// 记录按下的y坐标
this.downY = event.touches[0].y
this.lastMoveY = event.touches[0].y
break
case TouchType.Move: // 手指移动
// 下拉刷新中 或 加载更多中,不进入处理逻辑
if(this.isRefreshing || this.isLoading){
console.info('========Move刷新中,返回=========')
return
}
// 判断手势
let isDownPull = event.touches[0].y - this.lastMoveY > 0
// 下拉手势 或 已经进入了下拉刷新操作
if ((isDownPull || this.isPullRefreshOperation) && !this.isCanLoadMore) {
this.touchMovePullRefresh(event)
} else {
this.touchMoveLoadMore(event)
}
this.lastMoveY = event.touches[0].y
break
case TouchType.Up: // 手指抬起
case TouchType.Cancel: // 触摸意外中断:来电界面
// 刷新中 或 加载更多中,不进入处理逻辑
if(this.isRefreshing || this.isLoading){
console.info('========Up刷新中,返回=========')
return
}
if (this.isPullRefreshOperation) {
this.touchUpPullRefresh()
} else {
this.touchUpLoadMore()
}
break
}
}
//============================================加载更多==================================================
// 手指移动,处理加载更多
touchMoveLoadMore(event:TouchEvent) {
// 因为加载更多是在列表后面新增一个item,当一屏能够展示全部列表,endIndex 为 length+1
if (this.endIndex == this.data.totalCount() - 1 || this.endIndex == this.data.totalCount()) {
// 滑动的偏移量
this.offsetY = event.touches[0].y - this.downY
if (Math.abs(this.offsetY) > vp2px(this.loadMoreHeight)/2) {
// 可以刷新了
this.isCanLoadMore = true
// 显示加载更多布局
this.isVisibleLoadMore = true
// 偏移量缓慢增加
this.offsetY = - vp2px(this.loadMoreHeight) + this.offsetY * 0.1
}
}
}
// 手指抬起,处理加载更多
touchUpLoadMore() {
animateTo({
duration: 200, // 动画时长
}, () => {
// 偏移量设置为0
this.offsetY = 0
})
if (this.isCanLoadMore) {
console.info('======执行加载更多========')
// 加载中...
this.isLoading = true
// 模拟耗时操作
setTimeout(() => {
this.closeLoadMore()
this.loadMoreData()
}, 2000)
} else {
console.info('======关闭加载更多!未达到条件========')
this.closeLoadMore()
}
}
// 关闭加载更多
closeLoadMore() {
this.isCanLoadMore = false
this.isLoading = false
this.isVisibleLoadMore = false
}
// 手指抬起,处理下拉刷新
touchUpPullRefresh(){
// 是否可以刷新
if (this.isCanRefresh) {
console.info('======执行下拉刷新========')
// 偏移量为下拉刷新布局高度
this.offsetY = vp2px(this.pullRefreshHeight)
// 状态2:正在刷新
this.pullRefreshState(2)
// 模拟耗时操作
setTimeout(() => {
this.refreshData()
this.closeRefresh()
}, 2000)
} else {
console.info('======关闭下拉刷新!未达到条件========')
// 关闭刷新
this.closeRefresh()
}
}
// 刷新测试数据
private refreshData(){
// this.list = []
// for (var i = 0; i < 10; i++) {
// this.list.push(i)
// }
}
// 加载更多测试数据
private loadMoreData(){
// let initValue = this.list[this.list.length-1] + 1
// for (var i = initValue; i < initValue + 10; i++) {
// this.list.push(i)
// }
}
// 关闭刷新
closeRefresh() {
// 如果允许刷新,延迟进入,为了显示刷新中
setTimeout(() => {
var delay = 50
if (this.isCanRefresh) {
// 状态3:刷新成功
this.pullRefreshState(3)
// 为了显示刷新成功,延迟执行收缩动画
delay = 500
}
animateTo({
duration: 150, // 动画时长
delay: delay, // 延迟时长
onFinish: () => {
// 状态0:下拉刷新
this.pullRefreshState(0)
this.isPullRefreshOperation = false
}
}, () => {
this.offsetY = 0
})
}, this.isCanRefresh ? 500 : 0)
}
// 手指移动,处理下拉刷新
touchMovePullRefresh(event:TouchEvent){
// 当首部索引位于0
if (this.startIndex == 0) {
this.isPullRefreshOperation = true
// 下拉刷新布局高度
var height = vp2px(this.pullRefreshHeight)
// 滑动的偏移量
this.offsetY = event.touches[0].y - this.downY
// 偏移量大于下拉刷新布局高度,达到刷新条件
if (this.offsetY >= height) {
// 状态1:松开刷新
this.pullRefreshState(1)
// 偏移量的值缓慢增加
this.offsetY = height + this.offsetY * 0.15
} else {
// 状态0:下拉刷新
this.pullRefreshState(0)
}
if (this.offsetY < 0) {
this.offsetY = 0
this.isPullRefreshOperation = false
}
}
}
// 下拉刷新状态
// 0下拉刷新、1松开刷新、2正在刷新、3刷新成功
pullRefreshState(state:number){
switch (state) {
case 0:
// 初始状态
this.pullRefreshText = '下拉刷新'
this.isCanRefresh = false
this.isRefreshing = false
this.showAd = false
break;
case 1:
this.pullRefreshText = '松开刷新'
this.isCanRefresh = true
this.isRefreshing = false
break;
case 2:
this.offsetY = vp2px(this.pullRefreshHeight)
this.pullRefreshText = '正在刷新'
this.isCanRefresh = true
this.isRefreshing = true
break;
case 3:
this.pullRefreshText = '刷新成功'
this.isCanRefresh = true
this.isRefreshing = true
break;
case 4:
this.offsetY = vp2px(this.adHeight)
this.pullRefreshText = '展示广告'
this.isCanRefresh = false
this.isRefreshing = false
this.showAd = true
break;
}
}
}
项目迟点提交。
参考来源:
https://gitee.com/liangdidi/ListPullRefreshLoadMoreDemo/blob/master/entry/src/main/ets/default/pages/index.ets
https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-universal-attributes-location-0000001427584824-V3
鸿蒙有中文的开发文档,对于初次使用ArkTS的开发者来说很友好。有过Flutter开发经验的会较快上手。