public final class

FragmentContainerView

extends FrameLayout

 java.lang.Object

↳FrameLayout

↳androidx.fragment.app.FragmentContainerView

Overview

FragmentContainerView is a customized Layout designed specifically for Fragments. It extends FrameLayout, so it can reliably handle Fragment Transactions, and it also has additional features to coordinate with fragment behavior.

FragmentContainerView should be used as the container for Fragments, commonly set in the xml layout of an activity, e.g.:

 <androidx.fragment.app.FragmentContainerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/fragment_container_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 </androidx.fragment.app.FragmentContainerView>
 

FragmentContainerView can also be used to add a Fragment by using the android:name attribute. FragmentContainerView will perform a one time operation that:

You can optionally include an android:tag which allows you to use FragmentManager.findFragmentByTag(String) to retrieve the added Fragment.

 <androidx.fragment.app.FragmentContainerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/fragment_container_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.MyFragment"
        android:tag="my_tag">
 </androidx.fragment.app.FragmentContainerView>
 

FragmentContainerView should not be used as a replacement for other ViewGroups (FrameLayout, LinearLayout, etc) outside of Fragment use cases.

FragmentContainerView will only allow views returned by a Fragment's Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle). Attempting to add any other view will result in an java.lang.IllegalStateException.

Layout animations and transitions are disabled for FragmentContainerView for APIs above 17. Otherwise, Animations should be done through FragmentTransaction.setCustomAnimations(int, int, int, int). If animateLayoutChanges is set to true or FragmentContainerView.setLayoutTransition(LayoutTransition) is called directly an java.lang.UnsupportedOperationException will be thrown.

Fragments using exit animations are drawn before all others for FragmentContainerView. This ensures that exiting Fragments do not appear on top of the view.

Summary

Constructors
publicFragmentContainerView(Context context)

publicFragmentContainerView(Context context, AttributeSet attrs)

Do not call this constructor directly.

publicFragmentContainerView(Context context, AttributeSet attrs, int defStyleAttr)

Do not call this constructor directly.

Methods
public voidaddView(View child, int index, ViewGroup.LayoutParams params)

FragmentContainerView will only allow views returned by a Fragment's Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).

protected booleanaddViewInLayout(View child, int index, ViewGroup.LayoutParams params, boolean preventRequestLayout)

FragmentContainerView will only allow views returned by a Fragment's Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).

protected voiddispatchDraw(Canvas canvas)

protected booleandrawChild(Canvas canvas, View child, long drawingTime)

public voidendViewTransition(View view)

public WindowInsetsonApplyWindowInsets(WindowInsets insets)

public voidremoveAllViewsInLayout()

protected voidremoveDetachedView(View child, boolean animate)

public voidremoveView(View view)

public voidremoveViewAt(int index)

public voidremoveViewInLayout(View view)

public voidremoveViews(int start, int count)

public voidremoveViewsInLayout(int start, int count)

public voidsetLayoutTransition(LayoutTransition transition)

When called, this method throws a java.lang.UnsupportedOperationException on APIs above 17.

public voidstartViewTransition(View view)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public FragmentContainerView(Context context)

public FragmentContainerView(Context context, AttributeSet attrs)

Do not call this constructor directly. Doing so will result in an java.lang.UnsupportedOperationException.

public FragmentContainerView(Context context, AttributeSet attrs, int defStyleAttr)

Do not call this constructor directly. Doing so will result in an java.lang.UnsupportedOperationException.

Methods

public void setLayoutTransition(LayoutTransition transition)

When called, this method throws a java.lang.UnsupportedOperationException on APIs above 17. On APIs 17 and below, it calls FrameLayout This can be called either explicitly, or implicitly by setting animateLayoutChanges to true.

View animations and transitions are disabled for FragmentContainerView for APIs above 17. Use FragmentTransaction.setCustomAnimations(int, int, int, int) and FragmentTransaction.setTransition(int).

Parameters:

transition: The LayoutTransition object that will animated changes in layout. A value of null means no transition will run on layout changes.

public WindowInsets onApplyWindowInsets(WindowInsets insets)

The sys ui flags must be set to enable extending the layout into the window insets.

protected void dispatchDraw(Canvas canvas)

