鴻蒙學(xué)習(xí)筆記十五:大圖縮放+輪播圖+慣性滑動

最近要做大圖預(yù)覽功能媳否,這就繞不開大圖縮放奈应,為此花了點(diǎn)時間做這個功能
具體功能點(diǎn)如下:

1:雙指捏合圖片放大和縮小
2:雙擊放大和縮小
3:放大后的單指拖動和松開手指后的慣性滑動
4:可與輪播圖共用

遇到的主要問題點(diǎn)如下:

1:圖片怎么縮放
2:圖片怎么滑動
3:縮放后滑動時的邊界怎么處理
4:與輪播圖的滑動事件沖突
5:怎么在手指松開之后判斷是否進(jìn)行慣性滑動
6:怎么進(jìn)行慣性滑動
7:放大后松開手指時怎么不讓圖片滑動
8:怎么監(jiān)聽雙擊事件 ?

問題解答:
1:圖片縮放

使用Image的 transform() 進(jìn)行縮放叠聋,具體捏合手勢通過PinchGesture管理

2:圖片滑動

使用Image的 offset() 進(jìn)行滑動柒爸,具體滑動手勢通過PanGesture管理

3:邊界處理

在圖片縮放的過程中實(shí)時計算出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
    }

圖片放大后滑動到上下左右的某個邊界西傀,然后進(jìn)行縮小斤寇,縮小時會滑出邊界并且顯露出父組件的背景色,這時就需要邊縮小邊對圖片進(jìn)行移動——移動到邊界處拥褂,具體思路如下:

// 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:與輪播圖的滑動事件沖突

圖片在輪播圖中放大之后娘锁,本來只是想滑動圖片的,但是卻進(jìn)行了輪播圖的換頁肿仑,從這里看是圖片的滑動事件和輪播圖的滑動事件沖突了致盟,解決方法如下:
首先通過Swiper的disableSwipe()方法碎税,禁用輪播圖自帶的切換功能尤慰,然后在圖片滑動時進(jìn)行控制,在圖片滑動到左右邊界的時候雷蹂,通過SwiperController來進(jìn)行上一頁伟端、下一頁的操作,這樣會有切換的動畫匪煌,切記不要直接操作Swiper的index()方法责蝠,這樣會導(dǎo)致沒有切換動畫

swiperController.showNext()
swiperController.showPrevious()

圖片滑動到邊界時直接切換輪播圖的下一頁或者上一頁,就會導(dǎo)致滑動的時候很容易直接切換了輪播圖萎庭,但是我們此時還是想看當(dāng)前圖片霜医,因此需要滑動到邊界之后進(jìn)行二次滑動,再進(jìn)行輪播圖的翻頁驳规,通過增加isSliding這個boolean值進(jìn)行判斷肴敛,圖片滑動時設(shè)置為true,在PanGesture的onActionEnd()中設(shè)置為false——注意這里需要延遲設(shè)置為false,只有為false的時候才允許切換輪播圖

  //滑動圖片時進(jìn)行輪播圖翻頁
  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:怎么在手指松開之后判斷是否進(jìn)行慣性滑動

首先需要明確進(jìn)行慣性滑動的時機(jī)為PanGesture的onActionEnd()方法砸狞,這里是手指滑動操作結(jié)束的標(biāo)志;如果直接在這里進(jìn)行慣性滑動話镀梭,在手指滑動很慢然后抬起的情況下也會進(jìn)行慣性滑動刀森,這不符合操作習(xí)慣,所以就需要判斷手指抬起時在屏幕上的滑動速度报账,通過event.velocityX 和 event.velocityY我們可以獲取到手指抬起時Image在X軸和Y軸的滑動速度研底,當(dāng)著兩個值中的一個大于最小滑動速度時,我們就進(jìn)行慣性滑動

if (Math.max(Math.abs(event.velocityX), Math.abs(event.velocityY)) > this.mMinimumVelocity){
  //進(jìn)行慣性滑動             
}
6:怎么進(jìn)行慣性滑動

鴻蒙的官方開發(fā)文檔中有慣性滑動的方法笙什,但是效果不甚理想飘哨,首先是只能滑動event.offsetX的距離,這就導(dǎo)致滑動距離很短琐凭;其次我建議使用EaseOut作為滑動效果芽隆,因?yàn)槲覀兪侨较蚧瑒樱换瑒拥膭赢嫊r長建議設(shè)置成300毫秒统屈;滑動距離我們使用event.offsetX * 15 和 event.offsetY * 15胚吁,這樣滑動距離長,效果更好愁憔;非慣性滑動時還是滑動event.offsetX和event.offsetY腕扶,這里注意區(qū)分。
具體滑動代碼示例如下:

