设置透明色有残留怎么办_AE崩溃了怎么办?这可能是最全面的解决办法了!

本文汇总了Adobe After Effects(AE)在使用过程中遇到的各种崩溃问题及其解决方案,包括清除缓存、更新驱动、调整内存使用率等方法,帮助用户快速定位并解决问题。

a19fccfa1b84208b78df01bb51f5c37d.png

e859e6d68e9d4c2527f9d924c07184cd.gif

4fc3bb0e1b9d62ce2852d1d06f3d9da7.png

「1、AE显示无法继续,已崩溃怎么解决?」

dcef7c3c8d6c8286199c8a32db558bff.png

1、清除缓存和内存,重新安装AE,删除干净AE文件和AE注册列表。

2、查看一下电脑驱动是否是最新的,如若不是,更新驱动。

3、查看所有的第三方插件是否出现问题,将它们全部剪切出来,测试AE能否正常打开。第三方插件路径:你的盘符:Adobe After Effects CC 20xxSupport FilesPlug-ins。

「2、AE打不开,一直显示奔溃怎么办?」

31540bf50da5b2dc7cd2f9ca878c16b1.png

1、发生这种情况可能是激活时处于联网状态,被ADUOBE公司检测出来并查杀了。重装系统,在不联网状态下激活。

2、安装时设置了中文路径,把路径名改成英文。

「3、AE每次打开首选项就崩溃?」

4ba5176c8cf0d2fe5bb4a2fc6ebdf685.png

切换成英文即可。

「4、为什么AE导入文本崩溃?」

选择AE可识别的文字格式即可。

「5、AE导入OBJ格式模型出现崩溃,怎么办?」

换成英文的E3D,模型也改成英文名,有的AE版本无法识别中文名的模型,如果还不行就重新导入模型试试。

「6、AE无法导入MP4文件,一导入就奔溃怎么解决?」

0c55fd100861fd947fb0bcfdd92b4865.png

1、对MP4文件重新进行编码,可以导入转码软件进行转码,再重新导入AE即可。

2、如若不行,选择重装软件,恢复首选项,重新安装quicktime。、

3、更新软件到最新版本。

b4cc7c7f9e37e27ecb31b5d1962f5977.png

在这里注意有的AE软件因为版本问题,无法打开PR常用的H.264格式,只能打开MPEG4格式!

「7、AE错误:调用增效工具崩溃怎么办?」

1、很大概率是因为内存爆了,可以开着内存监看试试。

2、电脑运行CMD,输入命令netsh winsock reset catalog 按下回车键执行命令。

3、删除增效工具,注意这里有的版本的AE删除增效工具后会无法打开。

「8、2020版本的AE一安装plexus插件就崩溃?」

1、删除plexus插件,即可成功打开。

2、如果一定要用plexus插件,建议下载2019版本的AE,再重新安装plexus插件。

「9、AE导入视频进行摄像机跟踪解析就崩溃?」

1、有可能是软件bug,换成英文版的AE。

2、运动幅度过快或者像素问题,换成运动较小的素材即可。

「10、AE一开播放就奔溃?」

可能是程序崩溃,但进程尚在内存中运行,调用任务管理器,查找AE的进程,结束它,然后再次运行,即可解决该问题。

「11、AE使用模板时奔溃?」

AE模板大多是高清的,需要4G以上内存支持,有可能是因为电脑内存不足,清楚电脑内存或者重新下载一个模板即可。

「12、AE无法运行E3D插件,一运行就崩溃?」

将AE插件目录中的E3D插件暂时剪切出来,然后打开软件测试AE是否可以正常打开,若是可以正常打开,则将E3D插件重新放回即可。

「13、AE渲染时崩溃怎么办?」

1、在设置中更改渲染使用CPU使用率,将数值拉到最大,这样一来,导出的时候电脑会比较卡。

2、输出时选择half或者更低,三分之一甚至四分之一,就是预览的时候,不完整质量输出就行。

3、分段输出,一个视频分成几段来输出。

「14、AE显示报警,打不开怎么办?」

02b57826833f2f4d8a4eb0f9491d8862.png

可能是Adobe根目录文件残留的问题,找到下图的路径,点击最后一个文件夹,有多余的全部删除即可。

