/*
* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import lazy { ZoomNodeController } from './ZoomNodeController';
import curves from '@ohos.curves';
import animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator';
import lazy { ZoomParam, ZoomPointInfo } from './ZoomParam';
import lazy { HiLog } from '../../utils/HiLog';
import lazy { EventBusManager } from '../../worker/eventbus/EventBusManager';
import lazy { EventBus } from '../../worker/eventbus/EventBus';
import lazy { Dispatch, OhCombinedState, Unsubscribe, getStates, reduxSubscribe } from '../../redux';
import lazy { ActionData } from '../../redux/actions/Action';
import lazy { ZoomAction } from './ZoomAction';
import lazy { VibratorService } from '../../component/vibration/VibratorService';
import camera from '@ohos.multimedia.camera';
import lazy { ComponentIdKeys } from '../../utils/ComponentIdKeys';
import lazy { ModeType } from '../../mode/ModeType';
import lazy { RecordingState } from '../recordcontrol/RecordAction';
import lazy { CameraRunningState, CameraStartType, CameraStartTypeStruct } from '../../camera/uithread/CameraAction';
import lazy { WindowDirection } from '../../utils/WindowDirection';
import lazy { CameraBasicService } from '../../camera/uithread/CameraBasicService';
import lazy { FunctionId } from '../core/functionproperty/FunctionId';
import lazy { SavePowerModeActionData, XComponentSize } from '../../utils/types';
import lazy { ZoomInnerStatus, ZoomOuterStatus, ZoomStateType } from './ZoomStateType';
import lazy { ZoomOperation, ZoomScaleStruct } from './ZoomOperation';
import lazy { ChangeState } from '../../redux/Store';
import lazy { BaseComponent } from '../../worker/BaseComponent';
import lazy { EntryExitAnimParam } from 'appnative/Index';
import lazy { PhotoFormatMode } from '../enumbase/PhotoFormatMode';
import lazy { CameraAppCapability } from '../../camera/CameraAppCapability';
import lazy { FeatureManager } from '../core/FeatureManager';
import lazy { OutputType } from '../outputswitcher/OutputType';
import lazy { CameraProxy } from '../../camera/uithread/CameraProxy';
import lazy { DeviceInfo } from '../../component/deviceinfo/DeviceInfo';
import lazy { JSON } from '@kit.ArkTS';
import lazy { StoreManager } from '../../worker/StoreManager';
import lazy { AdaptiveLayoutService, ZOOM_VIEW_HEIGHT } from '../../service/UIAdaptive/AdaptiveLayoutService';
import lazy {
EnableData, RouletteZoomState, ZoomFuncValueStruct, ZoomViewDispatcher
} from './ZoomStruct';
import lazy { GlobalContext } from '../../utils/GlobalContext';
import lazy { OutputSwitcher } from '../outputswitcher/OutputSwitcher';
import ResGetter from '../../utils/ResGetter';
import lazy { CommonConstants } from '../../statistics/CommonConstants';
import lazy { TipService } from '../../component/tip/TipService';
import lazy { AccessibilityUtils } from '../../utils/AccessibilityUtils';
import lazy { AnimSpecifications } from '../../animation/AnimSpecifications';
import lazy { UIOperationType } from '../../component/uicomponent/UIOperationType';
import lazy { KeyCode } from '@ohos.multimodalInput.keyCode';
import lazy { PickerUtils } from '../../utils/PickerUtils';
import lazy { PickerWindowType } from '../../component/picker/PickerWindowType';
import lazy { OutputOperation } from '../outputswitcher/OutputOperation';
import lazy { DisplayService } from '../../service/UIAdaptive/DisplayService';
import lazy { SelfieStickKey } from '../../service/selfieStick/SelfieStickService';
import lottie, { AnimationItem } from '@ohos/lottie';
import lazy { RenderLocation } from '../core/functionproperty/RenderLocation';
import lazy { CaptureActionType } from '../../redux/actions/CaptureActionType';
import lazy { RecordActionType } from '../../redux/actions/RecordActionType';
import lazy { ActionType } from '../../redux/actions/ActionType';
import lazy { ContextActionType } from '../../redux/actions/ContextActionType';
import lazy { ZoomActionType } from '../../redux/actions/ZoomActionType';
import lazy { FunctionActionType } from '../../redux/actions/FunctionActionType';
import lazy { CameraActionType } from '../../redux/actions/CameraActionType';
import lazy { WindowActionType } from '../../redux/actions/WindowActionType';
import lazy { CollaborateControlActionType } from '../../redux/actions/CollaborateControlActionType';
import lazy { window } from '@kit.ArkUI';
import lazy { SystemLanguageUtil } from '../../utils/SystemLanguageUtil';
import lazy { QUICK_ZOOM_MARGIN_BOTTOM_28 } from '../../service/UIAdaptive/AdaptiveLandscapeLayoutService';
import lazy { BlurAnimateUtil } from '../../utils/BlurAnimateUtil';
import lazy { ZOOM_STEP } from '../../service/collaborateControl/CollaborateControlAction'
/* instrument ignore file */
const VIBRATOR_USAGE_PHYSIC = 'physicalFeedback';
const WAIT_TIME: number = 400;
export const QUICK_ZOOM_ITEM_SIZE: number = 32;
export const QUICK_ZOOM_POSITION_Y: number = 4;
const EPSILON: number = 0.1;
const FOCAL_ZOOM_EPSILON: number = 0.01;
const POINT_ZOOM: number = 1.5;
const POINT_ZOOM_EQUIVALENT_FOCAL: number = 35;
const TAG: string = 'RouletteZoomView';
const QUICK_ZOOM_MARGIN_LR_LONG: number = 34;
const QUICK_ZOOM_MARGIN_LR_SHORT: number = 16;
const selfieStickLongPressCount: number = 30; // 长按自拍杆,一秒内焦距变化的次数
const selfieStickLongPressZoom: number = 1.018; // 长按自拍杆,焦距一次的变化率,与之前一致
const setOnceSelfieStickZoom: number = 0.1; // 短按自拍杆一次,焦距的变化率,与之前一致
const SET_SELFIE_STICK_ZOOM_NUM: number = 10;
const COLORFUL_ZOOM_RING_CANVAS_PATH: string = 'common/lottie/colorful_zoom_ring.json';
const COLORFUL_ZOOM_RING_ANIM_NAME: string = 'colorfulZoomRing';
const INDICATOR_HEIGHT: number = 32;
const DISTANCE_INDICATOR_FOOTBAR: number = 4;
const ZOOM_CUSTOM_FILTER_VERTICAL_OFFSET: number = -160;
const DRAW_HEIGHT_COEFFICIENT: number = 0.16;
class LockedData {
public isLocked: boolean = false;
}
/*
* 快捷变焦点、轮盘变焦方案; UI布局、动效操作类;
* */
@Component
export struct RouletteZoomView {
private mBase: BaseComponent = new BaseComponent();
@State state: RouletteZoomState = new RouletteZoomState();
private mAction: ZoomViewDispatcher = new ZoomViewDispatcher();
private mEventBus: EventBus = EventBusManager.getInstance().getEventBus();
private mSubscriber: Unsubscribe = {
destroy: () => {
}
};
@State @Watch('changeRotateAngle') rotateAngleWatch: number = 0;
@State rotateAngle: number = 0;
@State windowHeight: number = 0;
@StorageLink('isLemCollaps') isLemCollaps: boolean = false;
// 变焦公共层变量
@State curZoomValue: number = 1.0; // 当前变焦值
@State isShowQuickZoom: boolean = true; // 是否显示快捷变焦点
@State isShowRouletteZoom: boolean = false; // 是否显示轮盘变焦条
@State isShowBigText: boolean = false; // 是否显示变焦大字体
@State quickZoomValArr: number[] = []; // 快捷变焦点对应值列表,产品配置项
@State @Watch('changeQuickZoomArr') quickZoomValArrLengthWatch: number = 0;
@State quickZoomValArrLength: number = 0;
@State isDepthFusionBarShow: boolean = false;
@State isStitchingTypeBarShow: boolean = false;
private isDepthFusionConflictLongPress: boolean = false;
private oldQuickZoomArrLength: number = 0;
@State opticalZoomValArr: number[] = []; // 轮盘变焦条光学变焦点对应值列表,产品配置项
@State @Watch('watchZoomInnerStatus') mZoomInnerStatus: ZoomInnerStatus = ZoomInnerStatus.QUICK_STATIC; // 变焦内部状态
private isExecAdsorptionType: number = 0; // 滑动变焦生命周期内,关键刻度吸附逃逸类型,1、2滑入滑出动效,5吸附暂态,0其它
private mExecAdsorptionTimerId: number = Number.MAX_VALUE; // 关键刻度吸附停留计时器
private mFrameAnimResultArr: AnimatorResult[] | null[] =
[null, null, null, null, null, null, null, null, null, null, null, null, null]; // 变焦帧回调动效发生器
// QuickZoom快捷变焦变量
@State curValIndex: number = 0; // 快捷变焦点对应当前index
private quickEquivalentFocalArr: number[] = []; // 快捷变焦点对应等效焦距
@State mQuickOtherAlpha: number = 1; // 快捷变焦-其他透明度
@State mQuickZoomBlur: number = 0; // 快捷变焦整体模糊
@State mQuickZoomPositionY: number = 0; // 快捷变焦整体位移
@State quickZoomScale: number = 1; // 快捷变焦-选中圆整体收缩比例
@State quickZoomAlpha: number = 1; // 快捷变焦-选中圆透明度
@State mQuickZoomOverColor: number = 0; // 快捷变焦整体叠加颜色
@State superMacroEnable: boolean = false; // 超级微距特性是否使能状态
// RouletteZoom轮盘变焦变量
private curZoomAngle: number = 0; // 当前焦距对应角度
private mNodeController: ZoomNodeController | undefined = undefined;
private opticalZoomDotIndexArr: number[] = []; // 光学变焦点所在的index
private opticalZoomDotAngleArr: number[] = []; // 光学变焦点所在的angle
private smallScaleDotAngleArr: number[] = []; // 仅变焦精细小刻度点(非光学变焦点)所在的angle
private lastGestureOffset: number = 0; // 滑动last位置记录
private lastSlideTouchRadian: number = 0; // 滑动last触摸弧度记录
private rouletteZoomTimerId: number = Number.MIN_VALUE; // 轮盘变焦定时器Id
@State longCurveHaloMatteScale?: number = 1; // 光晕动效参数
@State longCurveHaloOpacity?: number = 1;
@State spherePosition?: number = 0; // 光球参数
@State sphereScale?: number = 1;
@State sphereColor?: number = 0;
@State sphereHaloOpacity?: number = 1; // 光球光晕参数
@State redLineAndTextOpacity?: number = 1; // 焦圈中心刻度参数
@State scaleAllMatteScale?: number = 1; // 焦圈参数
@State scaleAllOpacity?: number = 1;
@State carmenLineMatteScale?: number = 1; // 卡门线参数
@State carmenLineOpacity?: number = 1;
private mStateChangeAnimator: AnimatorResult | null = null; // native变焦帧回调动效下发执行器
private redLineTopY: number = 0; // 红线顶部positionY
private redLineTopRadius: number = 0; // 红线顶部outer圆半径
// 变焦大字体变量
@State bigTextZoomValue: number = 1.0; // 变焦大字体变焦值
private bigTextTimerId: number = Number.MIN_VALUE; // 变焦大字体定时器Id
@State bigTextPositionY: number = 0; // 变焦大字体Y位置
@State bigTextScale: number = 1; // 变焦大字体收缩比例
@State bigTextOpacity: number = 1; // 变焦大字体透明度
// 变焦多端适配方案
@State zoomWidth: number = 0;
private zoomHeight: number = 0;
private drawHeight: number = 0;
@State @Watch('onDirectionChange') mDirection: WindowDirection = WindowDirection.TOP;
@State @Watch('updateDensity') density: number = 1 //DisplayService.getInstance().getDisplay().densityPixels;
@State isShowSubScreenRadius: boolean = false;
@State isHalfCollapsLEM: boolean = false;
private isSecurityCameraLast = false;
private quickZoomArray: number[] = [0.67, 0.8, 1];
private zoomScaleStruct: ZoomScaleStruct = new ZoomScaleStruct();
@State isShowSemiCollapsed: boolean = false;
@State isShowLandscape: boolean = false;
@State pickerWindowType: PickerWindowType = PickerWindowType.COLLAPSED_VERTICAL_FULL;
@Prop landscapeZoomWidth: number = 0;
@StorageLink('enableScreenReader') enableScreenReader: boolean = false;
@StorageLink('isBackground') @Watch('stateChange') isBackground: boolean = false;
@State isShowTricollaps: boolean = false;
@State isVdeLandscape: boolean = false;
private isSavePowerMode: boolean = false;
private quickZoomTimerId: number = Number.MIN_VALUE;
private selfieStickZoomTimerId: number = Number.MIN_VALUE;
private setSelfieStickZoom: boolean = true;
private isSelfieStickLongPress: boolean = false;
private isSelfieStickShortPress: boolean = false;
private selfieStickLongPressTimer: number = 0;
private selfieStickShortPressTimer: number = 0;
private setSelfieStickZoomTimerId: number = Number.MIN_VALUE;
private isSetSelfieStickZoom: boolean = true;
@StorageLink('isOpenTouchGuide') isOpenTouchGuide: boolean = false; // 浏览模式是否开启
// AI 构图彩色圆环
private antiAliasingSetting: RenderingContextSettings = new RenderingContextSettings(true);
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.antiAliasingSetting);
private animationItem: AnimationItem | undefined = undefined;
@StorageLink('isShowColorfulZoomRing') isShowColorfulZoomRing: boolean = false;
private isNeedVibratorWhenShowColorfulZoomRing: boolean = false;
@State colorfulZoomRingOpacity: number = 0;
@Prop @Watch('changeZoomWithCustomImmersive') isCustomFilterImmersive: boolean;
@Prop layoutService: AdaptiveLayoutService = AdaptiveLayoutService.getInstance();
@Prop currentFootBarLocation: number = 0;
@StorageLink('onCustomFilterTopLineChange') customFilterTopLineHeight: number = 0;
@Prop zoomLocation: number = 0;
@StorageLink('treasureBoxSelector') selector: FunctionId = FunctionId.NONE;
private panGestureZoomVelocity: number = CameraAppCapability.getInstance().getPanGestureZoomVelocity() ?? 1;
@Prop isHdr: boolean = false;
@State zoomIsHide: boolean = false;
@State zoomOpacity: number = 1;
@State mQuickOffsetY: number = 0;
private zoomPosAnimator: AnimatorResult | null = null;
@State isShowColumnDivisionLayout: boolean = false;
@State isShowCycleClickZoom: boolean = false; // 是否显示快捷变焦点上面的等效焦距
private cycleZoomData: number[] = []; // 当前快捷变焦点的循环点切数组
private cycleClickTimerId: number = Number.MIN_VALUE; // 等效焦距变焦大字体定时器Id
@State isDuringCycleClick: boolean = false; // 是否在循环点切的过程中
private zoomFunction = FeatureManager.getInstance().getFunction(FunctionId.ZOOM);
private isSupportSelfieStick = true;
private watchZoomInnerStatus(): void {
HiLog.i(TAG, `watchZoomInnerStatus: ${this.mZoomInnerStatus}.`);
}
aboutToAppear() {
HiLog.i(TAG, `aboutToAppear`);
this.rotateAngle = getStates().get<number>('contextReducer', 'rotateAngle');
this.mSubscriber = reduxSubscribe((state: OhCombinedState, changeState: ChangeState<RouletteZoomState>) => {
changeState(this.state, {
isShowDefault: state.get<boolean>('collapsReducer', 'isShowDefault'),
isShowSemiCollapsed: state.get<boolean>('collapsReducer', 'isShowSemiCollapsed'),
xComponentWidth: state.get<number>('previewReducer', 'xComponentWidth'),
xComponentHeight: state.get<number>('previewReducer', 'xComponentHeight'),
mode: state.get<ModeType>('modeReducer', 'mode'),
cameraPosition: state.get<camera.CameraPosition>('cameraReducer', 'cameraPosition'),
isShowPinch: state.get<boolean>('zoomReducer', 'isShowPinch'),
isShowZoomView: state.get<boolean>('zoomReducer', 'isShowZoomView'),
isShowZoomText: state.get<boolean>('zoomReducer', 'isShowZoomText'),
minZoomRatio: state.get<number>('zoomReducer', 'minZoomRatio'),
maxZoomRatio: state.get<number>('zoomReducer', 'maxZoomRatio'),
zoomRatio: state.get<number>('zoomReducer', 'zoomRatio'),
recordingState: state.get<RecordingState>('recordReducer', 'recordingState'),
zoomState: state.get<string>('zoomReducer', 'zoomState'),
isImmersive: state.get<boolean>('uiReducer', 'isImmersive'),
uiEnable: state.get<boolean>('contextReducer', 'uiEnable'),
cameraRunningState: state.get<CameraRunningState>('cameraReducer', 'cameraRunningState'),
mainTrigCompo: state.get<UIOperationType>('uiReducer', 'mainTrigCompo'),
isShowLowAngleShotView: state.get<boolean>('collapsReducer', 'isShowLowAngleShotView'),
tabSelector: state.get<FunctionId>('tabBarReducer', 'tabBarSelector'),
});
this.windowHeight = state.get<number>('windowReducer', 'windowHeight');
this.mDirection = state.get<WindowDirection>('contextReducer', 'direction');
this.rotateAngleWatch = state.get<number>('contextReducer', 'rotateAngle');
this.density = state.get<number>('collapsReducer', 'density');
this.isHalfCollapsLEM = state.get<boolean>('collapsReducer', 'isHalfCollapsLEM');
this.isShowLandscape = state.get<boolean>('collapsReducer', 'isShowLandscape');
this.isShowSemiCollapsed = state.get<boolean>('collapsReducer', 'isShowSemiCollapsed');
this.pickerWindowType = state.get<PickerWindowType>('windowReducer', 'pickerWindowType');
this.isShowTricollaps = state.get<boolean>('collapsReducer', 'isShowTricollaps');
this.isVdeLandscape = state.get<boolean>('collapsReducer', 'isVdeLandscape');
}, (dispatch: Dispatch): void => {
this.mAction.setDispatch(dispatch);
});
this.mNodeController = new ZoomNodeController(this.zoomWidth, this.zoomHeight, this.drawHeight,
(this.state.isShowDefault && DeviceInfo.isPhone()) ? 0 : 1);
let redLineTopHeight = this.drawHeight;
this.redLineTopY = 150 - redLineTopHeight;
this.redLineTopRadius = (this.zoomWidth * this.zoomWidth / 4 +
redLineTopHeight * redLineTopHeight) / (2 * redLineTopHeight);
this.initZoomViewUIData(this.getDefaultZoomRatio());
this.isShowQuickZoom = true;
this.isShowRouletteZoom = false;
this.quickZoomArray = CameraAppCapability.getInstance()
.getQuickZoomArray(this.state.cameraPosition, this.state.mode, OutputSwitcher.getInstance().getOutput());
if (this.state.zoomState !== ZoomStateType.QUICK_ZOOM) {
this.mAction.changeZoomState(ZoomStateType.QUICK_ZOOM);
}
this.registerListening(); // 注册事件监听回调
this.quickZoomValArrLength = this.getRoulette2QuickOrUpdateZoomArrayLength();
this.quickZoomValArrLengthWatch = this.quickZoomValArrLength;
this.roulette2QuickAnimOrUpdateZoomArrayExecAnim(false);
if (this.state.cameraRunningState === CameraRunningState.STARTED) { // 规避组件加载晚于启流完成时间
this.onCameraActionStarted({ type: CameraStartType.COLD_START });
}
this.onChangeXComponentSize({
xComponentWidth: this.state.xComponentWidth,
xComponentHeight: this.state.xComponentHeight
});
this.changeZoomWithCustomImmersive();
HiLog.i(TAG, 'aboutToAppear X.');
}
private onDirectionChange(): void {
HiLog.i(TAG, `onDirectionChange mDirection: ${this.mDirection}`);
this.mNodeController?.updateDirection(this.getZoomSelfDirectRotateAngle());
return;
}
private changeQuickZoomArr(): void {
let isZoomViewCreated: boolean = ZoomOperation.getInstance().isZoomViewCreated;
HiLog.i(TAG, `isZoomViewCreated: ${isZoomViewCreated}`);
HiLog.i(TAG, `quickLength: ${this.quickZoomValArrLength}, quickLengthWatch: ${this.quickZoomValArrLengthWatch}`);
if (!isZoomViewCreated || this.quickZoomValArrLength === this.quickZoomValArrLengthWatch) {
return;
}
animateToImmediately({
duration: 300,
curve: Curve.Friction,
iterations: 1,
playMode: PlayMode.Normal,
onFinish: () => {
HiLog.i(TAG, 'animation onFinish');
}
}, () => {
this.quickZoomValArrLength = this.quickZoomValArrLengthWatch;
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
this.doCycleClickZoomAnim(false);
this.isDuringCycleClick = false;
}
})
}
/**
* 初始化ZoomUI状态及data
* */
private initZoomViewUIData(mZoomRatio: number): void {
HiLog.i(TAG,
`initZoomViewUIData oldZoomValue: ${this.curZoomValue}, mZoomRatio: ${mZoomRatio}, quickZoomValArr: ${this.quickZoomValArr}.`);
let mCurrentZoom: number = Number(mZoomRatio.toFixed(3));
if ((this.quickZoomValArr && mCurrentZoom < this.quickZoomValArr[0]) ||
(this.opticalZoomValArr && mCurrentZoom > this.opticalZoomValArr[this.opticalZoomValArr.length - 1])) {
mCurrentZoom = this.getDefaultZoomRatio();
}
if (this.quickZoomValArr.length > 0 && mCurrentZoom < this.quickZoomValArr[0]) {
mCurrentZoom = this.quickZoomValArr[0];
}
this.curZoomValue = mCurrentZoom;
this.curValIndex = this.calZoomValIndex(this.curZoomValue);
this.bigTextZoomValue = this.curZoomValue;
}
/**
* 注册事件监听回调
* */
private registerListening(): void {
// 响应变焦数据及UI状态重置操作
this.mEventBus.on([ActionType.ACTION_PREVIEW_CLICKED, CaptureActionType.CAPTURE, RecordActionType.START,
ActionType.ACTION_NIGHT_CAPTURE_START, ContextActionType.CHANGE_WINDOW_EVENT_TYPE],
this.responsePreviewClick.bind(this), this.mBase.hashCode());
this.mEventBus.on(ZoomActionType.ACTION_CHANGE_ZOOM_RATIO,
this.handleZoomRatioChange.bind(this), this.mBase.hashCode());
this.mEventBus.on(FunctionActionType.ACTION_SET_FUNCTION_CONFLICT_PARAM,
this.onSetFunctionConflict.bind(this), this.mBase.hashCode());
this.mEventBus.on([ActionType.ACTION_IS_ENTER_IMMERSIVE, ActionType.ACTION_PHOTO_BROWSER_ON_BACK_PRESS],
(data: ActionData) => this.initDepthFusionState(), this.mBase.hashCode());
// 响应捏合变焦处理
this.mEventBus.on(ZoomActionType.ACTION_SWITCH_ZOOM_RATIO, this.onSwitchZoomRatio.bind(this),
this.mBase.hashCode());
this.mEventBus.on(FunctionActionType.ACTION_CHANGE_FUNCTION_VAL, this.pinchUpdateZoomAndVibrator.bind(this),
this.mBase.hashCode());
this.mEventBus.on(ZoomActionType.ACTION_UPDATE_SHOW_PINCH, this.pinchResetZoomAndBigText.bind(this),
this.mBase.hashCode());
// 设置焦距保持
this.mEventBus.on(ContextActionType.ABILITY_ON_FOREGROUND, this.onForegroundCallback.bind(this),
this.mBase.hashCode());
this.mEventBus.on([ContextActionType.ABILITY_ON_BACKGROUND, ContextActionType.SCREEN_LOCKED_STATE],
this.onBackgroundCallback.bind(this), this.mBase.hashCode());
this.mEventBus.on(ContextActionType.SCREEN_LOCKED_STATE, this.cameraLocked.bind(this), this.mBase.hashCode());
// 重置等效焦距
this.mEventBus.on([ZoomActionType.ACTION_RESET_ZOOM_RATIO,
ZoomActionType.ACTION_CHANGE_ZOOM_BY_FRAMERATE,
ZoomActionType.ACTION_CHANGE_ZOOM_BY_VIDEORESOLUTION,
CameraActionType.CHANGE_MODE,
CameraActionType.CHANGE_OUTPUT_TYPE,
CameraActionType.SWITCH_CAMERA,
CameraActionType.SWITCH_CAMERA_CHANGE_MODE
], () => {
this.quickZoomValArrLengthWatch = this.getRoulette2QuickOrUpdateZoomArrayLength()
}, this.mBase.hashCode());
this.mEventBus.on([ZoomActionType.ACTION_RESET_ZOOM_RATIO,
CameraActionType.CHANGE_OUTPUT_TYPE,
CameraActionType.SWITCH_CAMERA,
CameraActionType.CHANGE_MODE,
CameraActionType.SWITCH_CAMERA_CHANGE_MODE,
], this.updateCycleClickZoom.bind(this), this.mBase.hashCode());
// 录像模式开关防抖时重置等效焦距
this.mEventBus.on(CameraActionType.RESTART, () => {
if (this.state.mode === ModeType.VIDEO) {
this.quickZoomValArrLengthWatch = this.getRoulette2QuickOrUpdateZoomArrayLength()
}
}, this.mBase.hashCode())
this.mEventBus.on(CameraActionType.CHANGE_MODE, this.changeQuickZoomArr.bind(this), this.mBase.hashCode());
this.mEventBus.on(CameraActionType.CHANGE_MODE, () => {
this.isShowRouletteZoom = false;
this.isShowBigText = false;
}, this.mBase.hashCode());
this.mEventBus.on([ZoomActionType.ACTION_RESET_ZOOM_RATIO, CameraActionType.CHANGE_MODE,
CameraActionType.CHANGE_OUTPUT_TYPE,
CameraActionType.SWITCH_CAMERA, CameraActionType.SWITCH_CAMERA_CHANGE_MODE],
() => this.roulette2QuickAnimOrUpdateZoomArrayExecAnim(false), this.mBase.hashCode());
this.mEventBus.on(CameraActionType.STARTED, this.onCameraActionStarted.bind(this), this.mBase.hashCode());
// 重置多端适配布局
this.mEventBus.on(ActionType.ACTION_CHANGE_X_COMPONENT_SIZE,
this.onChangeXComponentSize.bind(this), this.mBase.hashCode());
// 自动广角逻辑处理
this.mEventBus.on(ZoomActionType.ACTION_CHANGE_ZOOM_TO_WIDE_ANGLE, this.switchZoomToWideAngle.bind(this),
this.mBase.hashCode());
this.mEventBus.on(ZoomActionType.ACTION_CHANGE_ZOOM_TO_DEFAULT, this.switchZoomToDefault.bind(this),
this.mBase.hashCode());
this.mEventBus.on(CaptureActionType.SELFIE_STICK_EVENT, this.selfieStickEvent.bind(this), this.mBase.hashCode());
this.mEventBus.on(ContextActionType.UPDATE_SAVE_POWER_MODE_STATE, (data: SavePowerModeActionData) =>
this.updateSavePowerModeState(data), this.mBase.hashCode());
// 遥控拍照变焦
this.mEventBus.on(CollaborateControlActionType.ZOOM_IN, this.onCollaborateZoomIn.bind(this), this.mBase.hashCode());
this.mEventBus.on(CollaborateControlActionType.ZOOM_OUT, this.onCollaborateZoomOut.bind(this),
this.mBase.hashCode());
// AI 构图
this.mEventBus.on(ZoomActionType.SHOW_COLORFUL_ZOOM_RING, this.handleShowColorfulBorder.bind(this),
this.mBase.hashCode());
// 智拍键滑动变焦
this.mEventBus.on(ZoomActionType.ACTION_SMART_CONTROL_ZOOM_RATIO, this.onSmartControlZoomRatio.bind(this),
this.mBase.hashCode());
this.mEventBus.on([ActionType.ACTION_NIGHT_CAPTURE_START, ActionType.ACTION_START_STITCHING_CAPTURE],
this.captureStart.bind(this), this.mBase.hashCode());
this.mEventBus.on([ActionType.ACTION_NIGHT_CAPTURE_END, ActionType.ACTION_END_STITCHING_CAPTURE],
this.captureEnd.bind(this), this.mBase.hashCode());
}
aboutToDisappear() {
HiLog.i(TAG, 'disappear');
this.mEventBus.clear(this.mBase.hashCode());
this.mSubscriber.destroy();
}
/**
* AI 构图展示彩色边框
* */
private handleShowColorfulBorder(data: Record<'isShow', boolean>): void {
if (data.isShow) {
this.showColorfulZoomRing();
} else {
this.hideColorfulZoomRing();
}
}
private showColorfulZoomRing(): void {
this.isShowColorfulZoomRing = true;
this.isNeedVibratorWhenShowColorfulZoomRing = true;
animateToImmediately({ curve: Curve.Sharp }, () => {
this.colorfulZoomRingOpacity = 1;
});
}
private hideColorfulZoomRing(): void {
this.isNeedVibratorWhenShowColorfulZoomRing = false;
animateToImmediately({
curve: Curve.Sharp, onFinish: () => {
this.isShowColorfulZoomRing = false;
}
}, () => {
this.colorfulZoomRingOpacity = 0;
});
}
/**
* 适配自拍杆调节焦距
* */
private captureStart(): void {
this.isSupportSelfieStick = false;
}
private captureEnd(): void {
this.isSupportSelfieStick = true;
}
private updateSavePowerModeState(data: SavePowerModeActionData): void {
this.isSavePowerMode = data.isDuringSavePowerMode;
}
private selfieStickEvent(data: SelfieStickKey): void {
let isShowtimeLapse = getStates().get<boolean>('settingReducer', 'isShowtimeLapse');
let isImmersive = getStates().get<boolean>('uiReducer', 'isImmersive');
if (this.isSavePowerMode || !this.isSupportSelfieStick || isImmersive || isShowtimeLapse) {
return;
}
HiLog.i(TAG, `selfieStickEvent finalKey: ${data['key'].keyValue}`);
if (data['key'].keyValue !== KeyCode.KEYCODE_ZOOMIN && data['key'].keyValue !== KeyCode.KEYCODE_ZOOMOUT) {
return;
}
if ((this.curZoomValue >= this.opticalZoomValArr[this.opticalZoomValArr.length - 1] &&
data['key'].keyValue === KeyCode.KEYCODE_ZOOMIN) || (this.curZoomValue <= this.opticalZoomValArr[0] &&
data['key'].keyValue === KeyCode.KEYCODE_ZOOMOUT)) {
return;
}
let index: number = this.calZoomValIndex();
if (data['key'].keyValue === KeyCode.KEYCODE_ZOOMIN) {
if (this.isBlockGestureEvent() && index !== (this.opticalZoomValArr.length - 1)) {
this.curZoomValue = this.opticalZoomValArr[index + 1];
} else {
this.setSelfieStickZoomUp(data);
}
} else if (data['key'].keyValue === KeyCode.KEYCODE_ZOOMOUT) {
if ((this.isBlockGestureEvent() && index !== 0)) {
this.curZoomValue = this.opticalZoomValArr[index - 1];
} else {
this.setSelfieStickZoomDown(data);
}
}
this.curValIndex = this.calZoomValIndex();
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue });
if (data['key'].preKeys !== undefined) {
this.isShowQuickZoom = false;
this.isShowRouletteZoom = false;
this.clearAndSetQuickZoom();
}
this.clearAndSetBigTextTimer();
this.clearSelStickShortPress();
this.isShowBigText = true;
}
private setSelfieStickZoomUp(data: SelfieStickKey): void {
let zoomValue: string;
if (data['key'].preKeys === undefined) { // 短按自拍杆
if (this.curZoomValue >= this.quickZoomValArr[this.quickZoomValArrLength -1]) {
if (this.isSetSelfieStickZoom) {
this.isSetSelfieStickZoom = false;
this.clearAndSelfieStickZoomTimerId();
zoomValue = (this.curZoomValue * selfieStickLongPressZoom).toFixed(3);
this.curZoomValue = parseFloat(zoomValue) > this.opticalZoomValArr[this.opticalZoomValArr.length - 1] ?
this.opticalZoomValArr[this.opticalZoomValArr.length - 1] : parseFloat(zoomValue);
}
} else {
zoomValue = (this.curZoomValue + setOnceSelfieStickZoom).toFixed(1);
this.curZoomValue = parseFloat(zoomValue);
}
this.isSelfieStickShortPress = true;
} else if (data['key'].preKeys !== undefined && this.setSelfieStickZoom) { // 长按自拍杆
this.isSelfieStickLongPress = true;
zoomValue = (this.curZoomValue * selfieStickLongPressZoom).toFixed(3);
this.curZoomValue = parseFloat(zoomValue) > this.opticalZoomValArr[this.opticalZoomValArr.length - 1] ?
this.opticalZoomValArr[this.opticalZoomValArr.length - 1] : parseFloat(zoomValue);
this.setSelfieStickZoom = false;
this.setSelfieLongPress();
this.selfieStickLongPressTimer = Date.now();
}
}
private setSelfieStickZoomDown(data: SelfieStickKey): void {
let zoomValue: string;
if (data['key'].preKeys === undefined) {
if (this.curZoomValue >= this.quickZoomValArr[this.quickZoomValArrLength -1]) {
if (this.isSetSelfieStickZoom) {
this.isSetSelfieStickZoom = false;
this.clearAndSelfieStickZoomTimerId();
zoomValue = (this.curZoomValue / selfieStickLongPressZoom).toFixed(3);
this.curZoomValue = parseFloat(zoomValue) < this.opticalZoomValArr[0] ?
this.opticalZoomValArr[0] : parseFloat(zoomValue);
}
} else {
zoomValue = (this.curZoomValue - setOnceSelfieStickZoom).toFixed(1);
this.curZoomValue = parseFloat(zoomValue);
}
this.isSelfieStickShortPress = true;
} else if (data['key'].preKeys !== undefined && this.setSelfieStickZoom) {
this.isSelfieStickLongPress = true;
zoomValue = (this.curZoomValue / selfieStickLongPressZoom).toFixed(3);
this.curZoomValue = parseFloat(zoomValue) < this.opticalZoomValArr[0] ?
this.opticalZoomValArr[0] : parseFloat(zoomValue);
this.setSelfieStickZoom = false;
this.setSelfieLongPress();
this.selfieStickLongPressTimer = Date.now();
}
}
private clearAndSelfieStickZoomTimerId(): void {
if (this.setSelfieStickZoomTimerId !== Number.MIN_VALUE) {
clearTimeout(this.setSelfieStickZoomTimerId);
this.setSelfieStickZoomTimerId = Number.MIN_VALUE;
}
this.setSelfieStickZoomTimerId = setTimeout((): void => {
this.isSetSelfieStickZoom = true;
}, SET_SELFIE_STICK_ZOOM_NUM);
}
private clearAndSetQuickZoom(): void {
this.clearQuickZoomTimer();
this.quickZoomTimerId = setTimeout((): void => {
this.isShowQuickZoom = true;
this.isSelfieStickLongPress = false;
}, 1500);
}
private clearQuickZoomTimer(): void {
if (this.quickZoomTimerId !== Number.MIN_VALUE) {
clearTimeout(this.quickZoomTimerId);
this.quickZoomTimerId = Number.MIN_VALUE;
}
}
private clearSelStickShortPress(): void {
if (this.selfieStickShortPressTimer !== Number.MIN_VALUE) {
clearTimeout(this.selfieStickShortPressTimer);
this.selfieStickShortPressTimer = Number.MIN_VALUE;
}
this.selfieStickShortPressTimer = setTimeout((): void => {
this.isSelfieStickShortPress = false;
}, 1500);
}
private setSelfieLongPress(): void {
if (this.selfieStickZoomTimerId !== Number.MIN_VALUE) {
clearTimeout(this.selfieStickZoomTimerId);
this.selfieStickZoomTimerId = Number.MIN_VALUE;
}
this.selfieStickZoomTimerId = setTimeout((): void => {
this.setSelfieStickZoom = true;
}, 1000 / selfieStickLongPressCount);
}
private handleDepthFusionBarVisibility(data: ActionData): void {
if (!this.isShowQuickZoom || this.isShowLandscape) {
return;
}
const targetShowState: boolean = data['isShow'];
if (this.isDepthFusionBarShow === targetShowState) {
return;
}
this.isDepthFusionBarShow = targetShowState;
HiLog.i(TAG, `is depthFusion bar show: ${this.isDepthFusionBarShow}`);
this.updateQuickZoomValArr(
this.isDepthFusionBarShow ? [this.quickZoomValArr[this.curValIndex]] : this.zoomScaleStruct.quickZoomValArr);
this.changeQuickZoomViewOpacity();
}
private handleStitchingTypeBarVisibility(data: ActionData): void {
const targetShowState: boolean = data['showStitchingBar'];
this.isStitchingTypeBarShow = targetShowState;
this.updateQuickZoomValArr(
this.isStitchingTypeBarShow ? [this.quickZoomValArr[this.curValIndex]] : this.zoomScaleStruct.quickZoomValArr);
this.changeQuickZoomViewOpacity();
}
private changeQuickZoomViewOpacity(): void {
if (this.isDepthFusionBarShow || this.isStitchingTypeBarShow) {
this.quickZoomValArrLengthWatch = 1;
this.initZoomViewUIData(this.state.zoomRatio);
animateTo({
duration: 100,
curve: Curve.Sharp,
iterations: 1,
playMode: PlayMode.Normal,
delay: 150,
onFinish: () => {
HiLog.i(TAG, 'depthFusion hide quickZoom animation onFinish');
},
}, () => {
this.mQuickOtherAlpha = 0;
});
} else {
animateTo({
duration: 100,
curve: Curve.Sharp,
iterations: 1,
playMode: PlayMode.Normal,
delay: 100,
onFinish: () => {
HiLog.i(TAG, 'depthFusion hide quickZoom animation onFinish');
}
}, () => {
this.mQuickOtherAlpha = 1;
});
setTimeout(() => {
this.initZoomViewUIData(this.state.zoomRatio)
}, 50);
setTimeout(() => {
this.quickZoomValArrLengthWatch = this.quickZoomValArr.length;
}, 150);
}
}
/**
* 切换至默认广角ZOOM_RATIO_WIDE
*/
private switchZoomToWideAngle(data: ActionData): void {
HiLog.i(TAG, `switchZoomToWideAngle ${data['zoomRatio']} - ${this.curZoomValue} - ${this.opticalZoomValArr[0]}`);
if (data['zoomRatio'] && this.curZoomValue === this.opticalZoomValArr[0]) {
HiLog.w(TAG, 'zoom value is w, can not change to 0.8x');
return;
}
let zoomRatio: number = data['zoomRatio'] || this.opticalZoomValArr[0];
if (this.opticalZoomValArr[0] <= zoomRatio && this.curZoomValue != zoomRatio) {
HiLog.i(TAG, `switch zoom ratio to ${zoomRatio} x.`);
this.curZoomValue = zoomRatio;
this.bigTextZoomValue = zoomRatio;
this.curValIndex = this.calZoomValIndex(zoomRatio);
this.quickZoomArray = CameraAppCapability.getInstance()
.getQuickZoomArray(this.state.cameraPosition, this.state.mode, OutputSwitcher.getInstance().getOutput());
this.changeZoomFunctionValue({
zoomRatio: this.curZoomValue,
isPrepareZoom: true,
isUnprepareZoom: true,
unprepareDelay: ZoomParam.UNPREPARE_DELAY_TIME
},
zoomRatio === ZoomOperation.getDirectionChangeAutoWide(this.quickZoomArray));
}
}
/**
* 广角切换至默认焦距1
*/
private switchZoomToDefault(): void {
HiLog.i(TAG, 'switchZoomToDefault.');
this.quickZoomArray = CameraAppCapability.getInstance()
.getQuickZoomArray(this.state.cameraPosition, this.state.mode, OutputSwitcher.getInstance().getOutput());
this.curZoomValue = 1;
this.bigTextZoomValue = 1;
this.curValIndex = this.calZoomValIndex(1);
HiLog.i(TAG, 'switch zoom ratio to 1x.');
this.changeZoomFunctionValue({
zoomRatio: this.curZoomValue,
isPrepareZoom: true,
isUnprepareZoom: true,
unprepareDelay: ZoomParam.UNPREPARE_DELAY_TIME
}, true);
}
private changeRotateAngle(): void {
if (this.rotateAngleWatch === this.rotateAngle) {
return;
}
animateToImmediately({
duration: AnimSpecifications.ANIM_DURATION_300,
curve: Curve.Friction,
}, () => {
this.rotateAngle = this.rotateAngleWatch;
});
}
/**
* 用于注册Preview区域点击事件类似行为监听的回调
* */
private responsePreviewClick(): void {
if (this.state.isShowPinch) {
return;
}
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_TO_QUICK) {
return;
}
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK) {
this.clearAndSetBigTextTimer();
this.isShowBigText = false;
return;
}
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_TO_ROULETTE ||
this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_SLIDE ||
this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_STATIC) {
this.cancelNativeAndFrameAnimators();
this.clearRouletteZoomTimer();
this.execRouletteZoom2QuickZoomAnim();
// 非zoomBar组件触发的沉浸式状态改变,则不响应
if (this.state.isImmersive && this.state.mainTrigCompo === UIOperationType.ZOOM_BAR) {
this.mAction.isEnterImmersive(false);
}
if (!this.state.uiEnable) {
this.mAction.uiStateWithMode(true);
}
return;
}
}
private getRoulette2QuickOrUpdateZoomArrayLength(isExec2QuickAnim = true): number {
HiLog.i(TAG, 'getRoulette2QuickOrUpdateZoomArrayLength isExec2QuickAnim : ' + isExec2QuickAnim);
if (isExec2QuickAnim) {
this.responsePreviewClick();
}
// initZoomData与动效时序控制
let mZoomScaleStruct = ZoomOperation.getRingZoomUIState(this.state.cameraPosition, this.state.mode);
this.oldQuickZoomArrLength = this.quickZoomValArr.length;
if (isExec2QuickAnim) {
HiLog.i(TAG, 'quickZoomValArr bendin : ' + this.quickZoomValArr + ', initZoomViewUIData : ' +
mZoomScaleStruct.quickZoomValArr);
}
this.updateQuickZoomValArr(mZoomScaleStruct.quickZoomValArr);
if (isExec2QuickAnim) {
HiLog.i(TAG, 'quickZoomValArr end : ' + this.quickZoomValArr);
}
this.opticalZoomValArr = mZoomScaleStruct.opticalZoomValArr;
this.opticalZoomDotIndexArr = mZoomScaleStruct.opticalZoomDotIndex;
this.zoomScaleStruct = mZoomScaleStruct;
this.opticalZoomDotAngleArr = [];
this.opticalZoomDotIndexArr.forEach((value: number) => {
this.opticalZoomDotAngleArr.push(-ZoomParam.SCALE_GAP_ANGLE * value);
});
this.initZoomViewUIData(this.state.zoomRatio);
this.mAction.initZoomRatio(this.opticalZoomValArr[0], this.opticalZoomValArr[this.opticalZoomValArr.length - 1]);
this.smallScaleDotAngleArr = [];
for (let i = 0; i < this.opticalZoomDotIndexArr.length - 1; i++) {
for (let j = this.opticalZoomDotIndexArr[i] + 1; j < this.opticalZoomDotIndexArr[i + 1]; j++) {
this.smallScaleDotAngleArr.push(-ZoomParam.SCALE_GAP_ANGLE * j);
}
}
if (this.isNeedShowEquivalentFocalBigText() || CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
this.updateEquivalentFocalArray(); // 模式切换时等效焦距需及时更新, 避免等效大字体显示上一个模式的焦距
}
return this.quickZoomValArr.length;
}
private roulette2QuickAnimOrUpdateZoomArrayExecAnim(isSuperMacroChange: boolean): void {
HiLog.i(TAG, 'roulette2QuickAnimOrUpdateZoomArrayExecAnim.');
if (!isSuperMacroChange) {
// 微距小花图标更新同样触发此方法,避免切换关闭微距选项时影响正在显示edof参数栏的场景
this.isDepthFusionBarShow = false;
this.mQuickOtherAlpha = 1;
}
this.responsePreviewClick();
// initZoomData与动效时序控制
let mZoomScaleStruct = ZoomOperation.getRingZoomUIState(this.state.cameraPosition, this.state.mode);
this.oldQuickZoomArrLength = this.quickZoomValArr.length;
this.updateQuickZoomValArr(mZoomScaleStruct.quickZoomValArr);
this.opticalZoomValArr = mZoomScaleStruct.opticalZoomValArr;
this.opticalZoomDotIndexArr = mZoomScaleStruct.opticalZoomDotIndex;
this.zoomScaleStruct = mZoomScaleStruct;
this.opticalZoomDotAngleArr = [];
this.opticalZoomDotIndexArr.forEach((value: number) => {
this.opticalZoomDotAngleArr.push(-ZoomParam.SCALE_GAP_ANGLE * value);
});
this.initZoomViewUIData(this.state.zoomRatio);
this.mAction.initZoomRatio(this.opticalZoomValArr[0], this.opticalZoomValArr[this.opticalZoomValArr.length - 1]);
this.smallScaleDotAngleArr = [];
for (let i = 0; i < this.opticalZoomDotIndexArr.length - 1; i++) {
for (let j = this.opticalZoomDotIndexArr[i] + 1; j < this.opticalZoomDotIndexArr[i + 1]; j++) {
this.smallScaleDotAngleArr.push(-ZoomParam.SCALE_GAP_ANGLE * j);
}
}
}
private changeZoomWithCustomImmersive(): void {
if (GlobalContext.get().getIsPicker()) {
return;
}
if (!this.isLandscapeIndex()) {
this.changeZoomVisWithAnim();
}
this.changeZoomPosWithAnim();
}
private changeZoomPosWithAnim() {
this.zoomPosAnimator?.pause();
let options: AnimatorOptions = {
duration: AnimSpecifications.ANIM_DURATION_300,
easing: 'cubic-bezier(0.33, 0.0, 0.67, 1.0)',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: this.mQuickOffsetY,
end: this.isCustomFilterImmersive ? ZOOM_CUSTOM_FILTER_VERTICAL_OFFSET : 0
};
if (this.zoomPosAnimator) {
try {
this.zoomPosAnimator.reset(options);
} catch (error) {
HiLog.i(TAG, `zoomPosAnimator error: ${JSON.stringify(error)}.`);
}
} else {
this.zoomPosAnimator = animator.create(options);
}
this.zoomPosAnimator.onframe = (value) => {
this.mQuickOffsetY = value;
};
this.zoomPosAnimator.onfinish = () => {
this.zoomPosAnimator = null;
}
this.zoomPosAnimator.oncancel = () => {
this.zoomPosAnimator = null;
}
this.zoomPosAnimator?.play();
}
private changeZoomVisWithAnim(): void {
if (this.isCustomFilterImmersive) {
animateToImmediately({
duration: 150,
curve: Curve.Sharp,
onFinish: () => {
this.zoomIsHide = true;
}
}, () => {
this.mQuickOtherAlpha = 0;
});
} else {
this.zoomIsHide = false;
animateToImmediately({
duration: 150,
delay: 100,
curve: Curve.Sharp
}, () => {
this.mQuickOtherAlpha = 1;
});
}
}
private updateQuickZoomValArr(targetArray: number[]): void {
if (!targetArray) {
return;
}
let countDiff = targetArray.length - this.quickZoomValArr.length;
let minCount = Math.min(targetArray.length, this.quickZoomValArr.length);
HiLog.i(TAG, 'countDiff ' + countDiff + ', minCount : ' + minCount);
for (let i = 0; i < minCount; i++) {
this.quickZoomValArr[i] = targetArray[i];
}
if (countDiff > 0) {
for (let i = minCount; i < targetArray.length; i++) {
this.quickZoomValArr.push(targetArray[i]);
}
} else if (countDiff < 0) {
this.quickZoomValArr.splice(minCount, this.quickZoomValArr.length - minCount);
}
HiLog.i(TAG, 'updateQuickZoomValArr');
}
private updateSuperMacroZoomView(data: EnableData): void {
if (this.superMacroEnable === data.superMacroEnable) {
return;
}
this.superMacroEnable = data.superMacroEnable;
let zoomRatio: number = 1;
const remainZoomRatio: boolean = ZoomOperation.getInstance().getRemainZoomRatio();
if (remainZoomRatio || this.state.mode === ModeType.PHOTO) {
zoomRatio = this.state.zoomRatio;
} else if (this.state.mode === ModeType.VIDEO) {
zoomRatio = FeatureManager.getInstance().getFunction(FunctionId.ZOOM).getValue();
}
HiLog.i(TAG, `mZoomRatio: ${zoomRatio}, ${this.state.zoomRatio}, remainZoomRatio: ${remainZoomRatio}.`);
this.quickZoomValArrLengthWatch = this.getRoulette2QuickOrUpdateZoomArrayLength(false);
this.roulette2QuickAnimOrUpdateZoomArrayExecAnim(true);
this.updateEquivalentFocalArray();
this.initZoomViewUIData(zoomRatio);
if (this.superMacroEnable && this.state.mode === ModeType.VIDEO &&
zoomRatio > this.opticalZoomValArr[this.opticalZoomValArr.length - 1]) {
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue });
}
}
private handleZoomRatioChange(data: Required<ZoomFuncValueStruct>): void {
if (this.state.isShowPinch) {
return;
}
if (data.zoomRatio !== this.curZoomValue) {
this.initZoomViewUIData(data.zoomRatio);
}
}
private onSetFunctionConflict(data: Record<'id', number>): void {
if (FunctionId.FRAME_RATE === data.id &&
OutputOperation.isPanVideoOutput(getStates().get<ModeType>('modeReducer', 'mode'))) {
this.quickZoomValArrLengthWatch = this.getRoulette2QuickOrUpdateZoomArrayLength();
return;
}
if (FunctionId.ZOOM !== data.id) {
return;
}
this.curZoomValue = Number((this.state.zoomRatio).toFixed(3));
this.curValIndex = this.calZoomValIndex();
this.initZoomViewUIData(this.curZoomValue);
}
/**
* 前置捏合变焦场景
*/
private onSwitchZoomRatio(data: Record<'toNext', boolean>): void {
if (data.toNext && this.curValIndex < this.opticalZoomValArr.length - 1) {
this.curZoomValue = this.opticalZoomValArr[this.curValIndex + 1];
this.curValIndex++;
this.bigTextZoomValue = this.curZoomValue;
HiLog.d(TAG, `pinch to switch Zoom value : ${this.curZoomValue}.`);
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue });
return;
}
if (!data.toNext && this.curValIndex > 0) {
this.curZoomValue = this.opticalZoomValArr[this.curValIndex - 1];
this.curValIndex--;
this.bigTextZoomValue = this.curZoomValue;
HiLog.d(TAG, `pinch to switch Zoom value : ${this.curZoomValue}.`);
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue });
return;
}
}
/**
* 智拍键滑动变焦场景
*/
private onSmartControlZoomRatio(data: Record<'toNext', boolean>): void {
this.responsePreviewClick();
if (data.toNext && this.curValIndex < this.quickZoomValArr.length - 1) {
this.curZoomValue = this.quickZoomValArr[this.curValIndex + 1];
this.curValIndex++;
this.bigTextZoomValue = this.curZoomValue;
HiLog.d(TAG, `pinch to switch Zoom value : ${this.curZoomValue}.`);
this.changeZoomFunctionValue({
zoomRatio: this.curZoomValue,
isPrepareZoom: true,
isUnprepareZoom: true,
isSmoothZoom: true
});
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
this.showBigTextAndEquivZoomText();
}
if (this.isShowBigText) {
this.clearAndSetBigTextTimer();
} else {
this.isShowBigText = true;
}
return;
}
if (!data.toNext && this.curValIndex > 0) {
this.curZoomValue = this.quickZoomValArr[this.curValIndex - 1];
this.curValIndex--;
this.bigTextZoomValue = this.curZoomValue;
HiLog.d(TAG, `pinch to switch Zoom value : ${this.curZoomValue}.`);
this.changeZoomFunctionValue({
zoomRatio: this.curZoomValue,
isPrepareZoom: true,
isUnprepareZoom: true,
isSmoothZoom: true
});
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
this.showBigTextAndEquivZoomText();
}
if (this.isShowBigText) {
this.clearAndSetBigTextTimer();
} else {
this.isShowBigText = true;
}
return;
}
}
/**
* 双指捏合过程中更新变焦状态,执行振动效果
* */
private pinchUpdateZoomAndVibrator(data: ActionData): void {
if (CameraAppCapability.getInstance().getNotSupportedVideo60fpsWide() &&
(getStates().get<ModeType>('modeReducer', 'mode') === ModeType.VIDEO) &&
getStates().get<camera.CameraPosition>('cameraReducer', 'cameraPosition') ==
camera.CameraPosition.CAMERA_POSITION_BACK &&
data['id'] === FunctionId.FRAME_RATE) {
this.getRoulette2QuickOrUpdateZoomArrayLength();
}
if (this.isBlockGestureEvent() || (data['id'] !== FunctionId.ZOOM && data['id'] !== FunctionId.ASPECT_RATIO)) {
return;
}
if (data['id'] === FunctionId.ASPECT_RATIO) {
this.responsePreviewClick();
}
let nowZoomValue = this.state.zoomRatio;
let prevZoomValue = this.curZoomValue;
if (!this.state.isShowPinch || this.curZoomValue === this.state.zoomRatio) {
return;
}
if (this.isAllowVibratorSound()) {
this.opticalZoomValArr.forEach((value, index) => {
// 捏合手势变焦至快捷变焦点执行振动音频
let isQuickZoom = ((prevZoomValue > value && value >= nowZoomValue) ||
(prevZoomValue < value && value <= nowZoomValue));
if (isQuickZoom) {
this.onPinchChangeZoomValueVibrate();
}
});
}
this.curZoomValue = this.state.zoomRatio;
this.curValIndex = this.calZoomValIndex();
this.bigTextZoomValue = this.curZoomValue;
}
private debounceTimer: number = -1; // 捏合震动节流使用
private onPinchChangeZoomValueVibrate(): void {
if (this.debounceTimer !== -1) {
return;
}
this.debounceTimer = setTimeout((): void => {
VibratorService.getInstance().triggerVibrator(VibratorService.CAMERA_CLICK_EFFECT_ID, VIBRATOR_USAGE_PHYSIC);
this.debounceTimer = -1;
}, 50);
}
/**
* 双指捏合起始时重置变焦态、清除动效定时器、重置大字体状态
* */
private pinchResetZoomAndBigText(): void {
this.clearBigTextTimer();
this.clearRouletteZoomTimer();
if (this.state.isShowPinch) {
this.mAction.changeZoomState(ZoomOuterStatus.PINCH_ZOOM);
this.mZoomInnerStatus = ZoomInnerStatus.PINCH_ZOOM;
this.bigTextZoomValue = this.state.zoomRatio; // 捏合变焦大字体显示之前先同步redux层数据
this.isShowQuickZoom = false;
this.isShowRouletteZoom = false;
this.isShowBigText = true;
this.isDuringCycleClick = false;
this.doCycleClickZoomAnim(false)
} else {
if (!this.isStitchingTypeBarShow) {
this.mQuickOtherAlpha = 1;
}
this.isShowQuickZoom = true;
this.isShowRouletteZoom = false;
this.isShowBigText = false;
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_STATIC;
this.mAction.changeZoomState(ZoomOuterStatus.QUICK_ZOOM);
if (!this.enableScreenReader) {
return;
}
if (this.curValIndex === 0 && this.state.zoomRatio <= this.quickZoomArray[0] && this.state.zoomRatio < 1) {
AccessibilityUtils.sendAnnounceAccessibilityEvent(ResGetter.getStringSafe($r('app.string.wide_angle')));
} else {
AccessibilityUtils.sendAnnounceAccessibilityEvent(
getContext(this).resourceManager
.getPluralStringValueSync($r('app.plural.accessibility_tips_zoom_scale_new').id,
this.roundOrSplitZoomVal(this.bigTextZoomValue)));
}
}
}
private getDefaultZoomRatio(): number {
let mode = getStates().get<ModeType>('modeReducer', 'mode');
let cameraPosition = getStates().get<camera.CameraPosition>('cameraReducer', 'cameraPosition');
return ZoomOperation.getDefaultZoomRatio(mode, cameraPosition);
}
private onForegroundCallback(): void {
let zoomRatio: number = this.getDefaultZoomRatio();
if (ZoomOperation.getInstance().getRemainZoomRatio()) {
zoomRatio = this.state.zoomRatio;
} else {
this.doCycleClickZoomAnim(false);
this.isDuringCycleClick = false;
}
// 更新变焦值,帧率互斥逻辑使用
StoreManager.getInstance().postMessage(ZoomAction.updateStateZoomRatio(zoomRatio));
this.initDepthFusionState();
this.initZoomViewUIData(zoomRatio);
}
private stateChange(): void {
if (!this.isBackground && GlobalContext.get().getT('isSecurityCamera') && !this.isSecurityCameraLast) {
ZoomOperation.getInstance().setRemainZoomRatio(false);
}
}
private cameraLocked(lockedData: LockedData): void {
if (!GlobalContext.get().getT('isSecurityCamera') && !GlobalContext.get().getIsPicker() && lockedData?.isLocked) {
// 桌面相机界面直接锁屏,亮屏相机焦距保持
ZoomOperation.getInstance().setRemainZoomRatio(true);
}
}
private initDepthFusionState(): void {
HiLog.i(TAG, `init depth fusion zoom bar ${this.zoomScaleStruct.quickZoomValArr}`);
if (this.isDepthFusionBarShow) {
this.quickZoomValArrLengthWatch = this.getRoulette2QuickOrUpdateZoomArrayLength()
}
this.isDepthFusionBarShow = false;
if (!this.isStitchingTypeBarShow) {
this.mQuickOtherAlpha = 1;
}
}
private onBackgroundCallback(data: ActionData): void {
this.isSecurityCameraLast = GlobalContext.get().getT('isSecurityCamera');
this.isShowRouletteZoom = false;
if (this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_STATIC) {
this.responsePreviewClick();
}
if (ZoomOperation.getInstance().getRemainZoomRatio()) {
return;
}
if (data['isScreenLockBack'] || data?.data?.isLocked) {
// 锁屏相机不退出息屏亮屏之后,焦距保持
ZoomOperation.getInstance().setRemainZoomRatio(true);
} else {
ZoomOperation.getInstance().setRemainZoomRatio(false);
}
}
private onCameraActionStarted(data?: CameraStartTypeStruct): void {
setTimeout(() => {
if (!ZoomOperation.getInstance().isZoomViewCreated) {
HiLog.i(TAG, `reset isZoomViewCreated true`);
ZoomOperation.getInstance().isZoomViewCreated = true;
this.changeQuickZoomArr();
}
}, 20);
if (PickerUtils.getIsPicker() && !PickerUtils.getIsFullScreen()) {
this.zoomWidth =
DeviceInfo.isTablet() ? this.getTabletPickerWidth() : this.state.isShowDefault ? this.state.xComponentWidth :
this.state.isShowSemiCollapsed ? 308 : this.landscapeZoomWidth;
} else if (DeviceInfo.isPhone() && this.state.isShowDefault) {
const size: Size = CameraBasicService.getInstance().getPreviewDisplaySize();
this.zoomWidth = size.width;
this.drawHeight = AdaptiveLayoutService.getInstance().preview4to3Height * 0.16;
}
this.mNodeController?.updateZoomLayout(this.zoomWidth, this.zoomHeight, this.drawHeight,
this.getZoomSelfCollapsStatus(), this.getZoomSelfDirectRotateAngle());
if (!(data && data.type === CameraStartType.RECORD)) {
this.updateEquivalentFocalArray();
}
}
private onChangeXComponentSize(data: XComponentSize): void {
if (this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_STATIC) {
this.responsePreviewClick();
}
this.onChangeCollapsStatus(); // 横屏大图组件长按退出场景刷新UI
return; // 悬浮窗变焦宽策略预留,根据UX检视执行
this.zoomWidth = data.xComponentWidth;
this.mNodeController?.updateZoomLayout(this.zoomWidth, this.zoomHeight, this.drawHeight,
this.getZoomSelfCollapsStatus(), this.getZoomSelfDirectRotateAngle());
let redLineTopHeight = this.drawHeight;
this.redLineTopY = 150 - redLineTopHeight;
this.redLineTopRadius = (this.zoomWidth * this.zoomWidth / 4 +
redLineTopHeight * redLineTopHeight) / (2 * redLineTopHeight);
}
private getTabletPickerWidth(): number {
HiLog.i(TAG, `getTabletPickerWidth pickerWindowType: ${this.pickerWindowType}`);
if (this.pickerWindowType === PickerWindowType.TABLET_TOP_BOTTOM_HALF_SPLIT_SCREEN) {
return 0.57 * this.windowHeight;
}
if (this.pickerWindowType === PickerWindowType.TABLET_LEFT_RIGHT_SPLIT_SCREEN) {
return 0.5 * this.windowHeight;
}
if (this.pickerWindowType === PickerWindowType.TABLET_IN_FLOATING) {
return 0.5 * this.windowHeight;
}
if (this.pickerWindowType === PickerWindowType.TABLET_1_3_SPLIT_SCREEN) {
return 0.75 * this.windowHeight;
}
if (this.pickerWindowType === PickerWindowType.TABLET_FULL_SCREEN) {
if (DisplayService.getInstance().isOrientationVertical()) {
return 0.28 * this.windowHeight;
} else {
const blackSideBlack16to9TopBottom: number =
(this.windowHeight - getStates().get<number>('windowReducer', 'windowWidth') * 9 / 16) / 2;
const remainingAreaHeight: number = this.windowHeight - blackSideBlack16to9TopBottom * 2 - 32;
return remainingAreaHeight * 0.44;
}
}
return 0.43 * this.windowHeight;
}
private onChangeCollapsStatus(): void {
if (!DeviceInfo.isPhone()) {
return;
}
const size: Size = CameraBasicService.getInstance().getPreviewDisplaySize();
if (PickerUtils.getIsPicker() && this.state.isShowDefault) {
this.zoomWidth =
DeviceInfo.isTablet() ? this.getTabletPickerWidth() : getStates().get<number>('windowReducer', 'windowWidth');
} else {
this.zoomWidth = this.state.isShowDefault ? size.width :
this.state.isShowSemiCollapsed ? 308 : this.landscapeZoomWidth;
}
}
private isAllowVibratorSound(): boolean {
if (this.state.recordingState === RecordingState.READY || this.state.recordingState === RecordingState.PAUSED) {
return true;
}
return false;
}
private getZoomSelfCollapsStatus(): number {
return (this.state.isShowDefault && DeviceInfo.isPhone()) ? 0 : this.state.isShowSemiCollapsed ? 2 : 1;
}
private getZoomSelfDirectRotateAngle(): number {
return this.rotateAngle - (this.state.isShowSemiCollapsed ? 180 :
((this.isShowLandscape || DeviceInfo.isTablet()) || this.isShowTricollaps) ? 90 : 0);
}
// 更新等效焦距范围
private updateEquivalentFocalArray(): void {
if (!this.quickZoomValArr?.length || !this.opticalZoomValArr?.length ||
this.opticalZoomValArr.length !== this.opticalZoomDotIndexArr.length) {
HiLog.w(TAG, 'updateEquivalentFocalArray: skip initNode, zoom arrays empty or length mismatch.');
this.quickEquivalentFocalArr = [];
return;
}
this.quickEquivalentFocalArr = [];
let zoomPointInfo: ZoomPointInfo = CameraProxy.getInstance().getZoomPointInfo();
this.quickZoomValArr.forEach((zoomVal: number) => {
this.quickEquivalentFocalArr.push(Math.round(zoomVal * zoomPointInfo?.equivalentFocalLength / 1));
// 临时规避框架bug Math.round(zoomVal * zoomPointInfo.equivalentFocalLength / zoomPointInfo.zoomRatio));
});
if (this.quickEquivalentFocalArr.length !== this.quickZoomValArr.length) {
HiLog.w(TAG, 'updateEquivalentFocalArray: equivalent focal length mismatch, skip initNode.');
return;
}
this.mNodeController?.initNode(this.quickZoomValArr, this.quickEquivalentFocalArr, this.opticalZoomValArr,
this.opticalZoomDotIndexArr);
}
/**
* 点切变焦按人因曲线下发,更新取景框内容动效
* */
private clickZoomDelivery(): void {
this.changeZoomFunctionValue({
zoomRatio: this.curZoomValue,
isPrepareZoom: this.enableScreenReader,
isUnprepareZoom: this.enableScreenReader,
isSmoothZoom: !this.isBlockGestureEvent() && !this.isPrepareZoomWithMode(),
unprepareDelay: this.enableScreenReader ? ZoomParam.UNPREPARE_DELAY_TIME : undefined
});
}
private isPrepareZoomWithMode(): boolean {
return false;
}
private clearAndSetRouletteZoomTimer(): void {
this.clearRouletteZoomTimer();
this.rouletteZoomTimerId = setTimeout((): void => {
this.execRouletteZoom2QuickZoomAnim();
}, this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_TO_ROULETTE ? 1000 : 2000);
}
private execRouletteZoom2QuickZoomAnim(): void {
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_TO_QUICK;
this.updateAnimInitParam(2);
// 临时规避animateTo接口初始化后设置组件显示,动效参数不生效问题
this.createPlayNativeAnimator(800, 2);
this.rouletteDisappearExecSphereAnim();
this.rouletteDisappearExecBigTextAnim();
this.rouletteDisappearExecRedLineAndScaleAllAnim();
this.rouletteDisappearExecCarmenLineAnim();
this.rouletteDisappearExecQuickZoomOutsideAnim();
}
private clearRouletteZoomTimer(): void {
if (this.rouletteZoomTimerId !== Number.MIN_VALUE) {
clearTimeout(this.rouletteZoomTimerId);
this.rouletteZoomTimerId = Number.MIN_VALUE;
}
}
private clearAndSetBigTextTimer(): void {
this.clearBigTextTimer();
this.bigTextTimerId = setTimeout((): void => {
this.isShowBigText = false;
}, 1500);
}
private clearBigTextTimer(): void {
if (this.bigTextTimerId !== Number.MIN_VALUE) {
clearTimeout(this.bigTextTimerId);
this.bigTextTimerId = Number.MIN_VALUE;
}
}
private clearAndSetCycleClickTimer(): void {
this.clearCycleClickTimer();
this.cycleClickTimerId = setTimeout((): void => {
this.doCycleClickZoomAnim(false);
}, 1500);
}
private clearCycleClickTimer(): void {
if (this.cycleClickTimerId !== Number.MIN_VALUE) {
clearTimeout(this.cycleClickTimerId);
this.cycleClickTimerId = Number.MIN_VALUE;
}
}
private getMarginBottom(): number {
if (this.isVdeLandscape) {
return 10;
}
if (PickerUtils.getIsPicker() && this.pickerWindowType !== PickerWindowType.COLLAPSED_VERTICAL_FULL &&
this.pickerWindowType !== PickerWindowType.COLLAPSED_HORIZONTAL_FULL) {
return 29;
}
return (this.isShowLandscape || DeviceInfo.isTablet()) ? QUICK_ZOOM_MARGIN_BOTTOM_28 : 20;
}
private playColorfulZoomRingLottieAnimation(): void {
lottie.pause(COLORFUL_ZOOM_RING_ANIM_NAME);
lottie.destroy(COLORFUL_ZOOM_RING_ANIM_NAME);
this.animationItem = lottie.loadAnimation({
container: this.canvasContext,
renderer: 'canvas',
loop: true,
path: COLORFUL_ZOOM_RING_CANVAS_PATH,
autoplay: true,
name: COLORFUL_ZOOM_RING_ANIM_NAME
});
this.animationItem?.addEventListener('DOMLoaded', (): void => {
HiLog.i(TAG, 'lottieCanvasBuilder loaded');
});
this.animationItem?.addEventListener('complete', (): void => {
HiLog.i(TAG, 'lottieCanvasBuilder Canvas complete.');
});
}
@Builder
ColorfulZoomRing() {
Canvas(this.canvasContext)
.onReady(() => {
this.playColorfulZoomRingLottieAnimation();
})
.onDisAppear((): void => {
HiLog.i(TAG, 'lottieCanvasBuilder Canvas onDisAppear.');
lottie.destroy(COLORFUL_ZOOM_RING_ANIM_NAME);
})
.width(QUICK_ZOOM_ITEM_SIZE + 8)
.height(QUICK_ZOOM_ITEM_SIZE + 8)
.borderRadius(QUICK_ZOOM_ITEM_SIZE / 2 + 4)
.draggable(false)
.opacity(this.colorfulZoomRingOpacity)
}
@Builder
quickZoomViewCircle() {
Stack() {
if (this.isShowColorfulZoomRing) {
this.ColorfulZoomRing();
}
Circle()
.width(QUICK_ZOOM_ITEM_SIZE)
.height(QUICK_ZOOM_ITEM_SIZE)
.fill($r('sys.color.ohos_id_color_bottom_tab_icon_auxcolor01'))
}
.width(QUICK_ZOOM_ITEM_SIZE)
.height(QUICK_ZOOM_ITEM_SIZE)
.position({ x: 4 + this.curValIndex * (32 + 8), y: QUICK_ZOOM_POSITION_Y })
.animation((this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_CLICK &&
this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_STATIC &&
this.mZoomInnerStatus !== ZoomInnerStatus.FRONT_QUICK_SLIDE) ?
undefined :
{
duration: 500,
curve: curves.springMotion(0.314, 0.95),
iterations: 1,
playMode: PlayMode.Normal,
onFinish: () => {
if (this.isNeedVibratorWhenShowColorfulZoomRing) {
this.isNeedVibratorWhenShowColorfulZoomRing = false;
VibratorService.getInstance().triggerVibrator(VibratorService.CAMERA_GEAR_SLIP_HEAVY_ID);
}
HiLog.i(TAG, 'animation onFinish');
}
})
.visibility(this.quickZoomValArr.length > 0 ? Visibility.Visible : Visibility.Hidden)
.scale({ x: this.quickZoomScale, y: this.quickZoomScale })
.key(`${ComponentIdKeys.QUICK_ZOOM_INDEX}${this.curValIndex}-choose`)
}
@Builder
quickZoomView(): void {
Row() { // Row快捷变焦点的父组件
this.quickZoomViewCircle();
ForEach(this.quickZoomValArr, (item: number, index: number = 0) => {
this.quickForEachItem(item, index);
})
}
.width(this.quickZoomValArrLength * (32 + 8))
.height(40)
.borderRadius(this.isShowSubScreenRadius ? 0 : 21)
.margin({ bottom: this.getMarginBottom() })
.backgroundColor('#664d4d4d')
.opacity(this.mQuickOtherAlpha)
.justifyContent(FlexAlign.Center)
.visibility(this.getQuickZoomVisibility())
.onClick((): void => { // QuickZoom空消费触摸事件,两侧不阻塞兄弟节点点击对焦事件
})
.key(ComponentIdKeys.QUICK_ZOOM_BAR)
.enabled(this.state.uiEnable)
.accessibilityLevel(CommonConstants.ACCESSIBILITY_LEVEL_NO)
}
private getQuickZoomVisibility(): Visibility {
return this.isShowQuickZoom && (!this.zoomIsHide || GlobalContext.get().getIsPicker()) &&
!AppStorage.get('isPSDCollaps') ? Visibility.Visible : Visibility.Hidden;
}
@Builder
quickForEachItem(item: number, index: number): void {
Column() {
if (this.enableScreenReader) {
Column() {
this.quickZoomItem(item, index);
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width(40)
.height(40)
.position({ x: -4, y: -4 })
.accessibilityLevel(CommonConstants.ACCESSIBILITY_LEVEL_YES)
.accessibilitySelected(index === this.curValIndex)
.accessibilityGroup(true)
.accessibilityText(this.getAccessibilityText(item, index))
.accessibilityRole(AccessibilityRoleType.BUTTON)
.onClick((): void => {
this.onZoomItemClick(item, index);
})
} else {
Column() {
this.quickZoomItem(item, index);
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.onTouch((event) => {
if (this.isDepthFusionBarShow || this.isStitchingTypeBarShow) {
return;
}
if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
if (this.mZoomInnerStatus === ZoomInnerStatus.FRONT_QUICK_SLIDE) {
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_STATIC;
} else {
this.quickZoomClickRespond(index);
}
// 由于onTouch的时序问题,为保证点切变焦后必定有一次unprepare
this.touchUnprepareZoom();
}
})
}
}
.width(QUICK_ZOOM_ITEM_SIZE)
.height(QUICK_ZOOM_ITEM_SIZE)
.position({ x: 4 + index * (32 + 8), y: QUICK_ZOOM_POSITION_Y })
.justifyContent(FlexAlign.Center)
.transition(ZoomOperation.getInstance().isZoomViewCreated ?
TransitionEffect.asymmetric(
index >= this.oldQuickZoomArrLength ?
TransitionEffect.OPACITY
.animation({
duration: 800,
curve: Curve.Friction
}) : TransitionEffect.IDENTITY,
index >= this.quickZoomValArr.length ?
TransitionEffect.opacity(0.8)
.animation({
duration: 300,
curve: Curve.Friction
}) : TransitionEffect.IDENTITY
) : TransitionEffect.IDENTITY)
}
private onZoomItemClick(item: number, index: number) {
AccessibilityUtils.sendAnnounceAccessibilityEvent(
ResGetter.getStringSafe(`${
ResGetter.getStringSafe($r('app.string.accessibility_checked'))} ${
this.getQuickZoomABText(this.curZoomValue, this.curValIndex, item, index)}`));
this.quickZoomClickRespond(index);
}
private getAccessibilityText(item: number, index: number): string {
HiLog.i(TAG, `getAccessibilityText item = ${item}, index = ${index}`);
return ResGetter.getStringSafe(this.isOpenTouchGuide ?
`${this.getQuickZoomABText(this.curZoomValue, this.curValIndex, item, index)}` : '');
}
private getQuickZoomABText(mCurZoomValue: number, mCurValIndex: number, opticalZoomVal: number,
index?: number): string {
let realVal = mCurValIndex === index ? mCurZoomValue : opticalZoomVal;
let showVal = this.roundOrSplitZoomVal(realVal);
let firstQuickIsOneDecimalPlace = this.quickZoomValArr[0] === Math.floor(this.quickZoomValArr[0] * 10) / 10;
if (index === 0 && this.quickZoomValArr[0] <= this.getQuickMaxZoomVarW() && (firstQuickIsOneDecimalPlace ?
showVal <= this.quickZoomValArr[0] :
(realVal === this.quickZoomValArr[0] || realVal < Math.ceil(this.quickZoomValArr[0] * 10) / 10))) {
// return '广角,单指双击即可切换,双击按住即可上下拖动'
return ResGetter.getStringSafe($r('app.string.wide_angle')); // 避免前置0.8焦段
}
return getContext(this)
.resourceManager
.getPluralStringValueSync($r('app.plural.accessibility_tips_zoom_scale_new').id, showVal);
}
@Builder
equivZoomBuilder(item: number, index: number) {
Column() {
Text(this.getQuickZoomFontText(this.curZoomValue, this.curValIndex, item, index)) // 快捷变焦字符显示
.height(14)
.maxFontScale(CommonConstants.TEXT_SIZE_NORMAL)
.fontSize('11vp')
.fontWeight(FontWeight.Bolder)
.textShadow({
radius: 2,
color: '#29000000',
offsetX: 0,
offsetY: 0
})
.fontColor('#000000')
.opacity(index === this.curValIndex ? 0.6 : 1)
.fontFeature('\"ss01\" on')
Text('mm')
.height(8)
.lineHeight(5)
.maxFontScale(CommonConstants.TEXT_SIZE_NORMAL)
.fontSize('9vp')
.fontWeight(FontWeight.Bolder)
.textShadow({
radius: 2,
color: '#29000000',
offsetX: 0,
offsetY: 0
})
.fontColor(index === this.curValIndex ? '#000000' : '#ffffff')
.opacity(index === this.curValIndex ? 0.6 : 0)
.fontFeature('\"ss01\" on')
}
.onDisAppear(() => {
HiLog.i(TAG, `cycleClickZoom disAppear`)
})
.rotate({
angle: this.rotateAngle +
(this.isShowSemiCollapsed ?
-180 : (this.isShowLandscape || DeviceInfo.isTablet() || this.isShowTricollaps) ? -90 : 0)
})
}
@Builder
commonQuickZoom(item: number, index: number) {
// 广角缩放
Text(this.getQuickZoomFontText(this.curZoomValue, this.curValIndex, item, index)) // 快捷变焦字符显示
.height(17)
.maxFontScale(CommonConstants.TEXT_SIZE_NORMAL)
.fontSize('11vp')
.fontWeight(index === this.curValIndex ? FontWeight.Bolder : FontWeight.Bold)
.textShadow({
radius: 2,
color: index === this.curValIndex ? '#29000000' : '#33000000',
offsetX: 0,
offsetY: 0
})
.fontColor(index === this.curValIndex ? '#000000' : '#ffffff')
.opacity(index === this.curValIndex ? 0.6 : 1)
.animation({
duration: 100,
curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1)
})
.rotate({
angle: this.rotateAngle +
(this.state.isShowSemiCollapsed ?
-180 : (this.isShowLandscape || DeviceInfo.isTablet() || this.isShowTricollaps) ? -90 : 0)
})
.key(ComponentIdKeys.QUICK_ZOOM_INDEX + index)
.fontFeature('\"ss01\" on')
}
@Builder
mixEquivZoomAndCommonZoom(item: number, index: number): void {
if (this.isShowCycleClickZoom && index === this.curValIndex) {
Row() {
this.equivZoomBuilder(item, index);
}
.transition(this.getCycleClickZoomAnim())
} else {
Row() {
this.commonQuickZoom(item, index);
}
.transition(this.getCommonZoomAnim())
}
}
@Builder
quickZoomItem(item: number, index: number): void {
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
this.mixEquivZoomAndCommonZoom(item, index);
} else {
this.commonQuickZoom(item, index);
}
}
private getQuickZoomFontText(mCurZoomValue: number, mCurValIndex: number, opticalZoomVal: number,
index?: number): string {
let realVal = mCurValIndex === index ? mCurZoomValue : opticalZoomVal;
let showVal = this.roundOrSplitZoomVal(realVal);
let firstQuickIsOneDecimalPlace = this.quickZoomValArr[0] === Math.floor(this.quickZoomValArr[0] * 10) / 10;
if (this.isShowCycleClickZoom && index === mCurValIndex) {
let zoomPointInfo: ZoomPointInfo = CameraProxy.getInstance().getZoomPointInfo();
let curZoomInfo = Math.round(this.curZoomValue * zoomPointInfo?.equivalentFocalLength / 1);
HiLog.i(TAG, `quickZoomClickShowEquivZoom : ${curZoomInfo}.`);
return `${curZoomInfo}`;
}
if (index === 0 && this.quickZoomValArr[0] < 1.0 && (firstQuickIsOneDecimalPlace ?
showVal <= this.quickZoomValArr[0] :
(realVal === this.quickZoomValArr[0] || realVal < Math.ceil(this.quickZoomValArr[0] * 10) / 10))) {
return 'W'; // 避免前置第二个焦段
}
return SystemLanguageUtil.isRTL() ? 'x' + showVal : showVal + 'x';
}
private quickZoomClickRespond(index: number): void {
HiLog.i(TAG, `quickZoomClickRespond, index: ${index}, curValIndex: ${this.curValIndex}, mZoomInnerStatus:
${this.mZoomInnerStatus}, quickZoomValArr length: ${this.quickZoomValArr.length}.`);
if (this.state.tabSelector !== FunctionId.NONE) {
this.mAction.changeTabBarSelector(FunctionId.NONE, RenderLocation.NONE);
}
if (this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_STATIC &&
this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_CLICK) {
return;
}
if (index === 0 && this.quickZoomValArr.length === 1) {
this.mAction.changeZoomState(ZoomOuterStatus.ROULETTE_ZOOM);
this.clearBigTextTimer();
this.clearRouletteZoomTimer();
if (this.isShowQuickZoom && !this.isShowRouletteZoom) {
HiLog.d(TAG, 'only one quickZoom ClickRespond E.');
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_TO_ROULETTE;
// 更新所有状态-务必保持动效初始状态
this.updateAnimInitParam(1); // 动效起始参数初始化,避免delay异常
this.createPlayNativeAnimator(1500, 1); // 开始触发帧回调刷新UI
this.rouletteAppearExecQuickSelectedAndOpacityAnim(); // 长按出轮盘变焦,执行快捷变焦选中圆和其他不透明度动效
this.rouletteAppearExecLongCurveHaloAnim(); // 长按出轮盘变焦,执行光晕动效
this.rouletteAppearExecSphereAnim(); // 长按出轮盘变焦,执行光球动效
this.rouletteAppearExecBigTextAnim(); // 长按出轮盘变焦,执行大字体动效
this.rouletteAppearExecRedLineAndScaleAllAnim(); // 长按出轮盘变焦,执行焦圈中心刻度、焦圈动效
this.rouletteAppearExecCarmenLineAnim(); // 长按出轮盘变焦,执行卡门线动效
this.rouletteAppearExecQuickZoomOutsideAnim(); // 长按出轮盘变焦,执行快捷变焦圆外层动效
}
this.clearAndSetRouletteZoomTimer(); // 定时器内包含大字体
return;
}
if (this.curValIndex !== index) {
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom() && this.isShowCycleClickZoomAndEquivZoom()) {
this.isDuringCycleClick = true;
this.doCycleClickZoomAnim(true);
this.clearAndSetCycleClickTimer();
}
if (this.isAllowVibratorSound()) {
VibratorService.getInstance().triggerVibrator(VibratorService.CAMERA_CLICK_EFFECT_ID);
}
this.curZoomValue = this.quickZoomValArr[index];
this.curValIndex = index;
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_CLICK;
this.clickZoomDelivery(); // 点切变焦执行下发
this.mAction.changeOtherComponent();
} else {
this.doCycleClickZoom();
return;
}
// if (index === 0) {
// this.mAction.updateShowExposureAndFocusMoveFlag(false);
// }
if (this.isShowBigText) {
this.clearAndSetBigTextTimer();
} else {
this.isShowBigText = true;
}
}
@Builder
touchRespondSectorHotZone(): void {
Row() {
Circle()
.fillOpacity(0)
.borderRadius(this.redLineTopRadius * 2 + 20)
.width(this.redLineTopRadius * 2 + 20)
.height(this.redLineTopRadius * 2 + 20)
.position({
x: (this.zoomWidth / 2 - this.redLineTopRadius - 10),
y: this.redLineTopY - 10
})
.onTouch((event?: TouchEvent) => {
this.nodeContainerOnTouch(event);
})
.key(ComponentIdKeys.RING_ZOOM_BAR)
.onClick((): void => {
}) // 空消费触摸事件
}
.width(this.zoomWidth)
.height(150)
.clip(true)
.gesture(
PanGesture({ fingers: 1, distance: 1 })
.onActionStart((event?: GestureEvent) => {
this.panGestureStart(event);
})
.onActionUpdate((event?: GestureEvent) => {
this.panGestureUpdate(event);
})
.onActionEnd((event?: GestureEvent) => {
if (this.isDepthFusionConflictLongPress) {
this.isDepthFusionConflictLongPress = false;
return;
}
this.panGestureEnd(event);
})
.onActionCancel((event?: GestureEvent) => {
if (this.isDepthFusionConflictLongPress) {
this.isDepthFusionConflictLongPress = false;
return;
}
this.panGestureEnd(event);
})
)
}
@Builder
bigTextView(): void {
Text(this.getBigText())
.fontSize(vp2px(px2fp(24)))
.fontWeight(FontWeight.Medium)
.width(this.zoomWidth)
.height(40)
.textAlign(TextAlign.Center)
.textShadow({
radius: 2,
color: '#33000000',
offsetX: 0,
offsetY: 0
})
.fontColor(Color.White)
.rotate({
centerX: '50%',
centerY: '50%',
angle: this.rotateAngle +
(this.state.isShowSemiCollapsed ? -180 : (this.isShowLandscape || DeviceInfo.isTablet()) ? -90 : 0)
})
.position(this.getBigTextPosition())
.transition(this.getBigTextAppearDisappearAnim())
.onAppear((): void => {
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK ||
this.mZoomInnerStatus === ZoomInnerStatus.FRONT_QUICK_SLIDE) {
this.clearAndSetBigTextTimer(); // 设置定时器
}
})
.onDisAppear((): void => {
})
.hitTestBehavior(HitTestMode.Transparent)
.key(ComponentIdKeys.ZOOM_BIG_TEXT)
}
@Builder
equivalentFocalZoomBigTextView(): void {
Text(this.getBigText())
.fontSize(vp2px(px2fp(24)))
.fontWeight(FontWeight.Medium)
.width(this.zoomWidth)
.height(40)
.textAlign(TextAlign.Center)
.textShadow({
radius: 2,
color: '#33000000',
offsetX: 0,
offsetY: 0
})
.fontColor(Color.White)
.transition(this.getAppearDisappearAnim(true))
.onAppear((): void => {
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK ||
this.mZoomInnerStatus === ZoomInnerStatus.FRONT_QUICK_SLIDE) {
this.clearAndSetBigTextTimer(); // 设置定时器
}
})
.onDisAppear((): void => {
})
.hitTestBehavior(HitTestMode.Transparent)
.key(ComponentIdKeys.ZOOM_BIG_TEXT)
}
@Builder
equivalentFocalBigTextView(): void {
Text(this.getEquivalentFocalBigText())
.fontSize(vp2px(px2fp(18)))
.fontWeight(FontWeight.Medium)
.width(this.zoomWidth)
.height(this.isDuringCycleClick ? 30 : 40)
.textAlign(TextAlign.Center)
.translate({ x: 0, y: -10 })
.opacity(0.8)
.textShadow({
radius: 2,
color: '#33000000',
offsetX: 0,
offsetY: 0
})
.visibility(this.getEquivalentFocalBigTextVis())
.fontColor(Color.White)
.transition(this.getAppearDisappearAnim(false))
.onAppear((): void => {
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK ||
this.mZoomInnerStatus === ZoomInnerStatus.FRONT_QUICK_SLIDE) {
this.clearAndSetBigTextTimer(); // 设置定时器
}
})
.onDisAppear((): void => {
})
.hitTestBehavior(HitTestMode.Transparent)
.key(ComponentIdKeys.EQUIVALENT_FOCAL_BIG_TEXT)
}
private isSupportedEquivalentFocalBigText(): boolean {
if (!CameraAppCapability.getInstance().getIsSupportedEquivalentFocalBigText()) {
return false;
}
if (this.state.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT ||
OutputSwitcher.getInstance().getOutput() === OutputType.VIDEO_OUTPUT) {
return false;
}
return true;
}
private getEquivalentFocalBigTextVis(): Visibility {
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
return this.opticalZoomValArr.indexOf(this.curZoomValue) !== -1 ? Visibility.Visible : Visibility.Hidden;
}
return Visibility.Visible;
}
private isNeedShowEquivalentFocalBigText(): boolean {
if (!this.isSupportedEquivalentFocalBigText()) {
return false;
}
if (this.curZoomValue >= POINT_ZOOM && this.curZoomValue - POINT_ZOOM < FOCAL_ZOOM_EPSILON) {
return true;
}
return this.quickZoomValArr.some(zoom => Math.abs(zoom - this.curZoomValue) < FOCAL_ZOOM_EPSILON);
}
private getEquivalentFocalBigText(): string | Resource {
HiLog.i(TAG, `getEquivalentFocalBigText: ${JSON.stringify(this.quickEquivalentFocalArr)}, ${this.curValIndex}}`);
let tempValue = this.quickEquivalentFocalArr[this.calZoomValIndex()];
if (this.curZoomValue >= POINT_ZOOM && this.curZoomValue - POINT_ZOOM < FOCAL_ZOOM_EPSILON) {
tempValue = POINT_ZOOM_EQUIVALENT_FOCAL;
}
if (this.isDuringCycleClick) {
let zoomPointInfo: ZoomPointInfo = CameraProxy.getInstance().getZoomPointInfo();
tempValue = Math.round(this.curZoomValue * zoomPointInfo?.equivalentFocalLength / 1);
}
return SystemLanguageUtil.isRTL() ? `mm${tempValue}` : `${tempValue}mm`;
}
private getBigText(): string | Resource {
let firstQuickIsOneDecimalPlace = this.quickZoomValArr[0] === Math.floor(this.quickZoomValArr[0] * 10) / 10;
let quickZoomVarW = this.getQuickMaxZoomVarW();
if (this.quickZoomValArr[0] <= quickZoomVarW && (firstQuickIsOneDecimalPlace ?
Math.floor(this.curZoomValue * 10) / 10 <= this.quickZoomValArr[0] :
(this.curZoomValue === this.quickZoomValArr[0] ||
this.curZoomValue < Math.ceil(this.quickZoomValArr[0] * 10) / 10))) { // 避免前置第二个焦段
return $r('app.string.wide_angle');
}
let tempValue = this.roundOrSplitZoomVal(this.curZoomValue);
if (Number.isInteger(tempValue)) {
return SystemLanguageUtil.isRTL() ? `x${tempValue}.0` : `${tempValue}.0x`;
}
return SystemLanguageUtil.isRTL() ? `x${tempValue}` : `${tempValue}x`;
}
private getABText(): string {
let firstQuickIsOneDecimalPlace = this.quickZoomValArr[0] === Math.floor(this.quickZoomValArr[0] * 10) / 10;
if (this.quickZoomValArr[0] <= this.getQuickMaxZoomVarW() && (firstQuickIsOneDecimalPlace ?
Math.floor(this.curZoomValue * 10) / 10 <= this.quickZoomValArr[0] :
(this.curZoomValue === this.quickZoomValArr[0] ||
this.curZoomValue < Math.ceil(this.quickZoomValArr[0] * 10) / 10))) { // 避免前置第二个焦段
return ResGetter.getStringSafe($r('app.string.wide_angle'));
}
let tempValue = this.roundOrSplitZoomVal(this.curZoomValue);
return getContext(this)
.resourceManager
.getPluralStringValueSync($r('app.plural.accessibility_tips_zoom_scale_new').id, tempValue);
}
private getBigTextPosition(): Position {
if (!this.isLandscapeIndex() && getStates().get<boolean>('customFilterStyleReducer', 'customFilterCardOpened')) {
let windowStatus = getStates().get<window.WindowStatusType>('windowReducer', 'windowStatus');
if (windowStatus === window.WindowStatusType.FLOATING) {
let indicatorLocationY = this.currentFootBarLocation - INDICATOR_HEIGHT - DISTANCE_INDICATOR_FOOTBAR +
this.layoutService.previewPosition;
return {
x: 0,
y: -this.zoomLocation + (indicatorLocationY - this.layoutService.previewPosition) / 2 +
this.layoutService.previewPosition + this.customFilterTopLineHeight - ZOOM_VIEW_HEIGHT / 2
}
}
let xComponentWidth = getStates().get<number>('previewReducer', 'xComponentWidth');
return {
x: 0,
y: -this.layoutService.rouletteZoomPosition + this.layoutService.previewPosition1to1 +
(xComponentWidth - ZOOM_VIEW_HEIGHT) / 2
}
}
if (this.isLemCollaps) {
return AppStorage.get('isPSDCollaps') ? { x: 0, y: -this.windowHeight } : { x: 0, y: -214 };
}
if (this.state.isShowLowAngleShotView && this.state.isShowSemiCollapsed) {
const componentSize: Size = CameraBasicService.getInstance().getSemiDisplaySize(this.state.mode);
if (componentSize.width === 0) {
HiLog.e(TAG, 'componentSizeWidth error, value: 0.');
return { x: 0, y: 0 };
}
if (Math.abs(componentSize.height / componentSize.width - 4 / 3) < EPSILON) {
return {
x: 0,
y: -this.drawHeight + 80 - 40 - 33 - 14 - 34 // - 34 是为了调整数字位置,预览在悬浮态下屏不被截断
};
}
}
return {
x: 0,
y: -this.drawHeight + 80 - 40 - 33 - 14 // - 14 是为了调整数字位置,屏展开态,录像1080p时不被截断
};
}
private getAppearDisappearAnim(isBigText: boolean): TransitionEffect {
if (!CameraAppCapability.getInstance().getIsSupportedEquivalentFocalBigText() ||
!CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
return this.getBigTextAppearDisappearAnim();
}
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK) {
// 点切视为一个整体由父组件设置动效
return TransitionEffect.IDENTITY;
}
if (!isBigText && (this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_SLIDE ||
this.mZoomInnerStatus === ZoomInnerStatus.PINCH_ZOOM)) {
// 等效焦距划切进场动效取消移动, 只淡入
return TransitionEffect.asymmetric(
TransitionEffect.OPACITY
.animation({
duration: 100,
curve: Curve.Friction
}),
TransitionEffect.OPACITY
.animation({
duration: 100,
curve: Curve.Sharp
})
);
}
// 大字体划切动效
return TransitionEffect.asymmetric(
TransitionEffect // 大字体进场动效
.translate({ x: 0, y: -2 })
.animation({
duration: 300,
curve: Curve.Friction
})
.combine(TransitionEffect.OPACITY
.animation({
duration: 100,
curve: Curve.Friction
})),
TransitionEffect.OPACITY // 大字体退场动效
.animation({
duration: 100,
curve: Curve.Sharp
})
);
}
private getFatherBigTextAnim(): TransitionEffect {
HiLog.i(TAG, `getFatherBigTextAnim: ${this.mZoomInnerStatus}.`);
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK) {
return TransitionEffect.asymmetric(
TransitionEffect // 大字体进场动效
.scale({ x: 0.9, y: 0.9 })
.animation({
duration: 500,
curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1)
})
.combine(TransitionEffect.OPACITY
.animation({
duration: 100,
curve: Curve.Sharp
})),
TransitionEffect // 大字体退场动效
.scale({ x: 0.9, y: 0.9 })
.animation({
duration: 250,
curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1)
})
.combine(TransitionEffect.OPACITY
.animation({
duration: 250,
curve: curves.cubicBezierCurve(0, 0, 0, 1)
}))
);
}
return TransitionEffect.IDENTITY;
}
private getBigTextAppearDisappearAnim(): TransitionEffect {
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_STATIC ||
this.mZoomInnerStatus === ZoomInnerStatus.QUICK_CLICK) {
return TransitionEffect.asymmetric(
TransitionEffect // 大字体进场动效
.scale({ x: 0.9, y: 0.9 })
.animation({
duration: 500,
curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1)
})
.combine(TransitionEffect.OPACITY
.animation({
duration: 100,
curve: Curve.Sharp
})),
TransitionEffect // 大字体退场动效
.scale({ x: 0.9, y: 0.9 })
.animation({
duration: 250,
curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1)
})
.combine(TransitionEffect.OPACITY
.animation({
duration: 250,
curve: curves.cubicBezierCurve(0, 0, 0, 1)
}))
);
}
return TransitionEffect.asymmetric(
TransitionEffect // 大字体进场动效
.translate({ x: 0, y: -2 })
.animation({
duration: 300,
curve: Curve.Friction
})
.combine(TransitionEffect.OPACITY
.animation({
duration: 100,
curve: Curve.Friction
})),
TransitionEffect.OPACITY // 大字体退场动效
.animation({
duration: 100,
curve: Curve.Sharp
})
);
}
private updateAnimInitParam(animType: number): void {
HiLog.i(TAG, `updateAnimInitParam animType: ${animType}.`);
this.longCurveHaloMatteScale = animType === 1 ? 0 : 1;
this.longCurveHaloOpacity = animType === 1 ? 0 : 1;
this.spherePosition = animType === 1 ? 8 : 0; // 入场动效,向上归位; 退场动效,向下退出
this.sphereScale = animType === 1 ? 0 : 1;
this.sphereColor = animType === 1 ? 0 : 1;
this.sphereHaloOpacity = animType === 1 ? 0 : 0.5; // 光球-光晕,0.5驻场及退场
this.redLineAndTextOpacity = animType === 1 ? 0 : 1;
this.scaleAllMatteScale = animType === 1 ? 0 : 1;
this.scaleAllOpacity = animType === 1 ? 0 : 1;
this.carmenLineMatteScale = animType === 1 ? 0 : 1;
this.carmenLineOpacity = animType === 1 ? 0 : 1;
}
// 创建/重置并开启native动效执行器
private createPlayNativeAnimator(mDuration: number, mAnimType: number): void {
HiLog.d(TAG, 'createPlayNativeAnimator E.');
if (mAnimType === 1 && !this.isShowBigText) {
this.clearBigTextTimer();
this.isShowBigText = true;
}
// 变焦环收起动效30fps绘帧,大帧数目减半,降低RS绘帧压力,降低预览卡顿概率
const expectedFrameRate: ExpectedFrameRateRange = {
min: 0,
max: 120,
expected: 30
}
let options: AnimatorOptions = {
duration: mDuration,
easing: 'friction',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0,
end: 1
};
if (this.mStateChangeAnimator) {
try {
this.mStateChangeAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `createPlayNativeAnimator error: ${error?.code}.`);
}
} else {
this.mStateChangeAnimator = animator.create(options);
}
this.mStateChangeAnimator.setExpectedFrameRateRange(expectedFrameRate);
this.mStateChangeAnimator.onFinish = () => {
if (!this.mNodeController?.getIsTextParallel2CarmenLine() && mAnimType === 1) {
HiLog.i(TAG, `createPlayNativeAnimator getIsTextParallel2CarmenLine mAnimType: ${mAnimType}.`);
this.mNodeController?.execLandscapeSlideExpandAnim(this.curZoomValue); // 横屏滑动关键刻度展开动效
}
this.nativeAnimatorOnFinish(mAnimType);
};
this.mStateChangeAnimator.onFrame = (value) => {
this.nativeAnimatorOnFrame(mAnimType);
};
this.mStateChangeAnimator?.play();
}
private nativeAnimatorOnFinish(mAnimType: number): void {
HiLog.i(TAG, `createPlayNativeAnimator finish AnimType: ${mAnimType}, InnerStatus: ${this.mZoomInnerStatus}.`);
if (mAnimType === 1 && this.mZoomInnerStatus === ZoomInnerStatus.QUICK_TO_ROULETTE) {
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_STATIC;
} else if (mAnimType === 2 && this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_TO_QUICK) {
if (this.isShowBigText && !this.isSelfieStickLongPress && !this.isSelfieStickShortPress) {
this.isShowBigText = false;
}
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_STATIC;
}
}
private nativeAnimatorOnFrame(mAnimType: number): void {
HiLog.d(TAG, `createPlayNativeAnimator onframe AnimType: ${mAnimType}, InnerStatus: ${this.mZoomInnerStatus}.`);
if (!this.isShowRouletteZoom || !this.curZoomValue ||
(mAnimType === 1 && this.mZoomInnerStatus !== ZoomInnerStatus.QUICK_TO_ROULETTE) ||
(mAnimType === 2 && this.mZoomInnerStatus !== ZoomInnerStatus.ROULETTE_TO_QUICK)) {
return;
}
try {
this.mNodeController?.invalidate(this.curZoomValue, 1000, this.getAnimParamData(mAnimType)); // 数据小容器传递
} catch (err) {
HiLog.e(TAG, `NodeController invalidate err: ${err}.`);
}
}
private getAnimParamData(mAnimType: number): EntryExitAnimParam {
return {
longCurveHaloMatteScale: this.longCurveHaloMatteScale,
longCurveHaloOpacity: this.longCurveHaloOpacity,
spherePosition: this.spherePosition,
sphereScale: this.sphereScale,
sphereColor: this.sphereColor,
sphereHaloOpacity: this.sphereHaloOpacity,
redLineAndTextOpacity: this.redLineAndTextOpacity,
scaleAllMatteScale: this.scaleAllMatteScale,
scaleAllOpacity: this.scaleAllOpacity,
carmenLineMatteScale: this.carmenLineMatteScale,
carmenLineOpacity: this.carmenLineOpacity,
animType: mAnimType,
};
}
// 创建/重置并开启frame各动效执行器
private createPlayFrameAnimators(index: number, mDelay: number, mEasing: string, mDuration: number, mBegin:
number, mEnd: number, frameCallback: Function, finishCallback?: Function): void {
let options: AnimatorOptions = {
duration: mDuration,
easing: mEasing,
delay: mDelay,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: mBegin,
end: mEnd
};
if (this.mFrameAnimResultArr[index]) {
try {
(this.mFrameAnimResultArr[index] as AnimatorResult).reset(options);
} catch (error) {
HiLog.i(TAG, `createPlayFrameAnimators error: ${error}.`);
}
} else {
this.mFrameAnimResultArr[index] = animator.create(options);
}
(this.mFrameAnimResultArr[index] as AnimatorResult).onFinish = () => {
HiLog.d(TAG, `createPlayFrameAnimators finish index: ${index}.`);
if (finishCallback) {
finishCallback();
}
};
(this.mFrameAnimResultArr[index] as AnimatorResult).onFrame = (value: number) => {
frameCallback(value);
};
(this.mFrameAnimResultArr[index] as AnimatorResult)?.play();
}
// 清除native、frame动效执行器
private cancelNativeAndFrameAnimators(): void {
HiLog.d(TAG, 'cancelNativeAndFrameAnimators E.');
this.mStateChangeAnimator?.cancel();
for (let i = 0; i < this.mFrameAnimResultArr.length; i++) {
this.mFrameAnimResultArr[i]?.cancel();
}
}
private getZoomViewWidth(): number {
let curMode: ModeType = getStates().get<ModeType>('modeReducer', 'mode');
let isOverlap: boolean = (curMode === ModeType.PHOTO || curMode === ModeType.VIDEO);
if (this.quickZoomValArr.length === 5 && isOverlap) { // 快捷变焦点为5个的话,考虑到部分模式与左右响应区重叠,重新调整响应区大小
return this.isShowRouletteZoom ? this.zoomWidth :
this.quickZoomValArr.length * (32 + 8) + 2 * QUICK_ZOOM_MARGIN_LR_SHORT;
} else {
return this.isShowRouletteZoom ? this.zoomWidth :
this.quickZoomValArr.length * (32 + 8) + 2 * QUICK_ZOOM_MARGIN_LR_LONG;
}
}
@Builder
equivalentZoomBigText() {
Column() {
this.equivalentFocalZoomBigTextView();
if (this.isNeedShowEquivalentFocalBigText() || this.isDuringCycleClick) {
this.equivalentFocalBigTextView();
}
}
.width(this.zoomWidth)
.height(80)
.position(this.getBigTextPosition())
.transition(this.getFatherBigTextAnim())
.rotate({
centerX: '50%',
centerY: '50%',
angle: this.rotateAngle +
(this.isShowSemiCollapsed ? -180 :
(this.isShowLandscape || DeviceInfo.isTablet()) ? -90 : 0)
})
.hitTestBehavior(HitTestMode.Transparent)
}
/**
* 变焦组件渲染规则: 快捷变焦组件与轮盘变焦条组件, 实现按需加载、部分渲染降低负载, 提升UI渲染性能;
*/
build() {
Stack({ alignContent: Alignment.Bottom }) {
if (this.isShowQuickZoom) {
this.quickZoomView();
}
if (this.isShowRouletteZoom &&
((!this.isLandscapeIndex() ? !this.isCustomFilterImmersive : true) ||
GlobalContext.get().getIsPicker())) {
NodeContainer(this.mNodeController)
.width(this.zoomWidth)
.height(150)
this.touchRespondSectorHotZone();
}
if (this.isShowBigText && !this.isDuringCycleClick) {
if (!this.isSupportedEquivalentFocalBigText()) {
this.bigTextView();
} else {
this.equivalentZoomBigText();
}
}
if (this.isShowBigText && this.isDuringCycleClick) {
this.equivalentZoomBigText();
}
Column()
.width(this.getZoomViewWidth())
.height(this.isVdeLandscape ? 60 : this.isShowRouletteZoom ? 80 : 78)
.offset({ y: this.isShowRouletteZoom ? 0 : -4 })
.hitTestBehavior(HitTestMode.Transparent)
.enabled(this.state.uiEnable)
.key(ComponentIdKeys.ROULETTE_ZOOM_VIEW_COLUMN)
.accessibilityLevel(CommonConstants.ACCESSIBILITY_LEVEL_NO)
.visibility((this.zoomIsHide && !GlobalContext.get().getIsPicker()) ? Visibility.Hidden :
Visibility.Visible)
.opacity(this.zoomOpacity)
.onTouch((event) => {
if (event.type === TouchType.Move) {
return;
}
if (event.type === TouchType.Down) {
this.touchPrepareZoom();
return;
}
if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
this.touchUnprepareZoom();
}
})
.gesture(
PanGesture({ fingers: 1, distance: 1, direction: PanDirection.Horizontal })
.onActionStart((event?: GestureEvent) => {
this.isDuringCycleClick = false;
this.doCycleClickZoomAnim(false);
this.panGestureStart(event);
if (this.state.tabSelector !== FunctionId.NONE) {
this.mAction.changeTabBarSelector(FunctionId.NONE, RenderLocation.NONE);
}
})
.onActionUpdate((event?: GestureEvent) => {
this.panGestureUpdate(event);
})
.onActionEnd((event?: GestureEvent) => {
if (this.isDepthFusionConflictLongPress) {
this.isDepthFusionConflictLongPress = false;
return;
}
this.panGestureEnd(event);
if (this.enableScreenReader) {
// 滚动条自动播报
AccessibilityUtils.sendAnnounceAccessibilityEvent(this.getABText())
}
})
.onActionCancel((event?: GestureEvent) => {
if (this.isDepthFusionConflictLongPress) {
this.isDepthFusionConflictLongPress = false;
return;
}
this.panGestureEnd(event);
}))
.parallelGesture(
LongPressGesture({ repeat: false, duration: 300 })
.onAction((event?: GestureEvent) => {
this.longPressGestureAction(event);
if (this.state.tabSelector !== FunctionId.NONE) {
this.mAction.changeTabBarSelector(FunctionId.NONE, RenderLocation.NONE);
}
})
.onActionEnd((event?: GestureEvent) => {
this.longPressGestureActionEnd(event);
})
.onActionCancel((event?: GestureEvent) => {
this.longPressGestureActionEnd(event);
}))
}
.onTouch((event: TouchEvent) => { // 变焦组件不在预览区域时,慢动作全焦段扩展变焦模糊没有截图
if (event.type !== TouchType.Down) {
return;
}
BlurAnimateUtil.generatePixelMapFromSurface();
})
.accessibilityLevel(CommonConstants.ACCESSIBILITY_LEVEL_YES)
// 开启屏幕朗读并且非平板
.key(ComponentIdKeys.ROULETTE_ZOOM_VIEW_STACK)
.width(this.zoomWidth)
.height(this.isVdeLandscape ? 60 : 80)
.offset(this.isShowLandscape ? { x: this.mQuickOffsetY } : undefined)
}
private touchPrepareZoom(): void {
this.changeZoomFunctionValue({ zoomRatio: undefined, isPrepareZoom: true });
}
private touchUnprepareZoom(): void {
this.changeZoomFunctionValue({
zoomRatio: undefined,
isUnprepareZoom: true,
unprepareDelay: ZoomParam.UNPREPARE_DELAY_TIME
});
}
private nodeContainerOnTouch(event?: TouchEvent): void {
if (!event || event.type === TouchType.Move) {
return;
}
if (event.type === TouchType.Down && this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_STATIC) {
this.touchPrepareZoom();
if (this.isAllowVibratorSound()) {
VibratorService.getInstance().triggerVibrator(VibratorService.MODE_SWITCH_EFFECT_ID);
}
this.clearBigTextTimer();
this.clearRouletteZoomTimer();
return;
}
if ((event.type === TouchType.Up || event.type === TouchType.Cancel)) {
if (this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_STATIC) {
let centreDiffTouchX = this.redLineTopRadius + 10 - event.touches[0].x;
let x = Math.abs(centreDiffTouchX);
let y = this.redLineTopRadius + 10 - event.touches[0].y;
let clickDotRadialLen = Math.sqrt(x * x + y * y);
let alpha = Math.asin(x / clickDotRadialLen) * (centreDiffTouchX > 0 ? -1 : 1);
let curZoomAngle: number = this.calZoomAngle(this.curZoomValue);
let nextZoomAngle = curZoomAngle - alpha * 180 / Math.PI;
let nextZoomValue = this.calZoomValue(nextZoomAngle);
let pickIndex = -1; // 光学变焦点附近吸附
for (let i = 0; i < this.opticalZoomDotAngleArr.length; i++) {
if (Math.abs(nextZoomAngle - this.opticalZoomDotAngleArr[i]) <= ZoomParam.SCALE_GAP_ANGLE * 3) {
pickIndex = i;
break;
}
}
if ((nextZoomValue < this.opticalZoomValArr[0] ||
nextZoomValue > this.opticalZoomValArr[this.opticalZoomValArr.length - 1]) && pickIndex !== -1) {
this.clearAndSetRouletteZoomTimer(); // 定时器内包含大字体
this.touchUnprepareZoom();
return;
}
this.execRouletteClickRespondAnim(pickIndex, nextZoomValue);
this.touchUnprepareZoom();
this.mAction.changeOtherComponent();
} else {
this.touchUnprepareZoom();
}
}
}
private execRouletteClickRespondAnim(pickIndex: number, nextZoomValue: number): void {
let oldZoomValue = this.curZoomValue;
if (pickIndex !== -1) {
this.curZoomValue = this.opticalZoomValArr[pickIndex];
} else {
this.curZoomValue = nextZoomValue;
}
this.curValIndex = this.calZoomValIndex(); // 当前变焦值在快捷变焦点列表的index下界
this.clickZoomDelivery(); // 点切变焦平滑曲线下发
let options: AnimatorOptions = {
duration: 400,
easing: 'friction',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0,
end: 1
};
if (this.mStateChangeAnimator !== null) {
try {
this.mStateChangeAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `createPlayNativeAnimator error: ${error?.code}.`);
}
} else {
this.mStateChangeAnimator = animator.create(options);
}
this.mStateChangeAnimator.onFinish = () => {
this.clearAndSetRouletteZoomTimer(); // 定时器内包含大字体
if (!this.mNodeController?.getIsTextParallel2CarmenLine()) {
this.mNodeController?.execLandscapeSlideExpandAnim(this.curZoomValue); // 横屏滑动关键刻度展开动效
}
};
this.mStateChangeAnimator.onFrame = (value) => {
if (!this.isShowRouletteZoom || !this.curZoomValue) {
return;
}
try {
this.mNodeController?.invalidate((this.curZoomValue - oldZoomValue) * value + oldZoomValue, 1000); // 数据小容器传递
} catch (err) {
HiLog.e(TAG, `NodeController invalidate err: ${err}.`);
}
};
this.mStateChangeAnimator?.play();
}
private longPressGestureAction(event?: GestureEvent): void {
if (!event || this.isBlockGestureEvent()) {
return;
}
if (this.isDepthFusionBarShow) {
this.isDepthFusionConflictLongPress = true;
HiLog.i(TAG, 'depthFusion conflict longPress');
return;
}
this.mAction.uiStateWithMode(false);
if (!this.state.isImmersive) {
this.mAction.isEnterImmersive(true);
}
this.mAction.changeZoomState(ZoomOuterStatus.ROULETTE_ZOOM);
this.clearBigTextTimer();
this.clearRouletteZoomTimer();
if (this.isShowQuickZoom && !this.isShowRouletteZoom) {
HiLog.d(TAG, 'outer LongPressGesture onAction E.');
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_TO_ROULETTE;
// 更新所有状态-务必保持动效初始状态
this.updateAnimInitParam(1); // 动效起始参数初始化,避免delay异常
this.createPlayNativeAnimator(1500, 1); // 开始触发帧回调刷新UI
this.rouletteAppearExecQuickSelectedAndOpacityAnim(); // 长按出轮盘变焦,执行快捷变焦选中圆和其他不透明度动效
this.rouletteAppearExecLongCurveHaloAnim(); // 长按出轮盘变焦,执行光晕动效
this.rouletteAppearExecSphereAnim(); // 长按出轮盘变焦,执行光球动效
this.rouletteAppearExecBigTextAnim(); // 长按出轮盘变焦,执行大字体动效
this.rouletteAppearExecRedLineAndScaleAllAnim(); // 长按出轮盘变焦,执行焦圈中心刻度、焦圈动效
this.rouletteAppearExecCarmenLineAnim(); // 长按出轮盘变焦,执行卡门线动效
this.rouletteAppearExecQuickZoomOutsideAnim(); // 长按出轮盘变焦,执行快捷变焦圆外层动效
}
}
private longPressGestureActionEnd(event?: GestureEvent): void {
if (!event || this.isBlockGestureEvent() || this.isDepthFusionConflictLongPress) {
this.isDepthFusionConflictLongPress = false;
return;
}
this.mAction.isEnterImmersive(false);
this.mAction.uiStateWithMode(true);
this.mAction.changeOtherComponent();
this.clearAndSetRouletteZoomTimer(); // 定时器内包含大字体
this.curValIndex = this.calZoomValIndex(); // 当前变焦值在快捷变焦点列表的index下界
}
// 长按出轮盘变焦,执行快捷变焦选中圆和其他不透明度动效
private rouletteAppearExecQuickSelectedAndOpacityAnim(): void {
if (this.quickZoomScale === 0) {
this.quickZoomScale = 1;
}
animateToImmediately({
// 快捷变焦-选中圆: scale 1-0.9; 0-150ms
curve: Curve.Friction,
duration: 150,
onFinish: () => {
this.isShowRouletteZoom = true;
}
}, () => {
this.quickZoomScale = 0.9;
});
animateToImmediately({
// 快捷变焦-选中圆: scale 0.9-1; 150ms-300ms
delay: 150,
curve: Curve.Sharp,
duration: 150
}, () => {
this.quickZoomScale = 1;
});
animateToImmediately({
delay: 200,
curve: Curve.Sharp,
duration: 250,
onFinish: () => {
if (this.mZoomInnerStatus === ZoomInnerStatus.QUICK_TO_ROULETTE) {
this.isShowQuickZoom = false;
}
}
}, () => {
this.quickZoomAlpha = 0; // 快捷变焦-选中圆: opacity 1-0; 200ms-450ms
this.mQuickOtherAlpha = 0; // 快捷变焦-其他: opacity 1-0; 200ms-450ms
});
}
// 长按出轮盘变焦,执行光晕动效
private rouletteAppearExecLongCurveHaloAnim(): void {
this.createPlayFrameAnimators(0, 250, 'friction', 1250, 0, 1, (frameValue: number) => {
this.longCurveHaloMatteScale = frameValue; // 光晕: 遮罩scale 0-1; 250-1500ms
});
this.createPlayFrameAnimators(1, 250, 'sharp', 100, 0, 1,
(frameValue: number) => {
this.longCurveHaloOpacity = frameValue; // 光晕: opacity 0-1; 250-350ms
},
() => {
this.mFrameAnimResultArr[1] = null;
this.createPlayFrameAnimators(1, 650, 'sharp', 500, 1, 0, (frameValue: number) => { // 生命周期内禁止reset自身对象
this.longCurveHaloOpacity = frameValue; // 光晕: opacity 1-0; 1000-1500ms
});
});
}
// 长按出轮盘变焦,执行光球动效
private rouletteAppearExecSphereAnim(): void {
this.createPlayFrameAnimators(2, 350, 'friction', 500, 8, 0, (frameValue: number) => {
this.spherePosition = frameValue; // 光球: 位移 向上8vp归位; 350-850ms
});
this.createPlayFrameAnimators(3, 150, 'friction', 350, 0, 1, (frameValue: number) => {
this.sphereScale = frameValue; // 光球: scale 0-1; 150-500ms
});
this.createPlayFrameAnimators(4, 150, 'friction', 200, 0, 1, (frameValue: number) => {
this.sphereColor = frameValue; // 光球: color E4E3E2-FFFFFF; 150-350ms; 使用0-1映射
});
this.createPlayFrameAnimators(5, 150, 'friction', 200, 0, 1,
(frameValue: number) => {
this.sphereHaloOpacity = frameValue; // 光球-光晕: opacity 0-1; 150-350ms
},
() => {
this.mFrameAnimResultArr[5] = null;
this.createPlayFrameAnimators(5, 0, 'sharp', 350, 1, 0.5, (frameValue: number) => { // 生命周期内禁止reset自身对象
this.sphereHaloOpacity = frameValue; // 光球-光晕: opacity 1-0.5; 350-700ms
});
});
}
// 长按出轮盘变焦,执行大字体动效
private rouletteAppearExecBigTextAnim(): void {
setTimeout((): void => { // 大字体: 位移 向下位移2vp归位 400-700ms、opacity 0-1; 400-500ms
if (!this.isShowBigText) {
this.clearBigTextTimer();
this.isShowBigText = true;
}
}, 400);
}
// 长按出轮盘变焦,执行焦圈中心刻度、焦圈动效
private rouletteAppearExecRedLineAndScaleAllAnim(): void {
this.createPlayFrameAnimators(6, 400, 'friction', 100, 0, 1, (frameValue: number) => {
this.redLineAndTextOpacity = frameValue; // 焦圈中心刻度: opacity 0-1; 400-500ms
});
this.createPlayFrameAnimators(7, 400, 'friction', 600, 0, 1, (frameValue: number) => {
this.scaleAllMatteScale = frameValue; // 焦圈: 遮罩scale 0-1; 400-1000ms
});
this.createPlayFrameAnimators(8, 400, 'sharp', 100, 0, 1, (frameValue: number) => {
this.scaleAllOpacity = frameValue; // 焦圈: opacity 0-1; 400-500ms
});
}
// 长按出轮盘变焦,执行卡门线动效
private rouletteAppearExecCarmenLineAnim(): void {
this.createPlayFrameAnimators(10, 300, 'sharp', 100, 0, 1,
(frameValue: number) => {
this.carmenLineOpacity = frameValue; // 卡门线: opacity 0-1; 300-400ms
},
() => {
this.createPlayFrameAnimators(9, 0, 'friction', 600, 0, 1, (frameValue: number) => {
this.carmenLineMatteScale = frameValue; // 卡门线: 遮罩scale 0-1; 400-1000ms
});
});
}
// 长按出轮盘变焦,执行快捷变焦圆外层动效
private rouletteAppearExecQuickZoomOutsideAnim(): void {
if (this.mQuickZoomBlur === 1) {
this.mQuickZoomBlur = 0;
}
if (this.mQuickZoomPositionY === 0) {
this.mQuickZoomPositionY = -2;
}
animateToImmediately({
delay: 200,
curve: Curve.Friction,
duration: 250
}, () => {
this.mQuickZoomBlur = 1; // 快捷变焦圆: 模糊 0-1; 200-450ms
this.mQuickZoomPositionY = 0; // 快捷变焦圆: 位移 向上?2vp归位; 200-450ms
});
if (this.mQuickZoomOverColor === 1) {
this.mQuickZoomOverColor = 0;
}
animateToImmediately({
// 快捷变焦圆: 叠加颜色 0-1; 300-450ms
delay: 300,
curve: Curve.Friction,
duration: 150
}, () => {
this.mQuickZoomOverColor = 1;
});
}
private isBlockGestureEvent(): boolean {
// if (this.state.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT &&
// !CameraAppCapability.getInstance().getIsSupportedFrontZoomSlide()) {
// return true;
// }
return false;
}
/**
* 轮盘变焦条滑动手势Start回调
* */
private panGestureStart(event?: GestureEvent): void {
HiLog.d(TAG, 'PanGesture onActionStart.');
if (!event || this.isSelfieStickLongPress) {
return;
}
if (this.isDepthFusionBarShow) {
this.isDepthFusionConflictLongPress = true;
HiLog.i(TAG, 'depthFusion conflict pan');
return;
}
this.onProHideTreasureBox(true);
if (this.selector !== FunctionId.NONE) {
HiLog.i(TAG, `close second Treasure selector: ${this.selector}`);
this.mAction.closeSecondTreasure();
}
if (this.isBlockGestureEvent()) {
if (event.offsetX > 0) {
this.quickZoomClickRespond(this.curValIndex < this.quickZoomValArr.length - 1 ? this.curValIndex + 1 :
this.quickZoomValArr.length - 1);
}
if (event.offsetX < 0) {
this.quickZoomClickRespond(this.curValIndex > 0 ? this.curValIndex - 1 : 0);
}
this.mZoomInnerStatus = ZoomInnerStatus.FRONT_QUICK_SLIDE;
return;
}
this.mAction.uiStateWithMode(false);
if (!this.state.isImmersive) {
this.mAction.isEnterImmersive(true);
}
this.cancelNativeAndFrameAnimators(); // 清除动效执行器
this.mAction.changeZoomState(ZoomOuterStatus.ROULETTE_ZOOM);
this.clearBigTextTimer();
this.clearRouletteZoomTimer();
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_SLIDE;
this.isShowQuickZoom = false;
this.isShowRouletteZoom = true;
this.isShowBigText = true;
if (CameraAppCapability.getInstance().getIsSupportedCycleClickZoom() && this.isShowCycleClickZoomAndEquivZoom()) {
this.doCycleClickZoomAnim(true);
this.clearAndSetCycleClickTimer();
this.isDuringCycleClick = true;
}
this.lastGestureOffset = event.offsetX; // 基于上一次回调的偏移量
this.curZoomAngle = this.calZoomAngle(this.curZoomValue);
let validFingerLocalX = event.fingerList[0]?.localX;
let validFingerLocalY = event.fingerList[0]?.localY;
if (validFingerLocalX === undefined || validFingerLocalY === undefined) {
let index: number | undefined = this.getValidFingerLocalIndex(event);
if (index === undefined) {
return;
}
validFingerLocalX = event.fingerList[index]?.localX;
validFingerLocalY = event.fingerList[index]?.localY;
}
let x = Math.abs(this.zoomWidth / 2 - validFingerLocalX);
let y = this.redLineTopRadius + 10 - validFingerLocalY;
let clickDotRadialLen = Math.sqrt(x * x + y * y);
this.lastSlideTouchRadian = ((validFingerLocalX > this.zoomWidth / 2) ?
Math.asin(x / clickDotRadialLen) : -Math.asin(x / clickDotRadialLen)) * this.panGestureZoomVelocity;
HiLog.d(TAG, `panGestureStart lastOffset: ${this.lastGestureOffset}, lastRadian: ${this.lastSlideTouchRadian}.`);
}
/**
* 轮盘变焦条滑动手势Update回调
* */
private panGestureUpdate(event?: GestureEvent): void {
if (!event || this.isBlockGestureEvent() || this.isSelfieStickLongPress) {
return;
}
if (!this.isSelfieStickLongPress && AppStorage.get('isSelfieLongPress') === true) {
// 长按自拍杆过程中,滑动手势处理
let timeNow = Date.now();
if ((timeNow - this.selfieStickLongPressTimer) > 1500) {
AppStorage.setOrCreate('isSelfieLongPress', false);
}
this.mAction.uiStateWithMode(false);
if (!this.state.isImmersive) {
this.mAction.isEnterImmersive(true);
}
this.cancelNativeAndFrameAnimators(); // 清除动效执行器
this.mAction.changeZoomState(ZoomOuterStatus.ROULETTE_ZOOM);
this.clearBigTextTimer();
this.clearRouletteZoomTimer();
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_SLIDE;
this.isShowQuickZoom = false;
this.isShowRouletteZoom = true;
this.isShowBigText = true;
}
if (Math.abs(event.offsetX - this.lastGestureOffset) < 0.001) {
return; // 性能优化
}
if (this.isDepthFusionBarShow) {
this.isDepthFusionConflictLongPress = true;
HiLog.i(TAG, 'depthFusion conflict pan');
return;
}
let validFingerLocalX = event.fingerList[0]?.localX;
let validFingerLocalY = event.fingerList[0]?.localY;
if (validFingerLocalX === undefined || validFingerLocalY === undefined) {
let index: number | undefined = this.getValidFingerLocalIndex(event);
if (index === undefined) {
HiLog.i(TAG, 'panGestureUpdate index === undefined');
return;
}
validFingerLocalX = event.fingerList[index]?.localX;
validFingerLocalY = event.fingerList[index]?.localY;
}
let isFineGrainedControl = Math.abs(event.offsetX - this.lastGestureOffset) < 0.5; // 精细控制体验优化
let x = Math.abs(this.zoomWidth / 2 - validFingerLocalX);
let y = this.redLineTopRadius + 10 - validFingerLocalY;
let clickDotRadialLen = Math.sqrt(x * x + y * y);
let curSlideTouchRadian = ((validFingerLocalX > this.zoomWidth / 2) ?
Math.asin(x / clickDotRadialLen) : -Math.asin(x / clickDotRadialLen)) * this.panGestureZoomVelocity;
let curSlideAngle = (curSlideTouchRadian - this.lastSlideTouchRadian) * 180 / Math.PI;
let isOpticalZoomIndex = this.opticalZoomDotAngleArr.findIndex((value: number) => {
return Math.abs(this.curZoomAngle + curSlideAngle - value) <= ZoomParam.SCALE_GAP_ANGLE / 2;
});
let oldZoomValue = this.curZoomValue;
let oldZoomAngle = this.curZoomAngle;
if (this.isExecAdsorptionType === 1) { // 滑入吸附动效过程中
HiLog.i(TAG, `isExecAdsorptionType: ${this.isExecAdsorptionType}.`);
return;
} else if (this.isExecAdsorptionType === 2) { // 滑出吸附动效过程中
HiLog.i(TAG, `isExecAdsorptionType: ${this.isExecAdsorptionType}.`);
return;
} else if (this.isExecAdsorptionType === 5) { // 吸附中暂态,不响应
HiLog.i(TAG, `isExecAdsorptionType: ${this.isExecAdsorptionType}.`);
this.lastSlideTouchRadian = curSlideTouchRadian;
this.lastGestureOffset = event.offsetX; // 基于上一次回调的偏移量
return;
}
if (this.curZoomValue <= this.opticalZoomValArr[0] && event.offsetX >= 0) { // 左侧极限焦距继续滑动,通过Angle控制
this.execRightSlideLimitZoomAnim(event, oldZoomAngle, curSlideAngle, curSlideTouchRadian);
} else if (this.curZoomValue >= this.opticalZoomValArr[this.opticalZoomValArr.length - 1] && event.offsetX <= 0) {
// 右侧极限焦距继续滑动,通过Angle控制
this.execLeftSlideLimitZoomAnim(event, oldZoomAngle, curSlideAngle, curSlideTouchRadian);
} else { // 正常滑动,通过zoom控制
this.execNormalSlideCal(event, isFineGrainedControl, isOpticalZoomIndex, curSlideAngle, curSlideTouchRadian);
}
if (Math.abs(this.curZoomValue - oldZoomValue) <= 0.005 && Math.abs(this.curZoomAngle - oldZoomAngle) <= 0.005) {
return;
}
if (this.curZoomValue !== oldZoomValue) {
this.execOpticalZoomVibrator(oldZoomValue); // 执行关键刻度振动音效
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue });
}
this.mNodeController?.invalidate(this.curZoomValue,
Math.abs(this.curZoomAngle - oldZoomAngle) <= 0.001 ? 1000 : this.curZoomAngle);
}
private getValidFingerLocalIndex(event: GestureEvent): number | undefined {
let validFingerLocalX: number | undefined = undefined;
let validFingerLocalY: number | undefined = undefined;
for (let i = 0; i < event.fingerList.length; i++) {
validFingerLocalX = event.fingerList[i]?.localX;
validFingerLocalY = event.fingerList[i]?.localY;
HiLog.d(TAG, `getValidFinger localX: ${validFingerLocalX}, localY: ${validFingerLocalY}.`);
if (validFingerLocalX === null || validFingerLocalX === undefined || validFingerLocalY === null ||
validFingerLocalY === undefined) {
continue;
} else {
return i;
}
}
return undefined;
}
private execRightSlideLimitZoomAnim(event: GestureEvent, oldZoomAngle: number, curSlideAngle:
number, curSlideTouchRadian: number): void {
let nextZoomAngle = oldZoomAngle + curSlideAngle * ZoomParam.RING_LOW_DYNAMIC_FOLLOW_UP_RATE;
let isInRightGap = this.curZoomAngle + nextZoomAngle > -ZoomParam.SCALE_GAP_ANGLE / 2; // 右间距内
let isInLeftGap = this.curZoomAngle + nextZoomAngle < 0; // 左间距内
if ((this.curZoomAngle === 0) && (isInLeftGap && isInRightGap)) {
this.lastGestureOffset = event.offsetX;
this.lastSlideTouchRadian = curSlideTouchRadian;
return;
}
HiLog.d(TAG, `execRightSlideLimitZoomAnim curAg:${curSlideAngle}, nxtAg:${nextZoomAngle}, oldAg:${oldZoomAngle}.`);
if (curSlideAngle < 0 && (nextZoomAngle <= ZoomParam.SCALE_GAP_ANGLE / 2)) {
this.isExecAdsorptionType = 1;
this.execSlideOuterLimitInAdsorptionAnim(0, 100);
return;
}
if (this.curZoomAngle !== nextZoomAngle && nextZoomAngle <= ZoomParam.SCALE_GAP_ANGLE * 4) {
this.curZoomAngle = nextZoomAngle;
}
if (nextZoomAngle >= ZoomParam.SCALE_GAP_ANGLE * 4) {
this.curZoomAngle = ZoomParam.SCALE_GAP_ANGLE * 4;
}
this.lastGestureOffset = event.offsetX;
this.lastSlideTouchRadian = curSlideTouchRadian;
}
private execLeftSlideLimitZoomAnim(event: GestureEvent, oldZoomAngle: number, curSlideAngle: number,
curSlideTouchRadian: number): void {
let nextZoomAngle = oldZoomAngle + curSlideAngle * ZoomParam.RING_LOW_DYNAMIC_FOLLOW_UP_RATE;
let maxZoomAngle = this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1];
if (curSlideAngle > 0 && (nextZoomAngle >= (maxZoomAngle - ZoomParam.SCALE_GAP_ANGLE / 2))) {
this.isExecAdsorptionType = 1;
this.execSlideOuterLimitInAdsorptionAnim(maxZoomAngle, 100);
return;
}
if (this.curZoomAngle !== nextZoomAngle && nextZoomAngle >=
this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1] - ZoomParam.SCALE_GAP_ANGLE * 4) {
this.curZoomAngle = nextZoomAngle;
}
if (nextZoomAngle <=
this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1] - ZoomParam.SCALE_GAP_ANGLE * 4) {
this.curZoomAngle =
this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1] - ZoomParam.SCALE_GAP_ANGLE * 4;
}
this.lastGestureOffset = event.offsetX;
this.lastSlideTouchRadian = curSlideTouchRadian;
}
private execNormalSlideCal(event: GestureEvent, isFineGrainedControl: boolean, isOpticalZoomIndex: number,
curSlideAngle: number, curSlideTouchRadian: number): void {
let isInRightGap = this.curZoomAngle + curSlideAngle >
this.opticalZoomDotAngleArr[isOpticalZoomIndex] - ZoomParam.SCALE_GAP_ANGLE / 2; // 右间距内
let isInLeftGap = this.curZoomAngle + curSlideAngle <
this.opticalZoomDotAngleArr[isOpticalZoomIndex] + ZoomParam.SCALE_GAP_ANGLE / 2; // 左间距内
if ((this.curZoomValue === this.opticalZoomValArr[isOpticalZoomIndex]) && (isInRightGap || isInLeftGap)) {
HiLog.d(TAG, `execNormalSlideCal isInRightGap: ${isInRightGap}, isInLeftGap: ${isInLeftGap}.`);
return;
}
if (this.getIsExecInAdsorption(event, isOpticalZoomIndex, curSlideAngle)) {
this.isExecAdsorptionType = 1;
this.execSlideInAdsorptionAnim(this.opticalZoomValArr[isOpticalZoomIndex], 100);
return;
}
if (isFineGrainedControl) {
if (event.offsetX < this.lastGestureOffset) { // 左滑
let nextZoomVal = Math.min(this.curZoomValue + 0.05, this.calZoomValue(this.curZoomAngle + curSlideAngle));
this.curZoomValue = isOpticalZoomIndex === -1 ? nextZoomVal :
Math.min(nextZoomVal, this.opticalZoomValArr[isOpticalZoomIndex]);
} else if (event.offsetX > this.lastGestureOffset) { // 右滑
let nextZoomVal = Math.max(this.curZoomValue - 0.05, this.calZoomValue(this.curZoomAngle + curSlideAngle));
this.curZoomValue = isOpticalZoomIndex === -1 ? nextZoomVal :
Math.max(nextZoomVal, this.opticalZoomValArr[isOpticalZoomIndex]);
} // else已被拦截
if (this.curZoomValue !== this.opticalZoomValArr[isOpticalZoomIndex]) {
this.lastGestureOffset = event.offsetX; // 基于上一次回调的偏移量
this.lastSlideTouchRadian = curSlideTouchRadian;
}
} else {
if (Math.abs(this.curZoomAngle + curSlideAngle - this.opticalZoomDotAngleArr[isOpticalZoomIndex]) <=
ZoomParam.SCALE_GAP_ANGLE / 2) {
this.curZoomValue = this.opticalZoomValArr[isOpticalZoomIndex];
} else {
this.curZoomValue = this.calZoomValue(this.curZoomAngle + curSlideAngle);
this.lastGestureOffset = event.offsetX; // 基于上一次回调的偏移量
this.lastSlideTouchRadian = curSlideTouchRadian;
}
}
let oldZoomAngle: number = this.curZoomAngle;
this.curZoomAngle = this.calZoomAngle(this.curZoomValue);
// 执行变焦精细小刻度振动
this.execSlideLightVibrator(oldZoomAngle);
}
private async execSlideLightVibrator(oldZoomAngle: number): Promise<void> {
if (Number((this.state.zoomRatio).toFixed(3)) === this.curZoomValue || !this.isAllowVibratorSound()) {
return;
}
let startIndex = Math.floor(-oldZoomAngle / ZoomParam.SCALE_GAP_ANGLE) - 5;
startIndex = Math.max(0, startIndex);
for (let i = startIndex; i < startIndex + 10 && i < this.smallScaleDotAngleArr.length; i++) {
let smallAngle: number = this.smallScaleDotAngleArr[i];
let isWhiteLineZoom = (smallAngle > oldZoomAngle && smallAngle <= this.curZoomAngle) ||
(smallAngle < oldZoomAngle && smallAngle >= this.curZoomAngle);
if (isWhiteLineZoom) {
this.mAction.handleZoomVibrator(VibratorService.CAMERA_GEAR_SLIP_LIGHT_ID);
break;
}
}
}
private execOpticalZoomVibrator(oldZoomValue: number): void {
for (let i = 0; i < this.opticalZoomValArr.length; i++) {
let isOpticalZoom = (oldZoomValue < this.opticalZoomValArr[i] && this.curZoomValue > this.opticalZoomValArr[i]) ||
(oldZoomValue > this.opticalZoomValArr[i] && this.curZoomValue < this.opticalZoomValArr[i]) ||
(oldZoomValue !== this.opticalZoomValArr[i] && this.curZoomValue === this.opticalZoomValArr[i]);
if (this.isAllowVibratorSound() && isOpticalZoom && this.curZoomAngle <= 0 &&
this.curZoomAngle >= this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1]) {
this.mAction.zoomSlide2QuickZoom(this.curZoomValue);
this.mAction.handleZoomVibrator(VibratorService.CAMERA_GEAR_SLIP_HEAVY_ID, VIBRATOR_USAGE_PHYSIC);
break;
}
}
}
private getIsExecInAdsorption(event: GestureEvent, isOpticalZoomIndex: number, curSlideAngle: number): boolean {
if (Math.abs(event.offsetX - this.lastGestureOffset) > 2) {
return false;
}
let isLeftIn: boolean = curSlideAngle < 0 &&
this.opticalZoomDotAngleArr[isOpticalZoomIndex] !== undefined &&
this.curZoomAngle > this.opticalZoomDotAngleArr[isOpticalZoomIndex] &&
this.curZoomAngle + curSlideAngle >
this.opticalZoomDotAngleArr[isOpticalZoomIndex] - ZoomParam.SCALE_GAP_ANGLE / 2; // 左滑需吸附至关键刻度点
let isRightIn: boolean = curSlideAngle > 0 &&
this.opticalZoomDotAngleArr[isOpticalZoomIndex] !== undefined &&
this.curZoomAngle < this.opticalZoomDotAngleArr[isOpticalZoomIndex] &&
this.curZoomAngle + curSlideAngle <
this.opticalZoomDotAngleArr[isOpticalZoomIndex] + ZoomParam.SCALE_GAP_ANGLE / 2; // 右滑需吸附至关键刻度点
return isLeftIn || isRightIn;
}
private execSlideInAdsorptionAnim(nextZoomValue: number, duration: number): void {
HiLog.i(TAG, `execSlideInAdsorptionAnim nextZoomValue: ${nextZoomValue}, duration: ${duration}.`);
let oldAdsorptionType = this.isExecAdsorptionType;
let startZoomVal = this.curZoomValue;
let options: AnimatorOptions = {
duration: duration,
easing: 'friction',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: startZoomVal,
end: nextZoomValue
};
if (this.mStateChangeAnimator !== null) {
try {
this.mStateChangeAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `createPlayNativeAnimator error: ${error?.code}.`);
}
} else {
this.mStateChangeAnimator = animator.create(options);
}
this.mStateChangeAnimator.onFinish = () => {
HiLog.i(TAG, `oldAdsorptionType: ${oldAdsorptionType}, isExecAdsorptionType: ${this.isExecAdsorptionType}.`);
if (!this.mNodeController?.getIsTextParallel2CarmenLine()) {
this.mNodeController?.execLandscapeSlideExpandAnim(nextZoomValue); // 横屏滑动关键刻度展开动效
}
if (oldAdsorptionType !== this.isExecAdsorptionType) {
return;
}
this.isExecAdsorptionType = 5;
this.mExecAdsorptionTimerId = setTimeout(() => {
this.isExecAdsorptionType = 0;
this.mExecAdsorptionTimerId = Number.MAX_VALUE;
}, 250);
this.curZoomValue = nextZoomValue;
this.curValIndex = this.calZoomValIndex(); // 当前变焦值在快捷变焦点列表的index下界
this.curZoomAngle = this.calZoomAngle(this.curZoomValue);
if (this.isAllowVibratorSound()) {
this.mAction.zoomSlide2QuickZoom(this.curZoomValue);
if (this.state.recordingState !== RecordingState.RECORDING) {
this.mAction.handleZoomVibrator(VibratorService.CAMERA_GEAR_SLIP_HEAVY_ID, VIBRATOR_USAGE_PHYSIC);
}
}
};
this.mStateChangeAnimator.onFrame = (value) => {
if (!this.isShowRouletteZoom || !this.curZoomValue || oldAdsorptionType !== this.isExecAdsorptionType) {
return;
}
this.curZoomValue = value;
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue });
this.mNodeController?.invalidate(value, 1000); // 数据小容器传递
};
this.mStateChangeAnimator?.play();
}
private execSlideOuterLimitInAdsorptionAnim(nextZoomAngle: number, duration: number): void {
HiLog.i(TAG, `execSlideOuterLimitInAdsorptionAnim nextZoomAngle: ${nextZoomAngle}, duration: ${duration}.`);
let oldAdsorptionType = this.isExecAdsorptionType;
let startZoomAng = this.curZoomAngle;
let options: AnimatorOptions = {
duration: duration,
easing: 'friction',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: startZoomAng,
end: nextZoomAngle
};
if (this.mStateChangeAnimator !== null) {
try {
this.mStateChangeAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `createPlayNativeAnimator error: ${error?.code}.`);
}
} else {
this.mStateChangeAnimator = animator.create(options);
}
this.mStateChangeAnimator.onFinish = () => {
HiLog.i(TAG, `oldAdsorptionType: ${oldAdsorptionType}, isExecAdsorptionType: ${this.isExecAdsorptionType}.`);
if (!this.mNodeController?.getIsTextParallel2CarmenLine()) {
this.mNodeController?.execLandscapeSlideExpandAnim(this.curZoomValue); // 横屏滑动关键刻度展开动效
}
if (oldAdsorptionType !== this.isExecAdsorptionType) {
return;
}
this.isExecAdsorptionType = 5;
this.mExecAdsorptionTimerId = setTimeout(() => {
this.isExecAdsorptionType = 0;
this.mExecAdsorptionTimerId = Number.MAX_VALUE;
}, 150);
};
this.mStateChangeAnimator.onFrame = (value) => {
if (!this.isShowRouletteZoom || !this.curZoomValue || oldAdsorptionType !== this.isExecAdsorptionType) {
return;
}
this.curZoomAngle = value;
this.mNodeController?.invalidate(this.curZoomValue, value);
};
this.mStateChangeAnimator?.play();
}
/**
* 轮盘变焦条滑动手势End回调
* */
private panGestureEnd(event?: GestureEvent): void {
if (!event || this.isBlockGestureEvent() || this.isSelfieStickLongPress) {
if (this.mZoomInnerStatus === ZoomInnerStatus.FRONT_QUICK_SLIDE) {
this.mZoomInnerStatus = ZoomInnerStatus.QUICK_STATIC;
}
return;
}
HiLog.d(TAG, `PanGesture onActionEnd offset event: ${event.speed}.`);
this.mAction.isEnterImmersive(false);
this.mAction.uiStateWithMode(true);
this.mAction.changeOtherComponent();
if (this.isExecAdsorptionType !== 0) {
this.isExecAdsorptionType = 0;
}
if (this.curZoomAngle > this.opticalZoomDotAngleArr[0] ||
this.curZoomAngle < this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1]) {
this.execLimitZoomSlideRecovery();
this.curValIndex = this.calZoomValIndex(); // 当前变焦值在快捷变焦点列表的index下界
return;
}
let nextZoomValue: number = this.curZoomValue;
if (event?.velocityX > ZoomParam.THROW_SLIDE_TRIGGER_VELOCITY) { // 执行抛滑策略
nextZoomValue = this.opticalZoomValArr[0] + (event?.velocityX > ZoomParam.THROW_SLIDE_UPPER_VELOCITY ? 0 :
((ZoomParam.THROW_SLIDE_UPPER_VELOCITY - event?.velocityX) * this.panGestureZoomVelocity /
(ZoomParam.THROW_SLIDE_UPPER_VELOCITY - ZoomParam.THROW_SLIDE_TRIGGER_VELOCITY) *
(this.curZoomValue - this.opticalZoomValArr[0])));
} else if (event?.velocityX < -ZoomParam.THROW_SLIDE_TRIGGER_VELOCITY) {
nextZoomValue = this.opticalZoomValArr[this.opticalZoomValArr.length - 1] -
(event?.velocityX < -ZoomParam.THROW_SLIDE_UPPER_VELOCITY ? 0 :
((ZoomParam.THROW_SLIDE_UPPER_VELOCITY + event?.velocityX) * this.panGestureZoomVelocity /
(ZoomParam.THROW_SLIDE_UPPER_VELOCITY - ZoomParam.THROW_SLIDE_TRIGGER_VELOCITY) *
(this.opticalZoomValArr[this.opticalZoomValArr.length - 1] - this.curZoomValue)));
} else {
this.clearAndSetRouletteZoomTimer();
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_STATIC;
this.curValIndex = this.calZoomValIndex(); // 当前变焦值在快捷变焦点列表的index下界
return;
}
let nextZoomAngle: number = this.calZoomAngle(nextZoomValue);
let isOpticalZoomIndex = this.opticalZoomDotAngleArr.findIndex((value: number) => {
return Math.abs(nextZoomAngle - value) <= ZoomParam.SCALE_GAP_ANGLE * 3;
});
if (isOpticalZoomIndex !== -1) {
nextZoomValue = this.opticalZoomValArr[isOpticalZoomIndex];
}
this.execThrowSlideRespondAnim(nextZoomValue, event?.velocityX);
}
private execLimitZoomSlideRecovery(): void {
let startZoomAngle = this.curZoomAngle;
let endZoomAngle = this.curZoomAngle > this.opticalZoomDotAngleArr[0] ?
this.opticalZoomDotAngleArr[0] : this.opticalZoomDotAngleArr[this.opticalZoomDotAngleArr.length - 1];
let options: AnimatorOptions = {
duration: Math.abs(startZoomAngle - endZoomAngle) / (ZoomParam.SCALE_GAP_ANGLE * 4) * 250,
easing: 'friction',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: startZoomAngle,
end: endZoomAngle
};
if (this.mStateChangeAnimator !== null) {
try {
this.mStateChangeAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `createPlayNativeAnimator error: ${error?.code}.`);
}
} else {
this.mStateChangeAnimator = animator.create(options);
}
this.mStateChangeAnimator.onFinish = () => {
this.clearAndSetRouletteZoomTimer(); // 定时器内包含大字体
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_STATIC;
if (!this.mNodeController?.getIsTextParallel2CarmenLine()) {
this.mNodeController?.execLandscapeSlideExpandAnim(this.curZoomValue); // 横屏滑动关键刻度展开动效
}
};
this.mStateChangeAnimator.onFrame = (value) => {
if (!this.isShowRouletteZoom || !this.curZoomValue) {
return;
}
this.mNodeController?.invalidate(this.curZoomValue, value);
};
this.mStateChangeAnimator?.play();
}
private execThrowSlideRespondAnim(nextZoomValue: number, mVelocityX: number): void {
let startZoomVal = this.curZoomValue;
this.curZoomValue = nextZoomValue;
this.curValIndex = this.calZoomValIndex(); // 当前变焦值在快捷变焦点列表的index下界
this.clickZoomDelivery(); // 点切变焦平滑曲线下发
this.touchUnprepareZoom();
let options: AnimatorOptions = {
duration: Math.abs(mVelocityX) > ZoomParam.THROW_SLIDE_UPPER_VELOCITY ? 132 : 264,
easing: 'friction',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: startZoomVal,
end: nextZoomValue
};
let oldDrawZoomVal: number = startZoomVal;
if (this.mStateChangeAnimator !== null) {
try {
this.mStateChangeAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `createPlayNativeAnimator error: ${error?.code}.`);
}
} else {
this.mStateChangeAnimator = animator.create(options);
}
this.mStateChangeAnimator.onFinish = () => {
this.clearAndSetRouletteZoomTimer(); // 定时器内包含大字体
this.mZoomInnerStatus = ZoomInnerStatus.ROULETTE_STATIC;
if (!this.mNodeController?.getIsTextParallel2CarmenLine()) {
this.mNodeController?.execLandscapeSlideExpandAnim(nextZoomValue); // 横屏滑动关键刻度展开动效
}
};
this.mStateChangeAnimator.onFrame = (value) => {
if (!this.isShowRouletteZoom || !this.curZoomValue) {
return;
}
this.throwSlideRespondAnimOnFrame(oldDrawZoomVal, value);
oldDrawZoomVal = value;
};
this.mStateChangeAnimator?.play();
}
private throwSlideRespondAnimOnFrame(oldDrawZoomVal: number, frameValue: number): void {
for (let i = 0; i < this.opticalZoomValArr.length; i++) {
let isOpticalZoom = (oldDrawZoomVal < this.opticalZoomValArr[i] && frameValue > this.opticalZoomValArr[i]) ||
(oldDrawZoomVal > this.opticalZoomValArr[i] && frameValue < this.opticalZoomValArr[i]) ||
(oldDrawZoomVal !== this.opticalZoomValArr[i] && frameValue === this.opticalZoomValArr[i]);
if (this.isAllowVibratorSound() && isOpticalZoom) {
this.mAction.zoomSlide2QuickZoom(this.curZoomValue);
this.mAction.handleZoomVibrator(VibratorService.CAMERA_GEAR_SLIP_HEAVY_ID, VIBRATOR_USAGE_PHYSIC);
break;
}
}
try {
this.mNodeController?.invalidate(frameValue, 1000); // 数据小容器传递
} catch (err) {
HiLog.e(TAG, `NodeController invalidate err: ${err}.`);
}
}
// 收起轮盘变焦,执行光球动效
private rouletteDisappearExecSphereAnim(): void {
this.createPlayFrameAnimators(2, 150, 'friction', 500, 0, 8, (frameValue: number) => {
this.spherePosition = frameValue; // 光球: 位移 向下8vp消失; 150-650ms
});
this.createPlayFrameAnimators(3, 350, 'friction', 400, 1, 0, (frameValue: number) => {
this.sphereScale = frameValue; // 光球: scale 1-0; 350-750ms
});
this.createPlayFrameAnimators(5, 250, 'friction', 400, 0.5, 1,
(frameValue: number) => {
this.sphereHaloOpacity = frameValue; // 光球-光晕: opacity 0.5-1; 250-650ms
},
() => {
this.mFrameAnimResultArr[5] = null;
this.createPlayFrameAnimators(5, 0, 'sharp', 150, 1, 0,
(frameValue: number) => { // 生命周期内禁止reset自身对象
this.sphereHaloOpacity = frameValue; // 光球-光晕: opacity 1-0; 650-800ms
}, () => {
this.isShowRouletteZoom = false;
});
});
setTimeout((): void => {
if (this.mZoomInnerStatus !== ZoomInnerStatus.ROULETTE_TO_QUICK) {
return;
}
this.mAction.changeZoomState(ZoomOuterStatus.QUICK_ZOOM); // 保障同步出现效果
}, 300);
}
// 收起轮盘变焦,执行大字体动效
private rouletteDisappearExecBigTextAnim(): void {
setTimeout((): void => { // 大字体: opacity 1-0; 350-450ms
if (this.isShowBigText && this.mZoomInnerStatus === ZoomInnerStatus.ROULETTE_TO_QUICK &&
!this.isSelfieStickLongPress && !this.isSelfieStickShortPress) {
this.isShowBigText = false;
}
}, 350);
}
// 收起轮盘变焦,执行焦圈中心刻度、焦圈动效
private rouletteDisappearExecRedLineAndScaleAllAnim(): void {
this.createPlayFrameAnimators(6, 250, 'sharp', 100, 1, 0, (frameValue: number) => {
this.redLineAndTextOpacity = frameValue; // 焦圈中心刻度: opacity 1-0; 250-350ms
});
this.createPlayFrameAnimators(7, 0, 'friction', 450, 1, 0, (frameValue: number) => {
this.scaleAllMatteScale = frameValue; // 焦圈: 遮罩scale 1-0; 0-450ms
});
this.createPlayFrameAnimators(8, 250, 'sharp', 100, 1, 0, (frameValue: number) => {
this.scaleAllOpacity = frameValue; // 焦圈: opacity 1-0; 250-350ms
});
}
// 收起轮盘变焦,执行卡门线动效
private rouletteDisappearExecCarmenLineAnim(): void {
this.createPlayFrameAnimators(9, 150, 'friction', 300, 1, 0,
(frameValue: number) => {
this.carmenLineMatteScale = frameValue; // 卡门线: 遮罩scale 1-0; 150-450ms
},
() => {
this.createPlayFrameAnimators(10, 0, 'sharp', 100, 1, 0, (frameValue: number) => {
this.carmenLineOpacity = frameValue; // 卡门线: opacity 1-0; 450-550ms
});
});
}
// 收起轮盘变焦,执行快捷变焦圆外层动效
private rouletteDisappearExecQuickZoomOutsideAnim(): void {
if (this.quickZoomAlpha === 1) {
this.quickZoomAlpha = 0;
}
if (this.mQuickOtherAlpha === 1) {
this.mQuickOtherAlpha = 0;
}
if (this.mQuickZoomOverColor === 0) {
this.mQuickZoomOverColor = 1;
}
if (!this.isShowQuickZoom) {
this.isShowQuickZoom = true;
}
animateToImmediately({
delay: 250,
curve: Curve.Sharp,
duration: 500
}, () => {
this.quickZoomAlpha = 1; // 快捷变焦圆: opacity 0-1; 250-750ms
this.mQuickOtherAlpha = 1;
this.mQuickZoomOverColor = 0; // 快捷变焦圆: 叠加颜色 1-0; 250-750ms
});
if (this.mQuickZoomPositionY === 2) {
this.mQuickZoomPositionY = 0;
}
animateToImmediately({
// 快捷变焦圆: 位移 向下2vp退出; 250-750ms
delay: 250,
curve: Curve.Friction,
duration: 500
}, () => {
this.mQuickZoomPositionY = 2;
});
if (this.mQuickZoomBlur === 0) {
this.mQuickZoomBlur = 1;
}
animateToImmediately({
// 快捷变焦圆: 模糊 1-0; 450-650ms
delay: 450,
curve: Curve.Friction,
duration: 200
}, () => {
this.mQuickZoomBlur = 0;
this.onProHideTreasureBox(false);
});
}
private onProHideTreasureBox(isHide: boolean): void {
return;
if (isHide) {
this.mAction.isProHideTreasureBox();
} else {
this.mAction.isProShowTreasureBox();
}
}
private changeZoomFunctionValue(zoomData: ZoomFuncValueStruct, autoTriggered?: boolean): void {
this.zoomFunction?.setValue(zoomData);
this.mAction.changedZoomByGesture(FunctionId.ZOOM, zoomData, autoTriggered);
}
private changeZoomValue(zoomVal: number): void {
if (zoomVal > getStates().get<number>('zoomReducer', 'maxZoomRatio')) {
zoomVal = getStates().get<number>('zoomReducer', 'maxZoomRatio')
}
if (zoomVal < getStates().get<number>('zoomReducer', 'minZoomRatio')) {
zoomVal = getStates().get<number>('zoomReducer', 'minZoomRatio');
}
this.curZoomValue = zoomVal;
this.curValIndex = this.calZoomValIndex();
}
private onCollaborateZoomIn(): void {
// 遥控拍照每次固定变焦,首次变焦前设置mIsPrepareZoom = true,变焦结束500ms后设置mIsPrepareZoom = false
this.changeZoomValue(this.getCollaborateTargetZoomValue(true));
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue }, true);
}
private onCollaborateZoomOut(): void {
this.changeZoomValue(this.getCollaborateTargetZoomValue(false));
this.changeZoomFunctionValue({ zoomRatio: this.curZoomValue }, true);
}
private getCollaborateTargetZoomValue(isZoomIn: boolean): number {
let targetZoomValue = this.curZoomValue;
if (this.state.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT &&
!CameraAppCapability.getInstance().getIsSupportedFrontZoomSlide()) {
// 不支持滑动变焦时,按照快捷变焦范围步进
let targetValIndex = isZoomIn ? this.curValIndex + 1 : this.curValIndex - 1;
if (targetValIndex >= 0 && targetValIndex <= this.quickZoomValArr.length - 1) {
targetZoomValue = this.quickZoomValArr[targetValIndex];
} else {
targetZoomValue = isZoomIn ? this.curZoomValue + ZOOM_STEP : this.curZoomValue - ZOOM_STEP;
}
}
return targetZoomValue;
}
/**
* 计算zoomValue变焦值居顶红刻度线重合时,需要旋转的角度;
* @param zoomValue
* @returns to rotate angle
*/
private calZoomAngle(zoomValue: number): number {
if (zoomValue <= this.opticalZoomValArr[0]) {
return 0;
} else if (zoomValue >= this.opticalZoomValArr[this.opticalZoomValArr.length - 1]) {
return -ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[this.opticalZoomDotIndexArr.length - 1];
}
let resultAngle = 0;
let index = 0;
while (index < this.opticalZoomValArr.length && this.opticalZoomValArr[index] <= zoomValue) {
index++;
}
index--;
resultAngle = -ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[index]; // 前序大刻度角度
let smallAngle = -ZoomParam.SCALE_GAP_ANGLE * (zoomValue - this.opticalZoomValArr[index]) /
(this.opticalZoomValArr[index + 1] - this.opticalZoomValArr[index]) *
(this.opticalZoomDotIndexArr[index + 1] - this.opticalZoomDotIndexArr[index]);
resultAngle += smallAngle;
return resultAngle;
}
/**
* 逆向思维;以第0个快捷变焦刻度居顶为初始,旋转zoomAngle角度后居顶的变焦值;
* @param zoomAngle
* @returns will zoomValue
*/
private calZoomValue(zoomAngle: number): number {
if (zoomAngle >= 0) {
return this.opticalZoomValArr[0];
} else if (zoomAngle <=
-ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[this.opticalZoomDotIndexArr.length - 1]) {
return this.opticalZoomValArr[this.opticalZoomValArr.length - 1];
}
let resultZoom = 0;
let index = 0;
while (index < this.opticalZoomDotIndexArr.length &&
(-ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[index]) >= zoomAngle) {
index++;
}
index--;
resultZoom = this.opticalZoomValArr[index];
let smallZoom = (zoomAngle + ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[index]) /
(-ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[index + 1] +
ZoomParam.SCALE_GAP_ANGLE * this.opticalZoomDotIndexArr[index]) *
(this.opticalZoomValArr[index + 1] - this.opticalZoomValArr[index]);
resultZoom += smallZoom;
return resultZoom;
}
/**
* 计算当前变焦值对应快捷变焦点列表的index下界-快捷变焦圆显示的index
* @return 变焦范围内index
* */
private calZoomValIndex(zoomValue?: number): number {
let nowZoomValue: number;
zoomValue ? (nowZoomValue = zoomValue) : (nowZoomValue = this.curZoomValue);
let index = 0;
while (index < this.quickZoomValArr.length && this.roundOrSplitZoomVal(this.quickZoomValArr[index]) <=
Number(Math.floor(nowZoomValue * 10) / 10)) {
index++;
}
index -= 1; // 当前变焦值在快捷变焦点列表的index下界
HiLog.i(TAG, `calZoomValIndex nowZoomValue: ${nowZoomValue}, index: ${index} X.`);
return index < 0 ? 0 : Math.min(index, this.quickZoomValArr.length - 1);
}
private updateDensity(): void {
HiLog.i(TAG, `updateDensity density: ${this.density}`);
return;
const size: Size = CameraBasicService.getInstance().getPreviewDisplaySize();
setTimeout((): void => {
this.zoomWidth = size.width;
}, WAIT_TIME);
}
private getQuickMaxZoomVarW(): number {
// 大字体或者zoom快捷点显示W 的阈值,默认阈值0.79,有上报w的话,以上报的w为阈值
return (this.quickZoomValArr.length > 0 && this.quickZoomValArr[0] > 0 && this.quickZoomValArr[0] < 1) ?
this.quickZoomValArr[0] : 0.79;
}
private roundOrSplitZoomVal(zoomVal: number): number {
// 截取至一位小数, 如果是.0则保留为整数
const truncatedNum = Math.floor(zoomVal * 10) / 10;
return truncatedNum === Math.floor(zoomVal) ? Math.floor(zoomVal) : truncatedNum;
}
private isLandscapeIndex(): boolean {
return this.isShowLandscape || this.isShowTricollaps;
}
private doCycleClickZoomAnim(isShow: boolean): void {
this.isShowCycleClickZoom = isShow;
}
private getCommonZoomAnim(): TransitionEffect {
return TransitionEffect.asymmetric(
TransitionEffect.OPACITY
.animation({
curve: Curve.Sharp,
duration: 100,
}),
TransitionEffect.OPACITY
.animation({
curve: Curve.Sharp,
duration: 100,
delay: 100,
})
);
}
private getCycleClickZoomAnim(): TransitionEffect {
return TransitionEffect.asymmetric(
TransitionEffect.OPACITY
.animation({
curve: Curve.Sharp,
duration: 100,
delay: 100,
}),
TransitionEffect.OPACITY
.animation({
curve: Curve.Sharp,
duration: 100,
})
);
}
// 更新循环点切的状态
private updateCycleClickZoom(): void {
if (ZoomOperation.getInstance().getRemainZoomRatio()) {
return;
}
this.doCycleClickZoomAnim(false);
}
// 执行循环点切或者焦距复位
private doCycleClickZoom(): void {
if (!CameraAppCapability.getInstance().getIsSupportedCycleClickZoom()) {
return;
}
if (!this.isShowCycleClickZoomAndEquivZoom()) {
return;
}
this.cycleZoomData = CameraAppCapability.getInstance().getZoomCycleList(this.state.mode,
this.quickZoomValArr[this.curValIndex]);
HiLog.i(TAG, `get cycleZoomData: ${this.cycleZoomData}`)
if (this.isBlockGestureEvent()) {
this.showBigTextAndEquivZoomText();
return;
}
// 焦距复位
let index = this.opticalZoomValArr.indexOf(this.curZoomValue);
if (!this.isShowCycleClickZoom && this.curZoomValue !== this.quickZoomValArr[this.curValIndex] &&
(index === -1 || index === this.opticalZoomValArr.length - 1)) {
this.curZoomValue = this.quickZoomValArr[this.curValIndex];
this.showBigTextAndEquivZoomText();
this.clickZoomDelivery(); // 点切变焦执行下发
return;
}
// 循环变焦
if (this.cycleZoomData.length === 0) { // 如果当前的循环点切数组为空的话,证明这个快捷变焦点不支持循环变焦,但是也要展示等效焦距
this.showBigTextAndEquivZoomText();
return;
}
// 当前模式的最大变焦值比循环点切焦距数组的最小值还小,直接返回
if (this.opticalZoomValArr[this.opticalZoomValArr.length -1] < this.cycleZoomData[0]) {
this.showBigTextAndEquivZoomText();
return;
}
index = this.cycleZoomData.indexOf(this.curZoomValue);
if (this.curValIndex < this.quickZoomValArr.length - 1 &&
this.cycleZoomData[index + 1] >= this.quickZoomValArr[this.curValIndex + 1]) {
return;
}
if (index !== -1) {
// 当前的焦距值已经在循环点切焦距数组里面了,并且下一个循环的焦距值是合法的
if (index + 1 < this.cycleZoomData.length &&
this.opticalZoomValArr[this.opticalZoomValArr.length -1] >= this.cycleZoomData[index + 1]) {
this.curZoomValue = this.cycleZoomData[index + 1];
} else {
this.curZoomValue = this.quickZoomValArr[this.curValIndex];
}
} else {
// 当前的焦距值不在循环点切焦距数组里面,就从头开始循坏变焦
this.curZoomValue = this.cycleZoomData[0];
}
this.showBigTextAndEquivZoomText();
this.clickZoomDelivery(); // 点切变焦执行下发
}
private showBigTextAndEquivZoomText(): void {
// 展示变焦大字体和等效焦距
this.doCycleClickZoomAnim(true);
this.clearAndSetCycleClickTimer();
this.isDuringCycleClick = true;
if (this.isShowBigText) {
this.clearAndSetBigTextTimer();
} else {
this.isShowBigText = true;
}
}
private isShowCycleClickZoomAndEquivZoom(): boolean {
return true;
}
}