最近要做大图预览功能,这就绕不开大图缩放,为此花了点时间做这个功能
具体功能点如下:
1:双指捏合图片放大和缩小
2:双击放大和缩小
3:放大后的单指拖动和松开手指后的惯性滑动
4:可与轮播图共用
遇到的主要问题点如下:
1:图片怎么缩放
2:图片怎么滑动
3:缩放后滑动时的边界怎么处理
4:与轮播图的滑动事件冲突
5:怎么在手指松开之后判断是否进行惯性滑动
6:怎么进行惯性滑动
7:放大后松开手指时怎么不让图片滑动
8:怎么监听双击事件 ?
问题解答:
1:图片缩放
使用Image的 transform() 进行缩放,具体捏合手势通过PinchGesture管理
2:图片滑动
使用Image的 offset() 进行滑动,具体滑动手势通过PanGesture管理
3:边界处理
在图片缩放的过程中实时计算出X轴和Y轴的最大滑动距离,大概思路如下:
// PinchGesture的onActionUpdate()方法
this.maxScrollX = (this.imageWidth * this.scaleInfo.scaleValue - this.screenWidth) / 2
this.maxScrollY = (this.imageHeight * this.scaleInfo.scaleValue - this.screenHeight) / 2
if (this.maxScrollX < 0){
this.maxScrollX = 0
}
if (this.maxScrollY < 0){
this.maxScrollY = 0
}
图片放大后滑动到上下左右的某个边界,然后进行缩小,缩小时会滑出边界并且显露出父组件的背景色,这时就需要边缩小边对图片进行移动——移动到边界处,具体思路如下:
// PinchGesture的onActionUpdate()方法
if (event.scale < 1) { //缩小
if (this.offsetInfo.currentX > this.maxScrollX) {
this.offsetInfo.currentX = this.maxScrollX
}
if (this.offsetInfo.currentX < -this.maxScrollX) {
this.offsetInfo.currentX = -this.maxScrollX
}
if (this.offsetInfo.currentY > this.maxScrollY) {
this.offsetInfo.currentY = this.maxScrollY
}
if (this.offsetInfo.currentY < -this.maxScrollY) {
this.offsetInfo.currentY = -this.maxScrollY
}
}
4:与轮播图的滑动事件冲突
图片在轮播图中放大之后,本来只是想滑动图片的,但是却进行了轮播图的换页,从这里看是图片的滑动事件和轮播图的滑动事件冲突了,解决方法如下:
首先通过Swiper的disableSwipe()方法,禁用轮播图自带的切换功能,然后在图片滑动时进行控制,在图片滑动到左右边界的时候,通过SwiperController来进行上一页、下一页的操作,这样会有切换的动画,切记不要直接操作Swiper的index()方法,这样会导致没有切换动画
swiperController.showNext()
swiperController.showPrevious()
图片滑动到边界时直接切换轮播图的下一页或者上一页,就会导致滑动的时候很容易直接切换了轮播图,但是我们此时还是想看当前图片,因此需要滑动到边界之后进行二次滑动,再进行轮播图的翻页,通过增加isSliding这个boolean值进行判断,图片滑动时设置为true,在PanGesture的onActionEnd()中设置为false——注意这里需要延迟设置为false,只有为false的时候才允许切换轮播图
//滑动图片时进行轮播图翻页
onSlidPic(event: GestureEvent){
if (this.isSliding == false) { //一次滑动只允许翻一页
if (Math.abs(event.offsetX) > this.mMinimumSlideSwiper){ //必须大于最小滑动距离,才能翻页
if (this.onSlideSwiper != null){
if (event.offsetX > 0) { //右滑
this.onSlideSwiper(false)
}
if (event.offsetX < 0) { //左滑
this.onSlideSwiper(true)
}
}
this.isSliding = true
}
}
}
5:怎么在手指松开之后判断是否进行惯性滑动
首先需要明确进行惯性滑动的时机为PanGesture的onActionEnd()方法,这里是手指滑动操作结束的标志;如果直接在这里进行惯性滑动话,在手指滑动很慢然后抬起的情况下也会进行惯性滑动,这不符合操作习惯,所以就需要判断手指抬起时在屏幕上的滑动速度,通过event.velocityX 和 event.velocityY我们可以获取到手指抬起时Image在X轴和Y轴的滑动速度,当着两个值中的一个大于最小滑动速度时,我们就进行惯性滑动
if (Math.max(Math.abs(event.velocityX), Math.abs(event.velocityY)) > this.mMinimumVelocity){
//进行惯性滑动
}
6:怎么进行惯性滑动
鸿蒙的官方开发文档中有惯性滑动的方法,但是效果不甚理想,首先是只能滑动event.offsetX的距离,这就导致滑动距离很短;其次我建议使用EaseOut作为滑动效果,因为我们是全方向滑动;滑动的动画时长建议设置成300毫秒;滑动距离我们使用event.offsetX * 15 和 event.offsetY * 15,这样滑动距离长,效果更好;非惯性滑动时还是滑动event.offsetX和event.offsetY,这里注意区分。
具体滑动代码示例如下:
//PanGesture的onActionEnd()方法中
//惯性滑动动画:当手指抬起时执行,手指滑动过程中不执行
if (Math.max(Math.abs(event.velocityX), Math.abs(event.velocityY)) > this.mMinimumVelocity){
animateTo({
duration: 300,
curve: Curve.EaseOut, //表示动画以低速结束
iterations: 1 ,
playMode: PlayMode.Normal,
onFinish: () => {
//动画结束100毫秒之后再保存移动距离,立即保存的话会产生动画卡顿
setTimeout(() => {
this.offsetInfo.stash()
this.isSliding = false
}, 100)
}
}, () => {
this.setOffSet(event,true)
})
}else {
this.offsetInfo.stash() //动画结束
}
7:放大后松开手指时怎么不让图片滑动
双指对图片进行缩放完松开手指时,因为手指离开屏幕时有先后顺序可能会触发滑动事件,导致图片的突然移动,因此我在PinchGesture的onActionEnd()方法中加了一个延迟以控制是否正在捏合操作的变量,示例代码如下:
setTimeout(() => {
this.isScaling = false //缩放完成150毫秒后再重置,防止刚缩放完手指离开屏幕时有先后顺序导致的滑动
}, 150)
8:怎么监听双击事件 ?
这个很简单,直接上代码:
//双击
TapGesture({ count: 2 })
.onAction((event: GestureEvent) => {
this.onDoubleClick()
})
完整代码如下:
import { CustomImage } from './customImage/CustomImage';
import { CustomSwiperDataSourceObject, CustomSwiper } from './customImage/CustomSwiper';
/**
* 图片预览组件
*/
@Component
export struct CustomImagePreview {
@Link show: boolean;
@Require @Prop imageList: string[]; //预览图片列表
@Require @Prop currentIndex: number; //当前预览第几张图
@State private swiperList: CustomSwiperDataSourceObject<string>[] = [];
private swiperController: SwiperController = new SwiperController() //轮播图翻页控制器
aboutToAppear(): void {
this.swiperList = this.imageList.map((item: string, _index: number) => {
let data: CustomSwiperDataSourceObject<string> = {
swiperItem: item
}
return data
})
}
@Builder
imageBuilder(item: CustomSwiperDataSourceObject<string>, index: number) {
Column() {
CustomImage({
imgUrl: item.swiperItem,
imgWidth: `100%`,
imgHeight: `100%`,
download: true,
imgBg: '#00000000',
canScal: true, //是否可以缩放,需要缩放时配置
onSlideSwiper: (isNextPage: boolean) => {
if (isNextPage) {
this.swiperController.showNext()
} else {
this.swiperController.showPrevious()
}
}
})
.onClick(() => {
this.show = false
})
}
}
build() {
Stack() {
Row() {
Text(`${this.currentIndex + 1}/${this.imageList.length}`)
.fontSize(vp2px(8))
.fontColor('#FFFFFF')
.textAlign(TextAlign.Center)
}
.width(`100%`)
.height(vp2px(10))
.position({
y: vp2px(0)
})
.zIndex(10)
.justifyContent(FlexAlign.Center)
Column() {
CustomSwiper({
swiperList: this.swiperList,
index: this.currentIndex,
// showIndicator: false,
autoPlay: false,
radius: 0,
swiperController: this.swiperController,
disableSwipe: true, //有图片缩放功能时,必须禁用组件滑动功能, 传值 true:禁用 false:启用
itemBuilder: (item: CustomSwiperDataSourceObject<string>, index: number) => {
this.imageBuilder(item, index);
},
onSwiperChange: (item: CustomSwiperDataSourceObject<ESObject>, index: number) => {
this.currentIndex = index;
}
})
}.width(`100%`)
.height(`100%`)
.justifyContent(FlexAlign.Center)
.zIndex(9)
}.width(`100%`)
.height(`100%`)
.backgroundColor('#000000')
}
}
import { CommonDataSource } from '../common/CommonDataSource';
/**
* 通用轮播组件
* @param swiperList 轮播列表
* @param swiperHeight 轮播区域高度
* @param radius 圆角
*/
@Preview
@Component
export struct CustomSwiper {
// swiperList规定了 swiper 对象
@Prop swiperList: CustomSwiperDataSourceObject<ESObject>[];
// 当前在容器中显示的子组件的索引值。开始
@Prop index: number = 0;
// 是否自动播放。
@Prop autoPlay: boolean = true;
// 自动播放时播放的时间间隔
@Prop interval: number = 3000;
// 是否开启循环。
@Prop loop: boolean = true;
// 子组件切换的动画时长,单位为毫秒
@Prop duration: number = 400;
// 纵向滑动。
@Prop vertical: boolean = false;
// 子组件与子组件之间间隙。
@Prop itemSpace: number = 0;
//设置Swiper的动画曲线
@Prop curve: Curve | string | ICurve = Curve.Linear;
// 预加载子组件个数。
@Prop cachedCount: number = 0;
// 后边距,用于露出后一项的一小部分。
@Prop nextMargin: number = 0;
// 前边距,用于露出前一项的一小部分
@Prop prevMargin: number = 0;
// 整个 swiper圆角
@Prop radius: BorderRadiuses | Length = vp2px(8)
// 是否禁用组件滑动切换功能
@Prop disableSwipe: boolean = false
// 内容 builder
@Require @BuilderParam itemBuilder: (item: CustomSwiperDataSourceObject<ESObject>, index: number) => void
@State currentIndex: number = 0
// swiper 控制器
private swiperController: SwiperController = new SwiperController()
// swiper 数据
private data: CommonDataSource<CustomSwiperDataSourceObject<ESObject>> = new CommonDataSource([])
onSwiperChange?: (item: CustomSwiperDataSourceObject<ESObject>, index: number) => void
aboutToAppear(): void {
this.data = new CommonDataSource(this.swiperList)
}
build() {
Stack({
alignContent: Alignment.BottomEnd
}) {
Swiper(this.swiperController) {
LazyForEach(this.data, (item: CustomSwiperDataSourceObject<ESObject>, index: number) => {
this.itemBuilder(item, index);
}, (item: string) => item)
}
.index(this.index)
.autoPlay(this.autoPlay)
.interval(this.interval)
.loop(this.loop)
.duration(this.duration)
.vertical(this.vertical)
.itemSpace(this.itemSpace)
.cachedCount(this.cachedCount)
.curve(this.curve)
.nextMargin(this.nextMargin)
.prevMargin(this.prevMargin)
.borderRadius(this.radius)
.disableSwipe(this.disableSwipe)
.indicator(false) //不展示指示器
.onChange((_index: number) => {
this.currentIndex = _index;
if (this.onSwiperChange) {
this.onSwiperChange(this.swiperList[_index], _index)
}
})
}
.width('100%')
}
}
export interface CustomSwiperDataSourceObject<T> {
swiperItem: T;
}
import { display, matrix4 } from '@kit.ArkUI';
import { CustomScaleModel } from './CustomScaleModel';
import { CustomOffsetModel } from './CustomOffsetModel';
// 图片信息
interface imgInfo {
width: number;
height: number;
componentWidth: number;
componentHeight: number;
loadingStatus: number;
contentWidth: number;
contentHeight: number;
contentOffsetX: number;
contentOffsetY: number;
}
/**
* KImage 图片组件
* @param imgUrl: PixelMap | ResourceStr | DrawableDescriptor
* @param imgWidth
* @param imgHeight
* @param imgAlt
* @param imgBorderRadius
* @param imgFit
* @param imgBg
* @callback onCompleteCall
* @callback onErrorCall
* */
@Component
export struct CustomImage {
// url
@Require @Prop imgUrl: PixelMap | ResourceStr | DrawableDescriptor
// 宽
@Prop imgWidth: Length
// 高
@Prop imgHeight: Length
//加载时的占位图
@Prop imgAlt: string | Resource = $r('app.media.imgError')
@Prop hasImgAlt: boolean = true
// 是否支持长按下载,默认不支持
@Prop download: boolean = true
// 圆角
@Prop imgBorderRadius: Length | BorderRadiuses
// ImageFit
@Prop imgFit: ImageFit = ImageFit.Contain
// 正常加载图片时的背景色,默认为 bg_lightgray4
@Prop imgBg: ResourceColor = "#F0F0F0"
// 加载图片异常时的背景色,按需配置
@Prop imgBgError: ResourceColor = "#F0F0F0"
// 图片宽高比
@Prop imgAspectRatio: number
@Prop imgBorder: BorderOptions | null
// 图片加载成功
onCompleteCall?: (imgInfo?: imgInfo) => void
// 图片加载失败
onErrorCall?: (error: ImageError) => void
// 加载失败
@State isError: Boolean = false
// 加载中
@State isLoading: Boolean = true
//============== 图片缩放相关 ==============
private screenWidth: number = 0
private screenHeight: number = 0
private imageWidth: number = 0
private imageHeight: number = 0
private maxScrollX: number = 0 //x轴最大移动距离
private maxScrollY: number = 0 //y轴最大移动距离
private onSlideSwiper?: (isNextPage: boolean) => void //轮播图切换
private mMinimumVelocity: number = 50 //手指抬起时可以进行惯性滑动的最小速度
private mMinimumSlideSwiper: number = 20 //手指抬起时可以进行惯性滑动的最小速度
private canScal: boolean = false //是否可以缩放
@State scaleInfo: CustomScaleModel = new CustomScaleModel(1.0, 1.0, 5.0)
@State offsetInfo: CustomOffsetModel = new CustomOffsetModel(0, 0)
@State matrix: matrix4.Matrix4Transit = matrix4.identity().copy()
@State isScaling: boolean = false //是否正在缩放,正在缩放时不允许滑动图片
@State isSliding: boolean = false //是否正在滑动
aboutToAppear(): void {
this.screenWidth = px2vp(display.getDefaultDisplaySync().width)
this.screenHeight = px2vp(display.getDefaultDisplaySync().height)
}
build() {
Stack() {
this.showImage()
if (this.isLoading || this.isError) {
if (this.hasImgAlt) {
this.showKfzLogo()
}
}
}
.width(this.imgWidth)
.height(this.imgHeight)
.clip(true)
.borderRadius(this.imgBorderRadius)
.backgroundColor(this.isError ? this.imgBgError : this.imgBg)
.alignContent(Alignment.Center)
}
// 占位图
@Builder
showKfzLogo() {
Image(this.imgAlt)
.fillColor("#FFFFFF")
.objectFit(ImageFit.Contain)
.width(typeof this.imgWidth === 'number' ? (this.imgWidth * 0.6) : '60%')
.height(typeof this.imgHeight === 'number' ? (this.imgHeight * 0.6) : '60%')
.aspectRatio(this.imgAspectRatio ?? null)
}
// 图片
@Builder
showImage() {
Image(this.imgUrl)
.width(this.imgWidth)
.height(this.imgHeight)
.objectFit(this.imgFit)
.aspectRatio(this.imgAspectRatio)
.border(this.imgBorder)
.borderRadius(this.imgBorderRadius)
.onComplete((imgInfo) => {
this.isLoading = false
this.isError = false
if (imgInfo) {
this.imageWidth = px2vp(imgInfo.contentWidth)
//得到图片绘制的高度 单位 vp的
this.imageHeight = px2vp(imgInfo.contentHeight)
}
if (this.onCompleteCall) {
this.onCompleteCall(imgInfo)
}
})
.onError((error) => {
this.isLoading = false
this.isError = true
if (this.onErrorCall) {
this.onErrorCall(error)
}
})
.transform(this.canScal ? this.matrix : null)// 通过matrix控制图片的缩放
.offset({
//通过offset控制图片的偏移
x: this.canScal ? this.offsetInfo.currentX : 0,
y: this.canScal ? this.offsetInfo.currentY : 0
})
.onImageGesture()
.onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent) => {
//捏合手势、滑动手势在非看大图模式下不响应
if ((gestureInfo.type == GestureControl.GestureType.PAN_GESTURE ||
gestureInfo.type == GestureControl.GestureType.PINCH_GESTURE) && !this.canScal) {
return GestureJudgeResult.REJECT
}
return GestureJudgeResult.CONTINUE
})
}
@Styles
onImageGesture(){
.gesture(
GestureGroup(GestureMode.Parallel,
//手指缩放
PinchGesture({ fingers: 2, distance: 1 })
.onActionUpdate((event: GestureEvent) => {
this.onPinchGestureUpDate(event)
})
.onActionEnd((event: GestureEvent) => {
this.scaleInfo.stash()
setTimeout(() => {
this.isScaling = false //缩放完成150毫秒后再重置,防止刚缩放完手指离开屏幕时有先后顺序导致的滑动
}, 150)
}),
//滑动图片
PanGesture({ fingers: 1, direction: PanDirection.All })
.onActionUpdate((event: GestureEvent) => {
this.onPanGestureUpDate(event)
})
.onActionEnd((event: GestureEvent) => {
if (this.isScaling) {
return
}
//惯性滑动动画:当手指抬起时执行,手指滑动过程中不执行
if (Math.max(Math.abs(event.velocityX), Math.abs(event.velocityY)) > this.mMinimumVelocity) {
animateTo({
duration: 300,
curve: Curve.EaseOut, //表示动画以低速结束
iterations: 1,
playMode: PlayMode.Normal,
onFinish: () => {
//动画结束100毫秒之后再保存移动距离,立即保存的话会产生动画卡顿
setTimeout(() => {
this.offsetInfo.stash()
this.isSliding = false
}, 100)
}
}, () => {
this.setOffSet(event, true)
})
} else {
this.offsetInfo.stash() //动画结束
}
}),
//双击
TapGesture({ count: 2 })
.onAction((event: GestureEvent) => {
this.onDoubleClick()
})
)
)
}
onDoubleClick() {
if (this.scaleInfo.scaleValue > this.scaleInfo.minScaleValue) {
//双击时已经放大了
this.scaleInfo.scaleValue = this.scaleInfo.minScaleValue
} else {
this.scaleInfo.scaleValue = this.scaleInfo.mediumScaleValue
}
this.scaleInfo.lastValue = this.scaleInfo.scaleValue
//matrix默认缩放中心为组件中心
this.matrix = matrix4.identity().scale({
x: this.scaleInfo.scaleValue,
y: this.scaleInfo.scaleValue,
}).copy()
}
//手势缩放
onPinchGestureUpDate(event: GestureEvent) {
this.isScaling = true
this.scaleInfo.scaleValue = this.scaleInfo.lastValue * event.scale
// 缩放时不允许大于最大缩放,不允许小于默认大小
if (this.scaleInfo.scaleValue > this.scaleInfo.maxScaleValue) {
this.scaleInfo.scaleValue = this.scaleInfo.maxScaleValue
}
if (this.scaleInfo.scaleValue < this.scaleInfo.minScaleValue) {
this.scaleInfo.scaleValue = this.scaleInfo.minScaleValue
}
//matrix默认缩放中心为组件中心
this.matrix = matrix4.identity().scale({
x: this.scaleInfo.scaleValue,
y: this.scaleInfo.scaleValue,
}).copy()
//计算X轴/Y轴最大移动距离
this.maxScrollX = (this.imageWidth * this.scaleInfo.scaleValue - this.screenWidth) / 2
this.maxScrollY = (this.imageHeight * this.scaleInfo.scaleValue - this.screenHeight) / 2
if (this.maxScrollX < 0) {
this.maxScrollX = 0
}
if (this.maxScrollY < 0) {
this.maxScrollY = 0
}
if (event.scale < 1) { //缩小
if (this.offsetInfo.currentX > this.maxScrollX) {
this.offsetInfo.currentX = this.maxScrollX
}
if (this.offsetInfo.currentX < -this.maxScrollX) {
this.offsetInfo.currentX = -this.maxScrollX
}
if (this.offsetInfo.currentY > this.maxScrollY) {
this.offsetInfo.currentY = this.maxScrollY
}
if (this.offsetInfo.currentY < -this.maxScrollY) {
this.offsetInfo.currentY = -this.maxScrollY
}
}
}
//手势滑动
onPanGestureUpDate(event: GestureEvent) {
//正在缩放,不允许移动
if (this.isScaling) {
return
}
if (this.scaleInfo.scaleValue === this.scaleInfo.minScaleValue) {
this.onSlidPic(event) //还未缩放就进行滑动,则轮播图翻页
} else {
this.setOffSet(event, false)
}
}
//设置图片偏移量
setOffSet(event: GestureEvent, isFly: boolean) {
//滑动到边界之后进行二次滑动,则轮播图翻页:注意这里需要区分左右两个边界的单独滑动
if (this.offsetInfo.currentX == this.maxScrollX && event.offsetX > 0) {
this.onSlidPic(event)
return
}
if (this.offsetInfo.currentX == -this.maxScrollX && event.offsetX < 0) {
this.onSlidPic(event)
return
}
if (this.maxScrollX >= 0) { //缩放后图片宽度大于屏幕宽度
//X轴移动
let preScrollX = 0
if (isFly) {
preScrollX = this.offsetInfo.lastX + event.offsetX * 15 //预计X轴要移动的距离
} else {
preScrollX = this.offsetInfo.lastX + event.offsetX //预计X轴要移动的距离
}
if (Math.abs(preScrollX) < this.maxScrollX) {
this.offsetInfo.currentX = preScrollX
} else {
if (preScrollX < 0) {
this.offsetInfo.currentX = -this.maxScrollX
} else {
this.offsetInfo.currentX = this.maxScrollX
}
}
this.isSliding = true
}
//Y轴移动
if (this.maxScrollY >= 0) { //缩放后图片高度大于屏幕高度
let preScrollY = 0
if (isFly) {
preScrollY = this.offsetInfo.lastY + event.offsetY * 15 //预计Y轴要移动的距离
} else {
preScrollY = this.offsetInfo.lastY + event.offsetY //预计Y轴要移动的距离
}
if (Math.abs(preScrollY) < this.maxScrollY) {
this.offsetInfo.currentY = preScrollY
} else {
if (preScrollY < 0) {
this.offsetInfo.currentY = -this.maxScrollY
} else {
this.offsetInfo.currentY = this.maxScrollY
}
}
this.isSliding = true
}
}
//滑动图片时进行轮播图翻页
onSlidPic(event: GestureEvent) {
if (this.isSliding == false) { //一次滑动只允许翻一页
if (Math.abs(event.offsetX) > this.mMinimumSlideSwiper) { //必须大于最小滑动距离,才能翻页
if (this.onSlideSwiper != null) {
if (event.offsetX > 0) { //右滑
this.onSlideSwiper(false)
}
if (event.offsetX < 0) { //左滑
this.onSlideSwiper(true)
}
}
this.isSliding = true
}
}
}
}
@Observed
export class CustomOffsetModel {
public currentX: number
public currentY: number
public lastX: number = 0
public lastY: number = 0
constructor(currentX: number = 0, currentY: number = 0) {
this.currentX = currentX;
this.currentY = currentY;
}
reset(): void {
this.currentX = 0
this.currentY = 0
this.lastX = 0
this.lastY = 0
}
stash(): void {
this.lastX = this.currentX
this.lastY = this.currentY
}
}
@Observed
export class CustomScaleModel {
public scaleValue: number //本次缩放值
public lastValue: number //记录上次缩放完后的缩放值
public maxScaleValue: number //最大默认缩放值
public minScaleValue: number = 1 //最小缩放值
public mediumScaleValue: number = 2.5 //双击的放大值
constructor(scaleValue: number = 1.0, lastValue: number = 1.0, maxScaleValue: number = 5,
mediumScaleValue: number = 2.5) {
this.scaleValue = scaleValue
this.lastValue = lastValue
this.maxScaleValue = maxScaleValue
this.mediumScaleValue = mediumScaleValue
}
reset(): void {
this.scaleValue = this.minScaleValue
this.lastValue = this.scaleValue
}
stash(): void {
this.lastValue = this.scaleValue
}
}
@Observed
export class ObservedArray<T> extends Array<T> {
constructor(args?: T[]) {
if (args instanceof Array) {
super(...args);
} else {
super();
}
}
}
@Observed
export class CommonDataSource<T> implements IDataSource {
private dataArray: T[] = [];
private listeners: DataChangeListener[] = [];
constructor(element: T[]) {
this.dataArray = element;
}
public getData(index: number) {
return this.dataArray[index]
}
public totalCount(): number {
return this.dataArray.length;
}
public addData(index: number, data: T): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public addDatas(index: number, datas: T[]): void {
this.dataArray = this.dataArray.concat(datas);
this.notifyDataAdd(index);
}
public pushAllData(newData: ObservedArray<T>): void {
this.clear();
this.dataArray.push(...newData);
this.notifyDataReload();
}
//分页加载时不要使用,会重新加载所有数据导致卡顿
public appendAllData(addData: ObservedArray<T>): void {
this.dataArray.push(...addData);
this.notifyDataReload()
}
//分页加载时不要使用,会重新加载所有数据导致卡顿
public addAllDatas(datas: T[]): void {
datas.forEach(data => {
this.dataArray.push(data);
})
this.notifyDataReload();
}
public setData(datas: T[]): void {
this.clear();
this.addAllDatas(datas);
}
public pushData(data: T): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
public deleteData(index: number): void {
this.dataArray.splice(index, 1);
this.notifyDataDelete(index)
}
public moveData(from: number, to: number): void {
let temp: T = this.dataArray[from];
this.dataArray[from] = this.dataArray[to];
this.dataArray[to] = temp;
this.notifyDataMove(from, to);
}
public changeData(index: number, data: T): void {
this.dataArray.splice(index, 1, data);
this.notifyDataChange(index);
}
public reloadData(): void {
this.notifyDataReload();
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
notifyDataReload(): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataReloaded();
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataAdd(index);
})
}
notifyDataChange(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataChange(index);
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataDelete(index);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDataMove(from, to);
})
}
public notifyDatasetChange(operations: DataOperation[]): void {
this.listeners.forEach((listener: DataChangeListener) => {
listener.onDatasetChange(operations);
})
}
getDataArray() {
return this.dataArray
}
getItemIndex(t: T): number {
return this.dataArray.indexOf(t)
}
public clear(): void {
this.dataArray.splice(0, this.dataArray?.length)
}
public deleteAll(): void {
while (this.dataArray.length > 0) {
this.deleteData(0)
}
}
}