自定义 Android 视图在线性和相对布局中显示不同
Posted
技术标签:
【中文标题】自定义 Android 视图在线性和相对布局中显示不同【英文标题】:Custom Android View displaying differently in Linear and Relative Layouts 【发布时间】:2014-02-15 17:40:29 【问题描述】:我创建了一个自定义视图 (ArrowContainer) 来包裹其他元素,为它们提供箭头形状的背景。但是,我的视图包含在相对布局中时的显示方式与包含在线性布局中时的显示方式不同。
这就是问题所在,顶部的 ArrowContainer 包含在 LinearLayout 中并且行为正确,底部的 ArrowContainer 包含在 RelativeLayout 中并且行为不正确。
以前有人见过这样的东西吗?我在 ArrowContainer.java 中插入的调试代码表明问题出在 RelativeLayout 测量视图两次,但我不确定为什么这会导致问题...
下面是代码:
ArrowContainer.java
package com.example.arrowcontainertest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public class ArrowContainer extends ViewGroup
private static final int ARROW_LEFT = 0;
private static final int ARROW_RIGHT = 1;
private static final int ARROW_BOTH = 2;
private static final int DEFAULT_COLOUR = 0xFFFF0000;
private static final int HORIZONTAL_PADDING = 150;
private Path path;
private Paint paint;
private int arrowSide = ARROW_RIGHT;
private int colour = DEFAULT_COLOUR;
private int downColour;
private Paint downPaint;
private Boolean isButton = false;
private View child;
public ArrowContainer(Context context)
super(context);
init();
public ArrowContainer(Context context, AttributeSet attrs)
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
finally
a.recycle();
init();
public ArrowContainer(Context context, AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
finally
a.recycle();
init();
private void init()
paint = new Paint();
paint.setColor(colour);
paint.setStyle(Style.FILL);
setWillNotDraw(false);
if (isButton)
setFocusable(true);
setClickable(true);
downColour = 0xFF00FF00;
downPaint = new Paint();
downPaint.setColor(downColour);
downPaint.setStyle(Style.FILL);
@Override
protected void onFinishInflate()
// Must have exactly 1 child
assert getChildCount()==1;
if (getChildCount() == 1)
child = getChildAt(0);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
// Debug
Log.e("DEBUG", "Type:" + getParent().getClass());
Log.e("DEBUG", "Width Mode: " + MeasureSpec.getMode(widthMeasureSpec));
Log.e("DEBUG", "Height Mode: " + MeasureSpec.getMode(heightMeasureSpec));
Log.e("DEBUG", "Width Size: " + MeasureSpec.getSize(widthMeasureSpec));
Log.e("DEBUG", "Height Size: " + MeasureSpec.getSize(heightMeasureSpec));
// Restrict the childs width to at most this components size minus a fixed value (HORIZONTAL_PADDING*numArrows)
int numArrows=0;
switch (arrowSide)
case ARROW_RIGHT:
numArrows = 1;
break;
case ARROW_LEFT:
numArrows = 1;
break;
case ARROW_BOTH:
numArrows = 2;
break;
int widthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)-HORIZONTAL_PADDING*numArrows, MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(widthSpec, heightSpec);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
setMeasuredDimension(width + (int) (numArrows*height/2f), height);
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
switch (arrowSide)
case ARROW_RIGHT:
// Hug left
child.layout(0, height/2 - childHeight/2, width - height/2, height/2 + childHeight/2);
break;
case ARROW_LEFT:
// Hug right
child.layout(height/2, height/2 - childHeight/2, width, height/2 + childHeight/2);
break;
case ARROW_BOTH:
// Center
child.layout(width/2 - childWidth/2, height/2 - childHeight/2, width/2 + childWidth/2, height/2 + childHeight/2);
break;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
path = new Path();
switch (arrowSide)
case ARROW_RIGHT:
path.lineTo(0, h);
path.lineTo(w-h/2f, h);
path.lineTo(w, h/2f);
path.lineTo(w-h/2f, 0);
break;
case ARROW_LEFT:
path.moveTo(h/2f, 0);
path.lineTo(0, h/2f);
path.lineTo(h/2f, h);
path.lineTo(w, h);
path.lineTo(w, 0);
break;
case ARROW_BOTH:
path.moveTo(h/2f, 0);
path.lineTo(0, h/2f);
path.lineTo(h/2f, h);
path.lineTo(w-h/2f, h);
path.lineTo(w, h/2f);
path.lineTo(w-h/2f, 0);
break;
path.close();
@Override
protected void onDraw(Canvas canvas)
invalidate();
if (isPressed())
canvas.drawPath(path, downPaint);
else
canvas.drawPath(path, paint);
super.onDraw(canvas);
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" xmlns:app="http://schemas.android.com/apk/res/com.example.arrowcontainertest">
<LinearLayout
android:layout_
android:layout_>
<com.example.arrowcontainertest.ArrowContainer
android:layout_
android:layout_
app:arrowSide="right">
<TextView android:layout_
android:layout_
android:text="Play"
android:textSize="50sp"/>
</com.example.arrowcontainertest.ArrowContainer>
</LinearLayout>
<RelativeLayout
android:layout_
android:layout_>
<com.example.arrowcontainertest.ArrowContainer
android:layout_
android:layout_
app:arrowSide="right">
<TextView android:layout_
android:layout_
android:text="Play"
android:textSize="50sp"/>
</com.example.arrowcontainertest.ArrowContainer>
</RelativeLayout>
MainActivity.java
package com.example.arrowcontainertest;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
【问题讨论】:
很高兴您找到了解决方案,但我想说,您应该使用父视图提供的视图尺寸的实际值而不是测量值。我的意思是l
、t
、r
和b
在onLayout
。如果您查看测量的自身,例如 100x100,但您想将其扩展为填充父级的宽度,如果您使用测量值而不是父级提供的值,您将一无所获。
谢谢,以后会这样做的!
【参考方案1】:
更新:
我无法解决这个问题,而且这个组件在其他情况下也引起了问题。因此,我决定重写组件以尽可能使用一些自定义功能。
我的解决方案是创建一个包含嵌套 LinearLayout 的自定义 LinearLayout。外部布局负责绘制背景,并应用足够的填充以留出空间来绘制箭头。所有的孩子都被传递到内部布局。这个解决方案并不完美,因为经常有多余的填充和浪费空间,但对于我的目的来说已经足够了。
代码在这里:
package com.example.arrowcontainertest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
public class NewArrowContainer extends LinearLayout
private static final int ARROW_LEFT = 0;
private static final int ARROW_RIGHT = 1;
private static final int ARROW_BOTH = 2;
private static final int DEFAULT_COLOUR = 0xFFFF0000;
private static final int ARROW_MAX_WIDTH = 150;
private LinearLayout childLayout;
private Path path;
private Paint paint;
private int arrowSide = ARROW_RIGHT;
private int colour = DEFAULT_COLOUR;
private int downColour;
private Paint downPaint;
private Boolean isButton = false;
public NewArrowContainer(Context context)
super(context);
init();
public NewArrowContainer(Context context, AttributeSet attrs)
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
finally
a.recycle();
init();
public NewArrowContainer(Context context, AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
finally
a.recycle();
init();
private void init()
paint = new Paint();
paint.setColor(colour);
paint.setStyle(Style.FILL);
setWillNotDraw(false);
if (isButton)
setFocusable(true);
setClickable(true);
downColour = 0xFF00FF00;
downPaint = new Paint();
downPaint.setColor(downColour);
downPaint.setStyle(Style.FILL);
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.arrow_container, this);
childLayout = (LinearLayout) findViewById(R.id.child);
// Pass properties to childLayout
childLayout.setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
childLayout.setOrientation(getOrientation());
// Give the padding sufficient for arrows
switch (arrowSide)
case ARROW_RIGHT:
setPadding(0, 0, ARROW_MAX_WIDTH, 0);
break;
case ARROW_LEFT:
setPadding(ARROW_MAX_WIDTH, 0, 0, 0);
break;
case ARROW_BOTH:
setPadding(ARROW_MAX_WIDTH, 0, ARROW_MAX_WIDTH, 0);
break;
public void setColour(int colour)
paint.setColor(colour);
@Override
public void onFinishInflate()
// Pass all children to the childLayout
while (getChildCount() > 1)
View v = getChildAt(1);
removeViewAt(1);
childLayout.addView(v);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
path = new Path();
switch (arrowSide)
case ARROW_RIGHT:
path.lineTo(0, h);
path.lineTo(w-ARROW_MAX_WIDTH, h);
path.lineTo(w-ARROW_MAX_WIDTH+h/2f, h/2f);
path.lineTo(w-ARROW_MAX_WIDTH, 0);
break;
case ARROW_LEFT:
path.moveTo(ARROW_MAX_WIDTH-h/2f, h/2f);
path.lineTo(ARROW_MAX_WIDTH, h);
path.lineTo(w, h);
path.lineTo(w, 0);
path.lineTo(ARROW_MAX_WIDTH, 0);
break;
case ARROW_BOTH:
path.moveTo(ARROW_MAX_WIDTH-h/2f, h/2f);
path.lineTo(ARROW_MAX_WIDTH, h);
path.lineTo(w-ARROW_MAX_WIDTH, h);
path.lineTo(w-ARROW_MAX_WIDTH+h/2f, h/2f);
path.lineTo(w-ARROW_MAX_WIDTH, 0);
path.lineTo(ARROW_MAX_WIDTH, 0);
break;
path.close();
@Override
protected void onDraw(Canvas canvas)
invalidate();
if (isPressed())
canvas.drawPath(path, downPaint);
else
canvas.drawPath(path, paint);
super.onDraw(canvas);
【讨论】:
【参考方案2】:MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)-HORIZONTAL_PADDING*numArrows, MeasureSpec.AT_MOST);
我很担心这条线,为什么你不检查提供给 makeMeasureSpec 的宽度是否不是负数?此方法不执行范围检查,因此由您负责。负宽度 = 无效的 measureSpec = 未定义的行为。
另外,当我实现了一些自定义布局时,我使用super.onMeasure
来确定最大可用尺寸,然后通过getMeasuredWidth()
、getMeasuredHeight()
使用它们。
【讨论】:
感谢您的评论,我没想过要检查,以后会做!然而,在这种情况下这似乎不是问题,因为在有问题的情况下它是积极的。以上是关于自定义 Android 视图在线性和相对布局中显示不同的主要内容,如果未能解决你的问题,请参考以下文章