If you have a calculator app and you want to write a layout that looks like this, how do you scale the buttons and display to fit on all screen sizes?
Ideas I have looked into:
Programatically calculating the height and width of each component. Programatically creating the views gives you the most power, but is not exactly ideal. I'd prefer to write my UI in XML.
Nesting LinearLayouts with Layout weights. This works, but lint gives performance warnings because I am nesting weights. On top of that, it does not take into account text size. So on small screens, text is chopped off. Conversely, on large screens, text is too small.
EDIT: 3. Using a TableLayout with nested weights. Considering these extend from LinearLayout, I assume the lack of lint warnings is irrelevant, this is still going to cause a loss in performance right?
Is there a better way? I feel like I am missing something obvious
EDIT 2: In case anyone is interested in the solution for this, I have created a custom layout (as raphw suggested) and will post the source code here:
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>
Usage as follows:
<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>
And the end result:
 

The GridLayout as suggested by someone else is not flexible enough to do this. To do this, use the android:layout_weight property. This allows you to fill the available space according to the fractions specified.
Example with the equal weights:
<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>

Example with different weights:
<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>

More complex example
Here is a more complex example for a layout like used in a calculator app that uses multiple 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>

You could simply write a ViewGroup subclass yourself which does what you want and still use this layout in an XML layout definition just as you would use any predefined layout. Alternatively, look at the GridLayout class. Maybe this ViewGroup implementation already does what you are looking for. (http://developer.android.com/reference/android/widget/GridLayout.html) In the end, these ViewGroup layouts programatically compute the size of their contained View components and if no predefined layout offers the functionality you require, there is no other way than implementing your individual requirements.
However, it should remain the responsibility of the button View instances to keep their content within the scope the size they received during their latest call of onMeasure.
Nesting layouts to the extend it would be necessary with your example picture and the LinearLayout class should indeed be avoided. It should be noted that is nevertheless a common practice since this is done implicitly when using a TableLayout.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With