//PanGesture的onActionEnd()方法中
//慣性滑動動畫:當(dāng)手指抬起時執(zhí)行吨掌,手指滑動過程中不執(zhí)行
            if (Math.max(Math.abs(event.velocityX), Math.abs(event.velocityY)) > this.mMinimumVelocity){
              animateTo({
                duration: 300,
                curve: Curve.EaseOut, //表示動畫以低速結(jié)束
                iterations: 1 ,
                playMode: PlayMode.Normal,
                onFinish: () => {
                  //動畫結(jié)束100毫秒之后再保存移動距離半抱,立即保存的話會產(chǎn)生動畫卡頓
                  setTimeout(() => {
                    this.offsetInfo.stash()
                    this.isSliding = false
                  }, 100)
                }
              }, () => {
                this.setOffSet(event,true)
              })
            }else {
              this.offsetInfo.stash()  //動畫結(jié)束
            }
7:放大后松開手指時怎么不讓圖片滑動

雙指對圖片進(jìn)行縮放完松開手指時,因?yàn)槭种鸽x開屏幕時有先后順序可能會觸發(fā)滑動事件膜宋,導(dǎo)致圖片的突然移動窿侈,因此我在PinchGesture的onActionEnd()方法中加了一個延遲以控制是否正在捏合操作的變量,示例代碼如下:

