chromium/ui/android/java/src/org/chromium/ui/widget/OptimizedFrameLayout.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.ui.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

import java.util.ArrayList;
import java.util.List;

/**
 * This class overrides {@link FrameLayout#onMeasure} so that it does not call onMeasure on its
 * children multiple times during the same {@link FrameLayout#onMeasure} call.
 */
public class OptimizedFrameLayout extends FrameLayout {
    private static class MeasurementState {
        final View mView;
        final int mWidthMeasureSpec;
        final int mHeightMeasureSpec;

        MeasurementState(View view, int widthMeasureSpec, int heightMeasureSpec) {
            mView = view;
            mWidthMeasureSpec = widthMeasureSpec;
            mHeightMeasureSpec = heightMeasureSpec;
        }
    }

    private final List<MeasurementState> mMatchParentChildren = new ArrayList<>();

    public OptimizedFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY
                        || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (getMeasureAllChildren() || child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec =
                        getChildMeasureSpec(
                                widthMeasureSpec,
                                paddingLeft + paddingRight + lp.leftMargin + lp.rightMargin,
                                lp.width);
                final int childHeightMeasureSpec =
                        getChildMeasureSpec(
                                heightMeasureSpec,
                                paddingTop + paddingBottom + lp.topMargin + lp.bottomMargin,
                                lp.height);

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                maxWidth =
                        Math.max(
                                maxWidth,
                                child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight =
                        Math.max(
                                maxHeight,
                                child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT
                            || lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(
                                new MeasurementState(
                                        child, childWidthMeasureSpec, childHeightMeasureSpec));
                    }
                }
            }
        }

        // Account for padding too
        maxWidth += paddingLeft + paddingRight;
        maxHeight += paddingTop + paddingBottom;

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        setMeasuredDimension(
                resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(
                        maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final MeasurementState measurementState = mMatchParentChildren.get(i);
                final View child = measurementState.mView;
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width =
                            Math.max(
                                    0,
                                    getMeasuredWidth()
                                            - paddingLeft
                                            - paddingRight
                                            - lp.leftMargin
                                            - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec =
                            getChildMeasureSpec(
                                    widthMeasureSpec,
                                    paddingLeft + paddingRight + lp.leftMargin + lp.rightMargin,
                                    lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height =
                            Math.max(
                                    0,
                                    getMeasuredHeight()
                                            - paddingTop
                                            - paddingBottom
                                            - lp.topMargin
                                            - lp.bottomMargin);
                    childHeightMeasureSpec =
                            MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec =
                            getChildMeasureSpec(
                                    heightMeasureSpec,
                                    paddingTop + paddingBottom + lp.topMargin + lp.bottomMargin,
                                    lp.height);
                }

                if (measurementState.mWidthMeasureSpec != childWidthMeasureSpec
                        || measurementState.mHeightMeasureSpec != childHeightMeasureSpec) {
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }
        }
        mMatchParentChildren.clear();
    }
}