Scaling layout with invariant aspect ratio in Andr

2019-03-31 18:54发布

问题:

I want to create a GUI for my app which is essentially a background image with some controls on top of it. The important (and tricky) part is that the background image should keep its aspect ratio and scale (with black borders if it's not a perfect fit), and that the controls are aligned with certain parts of the background image.

I was thinking of solving this by inheriting AbsoluteLayout (or my copy of it since it's deprecated), scaling/positioning it properly and have it draw my background image. Then, I would have it place its children according to "scaled absolute positions", using the scaling factor [Measured layout size] / [original background image size].

My question is, is there a better way to do this? It seems like a complicated way to do something I would believe is relatively common? (Aligning button images on a background image pixel perfectly). I'm thankful for all pointers and suggestions.


I ended up using the above strategy. For completion, here's what I did:

I created a class ScalingLayout, extending AbsoluteLayout, and added two xml attributes that allows me to specify "virtual dimensions" for the layout. Those are the dimensons I position my views along, and the layout makes sure those relative positions are scaled correctly when the whole layout is scaled. So in xml it looks like:

<ScalingLayout
    mynamespace:virtualWidth="100"
    mynamespace:virtualHeight="100"
    ...
>

For information on how to define custom xml attributes and getting those values in the class constructor, check out this question: Defining custom attrs

Furthermore, I overrided onMeasure and onLayout in my ScalingLayout.

// Overridden to retain aspect of this layout view
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
    double aspect = getSize(widthMeasureSpec) / (double)getSize(heightMeasureSpec);
    // Those are from XML layout
    double virtualAspect = _virtualWidth / (double)_virtualHeight;
    int width, height;

    measureChildren(widthMeasureSpec, heightMeasureSpec);

    if(aspect > virtualAspect) {
        height = getSize(heightMeasureSpec);
        width = height * virtualAspect;
    } else {
        width = getSize(widthMeasureSpec);
        height = width / virtualAspect;
    }

    setMeasuredDimension(width, height);
}

...

protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
    double factor = (right - left) / (double)_virtualWidth;

    nchildren = getChildCount();

    for(int i = 0; i < nchildren; i++) {
        View child = getChildAt(i);
        LayoutParams lp = child.getLayoutParams();
        // Scale child according to given space
        child.layout(lp.x * factor,
                     lp.y * factor,
                     (lp.x + child.getMeasuredWidth()) * factor,
                     (lp.y + child.getMeasuredHeight()) * factor );
    }
}

Now, just add views in XML and specify dimensions and positions just as for an AbsoluteLayout, but think in terms of the virtual dimensions.