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

LinearLayout中自定义View仅首个显示问题技术求助

解决自定义View仅首个显示的问题

看起来你遇到的问题很典型——虽然onMeasure和onDraw都被触发了,但后续添加的自定义View就是看不到,大概率是测量逻辑错误或者绘制坐标的理解偏差导致的,下面一步步帮你排查修复:

1. 修复onMeasure的核心逻辑

你的onMeasure方法直接用params.width作为首选宽度是完全错误的——LayoutParams的width/height如果是WRAP_CONTENT(对应值-2)或MATCH_PARENT(对应值-1),这些是布局标记,不是实际像素值。你需要根据文本的实际尺寸计算View的合理大小,而不是依赖LayoutParams的标记值。

修改后的onMeasure代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 先计算文本的实际宽高
    Rect textBounds = new Rect();
    textColor.getTextBounds(elementText, 0, elementText.length(), textBounds);
    int textWidth = textBounds.width();
    int textHeight = textBounds.height();

    // 加上内边距(可选,让文本和View边缘有间隙)
    int preferredWidth = textWidth + getPaddingLeft() + getPaddingRight();
    int preferredHeight = textHeight + getPaddingTop() + getPaddingBottom();

    // 处理MeasureSpec的三种模式
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int measuredWidth;
    int measuredHeight;

    if (widthMode == MeasureSpec.EXACTLY) {
        measuredWidth = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        measuredWidth = Math.min(preferredWidth, widthSize);
    } else {
        measuredWidth = preferredWidth;
    }

    if (heightMode == MeasureSpec.EXACTLY) {
        measuredHeight = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        measuredHeight = Math.min(preferredHeight, heightSize);
    } else {
        measuredHeight = preferredHeight;
    }

    setMeasuredDimension(measuredWidth, measuredHeight);
}

这里的关键是基于文本实际尺寸计算View的首选大小,这样才能保证每个CustomView都有足够的空间显示内容。

2. 修正drawText的坐标逻辑

你当前用的originX/originY是全局屏幕坐标,但onDraw里的Canvas坐标系是相对于当前View自身左上角的(即View内部的(0,0)是自己的左上角)。如果给后续CustomView设置了相同的全局坐标,它们的文本会画在自己View的同一个位置,一旦这个位置超出View边界,就会看不到。

正确的做法是基于View自身坐标系绘制,比如让文本垂直居中、水平靠左:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (elementText.isEmpty()) return;

    Rect textBounds = new Rect();
    textColor.getTextBounds(elementText, 0, elementText.length(), textBounds);
    // drawText的Y坐标是文本基线,计算垂直居中的基线位置
    float baselineY = getHeight() / 2f + textBounds.height() / 2f;
    // 水平靠左,加上左边距
    float startX = getPaddingLeft();

    canvas.drawText(elementText, startX, baselineY, textColor);
    Log.d("CustomView", elementText + " drawn at (" + startX + ", " + baselineY + ") | View size: " + getWidth() + "x" + getHeight());
}

这样不管View在LinearLayout的哪个位置,文本都会在自己的View内部正确显示。

3. 优化构造函数与参数传递

你的构造函数里重复赋值了this.params = params;,而且没必要把Context和Params保存为成员变量(View本身就有getContext()和getLayoutParams()方法),可以简化成:

public CustomView(Context context, Paint textColor) {
    super(context);
    this.textColor = textColor;
    this.elementText = "";
    // 给View设置默认布局参数
    setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
}

// 重载构造函数,支持直接传入初始文本
public CustomView(Context context, Paint textColor, String text) {
    this(context, textColor);
    this.elementText = text;
}

4. 正确添加到LinearLayout

确保添加CustomView时的逻辑正确:

LinearLayout listLayout = findViewById(R.id.ListPoint);
Paint textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));

// 添加多个自定义View
listLayout.addView(new CustomView(this, textPaint, "First Item"));
listLayout.addView(new CustomView(this, textPaint, "Second Item"));
listLayout.addView(new CustomView(this, textPaint, "Third Item"));

按照上面的修改后,所有添加的CustomView应该都能正常显示了。核心是让View的测量尺寸匹配内容大小,并且在自身坐标系内绘制文本,而不是依赖全局坐标。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:19:27