ContentFrameLayout.java

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.appcompat.widget;

import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getMode;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.core.view.ViewCompat;

/**
 * @hide
 */
@RestrictTo(LIBRARY)
public class ContentFrameLayout extends FrameLayout {

    public interface OnAttachListener {
        void onDetachedFromWindow();
        void onAttachedFromWindow();
    }

    private TypedValue mMinWidthMajor;
    private TypedValue mMinWidthMinor;
    private TypedValue mFixedWidthMajor;
    private TypedValue mFixedWidthMinor;
    private TypedValue mFixedHeightMajor;
    private TypedValue mFixedHeightMinor;

    private final Rect mDecorPadding;

    private OnAttachListener mAttachListener;

    public ContentFrameLayout(@NonNull Context context) {
        this(context, null);
    }

    public ContentFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ContentFrameLayout(
            @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDecorPadding = new Rect();
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    public void dispatchFitSystemWindows(Rect insets) {
        fitSystemWindows(insets);
    }

    public void setAttachListener(OnAttachListener attachListener) {
        mAttachListener = attachListener;
    }

    /**
     * Notify this view of the window decor view's padding. We use these values when working out
     * our size for the window size attributes.
     *
     * @hide
     */
    @RestrictTo(LIBRARY)
    public void setDecorPadding(int left, int top, int right, int bottom) {
        mDecorPadding.set(left, top, right, bottom);
        if (ViewCompat.isLaidOut(this)) {
            requestLayout();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;

        final int widthMode = getMode(widthMeasureSpec);
        final int heightMode = getMode(heightMeasureSpec);

        boolean fixedWidth = false;
        if (widthMode == AT_MOST) {
            final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
            if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
                int w = 0;
                if (tvw.type == TypedValue.TYPE_DIMENSION) {
                    w = (int) tvw.getDimension(metrics);
                } else if (tvw.type == TypedValue.TYPE_FRACTION) {
                    w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
                }
                if (w > 0) {
                    w -= (mDecorPadding.left + mDecorPadding.right);
                    final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.min(w, widthSize), EXACTLY);
                    fixedWidth = true;
                }
            }
        }

        if (heightMode == AT_MOST) {
            final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
            if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
                int h = 0;
                if (tvh.type == TypedValue.TYPE_DIMENSION) {
                    h = (int) tvh.getDimension(metrics);
                } else if (tvh.type == TypedValue.TYPE_FRACTION) {
                    h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
                }
                if (h > 0) {
                    h -= (mDecorPadding.top + mDecorPadding.bottom);
                    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.min(h, heightSize), EXACTLY);
                }
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        boolean measure = false;

        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);

        if (!fixedWidth && widthMode == AT_MOST) {
            final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
            if (tv != null && tv.type != TypedValue.TYPE_NULL) {
                int min = 0;
                if (tv.type == TypedValue.TYPE_DIMENSION) {
                    min = (int) tv.getDimension(metrics);
                } else if (tv.type == TypedValue.TYPE_FRACTION) {
                    min = (int) tv.getFraction(metrics.widthPixels, metrics.widthPixels);
                }
                if (min > 0) {
                    min -= (mDecorPadding.left + mDecorPadding.right);
                }
                if (width < min) {
                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
                    measure = true;
                }
            }
        }

        if (measure) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    public TypedValue getMinWidthMajor() {
        if (mMinWidthMajor == null) mMinWidthMajor = new TypedValue();
        return mMinWidthMajor;
    }

    public TypedValue getMinWidthMinor() {
        if (mMinWidthMinor == null) mMinWidthMinor = new TypedValue();
        return mMinWidthMinor;
    }

    public TypedValue getFixedWidthMajor() {
        if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
        return mFixedWidthMajor;
    }

    public TypedValue getFixedWidthMinor() {
        if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
        return mFixedWidthMinor;
    }

    public TypedValue getFixedHeightMajor() {
        if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
        return mFixedHeightMajor;
    }

    public TypedValue getFixedHeightMinor() {
        if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
        return mFixedHeightMinor;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mAttachListener != null) {
            mAttachListener.onAttachedFromWindow();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAttachListener != null) {
            mAttachListener.onDetachedFromWindow();
        }
    }
}