chromium/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/view/AspectRatioFrameLayout.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.download.home.list.view;

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

import org.chromium.chrome.browser.download.internal.R;

/**
 * Helper FrameLayout that knows how to make children fit certain aspect ratios.
 * TODO(dtrainor): Remove this once we get proper support for constraint layout.
 *
 * Below is an example usage to make an ImageView a square based on the width.  Note that you do not
 * need to set both layout_width and layout_height if setting layout_aspectRatio.
 *
 * <org.chromium.chrome.browser.download.home.list.view.AspectRatioFrameLayout
 *     xmlns:android="http://schemas.android.com/apk/res/android"
 *     xmlns:app="http://schemas.android.com/apk/res-auto"
 *     android:layout_width="match_parent"
 *     android:layout_height="wrap_content">
 *     <ImageView
 *         android:layout_width="match_parent"
 *         app:layout_aspectRatio="100%" />
 * </org.chromium.chrome.browser.download.home.list.view.AspectRatioFrameLayout>
 */
public class AspectRatioFrameLayout extends FrameLayout {
    /** Creates an instance of {@link AspectRatioFrameLayout}. */
    public AspectRatioFrameLayout(Context context) {
        this(context, null);
    }

    /** Creates an instance of {@link AspectRatioFrameLayout}. */
    public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /** Creates an instance of {@link AspectRatioFrameLayout}. */
    public AspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // FrameLayout implementation.
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

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

    @Override
    @SuppressWarnings("DrawAllocation")
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width =
                View.MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int height =
                View.MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();

        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);

            if (!(view.getLayoutParams() instanceof LayoutParams)) continue;
            LayoutParams params = (LayoutParams) view.getLayoutParams();

            params.mRestorableWidth = params.mOriginalWidth;
            params.mRestorableHeight = params.mOriginalHeight;

            float aspectRatio = params.aspectRatio;
            if (aspectRatio <= 0.f) continue;

            boolean widthMovable = params.mOverrodeWidth || params.mRestorableWidth == 0;
            boolean heightMovable = params.mOverrodeHeight || params.mRestorableHeight == 0;

            if (widthMovable) {
                int childHeight =
                        params.height == LayoutParams.MATCH_PARENT ? height : params.height;
                params.width = Math.round(childHeight * aspectRatio);
                params.mOverrodeWidth = true;
            }
            if (heightMovable) {
                int childWidth = params.width == LayoutParams.MATCH_PARENT ? width : params.width;
                params.height = Math.round(childWidth / aspectRatio);
                params.mOverrodeHeight = true;
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);

            if (!(view.getLayoutParams() instanceof LayoutParams)) continue;
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            if (params.mOverrodeWidth) params.width = params.mRestorableWidth;
            if (params.mOverrodeHeight) params.height = params.mRestorableHeight;
            params.mOverrodeWidth = false;
            params.mOverrodeHeight = false;
        }
    }

    /** A set of layout parameters for {@link AspectRatioFrameLayout}. */
    public static class LayoutParams extends FrameLayout.LayoutParams {
        /** The aspect ratio to use and enforce. */
        public float aspectRatio;

        // Restorable parameters.
        private boolean mOverrodeWidth;
        private boolean mOverrodeHeight;
        private int mRestorableWidth;
        private int mRestorableHeight;
        private int mOriginalWidth;
        private int mOriginalHeight;

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

            TypedArray array =
                    context.obtainStyledAttributes(
                            attrs, R.styleable.AspectRatioFrameLayout_Layout);
            aspectRatio =
                    array.getFraction(
                            R.styleable.AspectRatioFrameLayout_Layout_layout_aspectRatio,
                            1,
                            1,
                            0.f);
            array.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(FrameLayout.LayoutParams source) {
            super((MarginLayoutParams) source);
            gravity = source.gravity;
        }

        @Override
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            // Do not throw errors if width or height aren't supplied.
            width = a.getLayoutDimension(widthAttr, 0);
            height = a.getLayoutDimension(heightAttr, 0);
            mOriginalWidth = width;
            mOriginalHeight = height;
        }
    }
}