【问题标题】:Android - How do you scale views to fit a full screen application's height and width perfectly?Android - 如何缩放视图以完美适应全屏应用程序的高度和宽度?
【发布时间】:2013-05-11 13:18:20
【问题描述】:

如果您有一个计算器应用程序并且您想编写一个类似于this 的布局,您如何缩放按钮和显示以适应所有屏幕尺寸?

我研究过的想法:

  1. 以编程方式计算每个组件的高度和宽度。以编程方式创建视图可为您提供最大的功能,但并不完全理想。我更喜欢用 XML 编写我的 UI。

  2. 使用布局权重嵌套线性布局。这可行,但 lint 会发出性能警告,因为我正在嵌套权重。最重要的是,它不考虑文本大小。所以在小屏幕上,文本被截断。相反,在大屏幕上,文字太小了。

编辑: 3. 使用带有嵌套权重的 TableLayout。考虑到 LinearLayout 的这些扩展,我认为缺少 lint 警告是无关紧要的,这仍然会导致性能损失,对吗?

有没有更好的方法?我觉得我错过了一些明显的东西

编辑 2: 如果有人对此解决方案感兴趣,我已经创建了一个自定义布局(如 raphw 建议的那样)并将在此处发布源代码:

EvenSpaceGridLayout.java:

package com.example.evenspacegridlayout;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class EvenSpaceGridLayout extends ViewGroup {

    private int mNumColumns;

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

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

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.EvenSpaceGridLayout);
        try {
            mNumColumns = a.getInteger(
                    R.styleable.EvenSpaceGridLayout_num_columns, 1);
        } finally {
            a.recycle();
        }
    }

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

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // Calculate how many cells we need
        int cellCount = countCellsNeeded();

        // Calculate number of rows needed given the number of cells
        int numRows = cellCount / mNumColumns;

        // Calculate width/height of each individual cell
        int cellWidth = widthSize / mNumColumns;
        int cellHeight = heightSize / numRows;

        // Measure children
        measureChildrenViews(cellWidth, cellHeight);

        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
        }
    }

    private int countCellsNeeded() {

        int cellCount = 0;
        final int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {

            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            int spanColumns = lp.spanColumns;

            // If it's trying to span too far, make it span the maximum possible
            if (spanColumns > mNumColumns) {
                spanColumns = mNumColumns;
            }

            int remainingCellsInRow = mNumColumns - (cellCount % mNumColumns);
            if (remainingCellsInRow - spanColumns < 0) {
                cellCount += remainingCellsInRow + spanColumns;
            } else {
                cellCount += spanColumns;
            }
        }

        // Round off the last row
        if ((cellCount % mNumColumns) != 0) {
            cellCount += mNumColumns - (cellCount % mNumColumns);
        }

        return cellCount;
    }

    private void measureChildrenViews(int cellWidth, int cellHeight) {

        int cellCount = 0;
        final int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {

            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            int spanColumns = lp.spanColumns;

            // If it's trying to span too far, make it span the maximum possible
            if (spanColumns > mNumColumns) {
                spanColumns = mNumColumns;
            }

            // If it can't fit on the current row, skip those cells
            int remainingCellsInRow = mNumColumns - (cellCount % mNumColumns);
            if (remainingCellsInRow - spanColumns < 0) {
                cellCount += remainingCellsInRow;
            }

            // Calculate x and y coordinates of the view
            int x = (cellCount % mNumColumns) * cellWidth;
            int y = (cellCount / mNumColumns) * cellHeight;

            lp.x = x;
            lp.y = y;

            child.measure(MeasureSpec.makeMeasureSpec(cellWidth * spanColumns, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(cellHeight, MeasureSpec.EXACTLY));

            cellCount += spanColumns;
        }
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p.width, p.height);
    }

    public static class LayoutParams extends ViewGroup.LayoutParams {

        int x, y;

        public int spanColumns;

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.EvenSpaceGridLayout_LayoutParams);
            try {
                spanColumns = a
                        .getInteger(
                                R.styleable.EvenSpaceGridLayout_LayoutParams_span_columns,
                                1);

                // Can't span less than one column
                if (spanColumns < 1) {
                    spanColumns = 1;
                }
            } finally {
                a.recycle();
            }
        }

        public LayoutParams(int w, int h) {
            super(w, h);
        }
    }
}

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="EvenSpaceGridLayout">
        <attr name="num_columns" format="integer" />
    </declare-styleable>

    <declare-styleable name="EvenSpaceGridLayout_LayoutParams">
        <attr name="span_columns" format="integer" />
    </declare-styleable>

</resources>

用法如下:

<com.example.evenspacegridlayout.EvenSpaceGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:grid="http://schemas.android.com/apk/res/com.example.evenspacegridlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    grid:num_columns="4" >

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="CL" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Del" />

    <!-- empty cell -->
    <View 
        android:layout_width="0dp"
        android:layout_height="0dp" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="/" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="7" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="8" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="9" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="*" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="4" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="5" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="6" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="-" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="1" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="2" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="3" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="+" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="." />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="0" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="="
        grid:span_columns="2" />