4582289dcc23941092eb33c893208290.png
package com.android.example.cameraappxjava; import android.Manifest; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.net.Uri; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; import android.provider.MediaStore; import android.util.Log; import android.util.Size; import android.view.Surface; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import com.android.example.cameraappxjava.util.CameraGLRenderer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * 调用自定义渲染器的Demo:模拟YUV预览渲染 */ public class MainActivity2 extends AppCompatActivity { private static final String TAG = "camera2api"; private static final int REQUEST_CAMERA_PERMISSION = 100; // 1. 删除 TextureView 相关变量 // private TextureView textureView; // private boolean isTextureAvailable = false; // 2. 新增 GLSurfaceView + 自定义渲染器 private GLSurfaceView glSurfaceView; private CameraGLRenderer cameraGLRenderer; // 之前定义的自定义YUV渲染器 // 3. 新增:预览用 ImageReader(接收 Camera2 输出的 YUV 帧,给渲染器用) private ImageReader previewImageReader; // 4. 保留原有拍照用 ImageReader(JPEG格式,不修改) private ImageReader captureImageReader; // 5. 保留其他原有变量(相机设备、会话、按钮等) private Button captureButton; private CameraDevice cameraDevice; private CameraCaptureSession cameraCaptureSession; private CaptureRequest.Builder captureRequestBuilder; private String cameraId; private Handler backgroundHandler; private boolean isSessionClosed; private HandlerThread backgroundThread; private CameraManager manager; private volatile boolean isCapturing = false; private StreamConfigurationMap map; private long lastClickTime = 0; private static final long MIN_CLICK_INTERVAL = 1000; private File file; private ContentResolver resolver; private ContentValues values; private Uri imageUri; // ---------------------- 第三步:修改 onCreate(初始化 GLSurfaceView 和渲染器) ---------------------- @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "onCreate ——————————————————————"); // 1. 初始化 GLSurfaceView(替换原 TextureView) glSurfaceView = findViewById(R.id.glsurfaceView); // 2. 初始化拍照按钮(保留原有逻辑) captureButton = findViewById(R.id.btnCapture); // 3. 配置 GLSurfaceView + 自定义渲染器(核心) initGLRenderer(); // 4. 初始化相机参数(保留原有逻辑,但后续需补充预览ImageReader) initCamera(); // 5. 保留拍照按钮监听(原有逻辑不变) captureButton.setOnClickListener(v -> { long currentTime = SystemClock.elapsedRealtime(); if (currentTime - lastClickTime > MIN_CLICK_INTERVAL) { lastClickTime = currentTime; takePicture(); } else { Log.d(TAG, "点击过快,已忽略"); } }); } // ---------------------- 新增:初始化 GLSurfaceView 和自定义渲染器 ---------------------- private void initGLRenderer() { // 1. 设置 OpenGL 版本(必须是 2.0,匹配渲染器着色器) glSurfaceView.setEGLContextClientVersion(2); // 2. 创建自定义渲染器实例 cameraGLRenderer = new CameraGLRenderer(); // 3. 绑定渲染器到 GLSurfaceView glSurfaceView.setRenderer(cameraGLRenderer); // 4. 按需渲染(有新帧才重绘,节省性能) glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); } // ---------------------- 第四步:修改 initCamera(新增预览用 ImageReader) ---------------------- private void initCamera() { Log.d(TAG, "initCamera: 初始化相机配置"); try { // 1. 保留原有逻辑:初始化 CameraManager、相机ID、配置Map manager = (CameraManager) getSystemService(CAMERA_SERVICE); cameraId = manager.getCameraIdList()[0]; CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { Log.e(TAG, "错误: StreamConfigurationMap为空!!"); return; } // 2. 新增:初始化预览用 ImageReader(YUV_420_888 格式,给渲染器传数据) // 2.1 获取相机支持的 YUV 预览尺寸(用原有的尺寸选择逻辑) Size[] yuvSizes = map.getOutputSizes(ImageFormat.YUV_420_888); Size previewSize = chooseOptimalSize(yuvSizes, glSurfaceView.getWidth(), glSurfaceView.getHeight()); // 2.2 创建 ImageReader(尺寸=预览尺寸,格式=YUV_420_888,缓冲区=2) previewImageReader = ImageReader.newInstance( previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2 ); // 2.3 设置 ImageReader 回调(关键:获取 YUV 帧,传给渲染器) previewImageReader.setOnImageAvailableListener(reader -> { try (Image image = reader.acquireLatestImage()) { if (image == null || cameraGLRenderer == null) return; // 2.5 把 YUV 数据传给渲染器,触发重绘 cameraGLRenderer.setYUVData(image); glSurfaceView.requestRender(); // 触发渲染器 onDrawFrame } catch (Exception e) { Log.e(TAG, "预览帧处理失败: " + e.getMessage()); } }, backgroundHandler); // 在相机后台线程执行 // 3. 保留原有逻辑:初始化拍照用 ImageReader(JPEG格式) Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG); Size captureSize = chooseOptimalSize(jpegSizes,glSurfaceView.getWidth(),glSurfaceView.getHeight()); if (captureImageReader == null || captureImageReader.getWidth() != captureSize.getWidth()) { if (captureImageReader != null) captureImageReader.close(); captureImageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2 ); } // 4. 保留原有逻辑:图片保存参数 resolver = getContentResolver(); values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, "pic_" + System.currentTimeMillis() + ".jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); } catch (CameraAccessException e) { Log.e(TAG, "相机访问异常: " + e.getMessage()); } catch (NullPointerException e) { Log.e(TAG, "NPE: " + e.getMessage()); } } // ---------------------- 新增:提取 Image Plane 数据的工具方法 ---------------------- private byte[] extractPlaneData(Image.Plane plane) { ByteBuffer buffer = plane.getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); return data; } // ---------------------- 第五步:修改 openCamera(删除 TextureView 检查) ---------------------- private void openCamera() { // 1. 删除原 TextureView 相关检查(替换为 ImageReader 检查) if (previewImageReader == null || backgroundHandler == null) { Log.w(TAG, "预览ImageReader未就绪,延迟打开相机,1000ms后重试"); backgroundHandler.postDelayed(this::openCamera, 1000); return; } Log.d(TAG, "openCamera: 尝试打开相机"); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "1.打开相机: " + cameraId); manager.openCamera(cameraId, stateCallback, backgroundHandler); } else { Log.w(TAG, "相机权限未授予"); } } catch (CameraAccessException e) { Log.e(TAG, "打开相机失败: " + e.getMessage()); } catch (SecurityException e) { Log.e(TAG, "安全异常: " + e.getMessage()); } } // ---------------------- 第六步:修改 createCameraPreviewSession(替换预览 Surface) ---------------------- private void createCameraPreviewSession() { if (cameraDevice == null || previewImageReader == null) { Log.e(TAG, "创建预览会话失败: 相机或预览ImageReader不可用"); return; } try { // 1. 新增:获取预览 ImageReader 的 Surface(Camera2 输出目标) Surface previewSurface = previewImageReader.getSurface(); // 2. 保留:获取拍照 ImageReader 的 Surface Surface captureSurface = captureImageReader.getSurface(); // 3. 配置双输出 Surface(预览 + 拍照,替换原 TextureView 的 Surface) List<Surface> outputSurfaces = new ArrayList<>(2); outputSurfaces.add(previewSurface); // 预览:ImageReader 的 Surface(给渲染器) outputSurfaces.add(captureSurface); // 拍照:原有 ImageReader 的 Surface // 4. 保留原有逻辑:创建会话 + 配置预览请求 cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { Log.i(TAG, "2.2 预览会话配置成功"); cameraCaptureSession = session; try { // 配置预览请求(目标是预览 Surface) captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(previewSurface); // 替换为 ImageReader 的 Surface // 保留原有自动对焦/闪光灯配置 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); Log.i(TAG, "3.开始下发预览请求"); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "设置预览请求失败: " + e.getMessage()); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Log.e(TAG, "预览会话配置失败"); Toast.makeText(MainActivity2.this, "配置失败", Toast.LENGTH_SHORT).show(); } }, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "创建预览会话异常: " + e.getMessage()); } } // ---------------------- 第七步:修改生命周期方法(添加 GLSurfaceView 管理) ---------------------- @Override protected void onResume() { Log.d(TAG, "onResume —————————————————————— "); super.onResume(); // 1. 保留原有权限检查 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "没有相机权限——>开始请求相机权限"); ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); return; } // 2. 保留原有后台线程启动 startBackgroundThread(); // 3. 新增:恢复 GLSurfaceView(必须调用,否则渲染暂停) glSurfaceView.onResume(); // 4. 打开相机(替换原 TextureView 检查) openCamera(); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause ——————————————————————"); // 1. 新增:暂停 GLSurfaceView(必须调用,保存 OpenGL 上下文) glSurfaceView.onPause(); // 2. 保留原有预览暂停逻辑 if (!isCapturing && cameraCaptureSession != null) { try { cameraCaptureSession.stopRepeating(); Log.d(TAG, "onPause: 暂停预览重复请求(核心资源未释放)"); } catch (CameraAccessException e) { Log.e(TAG, "onPause: 停止预览失败", e); } } // 3. 保留原有拍照中延迟处理逻辑 if (isCapturing) { Log.w(TAG, "onPause: 拍照中,暂不处理预览暂停"); new Handler().postDelayed(() -> { if (!isCapturing && cameraCaptureSession != null) { try { cameraCaptureSession.stopRepeating(); Log.d(TAG, "onPause: 拍照完成后,暂停预览"); } catch (CameraAccessException e) { Log.e(TAG, "onPause: 延迟停止预览失败", e); } } }, 1000); } } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy: Activity 彻底销毁,释放所有资源"); // 1. 新增:释放预览 ImageReader 和渲染器资源 if (previewImageReader != null) { previewImageReader.close(); } if (cameraGLRenderer != null) { cameraGLRenderer.release(); } // 2. 保留原有资源释放逻辑(相机、拍照ImageReader、线程等) if (cameraCaptureSession != null) { cameraCaptureSession.close(); cameraCaptureSession = null; } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } if (captureImageReader != null) { captureImageReader.close(); captureImageReader = null; } stopBackgroundThread(); // 3. 置空新增的引用 glSurfaceView = null; cameraGLRenderer = null; previewImageReader = null; // 4. 保留原有置空逻辑 captureButton = null; manager = null; resolver = null; values = null; imageUri = null; backgroundHandler = null; backgroundThread = null; Log.d(TAG, "onDestroy: 所有资源释放完成"); } private boolean checkTakePicture() { if (cameraDevice == null) { Log.w(TAG, "拍照失败: 相机未初始化"); return false; } // 1. 检查会话有效性 if (cameraCaptureSession == null) { Log.e(TAG, "拍照错误: CameraCaptureSession为空"); return false; } // 2. 检查后台Handler if (backgroundHandler == null) { Log.e(TAG, "拍照错误: backgroundHandler未初始化"); startBackgroundThread(); // 初始化方法见下方 return false; } if (isSessionClosed) { Log.e(TAG, "当前会话已关闭"); } return true; } // ---------------------- 第八步:修改 takePicture(替换拍照用 ImageReader) ---------------------- private void takePicture() { Log.i(TAG, "4.开始拍照流程——————————"); try { // 1. 保留原有检查逻辑 boolean checkFlag = checkTakePicture(); if (!checkFlag) { Log.i(TAG, "拍照流程————检查未通过!退出拍照!"); return; } // 2. 替换:拍照请求目标为 captureImageReader(原有 JPEG 格式) CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(captureImageReader.getSurface()); // 用拍照专用 ImageReader // 3. 保留原有拍照参数配置 captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); int rotation = getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotation); // 4. 保留原有拍照 ImageReader 回调(保存 JPEG 图片) captureImageReader.setOnImageAvailableListener(reader -> { Log.d(TAG, "拍照图像数据可用"); try (Image image = reader.acquireLatestImage()) { if (image != null) { // 保留原有文件创建和保存逻辑 file = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "pic_" + System.currentTimeMillis() + ".jpg" ); // 提取 JPEG 数据(原有逻辑) Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); // 保存图片(原有逻辑) saveImage(bytes, file); // 保留原有广播和提示 Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); mediaScanIntent.setData(Uri.fromFile(file)); sendBroadcast(mediaScanIntent); runOnUiThread(() -> Toast.makeText(MainActivity2.this, "保存至: " + file, Toast.LENGTH_SHORT).show() ); } } catch (Exception e) { Log.e(TAG, "保存拍照图像错误: " + e.getMessage()); } finally { isCapturing = false; // 恢复预览(重新下发预览请求) if (cameraCaptureSession != null && captureRequestBuilder != null) { try { cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { Log.e(TAG, "恢复预览失败: " + e.getMessage()); } } } }, backgroundHandler); // 5. 保留原有拍照执行逻辑 Log.d(TAG, "停止预览"); cameraCaptureSession.stopRepeating(); Log.d(TAG, "4.下发拍照"); isCapturing = true; cameraCaptureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); Log.e(TAG, "拍照失败: " + failure.getReason()); isCapturing = false; } }, backgroundHandler); } catch (CameraAccessException | IllegalStateException | SecurityException e) { Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e); isCapturing = false; } } // ---------------------- 保留原有未修改的方法 ---------------------- // (包括:chooseOptimalSize、CompareSizesByArea、stateCallback、saveImage、onRequestPermissionsResult、startBackgroundThread、stopBackgroundThread、closeCamera、checkTakePicture) static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { Log.i(TAG, "相机已打开"); cameraDevice = camera; Log.i(TAG, "2.1 开始配置预览流"); createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice camera) { Log.w(TAG, "相机断开连接"); cameraDevice.close(); } @Override public void onError(@NonNull CameraDevice camera, int error) { Log.e(TAG, "相机错误: " + error); cameraDevice.close(); cameraDevice = null; } }; private void saveImage(byte[] bytes, File file) { Log.d(TAG, "保存图像: " + file.getAbsolutePath()); imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (imageUri != null) { try (FileOutputStream output = new FileOutputStream(file)) { output.write(bytes); Log.i(TAG, "图像保存成功, 大小: " + bytes.length + " bytes"); } catch (IOException e) { Log.e(TAG, "保存文件失败: " + e.getMessage()); } } } //触发时机:用户点击授权后调用 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.d(TAG, "权限请求结果: " + requestCode); if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults[0] == PackageManager.PERMISSION_DENIED) { Log.w(TAG, "用户拒绝相机权限"); Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show(); finish(); } else { Log.i(TAG, "用户授予相机权限"); startBackgroundThread(); openCamera(); } } } private void stopBackgroundThread() { if (backgroundThread != null) { Log.d(TAG, "停止后台线程"); backgroundThread.quitSafely(); try { backgroundThread.join(); backgroundThread = null; backgroundHandler = null; } catch (InterruptedException e) { Log.e(TAG, "停止线程失败: " + e.getMessage()); } } } private void startBackgroundThread() { if (backgroundThread == null) { backgroundThread = new HandlerThread("CameraBackground"); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); Log.d(TAG, "后台线程启动"); } } private void closeCamera() { Log.d(TAG, "关闭相机资源"); if (isCapturing) { Log.w(TAG, "正在拍照中,等待完成或取消..."); // 可以尝试等待一段时间或取消请求 try { cameraCaptureSession.abortCaptures(); // 取消所有进行中的捕获 } catch (CameraAccessException e) { throw new RuntimeException(e); } } if (cameraCaptureSession != null) { cameraCaptureSession.close(); cameraCaptureSession = null; } isSessionClosed = true; } private Size chooseOptimalSize(Size[] choices, int width, int height) { List<Size> bigEnough = new ArrayList<>(); for (Size option : choices) { float ratio = (float) option.getWidth() / option.getHeight(); float viewRatio = (float) width / height; if (Math.abs(ratio - viewRatio) <= 0.1 && option.getWidth() <= width && option.getHeight() <= height) { bigEnough.add(option); } } if (!bigEnough.isEmpty()) { return Collections.max(bigEnough, new CompareSizesByArea()); } Log.w(TAG, "未找到完美匹配尺寸,使用默认"); return choices[0]; } }package com.android.example.cameraappxjava.util; import android.graphics.ImageFormat; import android.media.Image; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * 无错误版 GLES 2.0 相机渲染器:处理 YUV_420_888 预览,彻底兼容低版本 * 核心优化:移除所有 GLES 2.0 不支持的 API,手动管理纹理尺寸 */ public class CameraGLRenderer implements GLSurfaceView.Renderer { private static final String TAG = "CameraGLRenderer"; private static final int TEXTURE_COUNT = 3; // Y/U/V 3个纹理(GLES 2.0 支持) // -------------------------- 1. GLES 2.0 兼容配置(无任何不支持API) -------------------------- /** * 顶点着色器(GLES 2.0 标准语法,必加精度声明) */ private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n" + // 顶点坐标(输入) "attribute vec2 vTexCoord;\n" + // 纹理坐标(输入) "varying vec2 texCoord;\n" + // 传递纹理坐标到片段着色器 "void main() {\n" + " gl_Position = vPosition;\n" + // 全屏顶点位置(-1~1 覆盖屏幕) " texCoord = vTexCoord;\n" + // 传递纹理坐标 "}"; /** * 片段着色器(GLES 2.0 兼容:用 GL_LUMINANCE 单通道格式,无 GL_RED) */ private static final String FRAGMENT_SHADER = "precision mediump float;\n" + // GLES 2.0 必须声明精度(中等精度平衡性能) "varying vec2 texCoord;\n" + // 从顶点着色器接收的纹理坐标 "uniform sampler2D yTex;\n" + // Y通道纹理采样器(纹理单元0) "uniform sampler2D uTex;\n" + // U通道纹理采样器(纹理单元1) "uniform sampler2D vTex;\n" + // V通道纹理采样器(纹理单元2) "void main() {\n" + // GLES 2.0 兼容:读取 GL_LUMINANCE 纹理的 r 通道(亮度值) " float y = texture2D(yTex, texCoord).r;\n" + " float u = texture2D(uTex, texCoord).r - 0.5;\n" + // U/V 偏移 0.5(YUV 标准) " float v = texture2D(vTex, texCoord).r - 0.5;\n" + // BT.601 YUV转RGB 公式(手机相机通用,避免偏色) " float r = y + 1.402 * v;\n" + " float g = y - 0.34414 * u - 0.71414 * v;\n" + " float b = y + 1.772 * u;\n" + // 限制 RGB 范围 0~1(避免颜色溢出,GLES 2.0 支持 clamp 函数) " r = clamp(r, 0.0, 1.0);\n" + " g = clamp(g, 0.0, 1.0);\n" + " b = clamp(b, 0.0, 1.0);\n" + " gl_FragColor = vec4(r, g, b, 1.0);\n" + // 输出 RGB 颜色(不透明) "}"; /** * 全屏顶点坐标(GLES 2.0 标准坐标,顺序:左上→左下→右上→右下) */ private static final float[] VERTEX_COORDS = { -1.0f, 1.0f, 0.0f, // 左上 -1.0f, -1.0f, 0.0f, // 左下 1.0f, 1.0f, 0.0f, // 右上 1.0f, -1.0f, 0.0f // 右下 }; /** * 纹理坐标(GLES 2.0 兼容,适配竖屏预览,解决画面颠倒) * 映射规则:纹理坐标 → 屏幕坐标(确保竖屏显示正常) */ private static final float[] TEX_COORDS = { 0.0f, 1.0f, // 纹理左上 → 屏幕左上 1.0f, 1.0f, // 纹理左下 → 屏幕左下 0.0f, 0.0f, // 纹理右上 → 屏幕右上 1.0f, 0.0f // 纹理右下 → 屏幕右下 }; // -------------------------- 2. 动态变量(新增:手动记录纹理尺寸,替代GL查询) -------------------------- private int mShaderProgram; // GLES 2.0 着色器程序ID private int[] mTextureIds = new int[TEXTURE_COUNT]; // Y/U/V 纹理ID(GPU资源) private FloatBuffer mVertexBuffer; // 顶点坐标缓冲区(GLES 2.0 要求Buffer格式) private FloatBuffer mTexBuffer; // 纹理坐标缓冲区(GLES 2.0 要求Buffer格式) private int mViewWidth, mViewHeight; // GLSurfaceView 宽高(渲染视口尺寸) // 关键:手动记录 Y/U/V 纹理的宽高(替代 GLES 2.0 不支持的 glGetTexLevelParameteriv) private int mYTexWidth = 0, mYTexHeight = 0; // Y纹理尺寸 private int mUTexWidth = 0, mUTexHeight = 0; // U纹理尺寸(Y的1/2) private int mVTexWidth = 0, mVTexHeight = 0; // V纹理尺寸(Y的1/2) // YUV 数据线程安全管理(避免相机线程与渲染线程竞争) private final Object mYuvLock = new Object(); private Image mPendingImage; // 待处理的相机Image(从Camera2接收) private byte[] mYData, mUData, mVData; // 提取后的 Y/U/V 字节数据 private int mYuvWidth, mYuvHeight; // 相机输出的 YUV 帧宽高 // -------------------------- 3. 对外接口(无修改,直接复用) -------------------------- /** * 设置相机预览Image(线程安全,GLES 2.0/3.0 通用) * * @param image 相机输出的 YUV_420_888 格式Image(必须关闭,避免内存泄漏) */ public void setYUVData(Image image) { if (image == null || image.getFormat() != ImageFormat.YUV_420_888) { Log.w(TAG, "无效Image:格式非 YUV_420_888 或 Image为空"); if (image != null) image.close(); // 必须关闭,避免相机缓冲区泄漏 return; } synchronized (mYuvLock) { // 先关闭之前未处理的Image(防止缓冲区堆积导致卡顿) if (mPendingImage != null) { mPendingImage.close(); Log.d(TAG, "关闭未处理的PendingImage,避免内存泄漏"); } mPendingImage = image; // 存储新的待处理Image } } /** * 释放所有资源(Activity/Fragment 销毁时调用,避免内存泄漏) */ public void release() { synchronized (mYuvLock) { // 1. 关闭待处理的Image if (mPendingImage != null) { mPendingImage.close(); mPendingImage = null; } // 2. 释放CPU端 YUV 数据 mYData = null; mUData = null; mVData = null; mYuvWidth = 0; mYuvHeight = 0; // 3. 重置手动记录的纹理尺寸 mYTexWidth = mYTexHeight = 0; mUTexWidth = mUTexHeight = 0; mVTexWidth = mVTexHeight = 0; } // 4. 释放 GLES 2.0 GPU 资源(纹理+着色器程序) if (mTextureIds != null) { GLES20.glDeleteTextures(TEXTURE_COUNT, mTextureIds, 0); mTextureIds = null; } if (mShaderProgram != 0) { GLES20.glDeleteProgram(mShaderProgram); mShaderProgram = 0; } // 5. 释放缓冲区(帮助GC回收) mVertexBuffer = null; mTexBuffer = null; Log.d(TAG, "所有资源释放完成(GLES 2.0 兼容)"); } // -------------------------- 4. GLES 2.0 生命周期回调(无任何不支持API) -------------------------- /** * 初始化回调:GLSurfaceView 首次创建时调用(仅1次) * 作用:初始化OpenGL环境、编译着色器、创建纹理、准备坐标缓冲区 */ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.d(TAG, "onSurfaceCreated(GLES 2.0):初始化OpenGL环境"); // GLES 2.0 基础配置:禁用混合(避免透明层干扰预览)、黑色背景 GLES20.glDisable(GLES20.GL_BLEND); GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 准备坐标缓冲区(GLES 2.0 仅支持 Buffer 格式,不支持直接用数组) mVertexBuffer = createFloatBuffer(VERTEX_COORDS); mTexBuffer = createFloatBuffer(TEX_COORDS); // 编译 GLES 2.0 着色器程序(创建渲染"画笔") mShaderProgram = compileShaderProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (mShaderProgram == 0) { Log.e(TAG, "着色器程序创建失败(GLES 2.0),预览不可用"); return; } // 创建 Y/U/V 3个纹理(GLES 2.0 2D纹理),配置基础参数 GLES20.glGenTextures(TEXTURE_COUNT, mTextureIds, 0); initTexture(mTextureIds[0]); // 初始化 Y 纹理 initTexture(mTextureIds[1]); // 初始化 U 纹理 initTexture(mTextureIds[2]); // 初始化 V 纹理 Log.d(TAG, "GLES 2.0 初始化完成,纹理ID:Y=" + mTextureIds[0] + ", U=" + mTextureIds[1] + ", V=" + mTextureIds[2]); } /** * 尺寸变化回调:GLSurfaceView 宽高改变时调用(如屏幕旋转) * 作用:设置渲染视口(画面显示范围),确保全屏渲染 */ @Override public void onSurfaceChanged(GL10 gl, int width, int height) { mViewWidth = width; mViewHeight = height; // GLES 2.0 设置视口:渲染范围 = GLSurfaceView 全屏(左上角(0,0),宽高=View宽高) GLES20.glViewport(0, 0, width, height); Log.d(TAG, "onSurfaceChanged(GLES 2.0):视口尺寸=" + width + "x" + height); } /** * 帧渲染回调:每帧调用1次(渲染线程执行,核心渲染逻辑) * 流程:处理待处理Image → 上传YUV数据到纹理 → 绑定着色器 → 执行渲染 */ @Override public void onDrawFrame(GL10 gl) { // 1. 处理待处理的Image(线程安全,提取Y/U/V数据) boolean hasNewData = processPendingImage(); if (!hasNewData) { // 无新数据:清除屏幕为黑色,避免显示上一帧残留 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); return; } // 2. 清除上一帧画面(避免画面重叠) GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // 3. 激活 GLES 2.0 着色器程序(使用"画笔") GLES20.glUseProgram(mShaderProgram); // 4. 上传 Y/U/V 数据到对应纹理(手动判断纹理尺寸,替代GL查询) uploadTexture(mTextureIds[0], mYData, mYuvWidth, mYuvHeight, true); // Y纹理 uploadTexture(mTextureIds[1], mUData, mYuvWidth / 2, mYuvHeight / 2, false); // U纹理(1/2尺寸) uploadTexture(mTextureIds[2], mVData, mYuvWidth / 2, mYuvHeight / 2, false); // V纹理(1/2尺寸) // 5. 绑定纹理到着色器采样器(让"画笔"找到"画布") bindTextureToSampler(); // 6. 传递顶点/纹理坐标(告诉"画笔"画在哪里) passVertexAndTexCoord(); // 7. 执行渲染:GLES 2.0 支持 GL_TRIANGLE_STRIP,4个顶点画2个三角形覆盖全屏 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COORDS.length / 3); // 8. 禁用顶点/纹理坐标输入(避免后续渲染干扰) int vPositionLoc = GLES20.glGetAttribLocation(mShaderProgram, "vPosition"); int vTexCoordLoc = GLES20.glGetAttribLocation(mShaderProgram, "vTexCoord"); GLES20.glDisableVertexAttribArray(vPositionLoc); GLES20.glDisableVertexAttribArray(vTexCoordLoc); } private void passVertexAndTexCoord() { int vPositionLoc = GLES20.glGetAttribLocation(mShaderProgram, "vPosition"); GLES20.glEnableVertexAttribArray(vPositionLoc); GLES20.glVertexAttribPointer( vPositionLoc, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer ); int vTexCoordLoc = GLES20.glGetAttribLocation(mShaderProgram, "vTexCoord"); GLES20.glEnableVertexAttribArray(vTexCoordLoc); GLES20.glVertexAttribPointer( vTexCoordLoc, 2, GLES20.GL_FLOAT, false, 2 * 4, mTexBuffer ); } // -------------------------- 5. GLES 2.0 辅助方法(无任何不支持API) -------------------------- /** * 创建 FloatBuffer:将 Java float 数组转为 GLES 2.0 支持的 Buffer 格式 * * @param array 原始 float 数组(顶点/纹理坐标) * @return GLES 2.0 可识别的 FloatBuffer */ private FloatBuffer createFloatBuffer(float[] array) { if (array == null || array.length == 0) return null; // 1. 分配直接内存(避免JVM GC移动,提升OpenGL访问效率) ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length * 4); // 1float=4字节 // 2. 设置字节序(必须与硬件一致,否则数据错乱) byteBuffer.order(ByteOrder.nativeOrder()); // 3. 转换为 FloatBuffer 并写入数据 FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); floatBuffer.put(array); // 4. 重置读指针(从缓冲区开头开始读取) floatBuffer.position(0); return floatBuffer; } /** * 编译 GLES 2.0 着色器程序:编译顶点+片段着色器,链接为可执行程序 * * @param vertexCode 顶点着色器代码 * @param fragmentCode 片段着色器代码 * @return 着色器程序ID(0 表示失败) */ private int compileShaderProgram(String vertexCode, String fragmentCode) { // 1. 编译顶点着色器(GLES 2.0) int vertexShader = compileSingleShader(GLES20.GL_VERTEX_SHADER, vertexCode); if (vertexShader == 0) return 0; // 2. 编译片段着色器(GLES 2.0) int fragmentShader = compileSingleShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode); if (fragmentShader == 0) { GLES20.glDeleteShader(vertexShader); // 清理已编译的顶点着色器 return 0; } // 3. 链接着色器程序(GLES 2.0) int program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vertexShader); // 绑定顶点着色器 GLES20.glAttachShader(program, fragmentShader); // 绑定片段着色器 GLES20.glLinkProgram(program); // 执行链接 // 4. 检查链接结果(GLES 2.0) int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, "着色器链接失败(GLES 2.0):" + GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); // 清理无效程序 program = 0; } // 5. 清理中间着色器(程序已链接,单个着色器可删除) GLES20.glDeleteShader(vertexShader); GLES20.glDeleteShader(fragmentShader); return program; } /** * 编译单个 GLES 2.0 着色器:编译顶点/片段着色器代码 * * @param shaderType 着色器类型(GL_VERTEX_SHADER / GL_FRAGMENT_SHADER) * @param shaderCode 着色器代码 * @return 着色器ID(0 表示失败) */ private int compileSingleShader(int shaderType, String shaderCode) { // 1. 创建着色器对象(GLES 2.0) int shader = GLES20.glCreateShader(shaderType); if (shader == 0) { Log.e(TAG, "创建着色器失败(GLES 2.0),类型=" + (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段")); return 0; } // 2. 绑定着色器代码并编译(GLES 2.0) GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); // 3. 检查编译结果(GLES 2.0) int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段") + "着色器编译失败(GLES 2.0):" + GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); // 清理无效着色器 shader = 0; } return shader; } /** * 初始化 GLES 2.0 纹理参数:配置过滤、边缘处理,确保画面清晰无重复 * * @param textureId 纹理ID(Y/U/V 纹理) */ private void initTexture(int textureId) { if (textureId == 0) return; // 绑定纹理(选中GPU"画布",GLES 2.0 必须先绑定再配置) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 1. 纹理过滤:缩小时线性过滤(画面平滑,避免锯齿) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); // 2. 纹理过滤:放大时线性过滤(画面平滑,避免像素块) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); // 3. 纹理边缘:水平方向超出范围时"夹紧"(不重复显示,避免边缘错乱) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); // 4. 纹理边缘:垂直方向超出范围时"夹紧" GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); // 解绑纹理(避免后续误操作其他纹理) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 处理待处理Image:从 mPendingImage 提取 Y/U/V 数据(线程安全) * * @return true=有新数据,false=无新数据 */ private boolean processPendingImage() { Image image = null; synchronized (mYuvLock) { if (mPendingImage == null) { return false; // 无待处理数据 } // 取出待处理Image(释放锁,避免长时间占用) image = mPendingImage; mPendingImage = null; } try { // 1. 提取Image的宽高和Planes(YUV_420_888 格式固定3个Planes) mYuvWidth = image.getWidth(); mYuvHeight = image.getHeight(); Image.Plane[] planes = image.getPlanes(); if (planes.length < 3) { Log.e(TAG, "Image Planes 数量不足3,无法提取 YUV 数据"); return false; } // 2. 提取 Y 通道数据(Plane[0]:Y通道,无交错) ByteBuffer yBuffer = planes[0].getBuffer(); mYData = byteBufferToByteArray(yBuffer); // 3. 提取 U/V 通道数据(区分 Semi-Planar 和 Planar 模式) if (planes[1].getPixelStride() == 2) { // 模式1:Semi-Planar(UV 交错存储在 Plane[1],Plane[2] 无数据) ByteBuffer uvBuffer = planes[1].getBuffer(); int uvLength = uvBuffer.remaining() / 2; // UV 总长度 = Y 长度 / 2 mUData = new byte[uvLength]; mVData = new byte[uvLength]; // 提取 U(偶数索引)和 V(奇数索引) for (int i = 0; i < uvLength; i++) { mUData[i] = uvBuffer.get(i * 2); // U:第0、2、4...字节 mVData[i] = uvBuffer.get(i * 2 + 1); // V:第1、3、5...字节 } } else { // 模式2:Planar(UV 分别存储在 Plane[1] 和 Plane[2],无交错) ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); mUData = byteBufferToByteArray(uBuffer); mVData = byteBufferToByteArray(vBuffer); } // 4. 验证 YUV 数据长度(避免后续渲染错误) int expectedYLength = mYuvWidth * mYuvHeight; int expectedUVLength = (mYuvWidth / 2) * (mYuvHeight / 2); if (mYData.length != expectedYLength || mUData.length != expectedUVLength || mVData.length != expectedUVLength) { Log.w(TAG, "YUV 数据长度不匹配,重置为正确长度"); mYData = new byte[expectedYLength]; mUData = new byte[expectedUVLength]; mVData = new byte[expectedUVLength]; return false; } Log.d(TAG, "处理 Image 完成(GLES 2.0):YUV 尺寸=" + mYuvWidth + "x" + mYuvHeight + ",数据长度 Y=" + mYData.length + ", U=" + mUData.length); return true; } catch (Exception e) { Log.e(TAG, "处理 Image 异常(GLES 2.0):" + e.getMessage(), e); return false; } finally { // 必须关闭 Image(释放相机缓冲区,避免卡顿的核心!) if (image != null) { image.close(); } } } private byte[] byteBufferToByteArray(ByteBuffer buffer) { if (buffer == null | buffer.remaining() == 0) return new byte[0]; int originalPos = buffer.position(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); buffer.position(originalPos); return data; } /** * 上传数据到 GLES 2.0 纹理(核心:手动记录纹理尺寸,替代 GL 查询) * * @param textureId 纹理ID * @param data 待上传的字节数据(Y/U/V) * @param width 纹理宽度 * @param height 纹理高度 * @param isYTexture 是否为 Y 纹理(用于区分尺寸记录变量) */ private void uploadTexture(int textureId, byte[] data, int width, int height, boolean isYTexture) { if (textureId == 0 || data == null || width <= 0 || height <= 0) { Log.w(TAG, "上传纹理参数无效(GLES 2.0):textureId=" + textureId + ", width=" + width + ", height=" + height); return; } // 绑定纹理(GLES 2.0 必须先绑定再操作) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); // 关键:设置像素对齐为 1(YUV 数据无字节对齐,避免数据错位) GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); // 手动判断纹理是否已创建(替代 GLES 2.0 不支持的 glGetTexLevelParameteriv) boolean isTextureCreated = false; if (isYTexture) { isTextureCreated = (mYTexWidth == width && mYTexHeight == height); } else { // U/V 纹理尺寸相同,共用一套判断 isTextureCreated = (mUTexWidth == width && mUTexHeight == height); } ByteBuffer dataBuffer = ByteBuffer.wrap(data); if (!isTextureCreated) { // 首次创建纹理:调用 glTexImage2D(分配GPU内存) GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, // 2D纹理,基础层级(固定为0) GLES20.GL_LUMINANCE, // GLES 2.0 核心:单通道亮度格式 width, height, 0, // 纹理宽高,边界宽度(必须为0) GLES20.GL_LUMINANCE, // 数据格式:与内部格式一致 GLES20.GL_UNSIGNED_BYTE, // 数据类型:无符号字节(YUV 数据类型) dataBuffer // 待上传的 Y/U/V 数据 ); // 更新手动记录的纹理尺寸(下次判断用) if (isYTexture) { mYTexWidth = width; mYTexHeight = height; Log.d(TAG, "创建 Y 纹理(GLES 2.0):尺寸=" + width + "x" + height); } else { mUTexWidth = width; mUTexHeight = height; Log.d(TAG, "创建 U/V 纹理(GLES 2.0):尺寸=" + width + "x" + height); } } else { // 复用纹理:调用 glTexSubImage2D(仅更新数据,不重新分配GPU内存,效率更高) GLES20.glTexSubImage2D( GLES20.GL_TEXTURE_2D, 0, // 2D纹理,基础层级 0, 0, // 数据起始坐标(x=0, y=0,全屏更新) width, height, // 数据宽高(与纹理尺寸一致) GLES20.GL_LUMINANCE, // 数据格式:与创建时一致 GLES20.GL_UNSIGNED_BYTE, // 数据类型:与创建时一致 dataBuffer // 待更新的 Y/U/V 数据 ); } // 解绑纹理(避免后续误操作) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } /** * 绑定纹理到 GLES 2.0 着色器采样器:将 Y/U/V 纹理与着色器的 uniform 变量关联 */ private void bindTextureToSampler() { // 1. 绑定 Y 纹理到采样器 yTex(纹理单元0) GLES20.glActiveTexture(GLES20.GL_TEXTURE0); // 激活纹理单元0(GLES 2.0 必须先激活) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0]); // 绑定 Y 纹理 // 关联采样器:将纹理单元0 与 着色器的 yTex 变量绑定 int yTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "yTex"); GLES20.glUniform1i(yTexLoc, 0); // 2. 绑定 U 纹理到采样器 uTex(纹理单元1) GLES20.glActiveTexture(GLES20.GL_TEXTURE1); // 激活纹理单元1 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1]); // 绑定 U 纹理 int uTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "uTex"); GLES20.glUniform1i(uTexLoc, 1); // 3. 绑定 V 纹理到采样器 vTex(纹理单元2) GLES20.glActiveTexture(GLES20.GL_TEXTURE2); //激活纹理单元2 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2]); int vTexLoc = GLES20.glGetUniformLocation(mShaderProgram, "vTex"); GLES20.glUniform1i(vTexLoc, 2); // 添加错误检查 if (yTexLoc == -1 || uTexLoc == -1 || vTexLoc == -1) { Log.e(TAG, "纹理采样器绑定失败: " + "yTex=" + yTexLoc + " uTex=" + uTexLoc + " vTex=" + vTexLoc); } } } 能够正常存储图片,但是预览黑屏不能显示
09-21
同上你看看我这个类还有什么没有删 或者还有什么没有加的import uiBase from "./uiBase.js"; import { Container, Graphics, Text, Assets, Sprite, Texture } from "pixi.js"; import gameData from "../Frame/gameDataMgr.js"; import networkMgr from "../Frame/networkMgr.js"; import { UIManager } from "../Frame/uiManager.js"; import { BlackMaskBg } from "../Frame/CommonUI/blackMaskBg"; import { LocalSprite } from "../Frame/CommonUI/LocalSprite"; import { UIBankData } from "../UIView/uiBankData.js"; class uiShop extends uiBase { mainContainer; // 主界面容器 blockBg; // 背景遮罩 shopBg3Container; // 弹窗容器 shopBgButton; // 按钮 buttonText; // 按钮文字 closeButton; // 叉叉按钮 introduceText; // 介绍背景 isInist = false; isPivotInitialized = false; CONTAINER_WIDTH = 650; CONTAINER_HEIGHT = 400; shopGoodsList = []; _rpLabel = gameData.currencySign; // 使用动态货币符号 // 记录当前选中的索引和金额值 selectedIndex = -1; // 当前选中的金额框索引 selectedAmountValue = 0; // 当前选中的金额数值 selectedStarCount = 0; // 当前赠送的星数 id = 0; // 当前选中的id inputDisplayText; // 显示选中金额的文本 rechargeYes; // 可点击亮色图 rechargeNo; // 不可点击灰色图 depositHistory; // 历史记录按钮 depositHistoryText; // 历史记录文本 // =====银行选择弹窗容器 ===== bankPopupContainer; // 充值支付银行弹窗容器 bankList = []; // 银行列表数据 bankSprites = []; // 银行图标精灵数组 bankShineSprites = []; // 银行图标发光边框数组 selectedBankIndex = -1; // 当前选中的银行索引 selectedBankData = null; // 当前选中的银行数据 bankConfirmYes; // 银行确认按钮亮色 bankConfirmNo; // 银行确认按钮灰色 // =====支付成功展示 ===== paymentSuccessContainer; // 支付成功展示容器 paymentAmountText; // 支付金额文本 paymentConfirmButton; // 支付成功确认按钮 scrollContainer; //创建滚动容器 // =====历史记录弹窗容器 ===== historyListContainer; recordList = []; historyTextObjects = []; // 用于存储每条记录的所有文本对象(因为要每条记录都参与本地化) bank=null; async init() { super.init(); // 创建主界面容器 this.mainContainer = new Container(); this.mainContainer.position.set(330, 210); this.container.addChild(this.mainContainer); // 预加载资源 await Assets.load([ "assets/UIShop/+.png", "assets/UIShop/复制.png", "assets/UIShop/商店底1_1.png", "assets/UIShop/商店底2.png", "assets/UIShop/商店底3.png", "assets/UIShop/赠送底.png", "assets/UIShop/赠送金额底.png", "assets/UIShop/UI1_02.png", "assets/UIShop/UI1_03.png", "assets/UIShop/UI1_05.png", "assets/UIShop/UI1_07.png", "assets/UIShop/UI1_09_2.png", "assets/UIShop/UI1_09.png", "assets/UIShop/UI1_11.png", "assets/UIShop/UI1_14.png", "assets/UIShop/UI1_17.png", "assets/UIShop/UI1_22.png", "assets/UIShop/商店底4.png", "assets/UIShop/UI8_6.png", "assets/UIShop/WithdrawalBankIcon_DANA.png", "assets/UIShop/WithdrawalBankIcon_OVO.png", "assets/UIShop/WithdrawalBankIcon_QRIS.png", "assets/UIShop/WithdrawalBankIcon_BNI.png", "assets/UIShop/WithdrawalBankIcon_BRI.png", "assets/UIShop/WithdrawalBankIcon_ALLIANCE.png", "assets/UIShop/WithdrawalBankIcon_BCA.png", "assets/UIShop/WithdrawalBankIcon_BIMB.png", "assets/UIShop/商店底5.png", "assets/UIShop/复制.png", "assets/UIShop/商店底1_12.png", ]); // 获取商店数据 await this.SendShopData(); // === 主界面内容 === // 创建主界面后面黑幕 const app = this.app; this.overallWidth = app.screen.width; this.overallHeight = app.screen.height; const opacity = 0.6; //黑幕 const blackMask = new Graphics(); blackMask.beginFill(0x000000, opacity); blackMask.drawRect(-this.overallWidth * 0.51, -this.overallHeight * 0.53, this.overallWidth*1.13, this.overallHeight); blackMask.endFill(); this.mainContainer.addChild(blackMask); //黑幕 //let blackMask = new BlackMaskBg({ parent: this.mainContainer }); const shopBg2 = new Sprite(Texture.from("assets/UIShop/商店底1_12.png")); shopBg2.width = this.overallWidth; shopBg2.height = this.overallHeight; const offsetX = -this.overallWidth * 0.5; // 向左偏移整体宽度的 50% const offsetY = -this.overallHeight * 0.53; // 向上偏移高度的 20% shopBg2.position.set(offsetX, offsetY); this.closeButton = new Sprite(Texture.from("assets/UIShop/UI1_03.png")); //主界面叉叉按钮 this.closeButton.width = 40; this.closeButton.height = 40; this.closeButton.anchor.set(1, 0); this.closeButton.position.set(this.overallWidth/2-10, -this.overallHeight/2); this.closeButton.eventMode = "static"; this.closeButton.cursor = "pointer"; this.closeButton.on("pointerdown", () => this.hide()); const textX = shopBg2.position.x + shopBg2.width * 0.5; const textY = shopBg2.position.y+10; // 主界面装饰文本 this.depositText = Object.assign( new Text("存款deposit", { fontFamily: "Arial", fontSize: 35, fill: 0xffff84, align: "center", stroke: "#FFA500", strokeThickness: 2, fontWeight: "bold", }), { anchor: { x: 0.5, y: 0 }, position: { x: textX, y: textY } }, ); this.topUpAmountText = Object.assign( new Text("haha", { fontFamily: "Arial", fontSize: 14, fill: 0xcf9f6c, align: "center", fontWeight: "bold", }), { anchor: { x: 0.5, y: 0 },position: { x: this.overallWidth * -0.15, y:this.overallHeight* -0.3 } }, ); this.inputAmountText = Object.assign( new Text("haha", { fontFamily: "Arial", fontSize: 14.5, fill: 0xd37744, align: "center", }), { anchor: { x: 0.5, y: 0 }, position: { x: this.overallWidth * -0.12, y:this.overallHeight* -0.23 } }, ); const inputBox = new Sprite(Texture.from("assets/UIShop/UI1_02.png")); // 输入框图 inputBox.width *= this.app.rat; inputBox.height *= this.app.rat; inputBox.anchor.set(0.5, 0.5); inputBox.position.set( this.overallWidth * -0.10+5, this.overallHeight* -0.21, ); // 显示选中金额的文本 this.inputDisplayText = new Text("", { fontSize: 18, fill: 0xb18b5b, fontFamily: "Arial", fontWeight: "bold", }); this.inputDisplayText.anchor.set(0.5); this.inputDisplayText.position.set( this.overallWidth * -0.15, this.overallHeight* -0.21, ); this.inputDisplayText.visible = false; // 初始不显示 const clearInput = new Sprite(Texture.from("assets/UIShop/UI1_17.png")); // 输入框里的叉叉 clearInput.width *= this.app.rat+0.2; clearInput.height *= this.app.rat+0.1; clearInput.anchor.set(0.5, 0.5); clearInput.position.set( this.overallWidth * 0.07, this.overallHeight* -0.21, ); clearInput.eventMode = "static"; clearInput.cursor = "pointer"; // 绑定叉叉点击事件:清除输入框中的金额 clearInput.on("pointerdown", () => { if (this.inputDisplayText.visible) { this.clearSelection(); // 清除状态 } }); const addPicture = new Sprite(Texture.from("assets/UIShop/+.png")); // 加号图 addPicture.width *= this.app.rat; addPicture.height *= this.app.rat; addPicture.anchor.set(0.5, 0.5); addPicture.position.set( this.overallWidth * 0.15, this.overallHeight* -0.21, ); const givePicture = new Sprite( Texture.from("assets/UIShop/赠送金额底.png"), ); // 赠送金额底 givePicture.width *= this.app.rat; givePicture.height *= this.app.rat; givePicture.anchor.set(0.5, 0.5); givePicture.position.set( this.overallWidth * 0.29, this.overallHeight* -0.21, ); const givePicture2 = new Sprite(Texture.from("assets/UIShop/赠送底.png")); // 赠送底 givePicture2.width *= this.app.rat; givePicture2.height *= this.app.rat; givePicture2.anchor.set(0.5, 0.5); givePicture2.position.set( this.overallWidth * 0.29, this.overallHeight* -0.21, ); this.freeAmountText = Object.assign( new Text("haha", { fontFamily: "Arial", fontSize: 11, fill: 0xcf9f6c, align: "center", fontWeight: "bold", }), { anchor: { x: 0.5, y: 0.5 }, position: { x: this.overallWidth * 0.29, y:this.overallHeight* -0.26 } }, ); this.rpText = Object.assign( new Text("haha", { fontFamily: "Arial", fontSize: 25, fill: 0xffffff, align: "center", fontWeight: "bold", }), { anchor: { x: 0.5, y: 0.5 }, position: { x: this.overallWidth * 0.28, y:this.overallHeight* -0.18 } }, ); this.selectAmountText = Object.assign( new Text("haha", { fontFamily: "Arial", fontSize: 15, fill: 0xcf9f6c, align: "center", fontWeight: "bold", }), { anchor: { x: 0.5, y: 0.5 }, position: { x: this.overallWidth * -0.1, y:this.overallHeight* -0.12 } }, ); //添加到舞台 画面上 this.mainContainer.addChild( shopBg2, // shopBg1, this.closeButton, inputBox, clearInput, addPicture, givePicture, givePicture2, ); this.mainContainer.addChild( this.depositText, this.topUpAmountText, this.inputAmountText, this.freeAmountText, this.rpText, this.selectAmountText, this.inputDisplayText, ); console.log("看一下有没有赋值进去~~~", this.shopGoodsList); // 创建金额框图8个、小红角星8张、发光边框8个 this.amountSprites = []; // 存储所有金额图的数组 this.starSprites = []; // 存储所有五角星的数组 this.shineSprites = []; // 存储所有发光边框的数组 this.selectedIndex = -1; // 当前选中的索引,-1表示没有选中 // === 创建滚动容器 === this.scrollContainer = new Container(); this.scrollContainer.x = 0; this.scrollContainer.y = 0; this.mainContainer.addChild(this.scrollContainer); // === 设置滚动参数 === const visibleRows = 2; // 显示2排 const colsPerRow = 4; // 每排3个 const totalRows = Math.ceil(this.shopGoodsList.length / colsPerRow); // 总排数 const scrollableHeight = (totalRows - visibleRows) * 60; // 可滚动的高度 // === 设置遮罩,限制显示区域 === const mask = new Graphics(); mask.beginFill(0x000000, opacity); mask.drawRect(-this.overallWidth * 0.51, -this.overallHeight * 0.53, this.overallWidth*1.13, this.overallHeight); mask.endFill(); this.scrollContainer.mask = mask; this.mainContainer.addChild(mask); // 调整金额框的间距参数 const startX = this.overallWidth * -0.2; // 起始X坐标 const startY = this.overallHeight* 0.01; // 起始Y坐标 const spacingX = 100; // X轴间距(左右间隔) const spacingY = 60; // Y轴间距(上下间隔) for (let i = 0; i < this.shopGoodsList.length; i++) { // 1. 创建发光边框图 const shine = new Sprite(Texture.from("assets/UIShop/UI1_11.png")); shine.width *= this.app.rat; shine.height *= this.app.rat; shine.anchor.set(0.5, 0.5); shine.visible = false; // 初始隐藏,只有选中时才显示 // 2. 创建金额框图 const amount = new Sprite(Texture.from("assets/UIShop/UI1_22.png")); amount.width *= this.app.rat; amount.height *= this.app.rat; amount.anchor.set(0.5, 0.5); amount.interactive = true; // 启用点击交互 amount.buttonMode = true; // 鼠标移到上面变成手型 // 3. 创建小红角星图 const star = new Sprite(Texture.from("assets/UIShop/UI1_14.png")); star.width *= this.app.rat; star.height *= this.app.rat; star.anchor.set(0.5, 0.5); // 4. 创建文本显示 - 金额 const amountText = new Text("", { fontSize: 15, fill: 0xff7e36, // 橘色 fontFamily: "Arial", }); amountText.anchor.set(0.5, 0.5); amountText.interactive = true; // 启用点击交互 amountText.buttonMode = true; // 鼠标移到上面变成手型 amountText.cursor = "pointer"; // 设置鼠标指针为手型 // 5. 创建文本显示 - 星星 const starText = new Text("", { fontSize: 11, fill: 0xffffff, // 白色 fontWeight: "normal", fontFamily: "Arial, sans-serif", }); starText.anchor.set(0.5, 0.5); starText.interactive = true; // 启用点击交互 starText.buttonMode = true; // 鼠标移到上面变成手型 starText.cursor = "pointer"; // 设置鼠标指针为手型 // 计算位置(每排3个,多排) const row = Math.floor(i / colsPerRow); const col = i % colsPerRow; const xPos = startX + col * spacingX; const yPos = startY + row * spacingY; // 设置所有元素的位置 shine.position.set(xPos, yPos); // 发光边框在底层 amount.position.set(xPos, yPos); // 金额框在中间 star.position.set(xPos + 18, yPos - 22); // 五角星在金额框右上方 amountText.position.set(xPos, yPos + 2); // 金额文本在金额框中心偏下 starText.position.set(xPos + 18, yPos - 25); // 星星数量文本在五角星中心 // shopGoodsList获取数据 let item, extraItem, count, starCount, id; if (this.shopGoodsList && this.shopGoodsList[i]) { item = this.shopGoodsList[i].items[0]; extraItem = this.shopGoodsList[i].extraItems[0]; count = item ? item.count : 0; starCount = extraItem ? extraItem.count : 0; id = this.shopGoodsList[i].id; } else { count = 0; starCount = 0; id = 0; } // 设置金额文本 const label = this._rpLabel; amountText.text = count > 0 ? `${label} ${count}` : "0"; // 只有当starCount大于0时才显示星星图片和文本 if (starCount > 0) { starText.text = `+${starCount}`; star.visible = true; starText.visible = true; star.interactive = true; star.buttonMode = true; } else { starText.text = ""; star.visible = false; starText.visible = false; star.interactive = false; star.buttonMode = false; } // 创建点击处理函数 const handleItemClick = () => { if (this.bankPopupContainer && this.bankPopupContainer.visible) { // 如果支付弹窗已显示 return; } // 先隐藏所有发光边框 for (let j = 0; j < this.shineSprites.length; j++) { this.shineSprites[j].visible = false; } // 显示当前点击的发光边框 this.shineSprites[i].visible = true; this.selectedIndex = i; this.selectedAmountValue = count; this.selectedStarCount = starCount; this.id = id; this.inputDisplayText.text = `${label} ${count}`; //// 输入框显示为"货币" this.inputDisplayText.visible = true; this.inputAmountText.visible = false; // 隐藏提示文字 // rpText 显示为纯数字 this.rpText.text = `${this._rpLabel} ${this.formatNumberWithUnit(starCount)}`; console.log( "选中了第", i + 1, "个金额图,金额:", count, "赠送星:", starCount, "id:", id, ); // 选中后切换为亮色可点击按钮 if (this.rechargeYes && this.rechargeNo) { this.rechargeYes.visible = true; this.rechargeNo.visible = false; this.rechargeYes.eventMode = "static"; this.rechargeYes.cursor = "pointer"; } }; // 给金额图添加点击事件 amount.on("pointerdown", handleItemClick); // 给金额文本添加点击事件 amountText.on("pointerdown", handleItemClick); // 当starCount大于0时,才给星星图片和文本添加点击事件 if (starCount > 0) { star.on("pointerdown", handleItemClick); starText.on("pointerdown", handleItemClick); } // 将创建的元素存储到数组 this.amountSprites.push(amount); this.starSprites.push(star); this.shineSprites.push(shine); // 按层级顺序添加到滚动容器(从下到上) this.scrollContainer.addChild(amount); // 底层:金额图 this.scrollContainer.addChild(shine); // 中间层:发光边框 // 只有当starCount大于0时,才添加星星图片和文本到容器 if (starCount > 0) { this.scrollContainer.addChild(star); // 上层:五角星 this.scrollContainer.addChild(starText); // 星星数量文本 } this.scrollContainer.addChild(amountText); // 金额文本 } // === 添加滚动功能 === if (scrollableHeight > 0) { // 只有需要滚动时才添加滚动功能 let isDragging = false; let lastY = 0; let currentScrollY = 0; // 鼠标/触摸按下事件 this.scrollContainer.interactive = true; this.scrollContainer.on("mousedown", (event) => { isDragging = true; lastY = event.data.global.y; }); this.scrollContainer.on("touchstart", (event) => { isDragging = true; lastY = event.data.global.y; }); // 鼠标/触摸移动事件 this.scrollContainer.on("mousemove", (event) => { if (isDragging) { const deltaY = event.data.global.y - lastY; lastY = event.data.global.y; // 更新滚动位置,限制在可滚动范围内 currentScrollY += deltaY; currentScrollY = Math.max( -scrollableHeight, Math.min(0, currentScrollY), ); this.scrollContainer.y = currentScrollY; } }); this.scrollContainer.on("touchmove", (event) => { if (isDragging) { const deltaY = event.data.global.y - lastY; lastY = event.data.global.y; // 更新滚动位置,限制在可滚动范围内 currentScrollY += deltaY; currentScrollY = Math.max( -scrollableHeight, Math.min(0, currentScrollY), ); this.scrollContainer.y = currentScrollY; } }); // 鼠标/触摸释放事件 this.scrollContainer.on("mouseup", () => { isDragging = false; }); this.scrollContainer.on("touchend", () => { isDragging = false; }); this.scrollContainer.on("mouseupoutside", () => { isDragging = false; }); this.scrollContainer.on("touchendoutside", () => { isDragging = false; }); // 鼠标滚轮事件 this.scrollContainer.on("wheel", (event) => { const scrollAmount = event.deltaY > 0 ? 30 : -30; // 滚轮滚动量 // 更新滚动位置,限制在可滚动范围内 currentScrollY += scrollAmount; currentScrollY = Math.max( -scrollableHeight, Math.min(0, currentScrollY), ); this.scrollContainer.y = currentScrollY; }); } // === 充值按钮:亮色(可点)与灰色(不可点)切换 === const rechargeYes = new Sprite(Texture.from("assets/UIShop/UI1_09.png")); // 可点击亮色图 rechargeYes.width *= this.app.rat; rechargeYes.height *= this.app.rat; rechargeYes.anchor.set(0.5, 0.5); rechargeYes.position.set(-20, 140); rechargeYes.visible = false; // 初始隐藏 rechargeYes.eventMode = "static"; // 启用交互 rechargeYes.cursor = "pointer"; const rechargeNo = new Sprite(Texture.from("assets/UIShop/UI1_09_2.png")); // 不可点击灰色图 rechargeNo.width *= this.app.rat; rechargeNo.height *= this.app.rat; rechargeNo.anchor.set(0.5, 0.5); rechargeNo.position.set(-20, 140); rechargeNo.visible = true; // 初始显示 rechargeNo.eventMode = "none"; rechargeNo.cursor = "default"; // 创建按钮文字 - 保持你的本地化逻辑 this.depositText2 = Object.assign( new Text("嘟嘟嘟", { fontFamily: "Arial", fontSize: 25, fill: 0xffffff, align: "center", stroke: "#FFA500", }), { anchor: { x: 0.5, y: 0.5 } } ); // 灰色按钮的文字 const depositTextNo = Object.assign( new Text("嘟嘟嘟", { fontFamily: "Arial", fontSize: 25, fill: 0xcccccc, // 灰色文字 align: "center", }), { anchor: { x: 0.5, y: 0.5 } } ); this.depositText2.position.set(0, -5); // 相对于亮色按钮的位置 depositTextNo.position.set(0, -5); // 相对于灰色按钮的位置 rechargeYes.addChild(this.depositText2); rechargeNo.addChild(depositTextNo); // 添加到主容器 this.mainContainer.addChild(rechargeYes, rechargeNo); this.rechargeYes = rechargeYes; this.rechargeNo = rechargeNo; // 保存灰色按钮文字引用 this.depositTextNo = depositTextNo; // 绑定点击事件 rechargeYes.on("pointerdown", async () => { // 先检查是否选择了金额 if (this.selectedIndex === -1) return; // 设置回调:当用户在银行弹窗点击【确认】时执行的操作 UIBankData.onPaymentRequest = async (selectedBank) => { // 执行支付请求 await this.sendPaymentRequest(selectedBank); // 关闭银行弹窗 UIBankData.hide(); }; // 显示银行弹窗(传入当前商品 ID) await UIBankData.showForGoods(this.id); }); // =======支付成功展示容器开始========= this.paymentSuccessContainer = new Container(); this.paymentSuccessContainer.visible = false; // 初始隐藏 this.paymentSuccessContainer.zIndex = 200; // 高层级 this.paymentSuccessContainer.eventMode = "static"; // 启用交互 // 创建全屏透明背景 const successOverlay = new Graphics(); successOverlay.beginFill(0x000000, 0.01); // 透明 successOverlay.drawRect(-375, -667, 750, 1334); successOverlay.endFill(); this.paymentSuccessContainer.addChild(successOverlay); // 创建支付金额文本 this.paymentAmountText = new Text("", { fontFamily: "Arial", fontSize: 60, fill: 0xffff00, // 黄色 align: "center", fontWeight: "bold", stroke: "#000000", strokeThickness: 4, }); this.paymentAmountText.anchor.set(0.5); this.paymentAmountText.position.set(0, 0); this.paymentSuccessContainer.addChild(this.paymentAmountText); // 创建确认按钮 this.createPaymentConfirmButton(); // 点击任意位置隐藏支付成功展示 this.paymentSuccessContainer.on("pointerdown", (event) => { // 检查点击的是否是确认按钮,如果不是则隐藏 if ( !event.target || !event.target.parent || event.target.parent !== this.paymentConfirmButton ) { this.hidePaymentSuccess(); } }); // 将支付成功容器添加到根容器 this.container.addChild(this.paymentSuccessContainer); // =======支付成功展示容器结束========= this.depositHistory = new Sprite(Texture.from("assets/UIShop/UI1_05.png")); //历史记录图按钮 this.depositHistory.width *= this.app.rat; this.depositHistory.height *= this.app.rat; this.depositHistory.anchor.set(0.5, 0.5); this.depositHistory.position.set(this.overallWidth/2-110, this.overallHeight/2-50); this.depositHistory.interactive = true; this.depositHistory.buttonMode = true; this.mainContainer.addChild(this.depositHistory); const threeStripes = new Sprite(Texture.from("assets/UIShop/UI1_07.png")); //三条杠图(历史) threeStripes.width *= this.app.rat; threeStripes.height *= this.app.rat; threeStripes.anchor.set(0.5, 0.5); threeStripes.position.set(this.overallWidth/2-160, this.overallHeight/2-50); threeStripes.interactive = true; // 添加交互 threeStripes.buttonMode = true; // 添加按钮模式 this.depositHistoryText = Object.assign( //提示历史文字(主界面点击的历史) new Text("haha", { fontFamily: "Arial", fontSize: 12, fill: 0xd87910, align: "center", }), { anchor: { x: 0.5, y: 0.5 }, position: { x: this.overallWidth/2-105, y: this.overallHeight/2-50 } }, ); this.depositHistoryText.interactive = true; // 添加交互 this.depositHistoryText.buttonMode = true; // 添加按钮模式 this.mainContainer.addChild(threeStripes, this.depositHistoryText); // 创建统一的点击处理函数 const handleHistoryClick = async () => { // 如果支付弹窗已显示,忽略点击 if (this.bankPopupContainer && this.bankPopupContainer.visible) { return; } console.log("depositHistory 被点击了"); await this.SendHistoryData(); }; // 为所有三个元素添加点击事件 this.depositHistory.on("pointerdown", handleHistoryClick); threeStripes.on("pointerdown", handleHistoryClick); this.depositHistoryText.on("pointerdown", handleHistoryClick); // === 历史记录弹窗开始=== this.historyListContainer = new Container(); this.historyListContainer.visible = false; // 默认隐藏 this.historyListContainer.zIndex = 100;//内容透明度 this.historyListContainer.position.set( this.overallWidth * -0.35, // 距离左侧 this.overallHeight * -0.3 // 距离顶部 ); // 背景图 const recordBg = new Sprite(Texture.from("assets/UIShop/商店底5.png")); recordBg.anchor.set(0.5, 0.5); recordBg.width = this.overallWidth * 1.2; recordBg.height = this.overallHeight * 1; recordBg.position.set( this.overallWidth * 0.35, this.overallHeight* 0.3, ); this.historyText = Object.assign( new Text("haha", { fontFamily: "Arial", fontSize: 20, fill: 0xfff1a5, align: "center", }), { anchor: { x: 0.5, y: 0.5 }, position: { x: this.overallWidth* 0.35, y: this.overallHeight* -0.14} }, ); this.historyListContainer.addChild(recordBg, this.historyText); // 关闭按钮 const closeHistory = new Sprite(Texture.from("assets/UIShop/UI1_03.png")); closeHistory.width *= this.app.rat; closeHistory.height *= this.app.rat; closeHistory.position.set( this.overallWidth * 0.7, this.overallHeight* -0.1, ); closeHistory.eventMode = "static"; closeHistory.cursor = "pointer"; closeHistory.on("pointerdown", () => { this.historyListContainer.visible = false; if (this.blockBg) this.blockBg.visible = false; this.enableMainContainerInteractivity(true); // 恢复主界面操作 if (this.selectedAmountValue > 0) { this.inputAmountText.visible = false; // 隐藏提示文字 } }); this.historyListContainer.addChild(closeHistory); this.container.addChild(this.historyListContainer); // === 历史记录弹窗结束 === // === 弹窗与阴影遮罩 === //黑幕 let blockBg = new BlackMaskBg({ parent: this.container }); this.blockBg=blockBg; const shopBg3 = new Sprite(Texture.from("assets/UIShop/商店底3.png")); shopBg3.width = 500; shopBg3.height = 300; shopBg3.position.set(0, 0); this.shopBg3Container = new Container(); this.shopBg3Container.position.set(-240, -140); this.shopBg3Container.addChild(shopBg3); this.container.addChild(this.shopBg3Container); this.introduceText = new Text("介绍背景", { fontFamily: "Arial", fontSize: 13, fill: 0xffffff, align: "center", stroke: " #000000", strokeThickness: 0, lineHeight: 25, }); this.introduceText.anchor.set(0.5); this.introduceText.position.set( shopBg3.width / 2 + 60, shopBg3.height / 2 - 5, ); this.shopBg3Container.addChild(this.introduceText); this.shopBgButton = new Sprite(Texture.from("assets/UIShop/UI1_09.png")); this.shopBgButton.width = 210; this.shopBgButton.height = 60; this.shopBgButton.position.set(-80, 100); this.shopBgButton.eventMode = "static"; this.shopBgButton.cursor = "pointer"; this.buttonText = new Text("哈咯", { fontSize: 28, fill: 0xffffff, align: "center", stroke: "#000000", strokeThickness: 2, }); this.buttonText.anchor.set(0.5); this.buttonText.position.set( this.shopBgButton.width / 2, this.shopBgButton.height / 2, ); this.shopBgButton.addChild(this.buttonText); this.shopBgButton.on("pointerdown", () => { // 点击按钮后隐藏弹窗 if (this.shopBg3Container) this.shopBg3Container.visible = false; if (this.shopBgButton) this.shopBgButton.visible = false; if (blockBg) blockBg.visible = false; if (this.closeButton) { this.closeButton.eventMode = "static"; this.closeButton.cursor = "pointer"; } this.enableMainContainerInteractivity(true); }); this.container.addChild(this.shopBgButton); // 只设置一次 pivot:围绕内容中心缩放 if (!this.isPivotInitialized) { this.mainContainer.pivot.set( this.CONTAINER_WIDTH / 2, this.CONTAINER_HEIGHT / 2, ); this.isPivotInitialized = true; } // 初始化状态 this.resetState(); this.localize(); this.isInist = true; } // 创建支付成功确认按钮 createPaymentConfirmButton() { // 创建确认按钮 this.paymentConfirmButton = new Sprite( Texture.from("assets/UIShop/UI1_09.png"), ); this.paymentConfirmButton.width = 200; this.paymentConfirmButton.height = 80; this.paymentConfirmButton.anchor.set(0.5, 0.5); this.paymentConfirmButton.position.set(5, 150); this.paymentConfirmButton.eventMode = "static"; this.paymentConfirmButton.cursor = "pointer"; // 创建按钮文字 this.confirmText = new Text("这是Confirm", { fontFamily: "Arial", fontSize: 30, fill: 0xffffff, align: "center", stroke: "#FFA500", }); this.confirmText.anchor.set(0.5, 0.5); this.confirmText.position.set(0, 0); this.paymentConfirmButton.addChild(this.confirmText); // 绑定点击事件 this.paymentConfirmButton.on("pointerdown", () => { this.hidePaymentSuccess(); }); // 添加到支付成功容器 this.paymentSuccessContainer.addChild(this.paymentConfirmButton); } // 显示支付成功动画 showPaymentSuccess(amount) { UIBankData.hide(); // 恢复主界面交互 this.enableMainContainerInteractivity(true); // 设置支付金额文本 显示数字 this.paymentAmountText.text = amount.toString(); // 显示支付成功容器 this.paymentSuccessContainer.visible = true; // 动画效果:透明到不透明,再放大 this.paymentAmountText.alpha = 0; this.paymentAmountText.scale.set(0.5); // 动画序列 const animate = () => { // 渐显 if (this.paymentAmountText.alpha < 1) { this.paymentAmountText.alpha += 0.05; requestAnimationFrame(animate); return; } // 放大 if (this.paymentAmountText.scale.x < 1.2) { this.paymentAmountText.scale.x += 0.02; this.paymentAmountText.scale.y += 0.02; requestAnimationFrame(animate); return; } // 缩小回正常 if (this.paymentAmountText.scale.x > 1) { this.paymentAmountText.scale.x -= 0.01; this.paymentAmountText.scale.y -= 0.01; requestAnimationFrame(animate); } }; // 开始动画 animate(); } // 隐藏支付成功展示 hidePaymentSuccess() { this.paymentSuccessContainer.visible = false; } // 启用或禁用主界面的交互性 enableMainContainerInteractivity(enable) { // 设置主容器内所有可交互元素的交互性 this.closeButton.eventMode = enable ? "static" : "none"; this.closeButton.cursor = enable ? "pointer" : "default"; // 设置金额框的交互性 for (const amount of this.amountSprites) { amount.eventMode = enable ? "static" : "none"; amount.cursor = enable ? "pointer" : "default"; } // 设置充值按钮的交互性 if (this.rechargeYes) { this.rechargeYes.eventMode = enable && this.selectedIndex !== -1 ? "static" : "none"; this.rechargeYes.cursor = enable && this.selectedIndex !== -1 ? "pointer" : "default"; } // 设置历史记录按钮的交互性 if (this.depositHistory) { this.depositHistory.eventMode = enable ? "static" : "none"; this.depositHistory.cursor = enable ? "pointer" : "default"; } // 设置清除输入按钮的交互性 const clearInput = this.mainContainer.children.find( (child) => child.texture && child.texture.textureCacheIds && child.texture.textureCacheIds.includes("assets/UIShop/UI1_17.png"), ); if (clearInput) { clearInput.eventMode = enable ? "static" : "none"; clearInput.cursor = enable ? "pointer" : "default"; } } // 清除选中状态 clearSelection() { this.inputDisplayText.visible = false; this.inputAmountText.visible = true; // 恢复提示文字 this.selectedIndex = -1; this.selectedAmountValue = 0; this.selectedStarCount = 0; this.rpText.text = `${this._rpLabel} 0`; // 恢复 rpText 为默认状态 for (const shine of this.shineSprites) { // 隐藏所有发光边框 shine.visible = false; } // 清除选择时切回灰色按钮 if (this.rechargeYes && this.rechargeNo) { this.rechargeYes.visible = false; this.rechargeNo.visible = true; this.rechargeYes.eventMode = "none"; this.rechargeYes.cursor = "default"; } } async show() { console.log(this.constructor.name + " 1"); if (!this.isInist) { await this.init(); } else { this.resetState(); } console.log(this.constructor.name + " 2"); this.container.visible = true; UIManager.hideLoading(); // 开启动画 //this.animateOpen(); } // 动画从中间向两边展开 animateOpen() { this.mainContainer.scale.set(0, 1); const duration = 500; const startTime = performance.now(); const tick = (currentTime) => { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); const easeProgress = 1 - Math.pow(1 - progress, 2); // 缓动效果 this.mainContainer.scale.x = easeProgress; if (progress < 1) { requestAnimationFrame(tick); } }; requestAnimationFrame(tick); } resetState() { if (this.shopBg3Container) this.shopBg3Container.visible = true; if (this.shopBgButton) this.shopBgButton.visible = true; if (this.blockBg) this.blockBg.visible = true; if (this.mainContainer) { this.mainContainer.alpha = 1; this.mainContainer.scale.set(1); } if (this.closeButton) { this.closeButton.eventMode = "none"; this.closeButton.cursor = "default"; } // 重置选择状态 this.clearSelection(); this.enableMainContainerInteractivity(false); } hide() { this.container.visible = false; } //本地化 async localize() { this.uiname = "uiShop"; await super.localize(); // 使用 setLocalWords 方法统一设置本地化文本 super.setLocalWords(this.buttonText, "Confirmation"); super.setLocalWords(this.introduceText, "introduce"); super.setLocalWords(this.depositText, "deposit"); super.setLocalWords(this.depositText2, "deposit"); super.setLocalWords(this.depositTextNo, "deposit"); super.setLocalWords(this.topUpAmountText, "topUpAmount"); super.setLocalWords(this.inputAmountText, "inputAmount"); super.setLocalWords(this.freeAmountText, "freeAmount"); super.setLocalWords(this.selectAmountText, "selectAmount"); super.setLocalWords(this.depositHistoryText, "depositHistory"); super.setLocalWords(this.confirmText, "confirm"); super.setLocalWords(this.historyText, "depositHistory"); // 使用动态货币符号 this._rpLabel = gameData.currencySign; this.rpText.text = `${this._rpLabel} 0`; this.inputAmountText.visible = true; // 历史记录本地化 if (this.historyTextObjects && this.historyTextObjects.length > 0) { this.historyTextObjects.forEach((recordTexts) => { if (recordTexts.amountLabel && recordTexts.amountLabel.localKey) { super.setLocalWords(recordTexts.amountLabel, recordTexts.amountLabel.localKey); } if (recordTexts.orderLabel && recordTexts.orderLabel.localKey) { super.setLocalWords(recordTexts.orderLabel, recordTexts.orderLabel.localKey); } if (recordTexts.timeLabel && recordTexts.timeLabel.localKey) { super.setLocalWords(recordTexts.timeLabel, recordTexts.timeLabel.localKey); } if (recordTexts.statusLabel && recordTexts.statusLabel.localKey) { super.setLocalWords(recordTexts.statusLabel, recordTexts.statusLabel.localKey); } if (recordTexts.statusFinished && recordTexts.statusFinished.localKey) { super.setLocalWords(recordTexts.statusFinished, recordTexts.statusFinished.localKey); } if (recordTexts.statusProcessing && recordTexts.statusProcessing.localKey) { super.setLocalWords(recordTexts.statusProcessing, recordTexts.statusProcessing.localKey); } if (recordTexts.statusWaiting && recordTexts.statusWaiting.localKey) { super.setLocalWords(recordTexts.statusWaiting, recordTexts.statusWaiting.localKey); } }); } // 无数据提示 if (this.noDataText) { super.setLocalWords(this.noDataText, "oDataText"); } } formatNumberWithUnit(value) { if (value == null || isNaN(value)) return "0"; let formatted; if (value >= 1_000_000_000_000) { formatted = (value / 1_000_000_000_000).toFixed(2).replace(/\.00$/, ""); return `${formatted}T`; } else if (value >= 1_000_000_000) { formatted = (value / 1_000_000_000).toFixed(2).replace(/\.00$/, ""); return `${formatted}B`; } else if (value >= 1_000_000) { formatted = (value / 1_000_000).toFixed(2).replace(/\.00$/, ""); return `${formatted}M`; } else if (value >= 1_000) { formatted = (value / 1_000).toFixed(2).replace(/\.00$/, ""); return `${formatted}K`; } else { return String(value); } } // 接口请求:获取商店商品列表 async SendShopData() { const requestData = { token: gameData.gameToken, playerIndex: gameData.playerDto.playerIndex, }; console.log("发送请求参数:", requestData); const response = await networkMgr.postMessage("getShopGoods", requestData); if (!response) { throw new Error("接口返回空数据"); } this.shopGoodsList = response.shopGoodsList || response["shopGoodsList"] || []; console.log("获取商店数据:", this.shopGoodsList); if (this.isInist) { this.localize(); } } // 获取银行数据 async preloadBankData() { if (this.shopGoodsList.length > 0) { try { const requestData = { token: gameData.gameToken, playerIndex: gameData.playerDto.playerIndex, goodsId: this.shopGoodsList[0].id, }; await networkMgr.postMessage("getPaymentBank", requestData); console.log("预加载银行数据完成"); } catch (e) { console.warn("预加载银行数据失败", e); } } } // 发送支付请求 async SendPaymentRequest() { if (!this.selectedBankData || !this.id) { console.error("未选择银行或商品"); return; } try { const requestData = { token: gameData.gameToken, playerIndex: gameData.playerDto.playerIndex, goodsId: this.id, bankId: this.selectedBankData.bankId, bankCode: this.selectedBankData.bankCode, }; console.log("发送支付请求参数:", requestData); const response = await networkMgr.postMessage("payment", requestData); if (!response) { throw new Error("支付接口返回空数据"); } console.log("支付接口返回:", response); if (response.result === 0) { console.log("支付成功,金额:", response.amount); // 显示支付成功动画 this.showPaymentSuccess(response.amount); } else { // 支付失败 console.error("支付失败:", response.msg); } } catch (error) { console.error("支付请求失败:", error); } } // 历史记录接口 async SendHistoryData(isInit = false) { try { const requestData = { token: gameData.gameToken, playerIndex: gameData.playerDto.playerIndex, }; console.log("发送历史请求参数:", requestData); const response = await networkMgr.postMessage( "getPaymentRecord", requestData, ); if (!response) { throw new Error("接口返回空数据"); } this.recordList = response.recordList || response["recordList"] || []; // 渲染历史记录列表 this.renderHistoryList(); // 如果不是初始化调用,显示弹窗 if (!isInit) { this.historyListContainer.visible = true; this.blockBg.visible = true; for (const shine of this.bankShineSprites) { shine.visible = false; } this.enableMainContainerInteractivity(false); } this.localize(); } catch (error) { console.error("获取历史记录失败:", error); this.recordList = []; // 渲染列表 this.renderHistoryList(); if (!isInit) { this.historyListContainer.visible = true; this.blockBg.visible = true; this.enableMainContainerInteractivity(false); } } } // 历史记录列表 renderHistoryList() { // 清空之前的记录和文本对象 if (this.scrollContent) { this.scrollContent.removeChildren(); } this.historyTextObjects = []; // 配置 const listWidth = this.overallWidth * 0.7; const listHeight = this.overallHeight * 0.7; const itemHeight = this.overallHeight * 0.28; //每条记录的内容多高 const contentStartX = this.overallWidth *-0.01; const contentStartY = 0; // 重新创建滚动内容容器 this.scrollContent = new Container(); this.scrollContent.position.set(contentStartX, contentStartY); this.historyListContainer.addChild(this.scrollContent); // 如果没有数据,显示提示文字 if (!this.recordList || this.recordList.length === 0) { this.noDataText = new Text("暂无充值记录", { fontSize: 18, fill: 0x999999, }); this.noDataText.anchor.set(0.5); this.noDataText.position.set(listWidth / 2, 100); this.scrollContent.addChild(this.noDataText); return; } // 有数据时遍历生成每一条记录 this.recordList.forEach((record, index) => { const y = index * itemHeight; // 为每条记录创建一个对象来存储所有文本元素 const recordTexts = {}; // 圆角背景框 const bg = new Graphics(); bg.lineStyle(2, 0x7d503a, 0.6); // 边框颜色 bg.beginFill(0x7d503a, 0.3); // 背景色 bg.drawRoundedRect( 10, // x y + 5, // y listWidth - 15, // 宽:左右各留边距 itemHeight - 10, // 高:上下留空 18, // 圆角半径 ); bg.endFill(); this.scrollContent.addChild(bg); // 时间格式化 const date = new Date(record.createDate); const formattedTime = date.toLocaleString("en-US", { month: "numeric", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", second: "2-digit", hour12: true, }); // 充值金额标签 recordTexts.amountLabel = new Text("Top up amount", { fontSize: 13, fill: 0xb88e7e, }); recordTexts.amountLabel.position.set(20, y + 12); recordTexts.amountLabel.localKey = "amountText"; // 添加本地化标识 // 充值金额值 recordTexts.amountValue = new Text(`${record.amount.toLocaleString()}`, { //recordTexts.amountValue = new Text(`${this._rpLabel} ${record.amount.toLocaleString()}`, { fontSize: 13, fill: 0xe1ae68, }); recordTexts.amountValue.position.set(180, y + 12); // 订单标签 recordTexts.orderLabel = new Text("Recharge Order", { fontSize: 13, fill: 0xb88e7e, }); recordTexts.orderLabel.position.set(20, y + 32); recordTexts.orderLabel.localKey = "rechargeOrderText"; // 订单号 recordTexts.orderId = new Text(record.id, { fontSize: 13, fill: 0xb77760, }); recordTexts.orderId.position.set(180, y + 32); // 时间标签 recordTexts.timeLabel = new Text("Top up time", { fontSize: 13, fill: 0xb88e7e, }); recordTexts.timeLabel.position.set(20, y + 52); recordTexts.timeLabel.localKey = "rechargeTimeText"; // 时间值 recordTexts.timeValue = new Text(formattedTime, { fontSize: 13, fill: 0xb77760, }); recordTexts.timeValue.position.set(180, y + 52); // 状态标签 recordTexts.statusLabel = new Text("Order status", { fontSize: 13, fill: 0xb88e7e, }); recordTexts.statusLabel.position.set(20, y + 72); recordTexts.statusLabel.localKey = "statusText"; // 状态文本 recordTexts.statusFinished = new Text("已成功", { fontSize: 12, fill: 0x00ff00, }); recordTexts.statusFinished.position.set(180, y + 72); recordTexts.statusFinished.visible = record.status === "1"; recordTexts.statusFinished.localKey = "status_1"; recordTexts.statusProcessing = new Text("在等待处理", { fontSize: 12, fill: 0xffff00, }); recordTexts.statusProcessing.position.set(180, y + 72); recordTexts.statusProcessing.visible = record.status === "0"; recordTexts.statusProcessing.localKey = "status_0"; recordTexts.statusWaiting = new Text("失败", { fontSize: 12, fill: 0xaa0000, }); recordTexts.statusWaiting.position.set(180, y + 72); recordTexts.statusWaiting.visible = !( record.status === "1" || record.status === "0" ); recordTexts.statusWaiting.localKey = "status_2"; // 添加到滚动容器 this.scrollContent.addChild( recordTexts.amountLabel, recordTexts.amountValue, recordTexts.orderLabel, recordTexts.orderId, recordTexts.timeLabel, recordTexts.timeValue, recordTexts.statusLabel, recordTexts.statusFinished, recordTexts.statusProcessing, recordTexts.statusWaiting, ); // 存储到数组 this.historyTextObjects.push(recordTexts); // 复制图标(点击复制订单号) const copyIcon = new Sprite(Texture.from("assets/UIShop/复制.png")); copyIcon.width = 55 * this.app.rat; copyIcon.height = 55 * this.app.rat; copyIcon.anchor.set(0.5); copyIcon.position.set(listWidth - 40, y + itemHeight / 2); // 靠右居中 copyIcon.eventMode = "static"; copyIcon.cursor = "pointer"; copyIcon.on("pointerdown", (e) => { e.stopPropagation(); navigator.clipboard .writeText(record.id) .then(() => { console.log(`已复制订单号: ${record.id}`); copyIcon.tint = 0x00ff00; setTimeout(() => { copyIcon.tint = 0xffffff; }, 300); }) .catch((err) => { alert("复制失败,请手动选择文本复制"); console.warn("Clipboard error:", err); }); }); this.scrollContent.addChild(copyIcon); }); // 限制可见范围 const clipMask = new Graphics(); clipMask.beginFill(0x000000, 0.01); clipMask.drawRect(0, 0, listWidth, listHeight); clipMask.endFill(); clipMask.position.set(0, 0); this.historyListContainer.addChild(clipMask); this.scrollContent.mask = clipMask; // 添加拖拽滚动功能 this.setupHistoryScroll(); } // 设置历史记录滚动功能 setupHistoryScroll() { const listHeight = 250; // 显示区域高度 const itemHeight = 95; // 每条记录的高度 let isDragging = false; let dragStartY = 0; let scrollStartY = 0; this.app.view.removeEventListener( "pointermove", this.handleHistoryPointerMove, ); this.app.view.removeEventListener("pointerup", this.handleHistoryPointerUp); this.app.view.removeEventListener( "pointerupoutside", this.handleHistoryPointerUp, ); this.handleHistoryPointerMove = (e) => { if (!isDragging) return; const dy = e.clientY - dragStartY; let newY = scrollStartY + dy; const maxScrollUp = 0; const totalItemsHeight = this.recordList.length * itemHeight; const maxScrollDown = listHeight - totalItemsHeight; if (newY > maxScrollUp) newY = maxScrollUp; else if (newY < maxScrollDown) newY = maxScrollDown; this.scrollContent.y = newY; }; this.handleHistoryPointerUp = () => { isDragging = false; this.scrollContent.cursor = "grab"; }; // 设置滚动容器的交互 this.scrollContent.eventMode = "static"; this.scrollContent.cursor = "grab"; this.scrollContent.on("pointerdown", (event) => { isDragging = true; dragStartY = event.global.y; scrollStartY = this.scrollContent.y; this.scrollContent.cursor = "grabbing"; event.stopPropagation(); }); // 绑定全局事件 this.app.view.addEventListener( "pointermove", this.handleHistoryPointerMove, ); this.app.view.addEventListener("pointerup", this.handleHistoryPointerUp); this.app.view.addEventListener( "pointerupoutside", this.handleHistoryPointerUp, ); } // 提供给外部 async sendPaymentRequest(bankData) { if (!bankData || !this.id) { console.error("缺少必要参数:bankData 或 goodsId"); return; } try { const requestData = { token: gameData.gameToken, playerIndex: gameData.playerDto.playerIndex, goodsId: this.id, bankId: bankData.bankId, bankCode: bankData.bankCode, }; const response = await networkMgr.postMessage("payment", requestData); if (response && response.result === 0) { console.log("支付成功,金额:", response.amount); this.showPaymentSuccess(response.amount); } else { console.error("支付失败:", response?.msg || "未知错误"); } } catch (error) { console.error("支付请求异常:", error); } } } export const UIShop = new uiShop();
最新发布
12-10
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值