3

如果你有一个计算器应用程序并且你想编写一个看起来像这样的布局,你如何缩放按钮和显示以适应所有屏幕尺寸?

我研究过的想法:

  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>

最终结果:

肖像 景观

4

2 回答 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>

更复杂的例子

这是一个更复杂的布局示例,例如在使用多个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>

于 2013-05-11T14:11:00.793 回答
2

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

但是,按钮View实例仍有责任将其内容保持在最近一次调用onMeasure.

您的示例图片需要将布局嵌套到扩展,并且LinearLayout确实应该避免使用该类。应该注意的是,这仍然是一种常见的做法,因为这是在使用TableLayout.

于 2013-05-11T13:26:48.757 回答