[go: nahoru, domu]

blob: 629bd7e5d710526c9de27b82e8be45982397c5c7 [file] [log] [blame]
jbwoodsb1757aa2019-06-12 11:17:06 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.fragment.app;
18
jbwoodsb1757aa2019-06-12 11:17:06 -070019import android.animation.LayoutTransition;
20import android.content.Context;
Jeremy Woodsf68a1e32019-09-05 12:48:57 -070021import android.content.res.TypedArray;
jbwoods2534c732019-06-20 12:42:37 -070022import android.graphics.Canvas;
Jeremy Woods2882e142019-09-17 13:56:12 -070023import android.os.Build;
Jeremy Woods6c5151d2019-07-26 16:58:58 -070024import android.os.Bundle;
jbwoodsb1757aa2019-06-12 11:17:06 -070025import android.util.AttributeSet;
Jeremy Woods6c5151d2019-07-26 16:58:58 -070026import android.view.LayoutInflater;
jbwoods2534c732019-06-20 12:42:37 -070027import android.view.View;
Jeremy Woods6c5151d2019-07-26 16:58:58 -070028import android.view.ViewGroup;
jbwoodsd46ea742019-06-14 16:18:00 -070029import android.view.WindowInsets;
jbwoodsb1757aa2019-06-12 11:17:06 -070030import android.widget.FrameLayout;
31
32import androidx.annotation.NonNull;
33import androidx.annotation.Nullable;
jbwoodsd46ea742019-06-14 16:18:00 -070034import androidx.annotation.RequiresApi;
Jeremy Woodsf68a1e32019-09-05 12:48:57 -070035import androidx.fragment.R;
jbwoods2534c732019-06-20 12:42:37 -070036
37import java.util.ArrayList;
jbwoodsb1757aa2019-06-12 11:17:06 -070038/**
39 * FragmentContainerView is a customized Layout designed specifically for Fragments. It extends
40 * {@link FrameLayout}, so it can reliably handle Fragment Transactions, and it also has additional
41 * features to coordinate with fragment behavior.
42 *
Jeremy Woods92946252019-07-25 09:39:22 -070043 * <p>FragmentContainerView should be used as the container for Fragments, commonly set in the
44 * xml layout of an activity, e.g.: <p>
45 *
46 * <pre class="prettyprint">
47 * &lt;androidx.fragment.app.FragmentContainerView
48 * xmlns:android="http://schemas.android.com/apk/res/android"
49 * xmlns:app="http://schemas.android.com/apk/res-auto"
50 * android:id="@+id/fragment_container_view"
51 * android:layout_width="match_parent"
52 * android:layout_height="match_parent"&gt;
53 * &lt;/androidx.fragment.app.FragmentContainerView&gt;
54 * </pre>
55 *
56 * <p>FragmentContainerView should not be used as a replacement for other ViewGroups (FrameLayout,
57 * LinearLayout, etc) outside of Fragment use cases.
58 *
Jeremy Woods6c5151d2019-07-26 16:58:58 -070059 * <p>FragmentContainerView will only allow views to returned by a Fragment's
60 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any other
61 * view will result in an {@link IllegalStateException}.
62 *
Jeremy Woods2882e142019-09-17 13:56:12 -070063 * <p>Layout animations and transitions are disabled for FragmentContainerView for APIs above 17.
64 * Otherwise, Animations should be done through
65 * {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}. IfanimateLayoutChanges is
66 * set to <code>true</code> or {@link #setLayoutTransition(LayoutTransition)} is called directly an
jbwoodsb1757aa2019-06-12 11:17:06 -070067 * {@link UnsupportedOperationException} will be thrown.
68 *
jbwoods2534c732019-06-20 12:42:37 -070069 * <p>Fragments using exit animations are drawn before all others for FragmentContainerView. This
Jeremy Woods92946252019-07-25 09:39:22 -070070 * ensures that exiting Fragments do not appear on top of the view.
jbwoodsb1757aa2019-06-12 11:17:06 -070071 */
Jeremy Woods8b13acf2019-08-27 15:49:43 -070072public final class FragmentContainerView extends FrameLayout {
jbwoods2534c732019-06-20 12:42:37 -070073
Jeremy Woodsf68a1e32019-09-05 12:48:57 -070074 private final String mName;
75 private final String mTag;
76 private final AttributeSet mAttributeSet;
77
jbwoods2534c732019-06-20 12:42:37 -070078 private ArrayList<View> mDisappearingFragmentChildren;
79
80 private ArrayList<View> mTransitioningFragmentViews;
jbwoodsb1757aa2019-06-12 11:17:06 -070081
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -070082 // Used to indicate whether the FragmentContainerView should override the default ViewGroup
83 // drawing order.
84 private boolean mDrawDisappearingViewsFirst = true;
85
jbwoodsb1757aa2019-06-12 11:17:06 -070086 public FragmentContainerView(@NonNull Context context) {
jbwoods2534c732019-06-20 12:42:37 -070087 this(context, null);
jbwoodsb1757aa2019-06-12 11:17:06 -070088 }
89
90 public FragmentContainerView(@NonNull Context context, @Nullable AttributeSet attrs) {
jbwoods2534c732019-06-20 12:42:37 -070091 this(context, attrs, 0);
jbwoodsb1757aa2019-06-12 11:17:06 -070092 }
93
94 public FragmentContainerView(
95 @NonNull Context context,
96 @Nullable AttributeSet attrs,
97 int defStyleAttr) {
98 super(context, attrs, defStyleAttr);
Jeremy Woodsf68a1e32019-09-05 12:48:57 -070099 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FragmentContainerView);
100 mName = a.getString(R.styleable.FragmentContainerView_android_name);
101 mTag = a.getString(R.styleable.FragmentContainerView_android_tag);
102 a.recycle();
103 mAttributeSet = attrs;
jbwoodsb1757aa2019-06-12 11:17:06 -0700104 }
105
106 /**
Jeremy Woods2882e142019-09-17 13:56:12 -0700107 * When called, this method throws a {@link UnsupportedOperationException} on APIs above 17.
108 * On APIs 17 and below, it calls {@link FrameLayout#setLayoutTransition(LayoutTransition)}
109 * This can be called either explicitly, or implicitly by setting animateLayoutChanges to
110 * <code>true</code>.
jbwoodsb1757aa2019-06-12 11:17:06 -0700111 *
Jeremy Woods2882e142019-09-17 13:56:12 -0700112 * <p>View animations and transitions are disabled for FragmentContainerView for APIs above 17.
113 * Use {@link FragmentTransaction#setCustomAnimations(int, int, int, int)} and
jbwoodsb1757aa2019-06-12 11:17:06 -0700114 * {@link FragmentTransaction#setTransition(int)}.
115 *
116 * @param transition The LayoutTransition object that will animated changes in layout. A value
117 * of <code>null</code> means no transition will run on layout changes.
118 * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
119 */
120 @Override
121 public void setLayoutTransition(@Nullable LayoutTransition transition) {
Jeremy Woods2882e142019-09-17 13:56:12 -0700122 if (Build.VERSION.SDK_INT < 18) {
123 // Transitions on APIs below 18 are using an empty LayoutTransition as a replacement
124 // for suppressLayout(true) and null LayoutTransition to then unsuppress it. If the
125 // API is below 18, we should allow FrameLayout to handle this call.
126 super.setLayoutTransition(transition);
127 return;
128 }
129
jbwoodsb1757aa2019-06-12 11:17:06 -0700130 throw new UnsupportedOperationException(
131 "FragmentContainerView does not support Layout Transitions or "
132 + "animateLayoutChanges=\"true\".");
133 }
jbwoods2534c732019-06-20 12:42:37 -0700134
jbwoodsd46ea742019-06-14 16:18:00 -0700135 /**
136 * {@inheritDoc}
137 *
138 * <p>The sys ui flags must be set to enable extending the layout into the window insets.
139 */
140 @NonNull
141 @RequiresApi(20)
142 @Override
143 public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
144 for (int i = 0; i < getChildCount(); i++) {
145 View child = getChildAt(i);
146 // Give child views fresh insets.
147 child.dispatchApplyWindowInsets(new WindowInsets(insets));
148 }
149 return insets;
150 }
151
jbwoods2534c732019-06-20 12:42:37 -0700152 @Override
Jeremy Woodsf68a1e32019-09-05 12:48:57 -0700153 protected void onAttachedToWindow() {
154 super.onAttachedToWindow();
Jeremy Woodsf68a1e32019-09-05 12:48:57 -0700155 // If there is a name we should add an inflated Fragment to the view.
156 if (mName != null) {
Jeremy Woods34a86292019-09-17 12:49:40 -0700157 int id = getId();
158 if (id <= 0) {
159 final String tagMessage = mTag == null
160 ? "."
161 : " with tag " + mTag + ".";
162 throw new IllegalStateException("FragmentContainerView must have an android:id to "
163 + "add Fragment " + mName + tagMessage);
164 }
165
166 FragmentManager fm = FragmentManager.findFragmentManager(this);
167 if (fm.findFragmentById(id) != null) {
168 return;
169 }
170
Jeremy Woodsf68a1e32019-09-05 12:48:57 -0700171 Fragment inflatedFragment =
172 fm.getFragmentFactory().instantiate(getContext().getClassLoader(), mName);
173 inflatedFragment.onInflate(getContext(), mAttributeSet, null);
174 fm.beginTransaction()
175 .setReorderingAllowed(true)
Jeremy Woods34a86292019-09-17 12:49:40 -0700176 .add(id, inflatedFragment, mTag)
Jeremy Woodsf68a1e32019-09-05 12:48:57 -0700177 .commitNow();
178 }
179 }
180
181 @Override
jbwoods2534c732019-06-20 12:42:37 -0700182 protected void dispatchDraw(@NonNull Canvas canvas) {
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700183 if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren != null) {
jbwoods2534c732019-06-20 12:42:37 -0700184 for (int i = 0; i < mDisappearingFragmentChildren.size(); i++) {
185 super.drawChild(canvas, mDisappearingFragmentChildren.get(i), getDrawingTime());
186 }
187 }
188 super.dispatchDraw(canvas);
189 }
190
191 @Override
192 protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700193 if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren != null
194 && mDisappearingFragmentChildren.size() > 0) {
jbwoods2534c732019-06-20 12:42:37 -0700195 // If the child is disappearing, we have already drawn it so skip.
196 if (mDisappearingFragmentChildren.contains(child)) {
197 return false;
198 }
199 }
200 return super.drawChild(canvas, child, drawingTime);
201 }
202
203 @Override
204 public void startViewTransition(@NonNull View view) {
205 if (view.getParent() == this) {
206 if (mTransitioningFragmentViews == null) {
207 mTransitioningFragmentViews = new ArrayList<>();
208 }
209 mTransitioningFragmentViews.add(view);
210 }
211 super.startViewTransition(view);
212 }
213
214 @Override
215 public void endViewTransition(@NonNull View view) {
216 if (mTransitioningFragmentViews != null) {
217 mTransitioningFragmentViews.remove(view);
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700218 if (mDisappearingFragmentChildren != null
219 && mDisappearingFragmentChildren.remove(view)) {
220 mDrawDisappearingViewsFirst = true;
jbwoods2534c732019-06-20 12:42:37 -0700221 }
222 }
223 super.endViewTransition(view);
224 }
225
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700226 // Used to indicate the container should change the default drawing order.
227 void setDrawDisappearingViewsLast(boolean drawDisappearingViewsFirst) {
228 mDrawDisappearingViewsFirst = drawDisappearingViewsFirst;
229 }
230
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700231 /**
232 * <p>FragmentContainerView will only allow views to returned by a Fragment's
233 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
234 * other view will result in an {@link IllegalStateException}.
235 *
236 * {@inheritDoc}
237 */
238 @Override
239 public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutParams params) {
240 if (FragmentManager.getViewFragment(child) == null) {
241 throw new IllegalStateException("Views added to a FragmentContainerView must be"
242 + " associated with a Fragment. View " + child + " is not associated with a"
243 + " Fragment.");
244 }
245 super.addView(child, index, params);
246 }
247
248 /**
249 * <p>FragmentContainerView will only allow views to returned by a Fragment's
250 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
251 * other view will result in an {@link IllegalStateException}.
252 *
253 * {@inheritDoc}
254 */
255 @Override
256 protected boolean addViewInLayout(@NonNull View child, int index,
257 @Nullable ViewGroup.LayoutParams params, boolean preventRequestLayout) {
258 if (FragmentManager.getViewFragment(child) == null) {
259 throw new IllegalStateException("Views added to a FragmentContainerView must be"
260 + " associated with a Fragment. View " + child + " is not associated with a"
261 + " Fragment.");
262 }
263 return super.addViewInLayout(child, index, params, preventRequestLayout);
264 }
265
jbwoods2534c732019-06-20 12:42:37 -0700266 @Override
267 public void removeViewAt(int index) {
268 View view = getChildAt(index);
269 addDisappearingFragmentView(view);
270 super.removeViewAt(index);
271 }
272
273 @Override
274 public void removeViewInLayout(@NonNull View view) {
275 addDisappearingFragmentView(view);
276 super.removeViewInLayout(view);
277 }
278
279 @Override
280 public void removeView(@NonNull View view) {
281 addDisappearingFragmentView(view);
282 super.removeView(view);
283 }
284
285 @Override
286 public void removeViews(int start, int count) {
287 for (int i = start; i < start + count; i++) {
288 final View view = getChildAt(i);
289 addDisappearingFragmentView(view);
290 }
291 super.removeViews(start, count);
292 }
293
294 @Override
295 public void removeViewsInLayout(int start, int count) {
296 for (int i = start; i < start + count; i++) {
297 final View view = getChildAt(i);
298 addDisappearingFragmentView(view);
299 }
300 super.removeViewsInLayout(start, count);
301 }
302
303 @Override
304 public void removeAllViewsInLayout() {
Jeremy Woods991f2682019-08-08 10:33:56 -0700305 for (int i = getChildCount() - 1; i >= 0; i--) {
jbwoods2534c732019-06-20 12:42:37 -0700306 final View view = getChildAt(i);
307 addDisappearingFragmentView(view);
308 }
309 super.removeAllViewsInLayout();
310 }
311
312 @Override
313 protected void removeDetachedView(@NonNull View child, boolean animate) {
314 if (animate) {
315 addDisappearingFragmentView(child);
316 }
317 super.removeDetachedView(child, animate);
318 }
319
320 /**
321 * This method adds a {@link View} to the list of disappearing views only if it meets the
322 * proper conditions to be considered a disappearing view.
323 *
324 * @param v {@link View} that might be added to list of disappearing views
325 */
326 private void addDisappearingFragmentView(@NonNull View v) {
327 if (v.getAnimation() != null || (mTransitioningFragmentViews != null
328 && mTransitioningFragmentViews.contains(v))) {
329 if (mDisappearingFragmentChildren == null) {
330 mDisappearingFragmentChildren = new ArrayList<>();
331 }
332 mDisappearingFragmentChildren.add(v);
333 }
334 }
jbwoodsb1757aa2019-06-12 11:17:06 -0700335}