Android - 如何缩放视图以完美地适应全屏应用程序的高度和宽度?

4
如果您有一个计算器应用程序,并希望编写一个类似于此界面的布局,那么如何调整按钮和显示以适配所有屏幕大小?
我看过的一些想法有:
1.程序计算每个组件的高度和宽度。程序化创建视图能够给你最大的控制权,但并不是最理想的方法。我更喜欢在XML中编写我的UI。
2.使用具有布局权重的嵌套LinearLayouts。这个方法可以运作,但是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>

最终结果如下:

肖像 风景


1
这个问题上的前几个答案应该能满足你的需求。https://dev59.com/T2855IYBdhLWcg3wj1QS - Robert Rowntree
这个问题的所有3个答案都是关于ImageView特定的。 - Bradley Campbell
此外,它不考虑文本大小。因此,在小屏幕上,文本被截断。相反,在大屏幕上,文本太小了。这不是LinearLayout方法的问题,而是你尝试做的事情的问题 - 任何解决方案都会有这个问题。对于LinearLayout解决方案,嵌套权重在不同的维度中,所以不应该是性能问题。要么警告很愚蠢,要么LinearLayout很愚蠢,不确定哪一个。 - Karu
2个回答

3
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>

更复杂的示例

这是一个更复杂的示例,用于类似计算器应用程序中使用多个LinearLayout的布局:

<?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>


请看我的第二点:“使用Layout权重嵌套LinearLayouts。这个方法可以实现,但是lint会给出性能警告,因为我在嵌套权重。此外,它也没有考虑文本大小。所以在小屏幕上,文本会被裁剪。相反,在大屏幕上,文本太小了。”目前这是我最好的解决方案,但正如您从截图中看到的那样,文本不会随着视图缩放。 - Bradley Campbell
1
@grandstaish 不用担心性能问题。我在我的应用程序中使用它,它在低端设备上也可以正常工作。您可以通过为不同的屏幕大小使用不同的布局来使文本按比例缩放。虽然它不是自动的,但这是您在非编程情况下最接近的方案了。 - Overv
没错。你甚至不需要额外的布局,只需为不同的屏幕尺寸使用不同的样式(即在每个样式目录中定义不同的textSize)。这似乎是最好的解决方案。除非有人提出其他建议,否则我明天会将其标记为答案。 - Bradley Campbell

2
您可以自己编写一个 ViewGroup 子类,以实现您所需的功能,并仍可在 XML 布局定义中使用此布局,就像使用任何预定义布局一样。或者,查看 GridLayout 类。也许这个 ViewGroup 实现已经满足了您的要求。(http://developer.android.com/reference/android/widget/GridLayout.html) 最终,这些 ViewGroup 布局会动态计算其包含的 View 组件的大小,如果没有预定义的布局提供所需的功能,则只能实现您的个性化需求。

然而,按钮 View 实例保持其内容在最新调用 onMeasure 时接收到的范围内,是其责任。

应避免嵌套布局,以达到您的示例图片和 LinearLayout 类所需的程度。值得注意的是,尽管如此,这仍然是一种常见的做法,因为使用 TableLayout 时会隐式执行此操作。


GridLayout并不能完全满足我的需求。文档中说它不支持权重。创建自己的自定义布局是一个有趣的选项,我之前没有考虑过。我以为可能会有比那更简单的解决方案。 - Bradley Campbell
这并不像许多人想象的那样需要很大的努力。我自己也曾经避免使用它相当长一段时间。现在,我甚至会使用它来优化嵌套布局。学习它是非常值得的,一旦你熟悉了它,你甚至可能开始将其视为许多事情的首选选项。 - Rafael Winterhalter
我已经阅读了有关创建自定义布局的内容,我同意你的看法。这实际上非常容易,而且绝对是解决这个问题的好方法。感谢你的建议。我今晚会制作一个布局。 - Bradley Campbell
如果有人感兴趣,我在问题中发布了自定义布局的源代码作为“Edit 2”。 - Bradley Campbell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接