• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import Matrix4 from '@ohos.matrix4';
17import { MediaItem } from './MediaItem';
18import { Log } from '../../../utils/Log';
19import { BroadCast } from '../../../utils/BroadCast';
20import { MathUtil } from '../../../utils/MathUtil';
21import { Constants as PhotoConstants } from './Constants';
22import { ScreenManager } from '../../common/ScreenManager';
23import { PhotoItemGestureCallback } from '../../browser/photo/PhotoItemGestureCallback';
24import { BigDataConstants, ReportToBigDataUtil } from '../../../utils/ReportToBigDataUtil';
25import { UiUtil } from '../../../utils/UiUtil';
26
27const TAG: string = 'common_EventPipeline';
28
29export type AnimationParam = {
30  duration: number,
31  curve: Curve
32};
33
34export class EventPipeline {
35  // 上次平移
36  private lastOffset: [number, number] = [0, 0];
37
38  // 当前平移
39  private offset: [number, number] = [0, 0];
40
41  // 上次缩放值
42  private lastScale: number = 1.0;
43
44  // 默认缩放值
45  private defaultScale: number = 1.0;
46
47  // 当前缩放值
48  private scale: number = 1.0;
49
50  // 缩放中心点,是相对于控件的百分比位置,非绝对位置
51  private center: [number, number] = [0.5, 0.5];
52
53  // 最左缩放中心,(1 - leftMost)即为最右缩放中心
54  private leftMost: number = 0.0;
55
56  // 最上缩放中心,(1 - topMost)即为最下缩放中心
57  private topMost: number = 0.0;
58
59  // 双击缩放比例
60  private doubleTapScale: number = 1.0;
61
62  // 最大缩放比例
63  private maxScale: number = 1.0;
64
65  // 是否已经到达最左边
66  private hasReachLeft: boolean = true;
67
68  // 是否已经到达最右边
69  private hasReachRight: boolean = true;
70
71  // 事件总线
72  private broadCast: BroadCast;
73
74  // 数据单个条目项
75  private item: MediaItem;
76
77  // 事件总线
78  private gestureCallback: PhotoItemGestureCallback;
79
80  // 宽度
81  private width: number;
82
83  // 高度
84  private height: number;
85
86  // 大图显示控件宽度
87  private componentWidth: number = vp2px(ScreenManager.getInstance().getWinLayoutWidth());
88
89  // 大图显示控件高度
90  private componentHeight: number = vp2px(ScreenManager.getInstance().getWinLayoutHeight());
91
92  // 是否在做动画
93  private isInAnimation: boolean = false;
94
95  // 下拉返回flag,防止触发多次
96  private isExiting: boolean = false;
97
98  private touchEventStartX: number = 0;
99
100  private touchEventStartY: number = 0;
101
102  private touchEventStartId: number = 0;
103
104  private isPullDownAndDragPhoto: boolean = false;
105
106  constructor(broadCastParam: BroadCast, item: MediaItem, gestureCallback: PhotoItemGestureCallback) {
107    this.broadCast = broadCastParam;
108    this.item = item;
109    this.gestureCallback = gestureCallback;
110    this.width = this.item.imgWidth == 0 ? PhotoConstants.DEFAULT_SIZE : this.item.imgWidth;
111    this.height = this.item.imgHeight == 0 ? PhotoConstants.DEFAULT_SIZE : this.item.imgHeight;
112    this.evaluateScales();
113  }
114
115  onDataChanged(item: MediaItem) {
116    this.item = item;
117    this.width = this.item.imgWidth == 0 ? PhotoConstants.DEFAULT_SIZE : this.item.imgWidth;
118    this.height = this.item.imgHeight == 0 ? PhotoConstants.DEFAULT_SIZE : this.item.imgHeight;
119    this.evaluateScales();
120  }
121
122  setDefaultScale(scale) {
123    this.defaultScale = scale;
124    this.lastScale = scale;
125  }
126
127  onComponentSizeChanged(componentWidth: number, componentHeight: number) {
128    Log.info(TAG, `onComponentSizeChanged componentWidth ${componentWidth} componentHeight ${componentHeight}`)
129    this.componentWidth = componentWidth;
130    this.componentHeight = componentHeight;
131    this.evaluateScales();
132    let animationEndMatrix: Matrix4.Matrix4Transit = this.evaluateAnimeMatrix(this.lastScale, this.center);
133    this.startAnimation(animationEndMatrix);
134  }
135
136  public canTouch(): Boolean {
137    return !(this.isInAnimation || this.isExiting);
138  }
139
140  onTouch(event: TouchEvent) {
141    Log.debug(TAG, `onTouch trigger: ${event.type}, ${[this.isInAnimation, this.isExiting]}`);
142    if (!this.canTouch()) {
143      return;
144    }
145    if (event.type == TouchType.Down || event.type == TouchType.Up) {
146      this.emitDirectionChange();
147    }
148    this.verifyPullDownAndDragPhotoStatus(event);
149
150    // 普通场景up时记录scale及offset,动画场景动画结束时记录
151    if (event.type == TouchType.Up) {
152      this.lastOffset = this.evaluateOffset();
153      this.lastScale = this.lastScale * this.scale;
154      // 必须重置scale及offset,否则多次操作间会串,但是center不用清,因为this.scale为1时center不起作用
155      this.scale = 1;
156      this.offset = [0, 0];
157    }
158  }
159
160  verifyPullDownAndDragPhotoStatus(event: TouchEvent): void {
161    let scale = this.lastScale * this.scale;
162    let isEnlarged = Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS));
163    let touchList = event.touches;
164
165    if (touchList.length > 1) {
166      return;
167    }
168    if (event.type === TouchType.Down) {
169      // 重置拖动图片场景参数
170      if (this.canPullDownAndDragPhoto()) {
171        this.touchEventStartX = 0;
172        this.touchEventStartY = 0;
173        this.isPullDownAndDragPhoto = false;
174      }
175
176      this.touchEventStartX = touchList[0].screenX;
177      this.touchEventStartY = touchList[0].screenY;
178      this.touchEventStartId = touchList[0].id;
179    } else if (event.type === TouchType.Move && this.touchEventStartId === touchList[0].id) {
180      let touchX = touchList[0].screenX;
181      let touchY = touchList[0].screenY;
182      let deltaX = Math.abs(touchX - this.touchEventStartX);
183      let deltaY = touchY - this.touchEventStartY;
184      if (deltaY - deltaX > PhotoConstants.CAN_PULL_DOWN_DRAG_THRESHOLD && !isEnlarged) {
185        this.isPullDownAndDragPhoto = true;
186      }
187    }
188
189    if (this.isPullDownAndDragPhoto) {
190      this.gestureCallback.onDirectionChangeRespond(PanDirection.All);
191    }
192    Log.debug(TAG, 'canPullDownAndDragPhoto ' + this.isPullDownAndDragPhoto);
193  }
194
195  canPullDownAndDragPhoto(): boolean {
196    return this.isPullDownAndDragPhoto;
197  }
198
199  onMoveStart(offsetX: number, offsetY: number) {
200    if (this.isInAnimation || this.isExiting) {
201      return;
202    }
203    // 拖动开始时重置offset,防止跳变
204    this.offset = [0, 0];
205    this.evaluateBounds();
206    let scale = this.lastScale * this.scale;
207    if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
208      // 有缩放拖动时隐藏bars
209      this.broadCast.emit(PhotoConstants.HIDE_BARS, [null]);
210    }
211    if (scale.toFixed(PhotoConstants.RESERVED_DIGITS) === this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS) && offsetY > 0) {
212      // 下拉返回开始先隐藏详情
213      this.broadCast.emit(PhotoConstants.PULL_DOWN_START, [null]);
214    }
215  }
216
217  /**
218   * 每次回调回来的是相对于此次手势开始点的位移
219   *
220   * @param offsetX offsetX
221   * @param offsetY offsetY
222   */
223  onMove(offsetX: number, offsetY: number) {
224    if (this.isInAnimation || this.isExiting) {
225      return;
226    }
227    let scale = this.lastScale * this.scale;
228    let limits = this.evaluateOffsetRange(scale);
229    let measureX = this.lastOffset[0] + (this.center[0] - 0.5) * this.componentWidth * (this.defaultScale - this.scale) * this.lastScale;
230    let measureY = this.lastOffset[1] + (this.center[1] - 0.5) * this.componentHeight * ((this.defaultScale - this.scale)) * this.lastScale;
231    let moveX = offsetX;
232    let moveY = offsetY;
233    let offX = measureX + moveX;
234    let offY = measureY + moveY;
235
236    if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
237      // 非缩小场景,始终限制x方向上的offset
238      offX = MathUtil.clamp(offX, limits[0], limits[1]);
239      if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
240        // 不可下拉返回场景,限制y
241        offY = MathUtil.clamp(offY, limits[PhotoConstants.NUMBER_2], limits[PhotoConstants.NUMBER_3]);
242      } else {
243        // 可下拉返回场景,只限制y向上拖动,即限制下界
244        offY = Math.max(limits[PhotoConstants.NUMBER_2], offY);
245      }
246    }
247    let tmpX = offX - measureX;
248    let tmpY = offY - measureY;
249    this.offset = [tmpX, tmpY];
250    this.emitTouchEvent();
251  }
252
253  onMoveEnd(offsetX: number, offsetY: number) {
254    if (this.isInAnimation || this.isExiting) {
255      return;
256    }
257
258    let isStartPullDown = false;
259    let scale = this.lastScale * this.scale;
260    if (scale.toFixed(PhotoConstants.RESERVED_DIGITS) === this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS) &&
261    offsetY > PhotoConstants.PULL_DOWN_THRESHOLD) {
262      // 触发下拉返回
263      this.emitPullDownToBackEvent();
264      isStartPullDown = true;
265    } else if (scale.toFixed(PhotoConstants.RESERVED_DIGITS) === this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS) &&
266    offsetY < -PhotoConstants.PULL_DOWN_THRESHOLD && !this.canPullDownAndDragPhoto()) {
267      // 触发上划,但当已处于下拉拖动图片状态时,不触发上滑
268      this.emitPullUpToDisplayEvent();
269    } else if (scale.toFixed(PhotoConstants.RESERVED_DIGITS) === this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS)) {
270      // 未到阈值,触发重置动画
271      this.startAnimation(Matrix4.identity().scale({
272        x: this.defaultScale,
273        y: this.defaultScale
274      }).copy());
275      this.emitPullDownCancelEvent()
276    } else {
277      this.emitDirectionChange();
278    }
279
280    // 非下拉返回场景,重置拖动相关参数
281    if (!isStartPullDown) {
282      UiUtil.resetGeometryTransitionParams();
283      this.touchEventStartX = 0;
284      this.touchEventStartY = 0;
285      this.isPullDownAndDragPhoto = false;
286    }
287  }
288
289  onScaleStart(scale: number, centerX: number, centerY: number) {
290    Log.info(TAG, `onScaleStart: ${[this.isInAnimation, this.isExiting]}`);
291    if (this.isInAnimation || this.isExiting) {
292      return;
293    }
294    // scale开始时重置this.scale为1
295    this.scale = 1;
296    this.evaluateBounds();
297    // Adjust action bar status
298    this.broadCast.emit(PhotoConstants.HIDE_BARS, []);
299    this.center = this.evaluateCenter(centerX, centerY);
300  }
301
302  onScale(scale: number) {
303    Log.info(TAG, `onScale: ${[this.isInAnimation, this.isExiting]}, scale: ${scale}`);
304    if (this.isInAnimation || this.isExiting) {
305      return;
306    }
307    this.evaluateBounds();
308    this.scale = scale;
309    if (this.lastScale * scale <= PhotoConstants.COMPONENT_SCALE_FLOOR) {
310      this.scale = PhotoConstants.COMPONENT_SCALE_FLOOR / this.lastScale;
311    }
312    if (this.lastScale * scale >= this.maxScale * PhotoConstants.OVER_SCALE_EXTRA_FACTOR) {
313      this.scale = this.maxScale * PhotoConstants.OVER_SCALE_EXTRA_FACTOR / this.lastScale;
314    }
315    this.emitTouchEvent();
316  }
317
318  onScaleEnd() {
319    Log.info(TAG, `onScaleEnd: ${[this.isInAnimation, this.isExiting]}`);
320    if (this.isInAnimation || this.isExiting) {
321      return;
322    }
323    this.evaluateBounds();
324    let scale = this.lastScale * this.scale;
325    if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) >= Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS)) && scale <= this.maxScale) {
326      Log.info(TAG, `does not need to do animation: ${scale}`);
327      this.emitDirectionChange();
328      return;
329    }
330    let animationEndMatrix: Matrix4.Matrix4Transit = null;
331    if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) <= Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
332      // 缩小过小,触发恢复的动画
333      animationEndMatrix = Matrix4.identity().scale({
334        x: this.defaultScale,
335        y: this.defaultScale
336      }).copy();
337    } else {
338      // 放大时做缩回maxScale的动画
339      animationEndMatrix = this.evaluateAnimeMatrix(this.maxScale, this.center);
340    }
341    this.startAnimation(animationEndMatrix);
342  }
343
344  /**
345   * 双击触发缩放,如果当前scale小于等于1,缩放到doubleTapScale;如果当前scale大于1,缩放到1;
346   *
347   * @param centerX 双击位置
348   * @param centerY 双击位置
349   */
350  onDoubleTap(centerX: number, centerY: number) {
351    if (this.isInAnimation || this.isExiting) {
352      Log.debug(TAG, `[onDoubleTap] not avaliable: ${[this.isInAnimation, this.isExiting]}`);
353      return;
354    }
355    // Adjust action bar status
356    this.broadCast.emit(PhotoConstants.HIDE_BARS, []);
357    let matrix: Matrix4.Matrix4Transit = undefined;
358    if (Number(this.lastScale.toFixed(PhotoConstants.RESERVED_DIGITS)) * this.scale > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
359      // scale大于1时缩放到原始状态
360      matrix = Matrix4.identity().scale({
361        x: this.defaultScale,
362        y: this.defaultScale
363      }).copy();
364      Log.debug(TAG, '[onDoubleTap] matrix scale 1');
365    } else {
366      // 放大状态根据点击位置计算放大中心
367      this.center = this.evaluateCenter(centerX, centerY);
368      // 图片宽高比小于控件宽高比时,centerX置为0.5,反之centerY置为0.5,保证双击放大后短边靠边
369      if (this.width / this.height < this.componentWidth / this.componentHeight) {
370        this.center[0] = PhotoConstants.COMPONENT_SCALE_FLOOR;
371      } else {
372        this.center[1] = PhotoConstants.COMPONENT_SCALE_FLOOR;
373      }
374      matrix = this.evaluateAnimeMatrix(this.doubleTapScale * this.defaultScale, this.center);
375      Log.debug(TAG, '[onDoubleTap] matrix scale center');
376    }
377    this.startAnimation(matrix);
378  }
379
380  reset() {
381    this.lastOffset = [0, 0];
382    this.offset = [0, 0];
383    this.lastScale = 1.0;
384    this.scale = 1;
385    this.hasReachLeft = true;
386    this.hasReachRight = true;
387    this.isInAnimation = false;
388    this.isExiting = false;
389    this.emitDirectionChange();
390  }
391
392  onDisAppear() {
393    Log.info(TAG, 'onDisAppear');
394    this.reset();
395  }
396
397  /**
398   * 动画结束,根据结束的变换矩阵刷新当前的各个参数值,保证连续性,防止下次手势操作时发生跳变
399   *
400   * @param animationEndMatrix 结束时的变换矩阵
401   */
402  onAnimationEnd(animationEndMatrix: Matrix4.Matrix4Transit): void {
403    if (animationEndMatrix) {
404      // @ts-ignore
405      let matrix = animationEndMatrix.matrix4x4;
406      Log.info(TAG, `onAnimationEnd: ${matrix}`);
407      this.lastScale = matrix[0];
408      this.scale = 1;
409      this.lastOffset = [matrix[PhotoConstants.NUMBER_12], matrix[PhotoConstants.NUMBER_13]];
410      this.offset = [0, 0];
411      this.evaluateBounds();
412      this.isInAnimation = false;
413      this.emitDirectionChange();
414    }
415  }
416
417  public setSwipeStatus(disable: Boolean): void {
418    this.broadCast.emit(PhotoConstants.SET_DISABLE_SWIPE, [disable]);
419  }
420
421  startAnimation(animationEndMatrix: Matrix4.Matrix4Transit): void {
422    this.isInAnimation = true;
423    let animationOption: AnimationParam = {
424      duration: PhotoConstants.OVER_SCALE_ANIME_DURATION,
425      curve: Curve.Ease
426    };
427    this.gestureCallback.onAnimationEventRespond(animationOption, animationEndMatrix);
428    this.setSwipeStatus(true);
429  }
430
431  isDefaultScale(): boolean {
432    return Number(this.lastScale.toFixed(PhotoConstants.RESERVED_DIGITS)) ===
433    Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS));
434  }
435
436  private emitDirectionChange(): void {
437    /**
438     * reachLeft reachRight scale>1这三个变量,只可能有下面五种情况(scale<=1下reachLeft、reachRight必然为true):
439     * T T T:Vertical
440     * T T F:Vertical(大图初始状态)
441     * T F T:Vertical | Left
442     * F T T:Vertical | Right
443     * F F T:All
444     */
445    let direction: any = undefined;
446    let scale = this.lastScale * this.scale;
447    let isEnlarged = Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS));
448    if (!this.hasReachLeft && !this.hasReachRight && isEnlarged) {
449      direction = PanDirection.All;
450    } else if (!this.hasReachLeft && this.hasReachRight && isEnlarged) {
451      direction = PanDirection.Vertical | PanDirection.Right;
452    } else if (this.hasReachLeft && !this.hasReachRight && isEnlarged) {
453      direction = PanDirection.Vertical | PanDirection.Left;
454    } else {
455      direction = PanDirection.Vertical;
456    }
457
458    if (this.isExiting) {
459      return;
460    }
461    if (direction == PanDirection.Vertical || direction == (PanDirection.Vertical | PanDirection.Left) ||
462    direction == (PanDirection.Vertical | PanDirection.Right)) {
463      this.setSwipeStatus(false);
464    } else {
465      this.setSwipeStatus(true);
466    }
467    Log.debug(TAG, `emitDirectionChange reaches: ${[this.hasReachLeft, this.hasReachRight]}, scale ${scale}, direction: ${direction}`);
468    this.gestureCallback.onDirectionChangeRespond(direction);
469  }
470
471  private evaluateOffset(): [number, number] {
472    Log.info(TAG, `evaluateOffset lastOffset: ${this.lastOffset}, offset: ${this.offset}`);
473    let centerX = (this.center[0] - 0.5) * this.componentWidth * (this.defaultScale - this.scale) * this.lastScale;
474    let centerY = (this.center[1] - 0.5) * this.componentHeight * (this.defaultScale - this.scale) * this.lastScale;
475    let offsetX = this.lastOffset[0] + this.offset[0] + centerX;
476    let offsetY = this.lastOffset[1] + this.offset[1] + centerY;
477    Log.info(TAG, `evaluateOffset offsetX: ${offsetX}, offsetY: ${offsetY}`);
478    return [offsetX, offsetY];
479  }
480
481  private emitTouchEvent(): void {
482    let offset: [number, number];
483    let scale = this.lastScale * this.scale;
484    if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
485      let limits = this.evaluateOffsetRange(scale);
486      offset = this.evaluateOffset();
487      // 非缩小场景,始终限制x方向上的offset
488      offset[0] = MathUtil.clamp(offset[0], limits[0], limits[1]);
489      if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
490        // 不可下拉返回场景,限制y
491        offset[1] = MathUtil.clamp(offset[1], limits[PhotoConstants.NUMBER_2], limits[PhotoConstants.NUMBER_3]);
492      } else {
493        // 可下拉返回场景,只限制y向上拖动,即限制下界
494        offset[1] = Math.max(limits[PhotoConstants.NUMBER_2], offset[1]);
495      }
496    } else {
497      // 缩小时调整缩放中心为显示中心点
498      offset = [0, 0];
499    }
500    let moveX = offset[0];
501    let moveY = offset[1];
502    let matrix = Matrix4.identity().scale({
503      x: scale,
504      y: scale,
505    }).translate({
506      x: moveX,
507      y: moveY
508    }).copy();
509    Log.info(TAG, `emitTouchEvent lastOffset: ${this.lastOffset}, offset: ${this.offset}, center: ${this.center}, scale: ${[this.lastScale, this.scale]}`);
510    this.gestureCallback.onTouchEventRespond(matrix);
511    this.evaluateBounds();
512  }
513
514  /**
515   * 大图放大倍率计算,主要包含:
516   * 1. 最大放大倍率maxScale
517   * 最大可放大到 maxScale * PhotoConstants.OVER_SCALE_EXTRA_FACTOR,然后回弹至maxScale;
518   * 如果小于doubleTapScale * PhotoConstants.MAX_SCALE_EXTRA_FACTOR,取该值作为放大倍率;
519   * 2. 双击放大倍率doubleTapScale
520   * 默认使图片长边对齐屏幕边缘,如果小于PhotoConstants.SAME_RATIO_SCALE_FACTOR(4/3)取该值
521   */
522  private evaluateScales(): void {
523    if (this.width * this.componentHeight < this.componentWidth * this.height) {
524      // 宽高比小于控件显示宽高比,控件高度与图片高度相等
525      this.maxScale = this.height / this.componentHeight;
526      // 双击放大的scale保证左右充满边界
527      this.doubleTapScale = this.componentWidth * this.height / this.width / this.componentHeight;
528      // leftMost = (1 - dspW / compW) / 2 = (1 - compH * imgW / imgH / compW) / 2
529      this.leftMost = (1 - this.componentHeight * this.width / this.height / this.componentWidth) / 2;
530      this.topMost = 0.0;
531    } else if (this.width * this.componentHeight == this.componentWidth * this.height) {
532      // 宽高比等于控件显示宽高比
533      this.doubleTapScale = PhotoConstants.SAME_RATIO_SCALE_FACTOR;
534      this.maxScale = this.doubleTapScale * PhotoConstants.MAX_SCALE_EXTRA_FACTOR;
535      this.leftMost = 0;
536      this.topMost = 0;
537    } else {
538      // 宽高比大于控件显示宽高比,控件宽度与图片宽度相等
539      this.maxScale = this.width / this.componentWidth;
540      // 双击放大的scale保证上下充满边界
541      this.doubleTapScale = this.componentHeight * this.width / this.height / this.componentWidth;
542      this.leftMost = 0.0;
543      // topMost = (1 - dspH / compH) / 2 = (1 - compW * imgH / imgW / compH) / 2
544      this.topMost = (1 - this.componentWidth * this.height / this.width / this.componentHeight) / 2;
545    }
546
547    this.maxScale = Math.max(this.maxScale, PhotoConstants.COMPONENT_SCALE_CEIL);
548    if (this.doubleTapScale > this.maxScale) {
549      this.maxScale = this.doubleTapScale * PhotoConstants.MAX_SCALE_EXTRA_FACTOR;
550    }
551    Log.debug(TAG, `evaluateScales: ${this.width}*${this.height} & ${this.componentWidth}*${this.componentHeight}, max: ${this.maxScale}, most: [${this.leftMost},${this.topMost}], double: ${this.doubleTapScale}`);
552  }
553
554  private evaluateCompBounds(): [number, number] {
555    let scale = this.lastScale * this.scale;
556    let offset = this.evaluateOffset();
557    // 组件左上角坐标,因放大带来的偏移是-compW*(scale-1)/2,再加上偏移,得到控件左边界,上边界同理
558    let result: [number, number] = [
559      offset[0] - this.componentWidth * (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) - Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) / 2,
560      offset[1] - this.componentHeight * (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) - Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) / 2
561    ];
562    Log.debug(TAG, `evaluateCompBounds: ${result}`);
563    return result;
564  }
565
566  private evaluateImgDisplaySize(): [number, number] {
567    let screenScale = 1;
568    let widthScale = this.componentWidth / this.item.imgWidth;
569    let heightScale = this.componentHeight / this.item.imgHeight;
570    screenScale = widthScale > heightScale ? heightScale : widthScale;
571    let scale = this.lastScale * this.scale * screenScale;
572    let imgDisplayWidth = 0;
573    let imgDisplayHeight = 0;
574    imgDisplayWidth = this.width * scale;
575    imgDisplayHeight = this.height * scale;
576    return [imgDisplayWidth, imgDisplayHeight];
577  }
578
579  private evaluateImgDisplayBounds(): [number, number] {
580    // 组件左边界,因放大带来的偏移是-compW*(scale-1)/2,再加上手势的偏移,得到控件左边界,上边界同理
581    let scale = this.lastScale * this.scale;
582    let leftTop = this.evaluateCompBounds();
583    let imgDisplaySize: [number, number] = this.evaluateImgDisplaySize();
584    let imgDisplayWidth = imgDisplaySize[0];
585    let imgDisplayHeight = imgDisplaySize[1];
586    let imgLeftBound = 0;
587    let imgTopBound = 0;
588    if (this.width / this.height > this.componentWidth / this.componentHeight) {
589      imgLeftBound = leftTop[0];
590      imgTopBound = leftTop[1] + (this.componentHeight * scale - imgDisplayHeight) / 2;
591    } else {
592      // 控件宽度减掉图片宽度,除以2就能得到图片左边到控件左边的距离,加上offsetX就是就是图片当前显示的左边界
593      imgLeftBound = (this.componentWidth * scale - imgDisplayWidth) / 2 + leftTop[0];
594      imgTopBound = leftTop[1];
595    }
596    return [imgLeftBound, imgTopBound];
597  }
598
599  // 计算图片显示边界
600  private evaluateBounds(): void {
601    let imgDisplaySize: [number, number] = this.evaluateImgDisplaySize();
602    let imgDisplayWidth = imgDisplaySize[0];
603
604    let imgDisplayBounds = this.evaluateImgDisplayBounds();
605    let imgLeftBound = imgDisplayBounds[0];
606
607    // 因底层计算有误差(小数点后6位),不能以0作为精确边界,左右分别容错1像素
608    this.hasReachLeft = imgLeftBound > -1;
609    this.hasReachRight = imgLeftBound + imgDisplayWidth < this.componentWidth + 1;
610    Log.info(TAG, `evaluateBounds scale: ${this.hasReachLeft}, offset: ${this.hasReachRight}`);
611
612  }
613
614  /**
615   * 计算当前scale下x及y方向上的offset上下界
616   *
617   * @param scale 当前控件显示倍率,通常是 this.lastScale * this.scale
618   * @returns 0&1 x方向offset下界&上界,2&3 y方向offset下界&上界
619   */
620  private evaluateOffsetRange(scale: number): [number, number, number, number] {
621    let result: [number, number, number, number] = [0, 0, 0, 0];
622    let screenScale = 1;
623    let widthScale = this.componentWidth / this.item.imgWidth;
624    let heightScale = this.componentHeight / this.item.imgHeight;
625    screenScale = widthScale > heightScale ? heightScale : widthScale;
626    let left = (screenScale * scale * this.width - this.componentWidth) / 2;
627    let top = (screenScale * scale * this.height - this.componentHeight) / 2;
628    top = Math.max(top, 0);
629    left = Math.max(left, 0);
630    result = [-left, left, -top, top];
631    Log.info(TAG, `evaluateOffsetRange scale: ${scale}, defaultScale: ${this.defaultScale}, result: ${result}`);
632    return result;
633  }
634
635  private emitPullDownToBackEvent(): void {
636    Log.debug(TAG, `emitPullDownToBackEvent`);
637    if (this.isExiting) {
638      Log.info(TAG, `emitPullDownToBack isExiting: ${this.isExiting}`);
639      return;
640    }
641    this.isExiting = true;
642    Log.info(TAG, `emitPullDownToBack change isExiting into: ${this.isExiting}`);
643    this.broadCast.emit(PhotoConstants.PULL_DOWN_END, []);
644    ReportToBigDataUtil.report(BigDataConstants.PHOTO_PULL_DOWN_ID, null);
645  }
646
647  private emitPullUpToDisplayEvent(): void {
648    Log.debug(TAG, 'emitPullUpToDisplayEvent');
649    if (!this.canTouch()) {
650      return;
651    }
652  }
653
654  private emitPullDownCancelEvent(): void {
655    Log.debug(TAG, 'emitPullDownCancelEvent');
656    this.broadCast.emit(PhotoConstants.PULL_DOWN_CANCEL, []);
657  }
658
659  /**
660   * 计算当前缩放中心相对控件的百分比位置
661   *
662   * @param centerX 触摸点在屏幕上的绝对位置
663   * @param centerY 触摸点在屏幕上的绝对位置
664   * @returns 当前缩放中心相对控件的百分比位置
665   */
666  private evaluateCenter(centerX: number, centerY: number): [number, number] {
667    // 计算出控件左上角相对于当前显示左上角的坐标
668    let scale = this.lastScale * this.scale;
669    let leftTop = this.evaluateCompBounds();
670
671    // 得出相对于控件的触摸坐标
672    let cxRelativeToComp = MathUtil.clamp((centerX - leftTop[0]) / (this.componentWidth * scale), this.leftMost, 1 - this.leftMost);
673    let cyRelativeToComp = MathUtil.clamp((centerY - leftTop[1]) / (this.componentHeight * scale), this.topMost, 1 - this.topMost);
674
675    let imgDisplaySize: [number, number] = this.evaluateImgDisplaySize();
676    let imgDisplayWidth = imgDisplaySize[0];
677    let imgDisplayHeight = imgDisplaySize[1];
678
679    let imgDisplayBounds = this.evaluateImgDisplayBounds();
680    let imgLeftBound = imgDisplayBounds[0];
681    let imgTopBound = imgDisplayBounds[1];
682
683    // 触摸中心点在图片显示区域外时,取中点
684    if (this.width / this.height > this.componentWidth / this.componentHeight) {
685      if (centerY < imgTopBound || centerY > imgTopBound + imgDisplayHeight) {
686        cyRelativeToComp = 0.5;
687      }
688    } else {
689      if (centerX < imgLeftBound || centerX > imgLeftBound + imgDisplayWidth) {
690        cxRelativeToComp = 0.5;
691      }
692    }
693
694    // 算出触摸的中心点百分比
695    let center: [number, number] = [cxRelativeToComp, cyRelativeToComp];
696    Log.info(TAG, `evaluateCenter center: ${center}, ${[centerX, centerY]}, size: ${imgDisplaySize}, bounds: ${imgDisplayBounds}, leftTop: ${leftTop}, compSize: ${[this.componentWidth * scale, this.componentHeight * scale]}`);
697    return center;
698  }
699
700  private evaluateAnimeMatrix(scale: number, center: [number, number]): Matrix4.Matrix4Transit {
701    let offset = [
702      this.lastOffset[0] + this.offset[0] + (center[0] - 0.5) * this.componentWidth * (this.defaultScale - scale / this.lastScale) * this.lastScale,
703      this.lastOffset[1] + this.offset[1] + (center[1] - 0.5) * this.componentHeight * (this.defaultScale - scale / this.lastScale) * this.lastScale
704    ]
705    if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
706      let limits = this.evaluateOffsetRange(scale);
707      offset[0] = MathUtil.clamp(offset[0], limits[0], limits[1]);
708      // 非缩小场景,始终限制x方向上的offset
709      offset[0] = MathUtil.clamp(offset[0], limits[0], limits[1]);
710      if (Number(scale.toFixed(PhotoConstants.RESERVED_DIGITS)) > Number(this.defaultScale.toFixed(PhotoConstants.RESERVED_DIGITS))) {
711        // 不可下拉返回场景,限制y
712        offset[1] = MathUtil.clamp(offset[1], limits[PhotoConstants.NUMBER_2], limits[PhotoConstants.NUMBER_3]);
713      } else {
714        // 可下拉返回场景,只限制y向上拖动,即限制下界
715        offset[1] = Math.max(limits[PhotoConstants.NUMBER_2], offset[1]);
716      }
717    } else {
718      // 缩小时调整缩放中心为显示中心点
719      offset = [0, 0];
720    }
721    let animationEndMatrix = Matrix4.identity().copy().scale({
722      x: scale,
723      y: scale,
724    }).translate({
725      x: offset[0],
726      y: offset[1]
727    }).copy();
728    Log.debug(TAG, `evaluateAnimeMatrix scale: ${scale}, center: ${center}, result: ${animationEndMatrix}`);
729    return animationEndMatrix;
730  }
731}