L’effetto fitsSystemWindows è andato per i frammenti aggiunti tramite FragmentTransaction

Ho un’attività con il cassetto di navigazione e il frammento pieno (con l’immagine in alto che deve apparire dietro la barra di sistema traslucida su Lollipop). Mentre disponevo di una soluzione provvisoria in cui il frammento era gonfiato semplicemente con il tag nell’XML di Activity, sembrava soddisfacente.

Poi ho dovuto sostituire con ed eseguire transazioni frammentate, e ora il frammento non appare più dietro la barra del sistema, nonostante fitsSystemWindows sia impostato su true su tutta la gerarchia richiesta.

Credo che ci possa essere una certa differenza tra il modo in cui viene gonfiato all’interno del layout di Activity o da solo. Ho cercato su Google e trovato alcune soluzioni per KitKat, ma nessuno di questi ha funzionato per me (Lollipop).

activity.xml

      

fragment.xml

   ... 

Ha funzionato quando activity.xml era così:

     

Quando usi , il layout restituito in onCreateView di Fragment viene direttamente collegato al posto del tag (non vedrai mai un tag se osservi la tua gerarchia di View.

Quindi nel caso del , hai

 DrawerLayout CoordinatorLayout AppBarLayout ... NavigationView 

Simile a come funziona il formaggio . Funziona perché, come spiegato in questo post del blog , DrawerLayout e CoordinatorLayout hanno entrambe regole diverse su come fitsSystemWindows applica fitsSystemWindows – entrambi lo usano per inserire le loro viste fitsSystemWindows , ma chiamano anche dispatchApplyWindowInsets () su ciascun figlio, consentendo loro l’accesso al fitsSystemWindows="true" proprietà fitsSystemWindows="true" .

Questa è una differenza rispetto al comportamento predefinito con layout come FrameLayout dove quando si usa fitsSystemWindows="true" si consumano tutti gli inserimenti, applicando ciecamente il padding senza informare alcuna vista fitsSystemWindows="true" (questa è la parte “profondità prima” del post del blog).

Quindi, quando sostituisci il tag con FrameLayout e FragmentTransactions, la tua gerarchia di visualizzazione diventa:

 DrawerLayout FrameLayout CoordinatorLayout AppBarLayout ... NavigationView 

come la vista Frammento è inserita nel FrameLayout . Quella vista non sa nulla del passaggio di fitsSystemWindows alle viste figlio, quindi il tuo CoordinatorLayout non potrà mai vedere quel flag o fare il suo comportamento personalizzato.

Risolvere il problema è in realtà abbastanza semplice: sostituisci il tuo FrameLayout con un altro CoordinatorLayout . Ciò assicura che fitsSystemWindows="true" venga passato sul nuovo CoordinatorLayout dal Fragment.

Le soluzioni alternative e ugualmente valide sarebbero quelle di creare una sottoclass personalizzata di FrameLayout e sovrascrivere onApplyWindowInsets () per inviare a ciascun figlio (nel tuo caso solo uno) o utilizzare il metodo ViewCompat.setOnApplyWindowInsetsListener () per intercettare la chiamata nel codice e inviare da lì (non è richiesta alcuna sottoclass). Meno codice è solitamente il più semplice da mantenere, quindi non raccomanderei necessariamente di seguire queste rotte sulla soluzione di CoordinatorLayout meno che non ci si senta fortemente a riguardo.

Il mio problema era simile al tuo: ho un Bottom Bar Navigation che sta sostituendo i frammenti di contenuto. Ora alcuni dei frammenti vogliono disegnare sulla barra di stato (con CoordinatorLayout , AppBarLayout ), altri no (con ConstraintLayout , Toolbar ).

 ConstraintLayout FrameLayout [the ViewGroup of your choice] BottomNavigationView 

Il suggerimento di ianhanniballake per aggiungere un altro livello di CoordinatorLayout non è quello che voglio, così ho creato un FrameLayout personalizzato che gestisce gli FrameLayout (come suggerito da lui), e dopo un po ‘di tempo mi sono imbattuto in questa soluzione che in realtà non è molto codice:

activity_main.xml

     

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) { } }); } } 

OK, dopo che diverse persone hanno sottolineato che fitsSystemWindows funziona in modo diverso, e non dovrebbe essere usato su ogni vista lungo la gerarchia, ho continuato a sperimentare e rimuovere la proprietà da diverse viste.

Ho ottenuto lo stato previsto dopo aver rimosso fitsSystemWindows da ogni nodo in activity.xml = \

Ho creato questo ultimo anno per risolvere questo problema: https://gist.github.com/cbeyls/ab6903e103475bd4d51b

Modifica: assicurati di capire che cosa fa per prima cosaSystemWindows. Quando lo si imposta su una vista significa sostanzialmente: “metti questa vista e tutti i suoi figli sotto la barra di stato e sopra la barra di navigazione”. Non ha senso impostare questo attributo sul primo contenitore.

Un altro approccio scritto in Kotlin,

Il problema:

Il FrameLayout si sta utilizzando non propaga fitsSystemWindows="true" ai suoi figli:

  

Una soluzione:

Estendi la class FrameLayout e sostituisci la funzione onApplyWindowInsets() per propagare i onApplyWindowInsets() della finestra ai frammenti allegati:

 @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 } } 

Usa questo layout come contenitore di frammenti invece del FrameLayout standard:

  

Extra:

Se vuoi saperne di più su questo checkout post di Chris Banes Diventare un montatore di windows master .