protected boolean drawChild(Canvas canvas, View child, long drawingTime)

public void startViewTransition(View view)

public void endViewTransition(View view)

public void addView(View child, int index, ViewGroup.LayoutParams params)

FragmentContainerView will only allow views returned by a Fragment's Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle). Attempting to add any other view will result in an java.lang.IllegalStateException.

protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params, boolean preventRequestLayout)

FragmentContainerView will only allow views returned by a Fragment's Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle). Attempting to add any other view will result in an java.lang.IllegalStateException.

public void removeViewAt(int index)

public void removeViewInLayout(View view)

public void removeView(View view)

public void removeViews(int start, int count)

public void removeViewsInLayout(int start, int count)

public void removeAllViewsInLayout()

protected void removeDetachedView(View child, boolean animate)

Source

/*
 * Copyright 2019 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.fragment.app;

import android.animation.LayoutTransition;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.R;

import java.util.ArrayList;
/**
 * FragmentContainerView is a customized Layout designed specifically for Fragments. It extends
 * {@link FrameLayout}, so it can reliably handle Fragment Transactions, and it also has additional
 * features to coordinate with fragment behavior.
 *
 * <p>FragmentContainerView should be used as the container for Fragments, commonly set in the
 * xml layout of an activity, e.g.: <p>
 *
 * <pre class="prettyprint">
 * &lt;androidx.fragment.app.FragmentContainerView
 *        xmlns:android="http://schemas.android.com/apk/res/android"
 *        xmlns:app="http://schemas.android.com/apk/res-auto"
 *        android:id="@+id/fragment_container_view"
 *        android:layout_width="match_parent"
 *        android:layout_height="match_parent"&gt;
 * &lt;/androidx.fragment.app.FragmentContainerView&gt;
 * </pre>
 *
 * <p> FragmentContainerView can also be used to add a Fragment by using the
 * <code>android:name</code> attribute. FragmentContainerView will perform a one time operation
 * that:
 *
 * <ul>
 * <li>Creates a new instance of the Fragment</li>
 * <li>Calls {@link Fragment#onInflate(Context, AttributeSet, Bundle)}</li>
 * <li>Executes a FragmentTransaction to add the Fragment to the appropriate FragmentManager</li>
 * </ul>
 *
 * <p> You can optionally include an <code>android:tag</code> which allows you to use
 * {@link FragmentManager#findFragmentByTag(String)} to retrieve the added Fragment.
 *
 * <pre class="prettyprint">
 * &lt;androidx.fragment.app.FragmentContainerView
 *        xmlns:android="http://schemas.android.com/apk/res/android"
 *        xmlns:app="http://schemas.android.com/apk/res-auto"
 *        android:id="@+id/fragment_container_view"
 *        android:layout_width="match_parent"
 *        android:layout_height="match_parent"
 *        android:name="com.example.MyFragment"
 *        android:tag="my_tag"&gt;
 * &lt;/androidx.fragment.app.FragmentContainerView&gt;
 * </pre>
 *
 * <p>FragmentContainerView should not be used as a replacement for other ViewGroups (FrameLayout,
 * LinearLayout, etc) outside of Fragment use cases.
 *
 * <p>FragmentContainerView will only allow views returned by a Fragment's
 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any other
 * view will result in an {@link IllegalStateException}.
 *
 * <p>Layout animations and transitions are disabled for FragmentContainerView for APIs above 17.
 * Otherwise, Animations should be done through
 * {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}. If animateLayoutChanges is
 * set to <code>true</code> or {@link #setLayoutTransition(LayoutTransition)} is called directly an
 * {@link UnsupportedOperationException} will be thrown.
 *
 * <p>Fragments using exit animations are drawn before all others for FragmentContainerView. This
 * ensures that exiting Fragments do not appear on top of the view.
 */
public final class FragmentContainerView extends FrameLayout {

    private ArrayList<View> mDisappearingFragmentChildren;

    private ArrayList<View> mTransitioningFragmentViews;

    // Used to indicate whether the FragmentContainerView should override the default ViewGroup
    // drawing order.
    private boolean mDrawDisappearingViewsFirst = true;

    public FragmentContainerView(@NonNull Context context) {
        super(context);
    }

