LinearLayout中自定义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

