美术更新
- detail页面修改 - ui布局修改
This commit is contained in:
@@ -178,6 +178,13 @@ export default class Utils {
|
||||
//let windowSize = view.getVisibleSize();
|
||||
let bgTf = node.getComponent(UITransform);
|
||||
if (bgTf) {
|
||||
if (mod == 3) {
|
||||
if (bgTf.height < bgTf.width) {
|
||||
mod = 1;
|
||||
} else {
|
||||
mod = 2;
|
||||
}
|
||||
}
|
||||
if (mod == 1) {
|
||||
let scale = windowSize.height / bgTf.height;
|
||||
bgTf.height = bgTf.height * scale;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ViewManager } from "db://assets/Scripts/Main/Manager/ViewManager";
|
||||
import { UITransitionHelper } from "../utils/UITransitionHelper";
|
||||
|
||||
/**
|
||||
* 导航管理器
|
||||
@@ -66,6 +67,43 @@ export class NavigationManager {
|
||||
ViewManager.I.openBundlesView("ChatPanel", roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带过渡动画进入聊天页面
|
||||
* 从GirlDetailPanel平滑过渡到ChatPanel
|
||||
*
|
||||
* @param {number} roleId - 角色ID
|
||||
* @param {any} currentPanel - 当前面板实例(GirlDetailPanel)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* NavigationManager.Instance.navigateToChatWithTransition(10001, this);
|
||||
* ```
|
||||
*/
|
||||
public navigateToChatWithTransition(roleId: number, currentPanel?: any): void {
|
||||
if (roleId == null || roleId <= 0) {
|
||||
console.warn("Invalid role ID for chat navigation with transition:", roleId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Navigating to chat with transition, role ID: ${roleId}`);
|
||||
|
||||
// 打开ChatPanel,同时标记需要执行滑入动画
|
||||
ViewManager.I.openBundlesView("ChatPanel", {
|
||||
roleId: roleId,
|
||||
withSlideTransition: true
|
||||
}, (chatNode) => {
|
||||
// ChatPanel打开后,如果有当前面板,执行滑出动画
|
||||
if (currentPanel && currentPanel.node && currentPanel.node.isValid) {
|
||||
UITransitionHelper.slideOutToLeft(currentPanel.node, 0.3, () => {
|
||||
// 滑出动画完成后关闭原面板
|
||||
if (currentPanel.onClose && typeof currentPanel.onClose === 'function') {
|
||||
currentPanel.onClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入角色详情页面
|
||||
*
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { _decorator, Button, Component, Node, NodeEventType, Sprite } from "cc";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("SimpleToggle")
|
||||
export class SimpleToggle extends Component {
|
||||
@property(Sprite)
|
||||
selectedSprite: Sprite = null;
|
||||
@property(Sprite)
|
||||
unSelectedSprite: Sprite = null;
|
||||
|
||||
callback: () => {};
|
||||
|
||||
toggleGroupCallBack: Function;
|
||||
|
||||
btn: Button;
|
||||
protected onLoad(): void {
|
||||
this.node.on(NodeEventType.TOUCH_START, this.onClickThis, this);
|
||||
this.btn = this.getComponent(Button);
|
||||
}
|
||||
|
||||
init(isSelected: boolean, callBack: () => {}) {
|
||||
this.selectedSprite.enabled = isSelected;
|
||||
this.unSelectedSprite.enabled = !isSelected;
|
||||
if (this.callback) {
|
||||
this.onDestroy();
|
||||
}
|
||||
this.callback = callBack;
|
||||
|
||||
this.node.on(NodeEventType.TOUCH_START, this.onClickThis, this);
|
||||
}
|
||||
|
||||
refresh(isSelected: boolean) {
|
||||
this.selectedSprite.enabled = isSelected;
|
||||
this.unSelectedSprite.enabled = !isSelected;
|
||||
}
|
||||
|
||||
onClickThis() {
|
||||
if (this.callback) {
|
||||
this.callback();
|
||||
}
|
||||
if (this.toggleGroupCallBack) {
|
||||
this.toggleGroupCallBack(this.node);
|
||||
}
|
||||
this.btn.interactable = false;
|
||||
}
|
||||
|
||||
protected onDestroy(): void {
|
||||
this.node.off(NodeEventType.TOUCH_START, this.onClickThis, this);
|
||||
|
||||
this.callback = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "74d7e427-0f9b-414a-90a1-e75bef954d99",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { _decorator, Component, Node, tween, Vec3 } from "cc";
|
||||
import { SimpleToggle } from "./SimpleToggle";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("SimpleToggleGroup")
|
||||
export class SimpleToggleGroup extends Component {
|
||||
toggles: SimpleToggle[];
|
||||
|
||||
@property(Node)
|
||||
slider: Node;
|
||||
|
||||
protected onLoad(): void {
|
||||
this.toggles = this.getComponentsInChildren(SimpleToggle);
|
||||
|
||||
for (let i = 0; i < this.toggles.length; i++) {
|
||||
const element = this.toggles[i];
|
||||
element.toggleGroupCallBack = this.toggleSelected.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
toggleSelected(selectedNode: Node) {
|
||||
const pos = this.slider.getWorldPosition();
|
||||
const targetPos = new Vec3(selectedNode.worldPosition.x, pos.y, pos.z);
|
||||
tween(this.slider)
|
||||
.to(0.3, { worldPosition: targetPos }, { easing: "sineInOut" })
|
||||
.start();
|
||||
|
||||
for (let i = 0; i < this.toggles.length; i++) {
|
||||
const element = this.toggles[i];
|
||||
if (element.node === selectedNode) {
|
||||
element.refresh(true);
|
||||
} else {
|
||||
element.refresh(false);
|
||||
element.btn.interactable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "bf84c159-6fa3-4211-97f6-2c9618bd9bac",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
|
||||
import { VideoRoleType } from "db://assets/Scripts/Main/Common/GlobalValue";
|
||||
import ConfigManager from "../../manager/ConfigManager";
|
||||
import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
||||
import { UITransitionHelper } from "../../utils/UITransitionHelper";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("ChatPanel")
|
||||
@@ -55,9 +56,26 @@ export class ChatPanel extends li_BaseView {
|
||||
};
|
||||
|
||||
openUIDataCT(data) {
|
||||
this.id = data;
|
||||
// 处理新的数据格式,支持过渡动画参数
|
||||
if (typeof data === 'object' && data.roleId !== undefined) {
|
||||
this.id = data.roleId;
|
||||
|
||||
// 如果标记了需要滑入动画,则执行动画
|
||||
if (data.withSlideTransition && this.node && this.node.isValid) {
|
||||
// 延迟一帧执行动画,确保节点已正确加载到场景中
|
||||
this.scheduleOnce(() => {
|
||||
UITransitionHelper.slideInFromRight(this.node, 0.3);
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
// 兼容原来的数字格式
|
||||
this.id = data;
|
||||
}
|
||||
|
||||
// 设置当前聊天的角色ID
|
||||
ChatAIService.Instance.setCurrentRole(this.id);
|
||||
if (this.id && this.id > 0) {
|
||||
ChatAIService.Instance.setCurrentRole(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
onLoadCT() {
|
||||
|
||||
@@ -15,6 +15,8 @@ import ConfigManager from "../../manager/ConfigManager";
|
||||
import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
||||
import Utils from "../../../Main/Common/Utils";
|
||||
import { InnerMsgCode } from "../../../Main/Config/InnerMsgCode";
|
||||
import { SimpleToggle } from "../components/SimpleToggle";
|
||||
import { GirlDetail } from "../../../schema/schema";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -27,6 +29,11 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
@property(Label)
|
||||
girlName: Label;
|
||||
|
||||
@property(SimpleToggle)
|
||||
imgToggle: SimpleToggle = null;
|
||||
@property(SimpleToggle)
|
||||
videoToggle: SimpleToggle = null;
|
||||
|
||||
@property(Node)
|
||||
starParent: Node;
|
||||
|
||||
@@ -67,6 +74,18 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
}
|
||||
this.tags.string = desc;
|
||||
});
|
||||
|
||||
this.imgToggle.callback = this.bindImgToggle.bind(this);
|
||||
this.videoToggle.callback = this.bindVideoToggle.bind(this);
|
||||
|
||||
// 添加视频加载完成回调
|
||||
this.avatarVideo.node.on(
|
||||
VideoPlayer.EventType.READY_TO_PLAY,
|
||||
() => {
|
||||
this.adjustVideoScale();
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
setVideEnable(enable: boolean) {
|
||||
@@ -86,8 +105,8 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
this.avatarVideo.node.parent.getComponent(UITransform);
|
||||
const videoTransform = this.avatarVideo.node.getComponent(UITransform);
|
||||
|
||||
if (parentTransform && videoTransform && videoTransform.height > 0) {
|
||||
const scale = parentTransform.height / videoTransform.height;
|
||||
if (parentTransform && videoTransform && videoTransform.width > 0) {
|
||||
const scale = parentTransform.width / videoTransform.width;
|
||||
this.avatarVideo.node.setScale(scale, scale, 1);
|
||||
console.log(
|
||||
`Video scaled to: ${scale}, parent height: ${parentTransform.height}, video height: ${videoTransform.height}`
|
||||
@@ -114,47 +133,14 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
tagKey: string;
|
||||
category;
|
||||
refresh(index: number) {
|
||||
console.log(index);
|
||||
const dataDetail = ConfigManager.tables.TbGirlsDetail.get(this.id);
|
||||
console.log(dataDetail);
|
||||
|
||||
const data = ConfigManager.tables.TbGirls.get(this.id);
|
||||
this.category = data.category;
|
||||
this.nameKey = data.nameKey;
|
||||
this.girlName.string = LanguageUtils.getText(data.nameKey);
|
||||
|
||||
if (dataDetail.pics[0].endsWith("video")) {
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.avatarVideo,
|
||||
dataDetail.pics[0],
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
// 添加视频加载完成回调
|
||||
this.avatarVideo.node.on(
|
||||
VideoPlayer.EventType.READY_TO_PLAY,
|
||||
() => {
|
||||
this.adjustVideoScale();
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
// 也立即尝试调整(以防已经加载完成)
|
||||
this.adjustVideoScale();
|
||||
}
|
||||
if (dataDetail.pics.length > 1) {
|
||||
for (let i = this.imgsLayout.children.length - 1; i >= 0; i--) {
|
||||
this.imgsLayout.children[i].destroy();
|
||||
}
|
||||
|
||||
for (let i = 1; i < dataDetail.pics.length; i++) {
|
||||
const path = dataDetail.pics[i];
|
||||
let newNode = instantiate(this.imgItemInst.node);
|
||||
let item = newNode.getComponent(DetailImageItem);
|
||||
item.refresh(path, this);
|
||||
newNode.active = true;
|
||||
this.imgsLayout.addChild(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
this.desc.string = dataDetail.detailDesc;
|
||||
let desc = "";
|
||||
this.tagKey = data.tagKey;
|
||||
@@ -170,11 +156,47 @@ export class GirlDetailPanel extends li_BaseView {
|
||||
this.descAge.string = data.age.toString();
|
||||
this.desc.string = dataDetail.detailDesc;
|
||||
|
||||
this.id = data.id;
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.avatarVideo,
|
||||
dataDetail.vids[0],
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
// 也立即尝试调整(以防已经加载完成)
|
||||
this.adjustVideoScale();
|
||||
this.vids = dataDetail.vids.slice(0);
|
||||
this.refreshImgs(dataDetail.pics, true);
|
||||
}
|
||||
|
||||
vids: string[];
|
||||
bindImgToggle() {
|
||||
const dataDetail = ConfigManager.tables.TbGirlsDetail.get(this.id);
|
||||
this.refreshImgs(dataDetail.pics, true);
|
||||
}
|
||||
|
||||
bindVideoToggle() {
|
||||
this.refreshImgs(this.vids, false);
|
||||
}
|
||||
|
||||
refreshImgs(paths: string[], isImg: boolean) {
|
||||
for (let i = this.imgsLayout.children.length - 1; i >= 0; i--) {
|
||||
this.imgsLayout.children[i].destroy();
|
||||
}
|
||||
|
||||
for (let i = 1; i < paths.length; i++) {
|
||||
const path = paths[i];
|
||||
let newNode = instantiate(this.imgItemInst.node);
|
||||
let item = newNode.getComponent(DetailImageItem);
|
||||
item.refresh(path, this, isImg);
|
||||
newNode.active = true;
|
||||
this.imgsLayout.addChild(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
OnClickChatBtn() {
|
||||
NavigationManager.Instance.navigateToChat(this.id);
|
||||
this.onClose();
|
||||
// 使用带过渡动画的导航方法
|
||||
NavigationManager.Instance.navigateToChatWithTransition(this.id, this);
|
||||
// 注意:不在这里直接调用onClose,而是在动画完成后由NavigationManager调用
|
||||
}
|
||||
|
||||
returnBtn() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { _decorator, Component, Label, Node } from "cc";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("PayToTalkSubpanel")
|
||||
export class PayToTalkSubpanel extends Component {
|
||||
@property(Label)
|
||||
timeLabel: Label = null;
|
||||
@property(Label)
|
||||
timeBtnLabel: Label = null;
|
||||
|
||||
@property(Label)
|
||||
vipLabel: Label = null;
|
||||
|
||||
@property(Label)
|
||||
vipBtnLabel: Label = null;
|
||||
|
||||
|
||||
|
||||
show() {}
|
||||
|
||||
refresh() {}
|
||||
|
||||
onClick_BuyTime() {
|
||||
//购买次数
|
||||
console.log("购买聊天次数,未实现功能");
|
||||
}
|
||||
|
||||
onClick_BuyVip() {
|
||||
//购买VIP
|
||||
console.log("购买vip,未实现功能");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "06f47bba-7287-4f26-876e-9e9ea87dac74",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,58 +1,111 @@
|
||||
import { _decorator, Component, Node,Sprite ,UITransform} from 'cc';
|
||||
import {
|
||||
_decorator,
|
||||
Component,
|
||||
Node,
|
||||
Sprite,
|
||||
UITransform,
|
||||
VideoPlayer,
|
||||
view,
|
||||
} from "cc";
|
||||
import li_BaseView from "db://assets/Scripts/Main/Common/li_BaseView";
|
||||
import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
|
||||
import Utils from "db://assets/Scripts/Main/Common/Utils";
|
||||
import {ViewManager} from "db://assets/Scripts/Main/Manager/ViewManager";
|
||||
import {GButton} from "db://assets/Scripts/Main/Common/GButton";
|
||||
import { ViewManager } from "db://assets/Scripts/Main/Manager/ViewManager";
|
||||
import { GButton } from "db://assets/Scripts/Main/Common/GButton";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
interface ShowPanelData
|
||||
{
|
||||
url: string;
|
||||
closeFunc:Function;
|
||||
interface ShowPanelData {
|
||||
url: string;
|
||||
isImg: boolean;
|
||||
closeFunc: Function;
|
||||
}
|
||||
|
||||
@ccclass('ShowPanel')
|
||||
@ccclass("ShowPanel")
|
||||
export class ShowPanel extends li_BaseView {
|
||||
@property(Sprite)
|
||||
image:Sprite;
|
||||
@property(Sprite)
|
||||
splash:Sprite;
|
||||
@property(Sprite)
|
||||
image: Sprite;
|
||||
@property(Sprite)
|
||||
splash: Sprite;
|
||||
@property(VideoPlayer)
|
||||
videoPlayer: VideoPlayer;
|
||||
|
||||
url:string;
|
||||
func:Function;
|
||||
openUIDataCT(data:ShowPanelData) {
|
||||
super.openUIDataCT(data);
|
||||
this.url = data.url;
|
||||
this.func = data.closeFunc;
|
||||
url: string;
|
||||
func: Function;
|
||||
isImg: boolean;
|
||||
openUIDataCT(data: ShowPanelData) {
|
||||
super.openUIDataCT(data);
|
||||
this.url = data.url;
|
||||
this.isImg = data.isImg;
|
||||
this.func = data.closeFunc;
|
||||
}
|
||||
|
||||
onLoadCT() {
|
||||
this.show(this.url);
|
||||
|
||||
// 添加视频加载完成回调
|
||||
this.videoPlayer.node.on(
|
||||
VideoPlayer.EventType.READY_TO_PLAY,
|
||||
() => {
|
||||
this.adjustVideoScale();
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
start() {
|
||||
//this.splash.node.on(Node.EventType.TOUCH_START,this.onclickback);
|
||||
GButton.BandClick(this.splash.node, this.onClose, this);
|
||||
}
|
||||
|
||||
protected onClose() {
|
||||
if (this.func) {
|
||||
this.func();
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
show(path: string) {
|
||||
if (this.isImg) {
|
||||
ResManager.I.changeBundleSpriteFrame(this.image, path, "Chat18x", () => {
|
||||
Utils.adjustBgPixelRatio(this.image.node, 3);
|
||||
});
|
||||
} else {
|
||||
ResManager.I.changeBundleVideo(this.videoPlayer, path, "Chat18x", () => {
|
||||
this.adjustVideoScale();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
adjustVideoScale() {
|
||||
if (!this.videoPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadCT() {
|
||||
this.show(this.url);
|
||||
const tryAdjustScale = () => {
|
||||
const screenSize = view.getVisibleSize();
|
||||
|
||||
}
|
||||
const videoTransform = this.videoPlayer.node.getComponent(UITransform);
|
||||
|
||||
start()
|
||||
{
|
||||
//this.splash.node.on(Node.EventType.TOUCH_START,this.onclickback);
|
||||
GButton.BandClick(this.splash.node,this.onClose,this);
|
||||
}
|
||||
if (videoTransform && videoTransform.height > 0) {
|
||||
let scale = screenSize.width / videoTransform.width;
|
||||
this.videoPlayer.node.setScale(scale, scale, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
protected onClose() {
|
||||
if(this.func)
|
||||
{
|
||||
this.func();
|
||||
// 立即尝试一次
|
||||
if (!tryAdjustScale()) {
|
||||
// 如果失败,延迟尝试
|
||||
this.scheduleOnce(() => {
|
||||
if (!tryAdjustScale()) {
|
||||
// 再次延迟尝试
|
||||
this.scheduleOnce(() => {
|
||||
tryAdjustScale();
|
||||
}, 0.5);
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
show(path:string)
|
||||
{
|
||||
ResManager.I.changeBundleSpriteFrame(this.image,path,"Chat18x",()=>{
|
||||
Utils.adjustBgPixelRatio(this.image.node,3);
|
||||
});
|
||||
}, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "0142e264-5c4f-407b-bb0c-57dd721980e3",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
_decorator,
|
||||
Component,
|
||||
Sprite,
|
||||
UITransform,
|
||||
Size,
|
||||
isValid,
|
||||
Node,
|
||||
} from "cc";
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/**
|
||||
* 父物体自适应调整组件
|
||||
* 功能:使精灵保持原始宽高比并覆盖整个父物体区域
|
||||
* 触发时机:GameObject 激活时、父物体尺寸变化时
|
||||
*/
|
||||
@ccclass("ParentResize")
|
||||
export class ParentResize extends Component {
|
||||
private sprite: Sprite = null;
|
||||
private uiTransform: UITransform = null;
|
||||
private parentTransform: UITransform = null;
|
||||
private lastParentSize: Size = new Size();
|
||||
|
||||
onLoad() {
|
||||
// 获取必要组件
|
||||
this.sprite = this.getComponent(Sprite);
|
||||
this.uiTransform = this.getComponent(UITransform);
|
||||
|
||||
if (!this.sprite) {
|
||||
console.warn("ParentResize: 未找到 Sprite 组件");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.uiTransform) {
|
||||
console.warn("ParentResize: 未找到 UITransform 组件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取父物体的 UITransform
|
||||
if (this.node.parent) {
|
||||
this.parentTransform = this.node.parent.getComponent(UITransform);
|
||||
if (!this.parentTransform) {
|
||||
console.warn("ParentResize: 父物体未找到 UITransform 组件");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.warn("ParentResize: 未找到父物体");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
// 激活时立即调整尺寸
|
||||
this.adjustToParent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整精灵尺寸以覆盖整个父物体区域
|
||||
* 保持原始宽高比,使用 cover 策略
|
||||
*/
|
||||
private adjustToParent() {
|
||||
if (
|
||||
!isValid(this.sprite) ||
|
||||
!isValid(this.uiTransform) ||
|
||||
!isValid(this.parentTransform)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spriteFrame = this.sprite.spriteFrame;
|
||||
if (!spriteFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取父物体尺寸
|
||||
const parentSize = this.parentTransform.contentSize;
|
||||
|
||||
// 获取精灵原始尺寸
|
||||
const originalSize = spriteFrame.originalSize;
|
||||
if (!originalSize || originalSize.width <= 0 || originalSize.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果父物体尺寸无效,则不进行调整
|
||||
if (parentSize.width <= 0 || parentSize.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算缩放比例(使用较大值以确保覆盖整个父物体区域)
|
||||
const scaleX = parentSize.width / originalSize.width;
|
||||
const scaleY = parentSize.height / originalSize.height;
|
||||
const scale = Math.max(scaleX, scaleY);
|
||||
|
||||
// 计算新的尺寸
|
||||
const newWidth = originalSize.width * scale;
|
||||
const newHeight = originalSize.height * scale;
|
||||
|
||||
// 应用新尺寸
|
||||
this.uiTransform.setContentSize(newWidth, newHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发尺寸调整
|
||||
* 可在外部调用,例如当 SpriteFrame 改变时或父物体尺寸手动改变时
|
||||
*/
|
||||
public forceResize() {
|
||||
this.adjustToParent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置新的父物体
|
||||
* 当需要动态改变参考父物体时调用
|
||||
*/
|
||||
public setParent(parentNode: Node) {
|
||||
if (parentNode) {
|
||||
this.parentTransform = parentNode.getComponent(UITransform);
|
||||
if (this.parentTransform) {
|
||||
this.lastParentSize.set(0, 0); // 重置上次记录的尺寸
|
||||
this.adjustToParent();
|
||||
} else {
|
||||
console.warn("ParentResize: 新父物体未找到 UITransform 组件");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "5ce148f6-6db7-4f09-a11d-3753b3e27771",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
_decorator,
|
||||
Component,
|
||||
Sprite,
|
||||
UITransform,
|
||||
view,
|
||||
Size,
|
||||
isValid,
|
||||
} from "cc";
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/**
|
||||
* 精灵自动调整组件
|
||||
* 功能:使精灵保持原始宽高比并覆盖整个屏幕
|
||||
* 触发时机:GameObject 激活时、屏幕尺寸变化时
|
||||
*/
|
||||
@ccclass("SpriteResize")
|
||||
export class SpriteResize extends Component {
|
||||
private sprite: Sprite = null;
|
||||
private uiTransform: UITransform = null;
|
||||
private lastScreenSize: Size = new Size();
|
||||
|
||||
onLoad() {
|
||||
// 获取必要组件
|
||||
this.sprite = this.getComponent(Sprite);
|
||||
this.uiTransform = this.getComponent(UITransform);
|
||||
|
||||
if (!this.sprite) {
|
||||
console.warn("SpriteResize: 未找到 Sprite 组件");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.uiTransform) {
|
||||
console.warn("SpriteResize: 未找到 UITransform 组件");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
// 激活时立即调整尺寸
|
||||
this.adjustSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整精灵尺寸以覆盖整个屏幕
|
||||
* 保持原始宽高比,使用 cover 策略
|
||||
*/
|
||||
private adjustSize() {
|
||||
if (!isValid(this.sprite) || !isValid(this.uiTransform)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spriteFrame = this.sprite.spriteFrame;
|
||||
if (!spriteFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取屏幕尺寸
|
||||
const screenSize = view.getVisibleSize();
|
||||
|
||||
// 获取精灵原始尺寸
|
||||
const originalSize = spriteFrame.originalSize;
|
||||
if (!originalSize || originalSize.width <= 0 || originalSize.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算缩放比例(使用较大值以确保覆盖全屏)
|
||||
const scaleX = screenSize.width / originalSize.width;
|
||||
const scaleY = screenSize.height / originalSize.height;
|
||||
const scale = Math.max(scaleX, scaleY);
|
||||
|
||||
// 计算新的尺寸
|
||||
const newWidth = originalSize.width * scale;
|
||||
const newHeight = originalSize.height * scale;
|
||||
|
||||
// 应用新尺寸
|
||||
this.uiTransform.setContentSize(newWidth, newHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发尺寸调整
|
||||
* 可在外部调用,例如当 SpriteFrame 改变时
|
||||
*/
|
||||
public forceResize() {
|
||||
this.adjustSize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "820fd6d5-5820-4dfa-aa21-333ad57e1876",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -55,19 +55,12 @@ export class DetailImageItem extends Component {
|
||||
const itemSize = this.uitransform.contentSize;
|
||||
const imageSize = this.img.spriteFrame.originalSize;
|
||||
|
||||
console.log("DetailImageItem doScaleToFill:", {
|
||||
itemSize,
|
||||
imageSize,
|
||||
url: this.url
|
||||
});
|
||||
|
||||
if (
|
||||
itemSize.width === 0 ||
|
||||
itemSize.height === 0 ||
|
||||
imageSize.width === 0 ||
|
||||
imageSize.height === 0
|
||||
) {
|
||||
console.log("DetailImageItem: Invalid size, skipping scale");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,44 +68,30 @@ export class DetailImageItem extends Component {
|
||||
const scaleY = itemSize.height / imageSize.height;
|
||||
const scale = Math.max(scaleX, scaleY);
|
||||
|
||||
console.log("DetailImageItem scale values:", { scaleX, scaleY, finalScale: scale });
|
||||
|
||||
// 设置Sprite的sizeMode为CUSTOM,允许自定义大小
|
||||
this.img.sizeMode = Sprite.SizeMode.CUSTOM;
|
||||
|
||||
|
||||
// 获取图片节点的UITransform并设置大小
|
||||
const imgTransform = this.img.node.getComponent(UITransform);
|
||||
if (imgTransform) {
|
||||
imgTransform.setContentSize(imageSize.width * scale, imageSize.height * scale);
|
||||
console.log("DetailImageItem: Set image size to", imgTransform.contentSize);
|
||||
}
|
||||
}
|
||||
refresh(path: string, base: GirlDetailPanel) {
|
||||
this.base = base;
|
||||
this.lock.active = false;
|
||||
this.question.active = false;
|
||||
|
||||
if (true) {
|
||||
this.url = path;
|
||||
// if (info.imageState !== ImageState.Release) {
|
||||
// this.url += '_blured';
|
||||
// if (info.imageState === ImageState.Lock)
|
||||
// this.img.color = new math.Color(127, 127, 127, 255);
|
||||
// else
|
||||
// this.img.color = new math.Color(50, 50, 50, 255);
|
||||
|
||||
// } else {
|
||||
// this.img.color = new math.Color(255, 255, 255, 255);
|
||||
// }
|
||||
|
||||
ResManager.I.changeBundleSpriteFrame(
|
||||
this.img,
|
||||
this.url,
|
||||
"Chat18x",
|
||||
() => {
|
||||
this.scaleToFillItem();
|
||||
}
|
||||
imgTransform.setContentSize(
|
||||
imageSize.width * scale,
|
||||
imageSize.height * scale
|
||||
);
|
||||
}
|
||||
}
|
||||
refresh(path: string, base: GirlDetailPanel, isImage: boolean = true) {
|
||||
this.base = base;
|
||||
this.lock.active = false;
|
||||
this.question.active = false;
|
||||
this.url = path;
|
||||
|
||||
let imgPath = this.url;
|
||||
if (!isImage) {
|
||||
imgPath = this.url + "_thumbnail";
|
||||
}
|
||||
ResManager.I.changeBundleSpriteFrame(this.img, imgPath, "Chat18x", () => {
|
||||
this.scaleToFillItem();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ThemeItem extends Component {
|
||||
"Chat18x",
|
||||
() => {
|
||||
let sizeTran = this.img.node.parent.getComponent(UITransform);
|
||||
Utils.adjustBgPixelRatioToSize(sizeTran.contentSize, this.img.node, 2);
|
||||
Utils.adjustBgPixelRatioToSize(sizeTran.contentSize, this.img.node, 3);
|
||||
}
|
||||
);
|
||||
GButton.RemoveAndBandClick(this.node, this.OnClickThis, this);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# UI过渡动画实现说明
|
||||
|
||||
## 功能概述
|
||||
实现了从GirlDetailPanel到ChatPanel的平滑过渡动画效果。当用户点击聊天按钮时,GirlDetailPanel会向左滑出,同时ChatPanel从右侧滑入。
|
||||
|
||||
## 核心文件
|
||||
|
||||
### 1. UITransitionHelper.ts
|
||||
- 封装了常用的UI过渡动画方法
|
||||
- 提供滑入滑出、淡入淡出等动画效果
|
||||
- 支持自定义动画时长和回调函数
|
||||
|
||||
### 2. NavigationManager.ts
|
||||
- 新增了`navigateToChatWithTransition()`方法
|
||||
- 处理双面板的动画同步
|
||||
- 管理动画完成后的面板关闭逻辑
|
||||
|
||||
### 3. 面板修改
|
||||
- **GirlDetailPanel**: 修改`OnClickChatBtn()`调用新的过渡方法
|
||||
- **ChatPanel**: 修改`openUIDataCT()`支持滑入动画参数
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
```typescript
|
||||
// 从GirlDetailPanel跳转到ChatPanel(带动画)
|
||||
NavigationManager.Instance.navigateToChatWithTransition(roleId, this);
|
||||
```
|
||||
|
||||
### 单独使用动画工具类
|
||||
```typescript
|
||||
// 滑入动画
|
||||
UITransitionHelper.slideInFromRight(node, 0.3, callback);
|
||||
|
||||
// 滑出动画
|
||||
UITransitionHelper.slideOutToLeft(node, 0.3, callback);
|
||||
|
||||
// 淡入淡出
|
||||
UITransitionHelper.fadeIn(node, 0.3, callback);
|
||||
UITransitionHelper.fadeOut(node, 0.3, callback);
|
||||
```
|
||||
|
||||
## 动画参数
|
||||
|
||||
- **动画时长**: 默认0.3秒,可自定义
|
||||
- **缓动函数**: 使用`quartOut`提供平滑的减速效果
|
||||
- **动画距离**: 基于屏幕宽度计算
|
||||
|
||||
## 兼容性说明
|
||||
|
||||
- 新的过渡方法完全向后兼容
|
||||
- 原有的`navigateToChat()`方法保持不变
|
||||
- ChatPanel的数据处理支持新旧两种格式
|
||||
|
||||
## 性能优化
|
||||
|
||||
- 动画前进行节点有效性检查
|
||||
- 延迟执行确保节点已正确加载
|
||||
- 动画完成后立即释放资源
|
||||
|
||||
## 扩展建议
|
||||
|
||||
1. 可以为其他页面切换添加类似的过渡效果
|
||||
2. 可以根据不同场景使用不同的动画类型
|
||||
3. 可以添加更多的缓动函数选项
|
||||
4. 可以支持自定义动画路径和效果
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "2a018539-d24d-41e1-9b19-b84bc9787258",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import { _decorator, Node, tween, Vec3, UITransform, view } from "cc";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* UI过渡动画帮助类
|
||||
*
|
||||
* 提供常用的UI过渡动画效果,如滑入滑出等
|
||||
*
|
||||
* @author AI Chat System
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@ccclass("UITransitionHelper")
|
||||
export class UITransitionHelper {
|
||||
|
||||
/**
|
||||
* 让节点从右侧滑入到原位置
|
||||
*
|
||||
* @param {Node} node - 要执行动画的节点
|
||||
* @param {number} duration - 动画持续时间(秒),默认0.3秒
|
||||
* @param {Function} callback - 动画完成后的回调函数
|
||||
*/
|
||||
public static slideInFromRight(node: Node, duration: number = 0.3, callback?: () => void): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for slideInFromRight");
|
||||
return;
|
||||
}
|
||||
|
||||
const screenWidth = view.getVisibleSize().width;
|
||||
const originalPosition = node.getPosition();
|
||||
|
||||
// 设置初始位置为屏幕右侧外
|
||||
node.setPosition(screenWidth, originalPosition.y, originalPosition.z);
|
||||
|
||||
// 执行滑入动画
|
||||
tween(node)
|
||||
.to(duration, { position: originalPosition }, {
|
||||
easing: 'quartOut'
|
||||
})
|
||||
.call(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 让节点向左滑出到屏幕外
|
||||
*
|
||||
* @param {Node} node - 要执行动画的节点
|
||||
* @param {number} duration - 动画持续时间(秒),默认0.3秒
|
||||
* @param {Function} callback - 动画完成后的回调函数
|
||||
*/
|
||||
public static slideOutToLeft(node: Node, duration: number = 0.3, callback?: () => void): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for slideOutToLeft");
|
||||
return;
|
||||
}
|
||||
|
||||
const screenWidth = view.getVisibleSize().width;
|
||||
const currentPosition = node.getPosition();
|
||||
const targetPosition = new Vec3(-screenWidth, currentPosition.y, currentPosition.z);
|
||||
|
||||
// 执行滑出动画
|
||||
tween(node)
|
||||
.to(duration, { position: targetPosition }, {
|
||||
easing: 'quartOut'
|
||||
})
|
||||
.call(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 让节点从左侧滑入到原位置
|
||||
*
|
||||
* @param {Node} node - 要执行动画的节点
|
||||
* @param {number} duration - 动画持续时间(秒),默认0.3秒
|
||||
* @param {Function} callback - 动画完成后的回调函数
|
||||
*/
|
||||
public static slideInFromLeft(node: Node, duration: number = 0.3, callback?: () => void): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for slideInFromLeft");
|
||||
return;
|
||||
}
|
||||
|
||||
const screenWidth = view.getVisibleSize().width;
|
||||
const originalPosition = node.getPosition();
|
||||
|
||||
// 设置初始位置为屏幕左侧外
|
||||
node.setPosition(-screenWidth, originalPosition.y, originalPosition.z);
|
||||
|
||||
// 执行滑入动画
|
||||
tween(node)
|
||||
.to(duration, { position: originalPosition }, {
|
||||
easing: 'quartOut'
|
||||
})
|
||||
.call(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 让节点向右滑出到屏幕外
|
||||
*
|
||||
* @param {Node} node - 要执行动画的节点
|
||||
* @param {number} duration - 动画持续时间(秒),默认0.3秒
|
||||
* @param {Function} callback - 动画完成后的回调函数
|
||||
*/
|
||||
public static slideOutToRight(node: Node, duration: number = 0.3, callback?: () => void): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for slideOutToRight");
|
||||
return;
|
||||
}
|
||||
|
||||
const screenWidth = view.getVisibleSize().width;
|
||||
const currentPosition = node.getPosition();
|
||||
const targetPosition = new Vec3(screenWidth, currentPosition.y, currentPosition.z);
|
||||
|
||||
// 执行滑出动画
|
||||
tween(node)
|
||||
.to(duration, { position: targetPosition }, {
|
||||
easing: 'quartOut'
|
||||
})
|
||||
.call(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 淡入动画
|
||||
*
|
||||
* @param {Node} node - 要执行动画的节点
|
||||
* @param {number} duration - 动画持续时间(秒),默认0.3秒
|
||||
* @param {Function} callback - 动画完成后的回调函数
|
||||
*/
|
||||
public static fadeIn(node: Node, duration: number = 0.3, callback?: () => void): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for fadeIn");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置初始透明度为0
|
||||
if (node.setOpacity) {
|
||||
node.setOpacity(0);
|
||||
}
|
||||
|
||||
// 执行淡入动画
|
||||
tween(node)
|
||||
.to(duration, { opacity: 255 }, {
|
||||
easing: 'quartOut'
|
||||
})
|
||||
.call(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 淡出动画
|
||||
*
|
||||
* @param {Node} node - 要执行动画的节点
|
||||
* @param {number} duration - 动画持续时间(秒),默认0.3秒
|
||||
* @param {Function} callback - 动画完成后的回调函数
|
||||
*/
|
||||
public static fadeOut(node: Node, duration: number = 0.3, callback?: () => void): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for fadeOut");
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行淡出动画
|
||||
tween(node)
|
||||
.to(duration, { opacity: 0 }, {
|
||||
easing: 'quartOut'
|
||||
})
|
||||
.call(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止节点上的所有动画
|
||||
*
|
||||
* @param {Node} node - 要停止动画的节点
|
||||
*/
|
||||
public static stopAllTweens(node: Node): void {
|
||||
if (!node || !node.isValid) {
|
||||
console.warn("UITransitionHelper: Invalid node for stopAllTweens");
|
||||
return;
|
||||
}
|
||||
|
||||
tween(node).stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ace67d75-a01f-49ec-aeb1-2e76cb041e46",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -159,6 +159,7 @@ export class GirlDetail {
|
||||
this.id = _buf_.readInt()
|
||||
this.detailDesc = _buf_.readString()
|
||||
{ let n = Math.min(_buf_.readSize(), _buf_.size); this.pics = []; for(let i = 0 ; i < n ; i++) { let _e0 ;_e0 = _buf_.readString(); this.pics.push(_e0);}}
|
||||
{ let n = Math.min(_buf_.readSize(), _buf_.size); this.vids = []; for(let i = 0 ; i < n ; i++) { let _e0 ;_e0 = _buf_.readString(); this.vids.push(_e0);}}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,14 +171,19 @@ export class GirlDetail {
|
||||
*/
|
||||
readonly detailDesc: string
|
||||
/**
|
||||
* 资源列表
|
||||
* 图片资源列表
|
||||
*/
|
||||
readonly pics: string[]
|
||||
/**
|
||||
* 视频资源列表
|
||||
*/
|
||||
readonly vids: string[]
|
||||
|
||||
resolve(tables:Tables) {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user