【问题标题】:Why is my android custom view not square?为什么我的 android 自定义视图不是方形的?
【发布时间】:2013-06-18 04:49:47
【问题描述】:

我创建了一个自定义视图来显示我正在开发的游戏的游戏板。游戏板必须始终是正方形。所以 with 和 height 应该是一样的。我跟着this tutorial 实现了这一目标。

我在自定义视图中添加了以下代码:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();
    int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom();

    int maxWidth = (int) (heigthWithoutPadding * RATIO);
    int maxHeight = (int) (widthWithoutPadding / RATIO);

    if (widthWithoutPadding > maxWidth) {
        width = maxWidth + getPaddingLeft() + getPaddingRight();
    } else {
        height = maxHeight + getPaddingTop() + getPaddingBottom();
    }

    setMeasuredDimension(width, height);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    Paint p = new Paint();
    p.setStyle(Paint.Style.STROKE);
    p.setStrokeWidth(3);
    canvas.drawRect(0, 0, getMeasuredWidth() - 1, getMeasuredHeight() - 1, p);
}

我的自定义视图的布局如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <view
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        class="com.peerkesoftware.nanograms.controls.GameBoard"
        android:id="@+id/view"
        android:background="@android:color/holo_purple"/>

</RelativeLayout>

onDraw 方法中绘制的正方形始终是正方形。这很好用。

在布局中,我添加了自定义视图并为视图提供了背景色。当我以纵向模式在设备上显示它时,一切正常,背景颜色填满了我在 onDraw 方法中绘制的正方形。

当我将设备切换到横向模式时,正方形仍然是正方形,但背景颜色的面积比那个大。它具有相同的高度,但具有更大的宽度。但我不知道为什么。 getMeasuredWidth() 和 getMeasuredHeight() 方法返回正确的值。怎么可能视野还那么大?

【问题讨论】:

  • 您是如何在布局中定义自定义视图的?
  • 横向宽度要大多少?可能不是这样,但您在纵向和横向使用不同的布局?

标签: android android-custom-view measure


【解决方案1】:

由于某种原因,您的视图在 RelativeLayout 中不起作用。对于横向测量,最终会遇到以下限制(在我的手机上):

宽度 = MeasureSpec:完全 404;高度 = MeasureSpec: AT_MOST 388

因为这个实际宽度最终与测量宽度不同(在我的手机上测量=388,实际=404)。这就是为什么你看到背景超出你的矩形。您可以通过向 onDraw 和 onMeasure 添加一些日志记录来自行检查。

此外,尊重测量模式也无济于事,测量仍以与上述相同的结果结束。

解决方案是使用不同的布局。 LinearLayout 和 FrameLayout 为我工作。 此外,在 onDraw 中使用实际视图大小,getWidth() 和 getHeight() 也是一个好主意。

如果你真的需要 RelativeLayout,你可以像这样添加一个子 FrameLayout 来保存你的视图:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        <view
            android:id="@+id/view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            class="com.peerkesoftware.nanograms.controls.GameBoard"
            android:background="@android:color/holo_purple" />
    </FrameLayout>
</RelativeLayout>

【讨论】:

  • 您的解决方案有效,但前提是我将视图的宽度和高度设置为 match_parent。我仍然存在的唯一问题是很难将其他视图放置在我的自定义视图旁边。使用相对布局会很容易。
  • 别介意我的最后一句话。我可以通过将 FrameLayout 替换为 LinearLayout 来轻松定位它。
【解决方案2】:

继承 ImageView 并覆盖 onMeasure 并传递给

super.onMeasure(widthMeasureSpec, widthMeasureSpec);

全班:

public class SquaredImageView extends ImageView {

        public SquaredImageView(Context context) {
                super(context);
        }

        public SquaredImageView(Context context, AttributeSet attrs) {
                super(context, attrs);
        }

        public SquaredImageView(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        }

}

编辑:我尝试了普通视图

public class CustomView extends View {

    public CustomView(Context context) {
        super(context);
        setBackgroundColor(Color.RED);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    int heigth = getMeasuredHeight();

    int dimen = (width > heigth) ? heigth : width;

    setMeasuredDimension(dimen, dimen);
    }
}

还有一个 TestActivity

public class TestActivity extends Activity {

    protected void onCreate(android.os.Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new CustomView(this));
    }

}

它会在我的手机屏幕上绘制一个红色方形矩形

【讨论】:

  • 我看不出扩展 ImageView 而不是普通视图的意义。我为自己的班级尝试过,但问题仍然存在。横向模式下视图的高度和宽度不同(不是正方形)。
  • 我的只是一个例子。你扩展了什么课程?
  • super.onMeasure(widthMeasureSpec, widthMeasureSpec) 对我有用!
