You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android游戏Credits界面可点击文本链接触摸无响应问题排查

问题排查:Credits界面可点击链接无响应

我在Roboyard应用的Credits界面添加了可在浏览器打开的链接,链接显示为蓝色,但触摸时无法触发点击事件。以下是相关代码,帮忙排查遗漏的部分:

RenderManager.java

package roboyard.eclabs;

import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.SparseArray;
import android.view.MotionEvent;

import java.io.IOException;
import java.io.InputStream;
import java.util.Random;

public class RenderManager {
    private Canvas target, mainTarget;
    private final Paint brush;
    private final Resources resources;
    private final SparseArray<Drawable> resourceMap;
    private final Random random;

    public RenderManager(Resources resources){
        this.target = null;
        this.brush = new Paint();
        this.brush.setColor(Color.WHITE);
        this.resources = resources;
        this.resourceMap = new SparseArray<>();
        this.random = new Random();
    }

...

    public void drawText(int x, int y, String str){
        this.target.drawText(str, x, y, this.brush);
    }

    public void drawClickableText(int x, int y, String text, int color, int textSize, ClickListener clickListener) {
        Rect bounds = new Rect();
        brush.setColor(color);
        brush.setTextSize(textSize);
        brush.getTextBounds(text, 0, text.length(), bounds);
        target.drawText(text, x, y + bounds.height() - textSize, brush);
        clickListener.setClickableBounds(x, y, x + bounds.width(), y + bounds.height());
    }

    public void handleTouchEvent(MotionEvent event, ClickListener clickListener) {
        int action = event.getAction();
        float x = event.getX();
        float y = event.getY();

        switch (action) {
            case MotionEvent.ACTION_UP:
                if (clickListener != null && clickListener.isClickable() && clickListener.isInsideClickableBounds(x, y)) {
                    clickListener.onClick();
                }
                break;
        }
    }

    public interface ClickListener {
        void onClick();
        void setClickableBounds(float left, float top, float right, float bottom);
        boolean isInsideClickableBounds(float x, float y);
        boolean isClickable();
    }
}

CreditsGameScreen.java

package roboyard.eclabs;

import android.content.Intent;
import android.net.Uri;
import android.graphics.Color;

/**
 * Represents the Credits game screen.
 * Created by Pierre on 04/02/2015.
 */
public class CreditsGameScreen extends GameScreen {

    public CreditsGameScreen(GameManager gameManager) {
        super(gameManager);
    }

    int hs2; // Half the screen height

    /**
     * Creates game objects for the Credits screen.
     */
    @Override
    public void create() {
        int ws2 = this.gameManager.getScreenWidth() / 2;
        hs2 = this.gameManager.getScreenHeight() / 2;
        // Add back button
        this.instances.add(new GameButtonGoto(7 * ws2 / 4 - 128, 9 * hs2 / 5 - 128, 128, 128, R.drawable.bt_back_up, R.drawable.bt_back_down, 0));
    }

    /**
     * Loads assets for rendering.
     *
     * @param renderManager The render manager
     */
    @Override
    public void load(RenderManager renderManager) {
        super.load(renderManager);
    }

    /**
     * Renders the Credits screen.
     *
     * @param renderManager The render manager
     */
    @Override
    public void draw(RenderManager renderManager) {
        // Get app version information
        int versionCode = BuildConfig.VERSION_CODE;
        String versionName = BuildConfig.VERSION_NAME;

        // Set background color
        //renderManager.setColor(Color.BLUE);
        renderManager.setColor(Color.parseColor("#B0CC99")); // Light green background
        renderManager.paintScreen();

        // Set text color and size
        renderManager.setColor(Color.BLACK);
        int ts = hs2 / 10; // Text size

        // Draw credits text
        renderManager.setTextSize((int) (1.2 * ts));
        renderManager.drawText(10, 2 * ts, "Created by");
        renderManager.setTextSize((int) (0.7 * ts));
        renderManager.drawText(10, 3 * ts, "Alain Caillaud");
        renderManager.drawText(10, 4 * ts, "Pierre Michel");
        renderManager.drawText(10, 5 * ts, "Ruben Barkow-Kuder");

        renderManager.setTextSize((int) (1.2 * ts));
        renderManager.drawText(10, 7 * ts, "Based on");
        renderManager.setTextSize((int) (0.7 * ts));
        renderManager.drawText(10, 8 * ts, "Ricochet Robots(r)");

        renderManager.setTextSize((int) (1.2 * ts));
        renderManager.drawText(10, 10 * ts, "Imprint/privacy policy");
        renderManager.setTextSize((int) (0.7 * ts));
        drawClickableLink(renderManager, 10, 11 * ts, "https://eclabs.de/datenschutz.html");

        renderManager.setColor(Color.BLACK);
        renderManager.setTextSize((int) (1.2 * ts));
        renderManager.drawText(10, 13 * ts, "Open Source");

        drawClickableLink(renderManager, 10, 14 * ts, "https://git.io/fjs5H");

        renderManager.setColor(Color.BLACK);
        renderManager.drawText(10, 17 * ts, "Version: " + versionName + " (Build " + versionCode + ")");

        super.draw(renderManager);
    }