</com.example.evenspacegridlayout.EvenSpaceGridLayout>

最终结果:

【问题讨论】:

  • 这个问题的最佳答案应该为你做。 stackoverflow.com/questions/4265804/…
  • 这个问题的所有 3 个答案都是 ImageView 特定的
  • "最重要的是,它不考虑文本大小。所以在小屏幕上,文本被截断。相反,在大屏幕上,文本太小。"这不是 LinearLayout 方法的问题,而是您尝试做的问题 - 任何解决方案都会有这个问题。同样对于 LinearLayout 解决方案,嵌套的权重具有不同的维度,因此它不应该是性能问题。要么警告是愚蠢的,要么是 LinearLayout 是愚蠢的,不确定是哪个。

标签: android


【解决方案1】:

其他人建议的GridLayout 不够灵活,无法做到这一点。为此,请使用android:layout_weight 属性。这允许您根据指定的分数填充可用空间。

权重相同的示例:

<LinearLayout android:layout_width="match_parent" android:layout_height="100dp">
    <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="A" />
    <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="B" />    
    <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="C" />
</LinearLayout>

不同权重的示例:

<LinearLayout android:layout_width="match_parent" android:layout_height="100dp">
    <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="A" />
    <Button android:layout_weight="2" android:layout_width="match_parent" android:layout_height="match_parent" android:text="B" />    
    <Button android:layout_weight="2" android:layout_width="match_parent" android:layout_height="match_parent" android:text="C" />
</LinearLayout>

更复杂的例子

这是一个更复杂的布局示例,例如在使用多个 LinearLayouts 的计算器应用中使用的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
    <LinearLayout android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="7" />
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="8" />    
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="9" />
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="x" />
    </LinearLayout>

    <LinearLayout android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="4" />
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="5" />    
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="6" />
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="-" />
    </LinearLayout>

    <LinearLayout android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="1" />
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="2" />    
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="3" />
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="+" />
    </LinearLayout>

    <LinearLayout android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="0" />
        <Button android:layout_weight="1.5" android:layout_width="match_parent" android:layout_height="match_parent" android:text="." />    
        <Button android:layout_weight="1.5" android:layout_width="match_parent" android:layout_height="match_parent" android:text="=" />
    </LinearLayout>
</LinearLayout>

【讨论】:

  • 查看我的第二点:“使用布局权重嵌套线性布局。这可行,但 lint 会发出性能警告,因为我正在嵌套权重。最重要的是,它没有考虑文本大小。等等小屏幕,文本被切掉。相反,在大屏幕上,文本太小。这也是我目前最好的解决方案,但正如您从屏幕截图中看到的那样,文本不会随视图缩放
  • @grandstaish 不要担心性能。我在我的应用程序中使用它,它在低端设备上也能正常工作。您可以通过为不同的屏幕尺寸使用不同的布局来正确缩放文本。它不会是自动的,但它与您以非编程方式获得的一样接近。
  • 是的。您甚至不需要额外的布局,您只需为不同的屏幕尺寸使用不同的样式(即在每个样式目录中定义不同的 textSize)。这看起来是最好的解决方案。除非其他人提出什么建议,否则我明天会将其标记为答案
【解决方案2】:

您可以简单地自己编写一个ViewGroup 子类来执行您想要的操作,并且仍然在 XML 布局定义中使用此布局,就像使用任何预定义布局一样。或者,查看GridLayout 类。也许这个ViewGroup 实现已经可以满足您的需求。 (http://developer.android.com/reference/android/widget/GridLayout.html) 最后,这些ViewGroup 布局以编程方式计算其包含的View 组件的大小,如果没有预定义的布局提供您需要的功能,那么除了实现您的个性化需求之外别无他法。

但是,按钮 View 实例仍应负责将其内容保持在最近一次调用 onMeasure 时收到的大小范围内。

嵌套布局到您的示例图片是必要的,并且确实应该避免使用LinearLayout 类。应该注意的是,这仍然是一种常见的做法,因为这是在使用 TableLayout 时隐式完成的。

【讨论】:

  • GridLayout 并不能完全满足我的要求。它在文档中说它不支持重量。创建我自己的自定义布局是我没有考虑过的一个有趣的选项。我认为它可能有比这更简单的解决方案。
  • 这并没有很多人想象的那么努力。我自己已经避免了很长一段时间。现在我什至只是为了优化嵌套布局。这是非常值得学习的东西,一旦你熟悉了它,你甚至可能会开始考虑将它作为许多事情的首选。
  • 我已阅读有关创建自定义布局的内容,我同意您的看法。这实际上非常容易,而且绝对是解决这个问题的方法。为这个建议喝彩。我今晚要做一个布局。
  • 如果有人感兴趣,我在问题中将自定义布局的源发布为“编辑 2”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
相关资源
最近更新 更多