[go: nahoru, domu]

blob: 3e309028ccf6c57b63c35882b2e8ce7092248071 [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 *
Jeremy Woodse84f9f92019-10-10 13:51:26 -070056 * <p> FragmentContainerView can also be used to add a Fragment by using the
57 * <code>android:name</code> attribute. FragmentContainerView will perform a one time operation
58 * that:
59 *
60 * <ul>
61 * <li>Creates a new instance of the Fragment</li>
62 * <li>Calls {@link Fragment#onInflate(Context, AttributeSet, Bundle)}</li>
63 * <li>Executes a FragmentTransaction to add the Fragment to the appropriate FragmentManager</li>
64 * </ul>
65 *
66 * <p> You can optionally include an <code>android:tag</code> which allows you to use
67 * {@link FragmentManager#findFragmentByTag(String)} to retrieve the added Fragment.
68 *
69 * <pre class="prettyprint">
70 * &lt;androidx.fragment.app.FragmentContainerView
71 * xmlns:android="http://schemas.android.com/apk/res/android"
72 * xmlns:app="http://schemas.android.com/apk/res-auto"
73 * android:id="@+id/fragment_container_view"
74 * android:layout_width="match_parent"
75 * android:layout_height="match_parent"
76 * android:name="com.example.MyFragment"
77 * android:tag="my_tag"&gt;
78 * &lt;/androidx.fragment.app.FragmentContainerView&gt;
79 * </pre>
80 *
Jeremy Woods92946252019-07-25 09:39:22 -070081 * <p>FragmentContainerView should not be used as a replacement for other ViewGroups (FrameLayout,
82 * LinearLayout, etc) outside of Fragment use cases.
83 *
Jeremy Woods0fe0dc42020-01-23 11:00:18 -080084 * <p>FragmentContainerView will only allow views returned by a Fragment's
Jeremy Woods6c5151d2019-07-26 16:58:58 -070085 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any other
86 * view will result in an {@link IllegalStateException}.
87 *
Jeremy Woods2882e142019-09-17 13:56:12 -070088 * <p>Layout animations and transitions are disabled for FragmentContainerView for APIs above 17.
89 * Otherwise, Animations should be done through
Jeremy Woods68de8e52020-01-29 16:44:38 -080090 * {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}. If animateLayoutChanges is
Jeremy Woods2882e142019-09-17 13:56:12 -070091 * set to <code>true</code> or {@link #setLayoutTransition(LayoutTransition)} is called directly an
jbwoodsb1757aa2019-06-12 11:17:06 -070092 * {@link UnsupportedOperationException} will be thrown.
93 *
jbwoods2534c732019-06-20 12:42:37 -070094 * <p>Fragments using exit animations are drawn before all others for FragmentContainerView. This
Jeremy Woods92946252019-07-25 09:39:22 -070095 * ensures that exiting Fragments do not appear on top of the view.
jbwoodsb1757aa2019-06-12 11:17:06 -070096 */
Jeremy Woods8b13acf2019-08-27 15:49:43 -070097public final class FragmentContainerView extends FrameLayout {
jbwoods2534c732019-06-20 12:42:37 -070098
99 private ArrayList<View> mDisappearingFragmentChildren;
100
101 private ArrayList<View> mTransitioningFragmentViews;
jbwoodsb1757aa2019-06-12 11:17:06 -0700102
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700103 // Used to indicate whether the FragmentContainerView should override the default ViewGroup
104 // drawing order.
105 private boolean mDrawDisappearingViewsFirst = true;
106
jbwoodsb1757aa2019-06-12 11:17:06 -0700107 public FragmentContainerView(@NonNull Context context) {
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700108 super(context);
jbwoodsb1757aa2019-06-12 11:17:06 -0700109 }
110
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700111 /**
112 * Do not call this constructor directly. Doing so will result in an
113 * {@link UnsupportedOperationException}.
114 */
jbwoodsb1757aa2019-06-12 11:17:06 -0700115 public FragmentContainerView(@NonNull Context context, @Nullable AttributeSet attrs) {
Jeremy Woods1185eb42020-02-18 12:58:37 -0800116 this(context, attrs, 0);
jbwoodsb1757aa2019-06-12 11:17:06 -0700117 }
118
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700119 /**
120 * Do not call this constructor directly. Doing so will result in an
121 * {@link UnsupportedOperationException}.
122 */
jbwoodsb1757aa2019-06-12 11:17:06 -0700123 public FragmentContainerView(
124 @NonNull Context context,
125 @Nullable AttributeSet attrs,
126 int defStyleAttr) {
127 super(context, attrs, defStyleAttr);
Jeremy Woods728527e2020-11-20 10:37:25 -0800128 if (attrs != null) {
129 String name = attrs.getClassAttribute();
130 String attribute = "class";
131 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FragmentContainerView);
132 if (name == null) {
133 name = a.getString(R.styleable.FragmentContainerView_android_name);
134 attribute = "android:name";
135 }
136 a.recycle();
137 if (name != null && !isInEditMode()) {
138 throw new UnsupportedOperationException("FragmentContainerView must be within "
139 + "a FragmentActivity to use " + attribute + "=\"" + name + "\"");
140 }
Jeremy Woods1185eb42020-02-18 12:58:37 -0800141 }
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700142 }
143
144 FragmentContainerView(
145 @NonNull Context context,
Jeremy Woodsb7f4e642019-10-14 16:20:27 -0700146 @NonNull AttributeSet attrs,
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700147 @NonNull FragmentManager fm) {
148 super(context, attrs);
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700149
Jeremy Woodsb7f4e642019-10-14 16:20:27 -0700150 String name = attrs.getClassAttribute();
Jeremy Woodsf68a1e32019-09-05 12:48:57 -0700151 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FragmentContainerView);
Jeremy Woodsb7f4e642019-10-14 16:20:27 -0700152 if (name == null) {
153 name = a.getString(R.styleable.FragmentContainerView_android_name);
154 }
Jeremy Woods1838e972019-10-14 13:52:11 -0700155 String tag = a.getString(R.styleable.FragmentContainerView_android_tag);
Jeremy Woodsf68a1e32019-09-05 12:48:57 -0700156 a.recycle();
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700157
158 int id = getId();
159 Fragment existingFragment = fm.findFragmentById(id);
160 // If there is a name and there is no existing fragment,
161 // we should add an inflated Fragment to the view.
162 if (name != null && existingFragment == null) {
163 if (id <= 0) {
Jeremy Woods1838e972019-10-14 13:52:11 -0700164 final String tagMessage = tag != null
165 ? " with tag " + tag
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700166 : "";
167 throw new IllegalStateException("FragmentContainerView must have an android:id to "
168 + "add Fragment " + name + tagMessage);
169 }
Jeremy Woods1838e972019-10-14 13:52:11 -0700170 Fragment containerFragment =
171 fm.getFragmentFactory().instantiate(context.getClassLoader(), name);
172 containerFragment.onInflate(context, attrs, null);
173 fm.beginTransaction()
174 .setReorderingAllowed(true)
175 .add(this, containerFragment, tag)
Jeremy Woods1cad37f2019-10-15 09:37:56 -0700176 .commitNowAllowingStateLoss();
Jeremy Woodse84f9f92019-10-10 13:51:26 -0700177 }
jbwoodsb1757aa2019-06-12 11:17:06 -0700178 }
179
180 /**
Jeremy Woods2882e142019-09-17 13:56:12 -0700181 * When called, this method throws a {@link UnsupportedOperationException} on APIs above 17.
182 * On APIs 17 and below, it calls {@link FrameLayout#setLayoutTransition(LayoutTransition)}
183 * This can be called either explicitly, or implicitly by setting animateLayoutChanges to
184 * <code>true</code>.
jbwoodsb1757aa2019-06-12 11:17:06 -0700185 *
Jeremy Woods2882e142019-09-17 13:56:12 -0700186 * <p>View animations and transitions are disabled for FragmentContainerView for APIs above 17.
187 * Use {@link FragmentTransaction#setCustomAnimations(int, int, int, int)} and
jbwoodsb1757aa2019-06-12 11:17:06 -0700188 * {@link FragmentTransaction#setTransition(int)}.
189 *
190 * @param transition The LayoutTransition object that will animated changes in layout. A value
191 * of <code>null</code> means no transition will run on layout changes.
192 * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
193 */
194 @Override
195 public void setLayoutTransition(@Nullable LayoutTransition transition) {
Jeremy Woods2882e142019-09-17 13:56:12 -0700196 if (Build.VERSION.SDK_INT < 18) {
197 // Transitions on APIs below 18 are using an empty LayoutTransition as a replacement
198 // for suppressLayout(true) and null LayoutTransition to then unsuppress it. If the
199 // API is below 18, we should allow FrameLayout to handle this call.
200 super.setLayoutTransition(transition);
201 return;
202 }
203
jbwoodsb1757aa2019-06-12 11:17:06 -0700204 throw new UnsupportedOperationException(
205 "FragmentContainerView does not support Layout Transitions or "
206 + "animateLayoutChanges=\"true\".");
207 }
jbwoods2534c732019-06-20 12:42:37 -0700208
jbwoodsd46ea742019-06-14 16:18:00 -0700209 /**
210 * {@inheritDoc}
211 *
212 * <p>The sys ui flags must be set to enable extending the layout into the window insets.
213 */
214 @NonNull
215 @RequiresApi(20)
216 @Override
217 public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
218 for (int i = 0; i < getChildCount(); i++) {
219 View child = getChildAt(i);
220 // Give child views fresh insets.
221 child.dispatchApplyWindowInsets(new WindowInsets(insets));
222 }
223 return insets;
224 }
225
jbwoods2534c732019-06-20 12:42:37 -0700226 @Override
227 protected void dispatchDraw(@NonNull Canvas canvas) {
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700228 if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren != null) {
jbwoods2534c732019-06-20 12:42:37 -0700229 for (int i = 0; i < mDisappearingFragmentChildren.size(); i++) {
230 super.drawChild(canvas, mDisappearingFragmentChildren.get(i), getDrawingTime());
231 }
232 }
233 super.dispatchDraw(canvas);
234 }
235
236 @Override
237 protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700238 if (mDrawDisappearingViewsFirst && mDisappearingFragmentChildren != null
239 && mDisappearingFragmentChildren.size() > 0) {
jbwoods2534c732019-06-20 12:42:37 -0700240 // If the child is disappearing, we have already drawn it so skip.
241 if (mDisappearingFragmentChildren.contains(child)) {
242 return false;
243 }
244 }
245 return super.drawChild(canvas, child, drawingTime);
246 }
247
248 @Override
249 public void startViewTransition(@NonNull View view) {
250 if (view.getParent() == this) {
251 if (mTransitioningFragmentViews == null) {
252 mTransitioningFragmentViews = new ArrayList<>();
253 }
254 mTransitioningFragmentViews.add(view);
255 }
256 super.startViewTransition(view);
257 }
258
259 @Override
260 public void endViewTransition(@NonNull View view) {
261 if (mTransitioningFragmentViews != null) {
262 mTransitioningFragmentViews.remove(view);
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700263 if (mDisappearingFragmentChildren != null
264 && mDisappearingFragmentChildren.remove(view)) {
265 mDrawDisappearingViewsFirst = true;
jbwoods2534c732019-06-20 12:42:37 -0700266 }
267 }
268 super.endViewTransition(view);
269 }
270
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700271 // Used to indicate the container should change the default drawing order.
272 void setDrawDisappearingViewsLast(boolean drawDisappearingViewsFirst) {
273 mDrawDisappearingViewsFirst = drawDisappearingViewsFirst;
274 }
275
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700276 /**
Jeremy Woods0fe0dc42020-01-23 11:00:18 -0800277 * <p>FragmentContainerView will only allow views returned by a Fragment's
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700278 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
279 * other view will result in an {@link IllegalStateException}.
280 *
281 * {@inheritDoc}
282 */
283 @Override
284 public void addView(@NonNull View child, int index, @Nullable ViewGroup.LayoutParams params) {
285 if (FragmentManager.getViewFragment(child) == null) {
286 throw new IllegalStateException("Views added to a FragmentContainerView must be"
287 + " associated with a Fragment. View " + child + " is not associated with a"
288 + " Fragment.");
289 }
290 super.addView(child, index, params);
291 }
292
293 /**
Jeremy Woods0fe0dc42020-01-23 11:00:18 -0800294 * <p>FragmentContainerView will only allow views returned by a Fragment's
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700295 * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. Attempting to add any
296 * other view will result in an {@link IllegalStateException}.
297 *
298 * {@inheritDoc}
299 */
300 @Override
301 protected boolean addViewInLayout(@NonNull View child, int index,
302 @Nullable ViewGroup.LayoutParams params, boolean preventRequestLayout) {
303 if (FragmentManager.getViewFragment(child) == null) {
304 throw new IllegalStateException("Views added to a FragmentContainerView must be"
305 + " associated with a Fragment. View " + child + " is not associated with a"
306 + " Fragment.");
307 }
308 return super.addViewInLayout(child, index, params, preventRequestLayout);
309 }
310
jbwoods2534c732019-06-20 12:42:37 -0700311 @Override
312 public void removeViewAt(int index) {
313 View view = getChildAt(index);
314 addDisappearingFragmentView(view);
315 super.removeViewAt(index);
316 }
317
318 @Override
319 public void removeViewInLayout(@NonNull View view) {
320 addDisappearingFragmentView(view);
321 super.removeViewInLayout(view);
322 }
323
324 @Override
325 public void removeView(@NonNull View view) {
326 addDisappearingFragmentView(view);
327 super.removeView(view);
328 }
329
330 @Override
331 public void removeViews(int start, int count) {
332 for (int i = start; i < start + count; i++) {
333 final View view = getChildAt(i);
334 addDisappearingFragmentView(view);
335 }
336 super.removeViews(start, count);
337 }
338
339 @Override
340 public void removeViewsInLayout(int start, int count) {
341 for (int i = start; i < start + count; i++) {
342 final View view = getChildAt(i);
343 addDisappearingFragmentView(view);
344 }
345 super.removeViewsInLayout(start, count);
346 }
347
348 @Override
349 public void removeAllViewsInLayout() {
Jeremy Woods991f2682019-08-08 10:33:56 -0700350 for (int i = getChildCount() - 1; i >= 0; i--) {
jbwoods2534c732019-06-20 12:42:37 -0700351 final View view = getChildAt(i);
352 addDisappearingFragmentView(view);
353 }
354 super.removeAllViewsInLayout();
355 }
356
357 @Override
358 protected void removeDetachedView(@NonNull View child, boolean animate) {
359 if (animate) {
360 addDisappearingFragmentView(child);
361 }
362 super.removeDetachedView(child, animate);
363 }
364
365 /**
366 * This method adds a {@link View} to the list of disappearing views only if it meets the
367 * proper conditions to be considered a disappearing view.
368 *
369 * @param v {@link View} that might be added to list of disappearing views
370 */
371 private void addDisappearingFragmentView(@NonNull View v) {
Jeremy Woods58875f62021-01-20 15:24:12 -0800372 if (mTransitioningFragmentViews != null && mTransitioningFragmentViews.contains(v)) {
jbwoods2534c732019-06-20 12:42:37 -0700373 if (mDisappearingFragmentChildren == null) {
374 mDisappearingFragmentChildren = new ArrayList<>();
375 }
376 mDisappearingFragmentChildren.add(v);
377 }
378 }
jbwoodsb1757aa2019-06-12 11:17:06 -0700379}