    /**
     * Draws a clickable link.
     *
     * @param renderManager The render manager
     * @param x             The x-coordinate of the link
     * @param y             The y-coordinate of the link
     * @param url           The URL of the link
     */
    private void drawClickableLink(RenderManager renderManager, int x, int y, String url) {
        renderManager.drawClickableText(x, y, url, Color.BLUE, (int) (0.7 * (hs2 / 10)), new RenderManager.ClickListener() {
            @Override
            public void onClick() {
                openLink(url);
            }

            @Override
            public void setClickableBounds(float left, float top, float right, float bottom) {
                // No need to assign values here
                // These bounds are set internally by the RenderManager
            }

            @Override
            public boolean isInsideClickableBounds(float x, float y) {
                // Check if the touch coordinates (x, y) are inside the clickable bounds
                // Not implemented in this snippet, but you would check the bounds here
                return true; // Placeholder return value
            }

            @Override
            public boolean isClickable() {
                // Determine if the text is clickable
                // Not implemented in this snippet, but you would handle the clickable state here
                return true; // Placeholder return value
            }
        });
    }

    /**
     * Opens a link in a web browser.
     *
     * @param url The URL to open
     */
    private void openLink(String url) {
        // Create an intent to open a web browser with the specified URL
        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        // Start the activity (open the web browser)
        gameManager.getActivity().startActivity(browserIntent);
    }

    /**
     * Updates the Credits screen.
     *
     * @param gameManager The game manager
     */
    @Override
    public void update(GameManager gameManager) {
        super.update(gameManager);
        // Handle back button press to return to the main menu
        if (gameManager.getInputManager().backOccurred()) {
            gameManager.setGameScreen(0); // Set the main menu screen
        }
    }

    /**
     * Cleans up resources used by the Credits screen.
     */
    @Override
    public void destroy() {
        super.destroy();
    }
}

核心问题与修复方案

1. 点击边界未被保存与校验

CreditsGameScreen中ClickListener的setClickableBounds是空实现,没有保存RenderManager传递的边界值,导致isInsideClickableBounds无法正确判断触摸位置是否在链接范围内。

修复代码:
修改drawClickableLink中的ClickListener实现,添加变量保存边界并做正确校验:

private void drawClickableLink(RenderManager renderManager, int x, int y, String url) {
    renderManager.drawClickableText(x, y, url, Color.BLUE, (int) (0.7 * (hs2 / 10)), new RenderManager.ClickListener() {
        private float left, top, right, bottom;

        @Override
        public void onClick() {
            openLink(url);
        }

        @Override
        public void setClickableBounds(float left, float top, float right, float bottom) {
            this.left = left;
            this.top = top;
            this.right = right;
            this.bottom = bottom;
        }

        @Override
        public boolean isInsideClickableBounds(float x, float y) {
            return x >= left && x <= right && y >= top && y <= bottom;
        }

        @Override
        public boolean isClickable() {
            return true;
        }
    });
}

2. 触摸事件未传递给链接监听器

当前代码没有将屏幕触摸事件传递给每个链接的ClickListener,RenderManager的handleTouchEvent方法从未被调用。

修复步骤:

  • 在CreditsGameScreen中添加列表保存所有链接监听器:
private List<RenderManager.ClickListener> linkClickListeners = new ArrayList<>();
  • 修改drawClickableLink,将创建的监听器加入列表:
private void drawClickableLink(RenderManager renderManager, int x, int y, String url) {
    RenderManager.ClickListener clickListener = new RenderManager.ClickListener() {
        // ... 上述修复后的实现 ...
    };
    linkClickListeners.add(clickListener);
    renderManager.drawClickableText(x, y, url, Color.BLUE, (int) (0.7 * (hs2 / 10)), clickListener);
}
  • 在屏幕触摸事件处理方法中遍历监听器,传递触摸事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
    for (RenderManager.ClickListener listener : linkClickListeners) {
        gameManager.getRenderManager().handleTouchEvent(event, listener);
    }
    return super.onTouchEvent(event);
}

3. 文本绘制Y轴偏移导致边界错位

RenderManager的drawClickableText中,文本绘制的Y坐标计算为y + bounds.height() - textSize,但传递给监听器的边界是原始的y值,导致边界与实际文本位置不匹配。

优化代码:
调整drawClickableText的边界计算,确保覆盖实际文本区域:

public void drawClickableText(int x, int y, String text, int color, int textSize, ClickListener clickListener) {
    Rect bounds = new Rect();
    brush.setColor(color);
    brush.setTextSize(textSize);
    brush.getTextBounds(text, 0, text.length(), bounds);
    // 使用y作为文本基线,与drawText默认行为一致
    target.drawText(text, x, y, brush);
    // 基于基线计算正确的上下边界
    clickListener.setClickableBounds(x, y - bounds.height(), x + bounds.width(), y);
}

内容的提问来源于stack exchange,提问作者rubo77

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:39:51