    /**
     * Do not call this constructor directly. Doing so will result in an
     * {@link UnsupportedOperationException}.
     */
    public FragmentContainerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Do not call this constructor directly. Doing so will result in an
     * {@link UnsupportedOperationException}.
     */
    public FragmentContainerView(
            @NonNull Context context,
            @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if (attrs != null) {
            String name = attrs.getClassAttribute();
            String attribute = "class";
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FragmentContainerView);
            if (name == null) {
                name = a.getString(R.styleable.FragmentContainerView_android_name);
                attribute = "android:name";
            }
            a.recycle();
            if (name != null && !isInEditMode()) {
                throw new UnsupportedOperationException("FragmentContainerView must be within "
                        + "a FragmentActivity to use " + attribute + "=\"" + name + "\"");
            }
        }
    }

    FragmentContainerView(
            @NonNull Context context,
            @NonNull AttributeSet attrs,
            @NonNull FragmentManager fm) {
        super(context, attrs);

        String name = attrs.getClassAttribute();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FragmentContainerView);
        if (name == null) {
            name = a.getString(R.styleable.FragmentContainerView_android_name);
        }
        String tag = a.getString(R.styleable.FragmentContainerView_android_tag);
        a.recycle();

        int id = getId();
        Fragment existingFragment = fm.findFragmentById(id);
        // If there is a name and there is no existing fragment,
        // we should add an inflated Fragment to the view.
        if (name != null && existingFragment == null) {
            if (id <= 0) {
                final String tagMessage = tag != null
                        ? " with tag " + tag
                        : "";
                throw new IllegalStateException("FragmentContainerView must have an android:id to "
                        + "add Fragment " + name + tagMessage);
            }
            Fragment containerFragment =
                    fm.getFragmentFactory().instantiate(context.getClassLoader(), name);
            containerFragment.onInflate(context, attrs, null);
            fm.beginTransaction()
                    .setReorderingAllowed(true)
                    .add(this, containerFragment, tag)
                    .commitNowAllowingStateLoss();
        }
    }

    /**
     * When called, this method throws a {@link UnsupportedOperationException} on APIs above 17.
     * On APIs 17 and below, it calls {@link FrameLayout#setLayoutTransition(LayoutTransition)}
     * This can be called either explicitly, or implicitly by setting animateLayoutChanges to
     * <code>true</code>.
     *
     * <p>View animations and transitions are disabled for FragmentContainerView for APIs above 17.
     * Use {@link FragmentTransaction#setCustomAnimations(int, int, int, int)} and
     * {@link FragmentTransaction#setTransition(int)}.
     *
     * @param transition The LayoutTransition object that will animated changes in layout. A value
     * of <code>null</code> means no transition will run on layout changes.
     * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
     */
    @Override
    public void setLayoutTransition(@Nullable LayoutTransition transition) {
        if (Build.VERSION.SDK_INT < 18) {
            // Transitions on APIs below 18 are using an empty LayoutTransition as a replacement
            // for suppressLayout(true) and null LayoutTransition to then unsuppress it. If the
            // API is below 18, we should allow FrameLayout to handle this call.
            super.setLayoutTransition(transition);
            return;
        }

        throw new UnsupportedOperationException(
                "FragmentContainerView does not support Layout Transitions or "
                        + "animateLayoutChanges=\"true\".");
    }

    /**
     * {@inheritDoc}
     *
     * <p>The sys ui flags must be set to enable extending the layout into the window insets.
     */
    @NonNull
    @RequiresApi(20)
    @Override
    public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            // Give child views fresh insets.
            child.dispatchApplyWindowInsets(new WindowInsets(insets));
        }
        return insets;
    }

    @Override
    protected void dispatchDraw(@NonNull Canvas canvas) {
        if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren != null) {
            for (int i = 0; i < mDisappearingFragmentChildren.size(); i++) {
                super.drawChild(canvas, mDisappearingFragmentChildren.get(i), getDrawingTime());
            }
        }
        super.dispatchDraw(canvas);
    }

    @Override
    protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
        if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren != null
                && mDisappearingFragmentChildren.size() > 0) {
            // If the child is disappearing, we have already drawn it so skip.
            if (mDisappearingFragmentChildren.contains(child)) {
                return false;
            }
        }
        return super.drawChild(canvas, child, drawingTime);
    }

    @Override
    public void startViewTransition(@NonNull View view) {
        if (view.getParent() == this) {
            if (mTransitioningFragmentViews == null) {
                mTransitioningFragmentViews = new ArrayList<>();
            }
            mTransitioningFragmentViews.add(view);
        }
        super.startViewTransition(view);
    }

    @Override
    public void endViewTransition(@NonNull View view) {
        if (mTransitioningFragmentViews != null) {
            mTransitioningFragmentViews.remove(view);
            if (mDisappearingFragmentChildren != null
                    && mDisappearingFragmentChildren.remove(view)) {
                mDrawDisappearingViewsFirst = true;
            }
        }
        super.endViewTransition(view);
    }

    // Used to indicate the container should change the default drawing order.
    void setDrawDisappearingViewsLast(boolean drawDisappearingViewsFirst) {
        mDrawDisappearingViewsFirst = drawDisappearingViewsFirst;
    }

    /**
     * <p>FragmentContainerView will only allow views returned by a Fragment's
     * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
     *  other view will result in an {@link IllegalStateException}.
     *
     * {@inheritDoc}
     */
    @Override
    public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutParams params) {
        if (FragmentManager.getViewFragment(child) == null) {
            throw new IllegalStateException("Views added to a FragmentContainerView must be"
                    + " associated with a Fragment. View " + child + " is not associated with a"
                    + " Fragment.");
        }
        super.addView(child, index, params);
    }

    /**
     * <p>FragmentContainerView will only allow views returned by a Fragment's
     * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
     *  other view will result in an {@link IllegalStateException}.
     *
     * {@inheritDoc}
     */
    @Override
    protected boolean addViewInLayout(@NonNull View child, int index,
            @Nullable ViewGroup.LayoutParams params, boolean preventRequestLayout) {
        if (FragmentManager.getViewFragment(child) == null) {
            throw new IllegalStateException("Views added to a FragmentContainerView must be"
                    + " associated with a Fragment. View " + child + " is not associated with a"
                    + " Fragment.");
        }
        return super.addViewInLayout(child, index, params, preventRequestLayout);
    }

    @Override
    public void removeViewAt(int index) {
        View view = getChildAt(index);
        addDisappearingFragmentView(view);
        super.removeViewAt(index);
    }

    @Override
    public void removeViewInLayout(@NonNull View view) {
        addDisappearingFragmentView(view);
        super.removeViewInLayout(view);
    }

    @Override
    public void removeView(@NonNull View view) {
        addDisappearingFragmentView(view);
        super.removeView(view);
    }

    @Override
    public void removeViews(int start, int count) {
        for (int i = start; i < start + count; i++) {
            final View view = getChildAt(i);
            addDisappearingFragmentView(view);
        }
        super.removeViews(start, count);
    }

    @Override
    public void removeViewsInLayout(int start, int count) {
        for (int i = start; i < start + count; i++) {
            final View view = getChildAt(i);
            addDisappearingFragmentView(view);
        }
        super.removeViewsInLayout(start, count);
    }

    @Override
    public void removeAllViewsInLayout() {
        for (int i = getChildCount() - 1; i >= 0; i--) {
            final View view = getChildAt(i);
            addDisappearingFragmentView(view);
        }
        super.removeAllViewsInLayout();
    }

    @Override
    protected void removeDetachedView(@NonNull View child, boolean animate) {
        if (animate) {
            addDisappearingFragmentView(child);
        }
        super.removeDetachedView(child, animate);
    }

    /**
     * This method adds a {@link View} to the list of disappearing views only if it meets the
     * proper conditions to be considered a disappearing view.
     *
     * @param v {@link View} that might be added to list of disappearing views
     */
    private void addDisappearingFragmentView(@NonNull View v) {
        if (v.getAnimation() != null || (mTransitioningFragmentViews != null
                && mTransitioningFragmentViews.contains(v))) {
            if (mDisappearingFragmentChildren == null) {
                mDisappearingFragmentChildren = new ArrayList<>();
            }
            mDisappearingFragmentChildren.add(v);
        }
    }
}