Android应用设计提示:Google Play和<supports-gl-texture>元素

GooglePlay通过支持的纹理压缩格式过滤应用,确保只在兼容设备上安装。开发者利用GPU纹理格式作为设备类型的过滤标准。

Google Play会根据应用程序所支持的纹理压缩格式来过滤应用程序,以确保应用程序只能安装在处理其纹理属性的的设备上。开发者能够使用基于GPU平台的纹理压缩格式,作为针对特定设备类型一种过滤方式。

import React, { useEffect, useRef, useState, useCallback, useContext } from "react"; import { Async } from "@/utils/utils"; // 创建共享的 PIXI.Application 上下文 const PixiAppContext = React.createContext<{ app: PIXI.Application | null; registerContainer: (id: string, container: PIXI.Container) => void; unregisterContainer: (id: string) => void; }>({ app: null, registerContainer: () => { }, unregisterContainer: () => { }, }); interface PixiAppProviderProps { children: React.ReactNode; containerRef: React.RefObject<HTMLDivElement>; // 新增:容器引用 zIndex?: number } export const PixiAppProvider: React.FC<PixiAppProviderProps> = ({ children, containerRef, zIndex = 100 }) => { const pixiAppRef = useRef<PIXI.Application | null>(null); const [app, setApp] = useState<PIXI.Application | null>(null); const containersRef = useRef<Map<string, PIXI.Container>>(new Map()); const registerContainer = useCallback((id: string, container: PIXI.Container) => { containersRef.current.set(id, container); }, []); const unregisterContainer = useCallback((id: string) => { containersRef.current.delete(id); }, []); useEffect(() => { const initPixiApp = async () => { const PIXI = (await import("pixi.js")).default || (await import("pixi.js")); if (!pixiAppRef.current && containerRef.current) { const containerElement = containerRef.current; const containerRect = containerElement.getBoundingClientRect(); const newApp = new PIXI.Application({ width: containerRect.width, height: containerRect.height, transparent: true, resolution: window.devicePixelRatio || 1, autoDensity: true, }); // 设置 canvas 样式 - 相对于容器定位 newApp.view.style.position = 'absolute'; newApp.view.style.top = '0'; newApp.view.style.left = '0'; newApp.view.style.width = '100%'; newApp.view.style.height = '100%'; newApp.view.style.pointerEvents = 'none'; newApp.view.style.zIndex = containerElement.style.zIndex; pixiAppRef.current = newApp; setApp(newApp); // 将 canvas 添加到容器 containerElement.appendChild(newApp.view); // 监听容器尺寸变化 const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const { width, height } = entry.contentRect; newApp.renderer.resize(width, height); } }); resizeObserver.observe(containerElement); return () => { resizeObserver.disconnect(); }; } }; initPixiApp(); return () => { if (pixiAppRef.current) { // 从容器移除 canvas if (containerRef.current && containerRef.current.contains(pixiAppRef.current.view)) { containerRef.current.removeChild(pixiAppRef.current.view); } pixiAppRef.current.destroy(true); pixiAppRef.current = null; } }; }, [containerRef]); return ( <PixiAppContext.Provider value={{ app, registerContainer, unregisterContainer }}> {children} </PixiAppContext.Provider> ); }; interface AnimatedProps { spritesheet: PIXI.Spritesheet | null; animationKey: string; animationSpeed?: number; scale: number; loop: boolean; onComplete?: () => void; onFirstFrame?: (e: boolean) => void, // 新增:用于定位的 props targetElement: HTMLDivElement | null; // 目标元素,动画将相对于此元素居中 // const fireContainerRef = useRef<HTMLDivElement>(null); } const CommonDragonAnimation: React.FC<AnimatedProps> = ({ spritesheet, animationKey, animationSpeed = 0.2, scale = 1, loop = true, onComplete, onFirstFrame, targetElement = null, }) => { const containerRef = useRef<PIXI.Container | null>(null); const spriteRef = useRef<PIXI.AnimatedSprite | null>(null); const [error, setError] = useState<string | null>(null); const mountedRef = useRef(false); const animationIdRef = useRef<string>(Math.random().toString(36).substr(2, 9)); const { app, registerContainer, unregisterContainer } = useContext(PixiAppContext); // 计算相对于容器的位置 const calculateRelativePosition = useCallback(() => { if (!containerRef.current || !app || !targetElement) return { x: 0, y: 0 }; // 获取容器的位置 const appContainer = app.view.parentElement; if (!appContainer) return { x: 0, y: 0 }; const containerRect = appContainer.getBoundingClientRect(); const targetRect = targetElement.getBoundingClientRect(); // 计算相对于容器的位置 const relativeX = targetRect.left - containerRect.left + targetRect.width / 2; const relativeY = targetRect.top - containerRect.top + targetRect.height / 2; return { x: relativeX, y: relativeY }; }, [app, targetElement]); // 更新容器位置 const updateContainerPosition = useCallback(() => { if (!containerRef.current || !app) return; const position = calculateRelativePosition(); containerRef.current.x = position.x; containerRef.current.y = position.y; }, [app, calculateRelativePosition]); // 创建动画精灵 const createAnimatedSprite = useCallback( (textures: PIXI.Texture[]) => { if (!app || textures.length === 0) { setError("无法创建动画精灵:无有效纹理或 PIXI 应用未初始化"); return; } // 创建容器(如果不存在) if (!containerRef.current) { containerRef.current = new PIXI.Container(); containerRef.current.pivot.set(containerRef.current.width / 2, containerRef.current.height / 2); app.stage.addChild(containerRef.current); registerContainer(animationIdRef.current, containerRef.current); } // 更新容器位置 updateContainerPosition(); if (spriteRef.current) { spriteRef.current.scale.set(scale); spriteRef.current.textures = textures; spriteRef.current.loop = loop; spriteRef.current.animationSpeed = animationSpeed; if (onComplete) { spriteRef.current.onComplete = onComplete; } spriteRef.current.gotoAndPlay(0); } else { const animatedSprite = new PIXI.AnimatedSprite(textures); animatedSprite.animationSpeed = animationSpeed; animatedSprite.loop = loop; animatedSprite.scale.set(scale); animatedSprite.anchor.set(0.5, 0.5); animatedSprite.x = 0; animatedSprite.y = 0; animatedSprite.play(); if (onFirstFrame && textures.length > 0) { onFirstFrame(true); } if (onFirstFrame) { const handleFrameChange = (currentFrame: number) => { if (currentFrame === 0) { onFirstFrame(true); animatedSprite.off('frameChange', handleFrameChange); } }; animatedSprite.on('frameChange', handleFrameChange); } if (onComplete) { animatedSprite.onComplete = onComplete; } containerRef.current.addChild(animatedSprite); spriteRef.current = animatedSprite; } }, [app, animationSpeed, loop, onComplete, scale, registerContainer, updateContainerPosition, onFirstFrame] ); // 获取动画纹理(保持不变) const getTextures = useCallback( (animationKey: string) => { if (!spritesheet) { setError("精灵表未加载"); return []; } const frames = spritesheet.data.animations[animationKey]; if (!frames) { setError(`未找到动画: ${animationKey}`); return []; } const textures: PIXI.Texture[] = []; frames.forEach((frameKey: string) => { const texture = spritesheet.textures[frameKey]; if (texture) { textures.push(texture); } else { console.warn(`未找到纹理: ${frameKey}`); } }); return textures; }, [spritesheet] ); // 初始化动画 useEffect(() => { mountedRef.current = true; if (!app) { setError("PIXI 应用未初始化"); return; } if (!spritesheet) { setError("精灵表尚未加载"); return; } Async(async () => { if (!mountedRef.current) return; const textures = getTextures(animationKey); if (textures.length > 0) { createAnimatedSprite(textures); } else { setError("没有有效的帧纹理可用于动画"); } }); return () => { mountedRef.current = false; }; }, [spritesheet, animationKey, app, getTextures, createAnimatedSprite]); // 清理资源 useEffect(() => { return () => { mountedRef.current = false; if (spriteRef.current) { spriteRef.current.stop(); spriteRef.current.destroy(); spriteRef.current = null; } if (containerRef.current) { if (app && app.stage) { app.stage.removeChild(containerRef.current); } containerRef.current.destroy(); containerRef.current = null; } unregisterContainer(animationIdRef.current); }; }, [app, unregisterContainer]); if (error) { return <div style={{ color: "red" }}>错误: {error}</div>; } return null; }; export default React.memo(CommonDragonAnimation); import { useEffect, useRef, useState, useMemo } from "react"; import { Async } from "@/utils/utils"; interface SpritesheetHookResult { spritesheet: PIXI.Spritesheet | null; error: string | null; } // 全局缓存清理标记 let globalCacheCleanupDone = false; /** * 清理 PIXI 全局缓存中的特定资源 */ function cleanupPIXICache(resourceId: string, imagePath: string) { if (!(window as any).PIXI) return; const PIXI = (window as any).PIXI; // 清理特定资源的缓存 if (PIXI.utils.TextureCache && PIXI.utils.TextureCache[resourceId]) { PIXI.utils.TextureCache[resourceId].destroy(true); delete PIXI.utils.TextureCache[resourceId]; } if (PIXI.utils.BaseTextureCache && PIXI.utils.BaseTextureCache[resourceId]) { PIXI.utils.BaseTextureCache[resourceId].destroy(); delete PIXI.utils.BaseTextureCache[resourceId]; } // 清理带路径的缓存项(错误信息中显示的这种格式) const pathKeys = Object.keys(PIXI.utils.BaseTextureCache || {}).filter(key => key.includes(resourceId) || key.includes(imagePath) ); pathKeys.forEach(key => { PIXI.utils.BaseTextureCache[key]?.destroy(); delete PIXI.utils.BaseTextureCache[key]; PIXI.utils.TextureCache[key]?.destroy(true); delete PIXI.utils.TextureCache[key]; }); } /** * 执行一次性全局缓存清理 */ function performGlobalCacheCleanup() { if (globalCacheCleanupDone || !(window as any).PIXI) return; const PIXI = (window as any).PIXI; // 清理所有纹理缓存 if (PIXI.utils.TextureCache) { Object.keys(PIXI.utils.TextureCache).forEach(key => { if (!key.startsWith('global_')) { // 保留一些全局纹理 PIXI.utils.TextureCache[key]?.destroy(true); delete PIXI.utils.TextureCache[key]; } }); } if (PIXI.utils.BaseTextureCache) { Object.keys(PIXI.utils.BaseTextureCache).forEach(key => { if (!key.startsWith('global_')) { PIXI.utils.BaseTextureCache[key]?.destroy(); delete PIXI.utils.BaseTextureCache[key]; } }); } globalCacheCleanupDone = true; } export function useSpriteSheetJson(imagePath: string, spriteSheetData: any): SpritesheetHookResult { const [spritesheet, setSpritesheet] = useState<PIXI.Spritesheet | null>(null); const [error, setError] = useState<string | null>(null); const loaderRef = useRef<PIXI.Loader | null>(null); const mountedRef = useRef(true); // 生成唯一资源 ID - 添加时间戳确保唯一性 const resourceId = useMemo(() => { const fileName = imagePath.split("/").pop()?.replace(/\.[^/.]+$/, "") || "default"; return `${fileName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }, [imagePath]); // 修改 spriteSheetData,为帧名称动画名称添加唯一前缀 const modifiedSpriteSheetData = useMemo(() => { const newData = JSON.parse(JSON.stringify(spriteSheetData)); const newFrames: Record<string, any> = {}; Object.keys(newData.frames).forEach((key) => { newFrames[`${resourceId}_${key}`] = newData.frames[key]; }); newData.frames = newFrames; Object.keys(newData.animations).forEach((animKey) => { newData.animations[animKey] = newData.animations[animKey].map( (frame: string) => `${resourceId}_${frame}` ); }); return newData; }, [spriteSheetData, resourceId]); useEffect(() => { // 在组件挂载时执行全局缓存清理 performGlobalCacheCleanup(); Async(async () => { const PIXI = (await import("pixi.js")).default || (await import("pixi.js")); if (!(window as any).PIXI) (window as any).PIXI = PIXI; // 清理旧资源 - 先清理缓存 cleanupPIXICache(resourceId, imagePath); if (loaderRef.current) { loaderRef.current.reset(); loaderRef.current.destroy(); loaderRef.current = null; } if (spritesheet) { // 安全地销毁精灵表 try { spritesheet.destroy(true); } catch (e) { console.warn('销毁精灵表时出错:', e); } setSpritesheet(null); } // 创建新的加载器实例 loaderRef.current = new PIXI.Loader(); // 设置加载器前缀避免冲突 loaderRef.current.baseUrl = ''; loaderRef.current.add(resourceId, imagePath).load((loader, resources) => { if (!mountedRef.current) return; const baseTexture: any = resources[resourceId]?.texture?.baseTexture; if (!baseTexture) { setError(`无法加载精灵表图片: ${imagePath}`); return; } // 设置基纹理的缓存ID,避免冲突 baseTexture.textureCacheIds = [resourceId]; const spritesheetInstance = new PIXI.Spritesheet(baseTexture, modifiedSpriteSheetData); // 解析精灵表 spritesheetInstance.parse(() => { if (!mountedRef.current) return; setSpritesheet(spritesheetInstance); }); }); loaderRef.current.onError.add((err) => { if (mountedRef.current) { setError(`加载精灵表失败: ${err.message}`); } }); }); return () => { mountedRef.current = false; // 清理资源 if (loaderRef.current) { try { loaderRef.current.reset(); loaderRef.current.destroy(); } catch (e) { console.warn('销毁加载器时出错:', e); } loaderRef.current = null; } if (spritesheet) { try { spritesheet.destroy(true); } catch (e) { console.warn('销毁精灵表时出错:', e); } setSpritesheet(null); } // 清理缓存 setTimeout(() => cleanupPIXICache(resourceId, imagePath), 100); }; }, [imagePath, modifiedSpriteSheetData, resourceId]); return { spritesheet, error }; } 以上代码为封装的动画组件,请分析并解决问题: 在ios与PC端设备中,一切正常,但在安卓手机设备中,动画位置显示全黑。经测试验证,排除了webGL支持问题、精灵表也能正常解析,动画播放大小都正常,仅画面全黑。该如何修复?
10-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值