【解决方案3】:

这是我的代码,我正在使用此代码,它运行良好。 不完全符合您的要求,但可能会给您一个想法。 根据我的要求,图像视图的高度应与宽度相同。

public class SquareImageView extends ImageView {

    private static final String TAG = "SquareImageView";

    public SquareImageView(Context context) {
        super(context);
    }

    public SquareImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //int h = this.getMeasuredHeight();
        int w = this.getMeasuredWidth();

        setMeasuredDimension(w, w);
    }
}

在xml中定义为:

<com.something.widget.SquareImageView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/ivProfilePicture"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:layout_alignParentTop="true"
                android:adjustViewBounds="true"
                android:scaleType="fitXY"
                android:background="@drawable/bg_curve_white_2"
                android:contentDescription="@string/general_content_description"
                android:src="@drawable/ic_default_logo"
                android:layout_marginTop="@dimen/side_margin"/>

最后在 Fragment 中是这样的:

SquareImageView ivProfilePicture = (SquareImageView) view.findViewById(R.id.ivProfilePicture);

【讨论】:

  • 我还没有测试过这个,但我认为这在横向模式下会有同样的问题。
【解决方案4】:

我不确定哪种解决方案能满足需求,但本教程中的代码存在基本设计问题 - 它无法正确解释 onMeasure()MeasureSpec 参数。 onMeasure custom view explanation 中的回答解释了这些参数的正确解释。

在这个例子中发生了什么,onMeasure() 为自定义视图的每次绘制调用两次。

对于第一次调用,两个 MeasureSpec 都指定模式 AT_MOST 并给出初始值,而不考虑边距和填充。因此,教程中的代码合法地使宽度和高度匹配。

但是,对于第二次调用,包含 RelativeLayout 会考虑边距和填充,并以 EXACT 模式传递 MeasureSpecWidth,而 HeightAT_MOST 模式传递。

对于垂直屏幕,这可以正常工作,因为Width 减少了边距,并且 height 的值仍然是屏幕的高度(也减少了边距)。因此,在第二次调用期间,代码合法地降低了高度以匹配现在较小的宽度,并且一切正常。

但是,对于水平屏幕方向,“宽度”设置为第一次调用期间定义的值,而Height 会减少边距。当代码尝试将Width 设置为匹配Height 时,此设置操作无法正常工作,因为Width 模式为EXACT。结果,视图WidthMeasuredWidth 的属性最终具有不同的值。

有几种简单的方法可以使有问题的代码工作:您可以简单地删除垂直边距:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/zero"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/zero"      
    tools:context=".MainActivity" >

或者,如果边距很重要,您可以将包含布局更改为绝对:

<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

在这两种情况下,将onMeasure() 的实现更改为尊重EXACT 模式似乎是有意义的(至少记录一个问题)。

【讨论】:

  • 我建议不要使用 AbsoluteLayout。自 Android API 级别 3 起已弃用!
  • @LJoosse True,AbsoluteLayout 已弃用。但问题是“为什么宽度与横向高度不匹配”,我试图说明问题的根本原因。在不了解屏幕布局的实际要求的情况下,很难提出具体的解决方案。
【解决方案5】:

您的onMeasure() 代码并不简单。 更改代码如下 & 将 match_parent 设置为 xml 中的宽度和高度(实际上没关系)

void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();
    int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom();

    int maxWidth = (int) (heigthWithoutPadding * RATIO);
    int maxHeight = (int) (widthWithoutPadding / RATIO);

    if (widthWithoutPadding > maxWidth) {
        width = maxWidth + getPaddingLeft() + getPaddingRight();
    } else {
        height = maxHeight + getPaddingTop() + getPaddingBottom();
    }

    setMeasuredDimension(width, height);
}

【讨论】:

    【解决方案6】:

    这个简单的代码(类似于带有更正的 Blackbelt 代码)在 LinearLayout 和 FrameLayout 中适用于我:

    protected int minNotZero(int valueA, int valueB)
    {
        if (valueA != 0)
            if (valueB != 0)
                return Math.min(valueA, valueB);
            else
                return valueA;
        else
            return valueB;
    }
    
    
    
    @Override
    protected void onMeasure(int measureSpecW, int measureSpecH)
    {
        super.onMeasure(measureSpecW, measureSpecH);
    
        int width  = this.getMeasuredWidth();
        int heigth = this.getMeasuredHeight();
    
        int dimen = this.minNotZero(width, heigth);
    
        this.setMeasuredDimension(dimen, dimen);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-12
      • 2015-09-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-01
      相关资源
      最近更新 更多