tutorial - navigation drawer android
El efecto de fitSystemWindows se ha ido para fragmentos agregados a través de FragmentTransaction (6)
Tengo una actividad con el cajón de navegación y el fragmento de sangrado completo (con una imagen en la parte superior que debe aparecer detrás de la barra del sistema translúcido en Lollipop).
Si bien tenía una solución provisional en la que el Fragmento se inflaba simplemente con la etiqueta
<fragment>
en el XML de Activity, se veía bien.
Luego tuve que reemplazar
<fragment>
con
<FrameLayout>
y realizar transacciones de fragmentos, y ahora el fragmento ya no aparece detrás de la barra del sistema, a pesar de que
fitsSystemWindows
está establecido en
true
en toda la jerarquía requerida.
Creo que podría haber alguna diferencia entre cómo se infla
<fragment>
dentro del diseño de Activity versus por sí solo.
Busqué en Google y encontré algunas soluciones para KitKat, pero ninguna de ellas funcionó para mí (Lollipop).
activity.xml
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/fragment_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
</FrameLayout>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"/>
</android.support.v4.widget.DrawerLayout>
fragment.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="224dp"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
...
Funcionó cuando activity.xml era de esta manera:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment"
android:name="com.actinarium.random.ui.home.HomeCardsFragment"
tools:layout="@layout/fragment_home"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"/>
</android.support.v4.widget.DrawerLayout>
Bien, después de que varias personas
fitsSystemWindows
que el sistema
fitsSystemWindows
funciona de manera diferente, y que no debería usarse en cada vista de la jerarquía, seguí experimentando y eliminando la propiedad de diferentes vistas.
fitsSystemWindows
estado esperado después de eliminar
fitsSystemWindows
de cada nodo en
activity.xml
= /
Creé este último año para resolver este problema: https://gist.github.com/cbeyls/ab6903e103475bd4d51b
Editar: asegúrese de comprender lo que se ajusta primero a SystemSindows. Cuando lo configura en una Vista, básicamente significa: "coloque esta Vista y todos sus elementos secundarios debajo de la barra de estado y encima de la barra de navegación". No tiene sentido establecer este atributo en el contenedor superior.
Cuando usa
<fragment>
, el diseño devuelto en
onCreateView
su Fragment se adjunta directamente
en lugar de
la etiqueta
<fragment>
(en realidad nunca verá una etiqueta
<fragment>
si mira su jerarquía de Vista.
Por lo tanto, en el caso
<fragment>
, tienes
DrawerLayout
CoordinatorLayout
AppBarLayout
...
NavigationView
Similar a cómo funciona
cheesesquare
.
Esto funciona porque, como se explica en
esta publicación de blog
,
DrawerLayout
y
CoordinatorLayout
tienen reglas diferentes sobre cómo se aplica a
fitsSystemWindows
: ambos lo usan para insertar sus Vistas secundarias, pero
también
llaman a
dispatchApplyWindowInsets()
en cada elemento secundario, lo que les permite acceder a
fitsSystemWindows="true"
propiedad
fitsSystemWindows="true"
.
Esta es una diferencia con respecto al comportamiento predeterminado con diseños como
FrameLayout
donde cuando se utiliza
fitsSystemWindows="true"
se consumen todas las inserciones, aplicando a ciegas el relleno sin informar las vistas secundarias (esa es la parte ''profunda primero'' de la publicación del blog).
Entonces, cuando reemplaza la etiqueta
<fragment>
con
FrameLayout
y FragmentTransactions, su jerarquía de vistas se convierte en:
DrawerLayout
FrameLayout
CoordinatorLayout
AppBarLayout
...
NavigationView
a medida que la vista del Fragmento se inserta en
FrameLayout
.
Esa vista no sabe nada sobre cómo pasar los
fitsSystemWindows
de
fitsSystemWindows
a las vistas secundarias, por lo que su
CoordinatorLayout
nunca puede ver esa bandera o hacer su comportamiento personalizado.
En realidad, solucionar el problema es bastante simple:
reemplace su
FrameLayout
con otro
CoordinatorLayout
.
Esto asegura que el
fitsSystemWindows="true"
pase al
CoordinatorLayout
recién inflado desde el Fragmento.
Las soluciones alternativas e igualmente válidas serían hacer una subclase personalizada de
FrameLayout
y anular
onApplyWindowInsets()
para enviar a cada hijo (en su caso solo el uno) o usar el método
ViewCompat.setOnApplyWindowInsetsListener()
para interceptar la llamada en código y despachar desde allí (no se requiere subclase).
Por lo general, es menos fácil mantener menos código, por lo que no recomendaría necesariamente seguir estas rutas a través de la solución
CoordinatorLayout
menos que se sienta muy convencido al respecto.
El otro problema horrendo con el envío de inserciones de ventana es que la primera vista que consume inserciones de ventana en una búsqueda profunda primero evita que todas las demás vistas en la jerarquía vean inserciones de ventana.
El siguiente fragmento de código permite que más de un hijo maneje inserciones de ventanas. Extremadamente útil si está intentando aplicar inserciones de ventanas a decoraciones fuera de una NavigationView (o CoordinatorLayout). Anular en el ViewGroup de su elección.
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
if (!insets.isConsumed()) {
// each child gets a fresh set of window insets
// to consume.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
WindowInsets freshInsets = new WindowInsets(insets);
getChildAt(i).dispatchApplyWindowInsets(freshInsets);
}
}
return insets; // and we don''t.
}
También útil:
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
return insets.consume(); // consume without adding padding!
}
lo que permite que las vistas ordinarias simples que son elementos secundarios de esta vista se distribuyan sin ventanas.
Mi problema era similar al tuyo: tengo una
barra de navegación inferior
que está reemplazando los fragmentos de contenido.
Ahora algunos de los fragmentos quieren dibujar sobre la barra de estado (con
CoordinatorLayout
,
AppBarLayout
), otros no (con
ConstraintLayout
,
Toolbar
).
ConstraintLayout
FrameLayout
[the ViewGroup of your choice]
BottomNavigationView
La sugerencia de
ianhanniballake
para agregar otra capa
CoordinatorLayout
no es lo que quiero, así que creé un
FrameLayout
personalizado que maneja las inserciones (como sugirió), y después de un tiempo encontré esta solución que realmente no es mucho código:
activity_main.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.app.WindowInsetsFrameLayout
android:id="@+id/fragment_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
WindowInsetsFrameLayout.java
/**
* FrameLayout which takes care of applying the window insets to child views.
*/
public class WindowInsetsFrameLayout extends FrameLayout {
public WindowInsetsFrameLayout(Context context) {
this(context, null);
}
public WindowInsetsFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WindowInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Look for replaced fragments and apply the insets again.
setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
requestApplyInsets();
}
@Override
public void onChildViewRemoved(View parent, View child) {
}
});
}
}
Otro enfoque escrito en Kotlin,
El problema:
El
FrameLayout
que está utilizando no propaga
fitsSystemWindows="true"
a sus hijos:
<FrameLayout
android:id="@+id/fragment_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
Una solución:
Extienda la clase
FrameLayout
y
anule
la función
onApplyWindowInsets()
para propagar las inserciones de ventana a los fragmentos adjuntos:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class BetterFrameLayout : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
childCount.let {
// propagates window insets to children''s
for (index in 0 until it) {
getChildAt(index).dispatchApplyWindowInsets(windowInsets)
}
}
return windowInsets
}
}
Utilice este diseño como un contenedor de fragmentos en lugar del
FrameLayout
estándar:
<com.foo.bar.BetterFrameLayout
android:id="@+id/fragment_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
Extra:
Si desea saber más acerca de este pago, haga clic en la publicación del blog de Chris Banes Convertirse en un instalador de ventanas maestro .