Skip to content

Instantly share code, notes, and snippets.

@Zhuinden
Last active November 28, 2022 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zhuinden/6ccd443d09c49f43f13b6169ae3d7966 to your computer and use it in GitHub Desktop.
Save Zhuinden/6ccd443d09c49f43f13b6169ae3d7966 to your computer and use it in GitHub Desktop.
CustomFragmentContainerView
import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
public class CustomFragmentContainerView extends FrameLayout {
private ArrayList<View> mAppearingFragmentChildren = new ArrayList<>();
private ArrayList<View> mDisappearingFragmentChildren = new ArrayList<>();
private ArrayList<View> mTransitioningFragmentViews = new ArrayList<>();
private boolean mDrawAppearingViewsFirst = false;
private boolean mDrawDisappearingViewsFirst = false;
public CustomFragmentContainerView(@NonNull Context context) {
super(context);
}
public CustomFragmentContainerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomFragmentContainerView(
@NonNull Context context,
@Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 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 CustomFragmentContainerView 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(
"CustomFragmentContainerView 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) {
for (int i = 0; i < mDisappearingFragmentChildren.size(); i++) {
super.drawChild(canvas, mDisappearingFragmentChildren.get(i), getDrawingTime());
}
}
if (mDrawAppearingViewsFirst) {
for (int i = 0; i < mAppearingFragmentChildren.size(); i++) {
super.drawChild(canvas, mAppearingFragmentChildren.get(i), getDrawingTime());
}
}
super.dispatchDraw(canvas);
}
@Override
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren.size() > 0) {
// If the child is disappearing, we have already drawn it so skip.
if (mDisappearingFragmentChildren.contains(child)) {
return false;
}
}
if (mDrawAppearingViewsFirst && mAppearingFragmentChildren.size() > 0) {
// If the child is appearing, we have already drawn it so skip.
if (mAppearingFragmentChildren.contains(child)) {
return false;
}
}
return super.drawChild(canvas, child, drawingTime);
}
@Override
public void startViewTransition(@NonNull View view) {
if (view.getParent() == this) {
mTransitioningFragmentViews.add(view);
}
super.startViewTransition(view);
}
@Override
public void endViewTransition(@NonNull View view) {
mTransitioningFragmentViews.remove(view);
mDisappearingFragmentChildren.remove(view);
mAppearingFragmentChildren.remove(view);
super.endViewTransition(view);
}
// Used to indicate the container should change the default drawing order.
public void setDrawAppearingViewsLast(boolean drawAppearingViewsFirst) {
mDrawAppearingViewsFirst = drawAppearingViewsFirst;
}
// Used to indicate the container should change the default drawing order.
public void setDrawDisappearingViewsLast(boolean drawDisappearingViewsFirst) {
mDrawDisappearingViewsFirst = drawDisappearingViewsFirst;
}
/**
* <p>CustomFragmentContainerView 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>
* {@inheritDoc}
*/
@Override
public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutParams params) {
//noinspection unused
Fragment fragment = FragmentManager.findFragment(child);
addAppearingFragmentView(child);
super.addView(child, index, params);
}
/**
* <p>CustomFragmentContainerView 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>
* {@inheritDoc}
*/
@Override
protected boolean addViewInLayout(@NonNull View child, int index,
@Nullable ViewGroup.LayoutParams params, boolean preventRequestLayout) {
//noinspection unused
Fragment fragment = FragmentManager.findFragment(child);
addAppearingFragmentView(child);
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.contains(v)) {
mDisappearingFragmentChildren.add(v);
}
}
/**
* 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 addAppearingFragmentView(@NonNull View v) {
if (v.getAnimation() != null || mTransitioningFragmentViews.contains(v)) {
mAppearingFragmentChildren.add(v);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment