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}