setTimeout(() => {
              this.isScaling = false //縮放完成150毫秒后再重置秋茫,防止剛縮放完手指離開屏幕時有先后順序?qū)е碌幕瑒?            }, 150)
8:怎么監(jiān)聽雙擊事件 史简?

這個很簡單,直接上代碼:

//雙擊
        TapGesture({ count: 2 })
          .onAction((event: GestureEvent) => {
            this.onDoubleClick()
          })
完整代碼如下:
import { CustomImage } from './customImage/CustomImage';
import { CustomSwiperDataSourceObject, CustomSwiper } from './customImage/CustomSwiper';

/**
 * 圖片預(yù)覽組件
 */
@Component
export struct CustomImagePreview {
  @Link show: boolean;
  @Require @Prop imageList: string[]; //預(yù)覽圖片列表
  @Require @Prop currentIndex: number; //當(dāng)前預(yù)覽第幾張圖
  @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 輪播區(qū)域高度
 * @param radius 圓角
 */
@Preview
@Component
export struct CustomSwiper {
  // swiperList規(guī)定了 swiper 對象
  @Prop swiperList: CustomSwiperDataSourceObject<ESObject>[];
  // 當(dāng)前在容器中顯示的子組件的索引值枢贿。開始
  @Prop index: number = 0;
  // 是否自動播放殉农。
  @Prop autoPlay: boolean = true;
  // 自動播放時播放的時間間隔
  @Prop interval: number = 3000;
  // 是否開啟循環(huán)。
  @Prop loop: boolean = true;
  // 子組件切換的動畫時長局荚,單位為毫秒
  @Prop duration: number = 400;
  // 縱向滑動超凳。
  @Prop vertical: boolean = false;
  // 子組件與子組件之間間隙。
  @Prop itemSpace: number = 0;
  //設(shè)置Swiper的動畫曲線
  @Prop curve: Curve | string | ICurve = Curve.Linear;
  // 預(yù)加載子組件個數(shù)。
  @Prop cachedCount: number = 0;
  // 后邊距聪建,用于露出后一項(xiàng)的一小部分钙畔。
  @Prop nextMargin: number = 0;
  // 前邊距,用于露出前一項(xiàng)的一小部分
  @Prop prevMargin: number = 0;
  // 整個 swiper圓角
  @Prop radius: BorderRadiuses | Length = vp2px(8)
  // 是否禁用組件滑動切換功能
  @Prop disableSwipe: boolean = false
  // 內(nèi)容 builder
  @Require @BuilderParam itemBuilder: (item: CustomSwiperDataSourceObject<ESObject>, index: number) => void
  @State currentIndex: number = 0
  // swiper 控制器
  private swiperController: SwiperController = new SwiperController()
  // swiper 數(shù)據(jù)
  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
  // 是否支持長按下載金麸,默認(rèn)不支持
  @Prop download: boolean = true
  // 圓角
  @Prop imgBorderRadius: Length | BorderRadiuses
  // ImageFit
  @Prop imgFit: ImageFit = ImageFit.Contain
  // 正常加載圖片時的背景色擎析,默認(rèn)為 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
  //============== 圖片縮放相關(guān) ==============
  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 //手指抬起時可以進(jìn)行慣性滑動的最小速度
  private mMinimumSlideSwiper: number = 20 //手指抬起時可以進(jìn)行慣性滑動的最小速度
  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) => {
        //捏合手勢揍魂、滑動手勢在非看大圖模式下不響應(yīng)
        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毫秒后再重置,防止剛縮放完手指離開屏幕時有先后順序?qū)е碌幕瑒?            }, 150)
          }),

        //滑動圖片
        PanGesture({ fingers: 1, direction: PanDirection.All })
          .onActionUpdate((event: GestureEvent) => {
            this.onPanGestureUpDate(event)
          })
          .onActionEnd((event: GestureEvent) => {
            if (this.isScaling) {
              return
            }
            //慣性滑動動畫:當(dāng)手指抬起時執(zhí)行棚瘟,手指滑動過程中不執(zhí)行
            if (Math.max(Math.abs(event.velocityX), Math.abs(event.velocityY)) > this.mMinimumVelocity) {
              animateTo({
                duration: 300,
                curve: Curve.EaseOut, //表示動畫以低速結(jié)束
                iterations: 1,
                playMode: PlayMode.Normal,
                onFinish: () => {
                  //動畫結(jié)束100毫秒之后再保存移動距離现斋,立即保存的話會產(chǎn)生動畫卡頓
                  setTimeout(() => {
                    this.offsetInfo.stash()
                    this.isSliding = false
                  }, 100)
                }
              }, () => {
                this.setOffSet(event, true)
              })
            } else {
              this.offsetInfo.stash() //動畫結(jié)束
            }
          }),

        //雙擊
        TapGesture({ count: 2 })
          .onAction((event: GestureEvent) => {
            this.onDoubleClick()
          })
      )
    )
  }

  onDoubleClick() {
    if (this.scaleInfo.scaleValue > this.scaleInfo.minScaleValue) {
      //雙擊時已經(jīng)放大了
      this.scaleInfo.scaleValue = this.scaleInfo.minScaleValue
    } else {
      this.scaleInfo.scaleValue = this.scaleInfo.mediumScaleValue
    }
    this.scaleInfo.lastValue = this.scaleInfo.scaleValue

    //matrix默認(rèn)縮放中心為組件中心
    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
    // 縮放時不允許大于最大縮放,不允許小于默認(rèn)大小
    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默認(rèn)縮放中心為組件中心
    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) //還未縮放就進(jìn)行滑動庄蹋,則輪播圖翻頁
    } else {
      this.setOffSet(event, false)
    }
  }

  //設(shè)置圖片偏移量
  setOffSet(event: GestureEvent, isFly: boolean) {
    //滑動到邊界之后進(jìn)行二次滑動,則輪播圖翻頁:注意這里需要區(qū)分左右兩個邊界的單獨(dú)滑動
    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 //預(yù)計X軸要移動的距離
      } else {
        preScrollX = this.offsetInfo.lastX + event.offsetX //預(yù)計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ù)計Y軸要移動的距離
      } else {
        preScrollY = this.offsetInfo.lastY + event.offsetY //預(yù)計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
    }
  }

  //滑動圖片時進(jìn)行輪播圖翻頁
  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 //最大默認(rèn)縮放值
  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();
  }

  //分頁加載時不要使用限书,會重新加載所有數(shù)據(jù)導(dǎo)致卡頓
  public appendAllData(addData: ObservedArray<T>): void {
    this.dataArray.push(...addData);
    this.notifyDataReload()
  }

  //分頁加載時不要使用,會重新加載所有數(shù)據(jù)導(dǎo)致卡頓
  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)
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載章咧,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者倦西。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赁严,隨后出現(xiàn)的幾起案子扰柠,更是在濱河造成了極大的恐慌,老刑警劉巖疼约,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卤档,死亡現(xiàn)場離奇詭異,居然都是意外死亡忆谓,警方通過查閱死者的電腦和手機(jī)裆装,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門踱承,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倡缠,“玉大人,你說我怎么就攤上這事茎活£悸伲” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵载荔,是天一觀的道長盾饮。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么丘损? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任普办,我火速辦了婚禮,結(jié)果婚禮上徘钥,老公的妹妹穿的比我還像新娘衔蹲。我一直安慰自己,他們只是感情好呈础,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布舆驶。 她就那樣靜靜地躺著,像睡著了一般而钞。 火紅的嫁衣襯著肌膚如雪沙廉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天臼节,我揣著相機(jī)與錄音撬陵,去河邊找鬼。 笑死网缝,一個胖子當(dāng)著我的面吹牛袱结,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播途凫,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼垢夹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起俺夕,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤靡羡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后而晒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阅畴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年倡怎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贱枣。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡监署,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纽哥,到底是詐尸還是另有隱情钠乏,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布春塌,位于F島的核電站晓避,受9級特大地震影響簇捍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俏拱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一暑塑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锅必,春花似錦梯投、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尔许,卻和暖如春么鹤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背味廊。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工蒸甜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人余佛。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓柠新,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辉巡。 傳聞我的和親對象是個殘疾皇子恨憎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容