web-dev-qa-db-fra.com

Android 4.4 - Les barres d'état/de navigation translucides - convientSystemWindows/clipToPadding ne fonctionnent pas avec les transactions de fragments

Lorsque vous utilisez les barres d'état et de navigation translucides des nouvelles API KitKat d'Android 4.4, la définition de fitsSystemWindows="true" et clipToPadding="false" sur ListView fonctionne initialement. fitsSystemWindows="true" conserve la liste sous la barre d’action et au-dessus de la barre de navigation, clipToPadding="false" permet à la liste de défiler sous la barre de navigation transparente et fait défiler le dernier élément de la liste suffisamment loin pour passer la barre de navigation.

Toutefois, lorsque vous remplacez le contenu par un autre Fragment par un FragmentTransaction, l’effet de fitsSystemWindows disparaît et le fragment passe sous la barre d’action et la barre de navigation.

J'ai une base de code de code source de démonstration ici avec un APK téléchargeable à titre d'exemple: https://github.com/afollestad/KitKat-transparency-demo . Pour voir de quoi je parle, ouvrez l'application de démonstration à partir d'un appareil exécutant KitKat, appuyez sur un élément de la liste (qui ouvrira une autre activité), puis appuyez sur un élément de la nouvelle activité qui s'ouvre. Le fragment qui remplace le contenu passe sous la barre d'actions et clipToPadding ne fonctionne pas correctement (la barre de navigation couvre le dernier élément de la liste lorsque vous faites défiler l'écran jusqu'au bas).

Des idées? Des éclaircissements nécessaires? J'ai posté les captures d'écran avant et après de mon application personnelle en cours de développement pour mon employeur. 

OneTwo

27
afollestad

J'ai résolu le problème en utilisant la bibliothèque. J'utilise l'option Définir la couleur de la barre d'état translucide.

La classe SystemBarConfig de SystemBarTint (comme indiqué ici https://github.com/jgilfelt/SystemBarTint#systembarconfig ) vous permet d’obtenir des encarts que je définis comme le remplissage de la liste dans chaque fragment, avec l’utilisation de clipToPadding="false" sur la liste.

J'ai les détails de ce que j'ai fait sur ce post: http://mindofaandroiddev.wordpress.com/2013/12/28/making-the-status-bar-and-navigation-bar-transparent-with-a -listview-on-Android-4-4-KitKat/ /

9
afollestad

J'ai eu du mal avec le même problème hier. Après avoir beaucoup réfléchi, j'ai trouvé une solution élégante à ce problème.

Premièrement, j'ai vu la méthode requestFitSystemWindows() on ViewParent et j'ai essayé de l'appeler dans la fonction onActivityCreated() du fragment (après que le fragment soit attaché à la hiérarchie des vues), mais malheureusement, cela n'a eu aucun effet. J'aimerais voir un exemple concret d'utilisation de cette méthode.

Ensuite, j'ai trouvé une solution de contournement: j'ai créé une FitsSystemWindowsFrameLayout personnalisée que j'utilise comme conteneur fragment dans mes mises en page, en remplacement immédiat d'une FrameLayout classique. Ce qu'il fait, c'est mémoriser les encarts de fenêtre lorsque fitSystemWindows() est appelé par le système, puis il répète l'appel à sa disposition enfant (la disposition de fragment) dès que le fragment est ajouté/attaché.

Voici le code complet:

public class FitsSystemWindowsFrameLayout extends FrameLayout {

    private Rect windowInsets = new Rect();
    private Rect tempInsets = new Rect();

    public FitsSystemWindowsFrameLayout(Context context) {
        super(context);
    }

    public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected boolean fitSystemWindows(Rect insets) {
        windowInsets.set(insets);
        super.fitSystemWindows(insets);
        return false;
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        tempInsets.set(windowInsets);
        super.fitSystemWindows(tempInsets);
    }
}

Je pense que cela est beaucoup plus simple et robuste que les hacks qui essaient de déterminer la taille des éléments de l'interface utilisateur en accédant à des propriétés système masquées pouvant varier dans le temps, puis en appliquant manuellement un remplissage aux éléments.

12
BladeCoder

Ok, donc c'est incroyablement bizarre. J'ai récemment rencontré ce même problème, sauf que le mien implique un clavier logiciel . Cela fonctionne initialement mais si j'ajoute une transaction de fragment, le Android:fitsSystemWindows="true" ne fonctionne plus. J'ai essayé toute la solution ici, aucune d'entre elles n'a fonctionné pour moi.

Voici mon problème: enter image description here

Au lieu de redimensionner mon point de vue, cela me pousse vers le haut et c'est le problème.

Cependant, j'ai eu de la chance et suis tombé par hasard sur une réponse qui a fonctionné pour moi!

Alors la voici:

Tout d’abord, mon thème d’application est: Theme.AppCompat.Light.NoActionBar (si cela est pertinent, c’est peut-être vrai, Android est étrange).

Maurycy a souligné quelque chose de très intéressant ici, alors je voulais tester ce qu'il disait être vrai ou pas. Ce qu'il a dit était également vrai dans mon cas ... À MOINS que vous ajoutiez cet attribut à votre activité dans le manifeste Android de votre application:

enter image description here

Une fois que vous ajoutez: 

Android:windowSoftInputMode="adjustResize" 

pour votre activité, Android:fitsSystemWindows="true" n'est plus ignoré après la transaction de fragment!

Cependant, je préfère que vous appeliez Android:fitsSystemWindows="true" PAS sur la disposition racine de votre fragment. L'un des plus grands endroits où ce problème se produira est celui où vous avez EditText ou un ListView. Si vous êtes bloqué comme moi dans cette situation difficile, définissez Android:fitsSystemWindows="true" dans l'enfant de la mise en page racine comme suit:

enter image description here

OUI, cette solution fonctionne sur tous les appareils Lollipop et pré-Lollipop.

Et voici la preuve: enter image description here

Il redimensionne au lieu de pousser la mise en page vers le haut… donc j'espère que j'ai aidé quelqu'un qui est sur le même bateau que moi.

Merci beaucoup à tous!

8
Yoosuf

Un avertissement pour certaines personnes qui rencontrent ce problème. 

Une information clé de la méthode fitSystemWindows qui effectue une grande partie du travail:

Le parcours de cette fonction dans la hiérarchie est d'abord la profondeur. Le même Le contenu de l'objet insets est propagé dans la hiérarchie, ainsi toutes les modifications toutes les vues suivantes (y compris potentiellement celles ci-dessus dans la hiérarchie, car il s’agit d’une traversée en profondeur). La première vue qui retourne vrai annulera toute la traversée.

Par conséquent, si vous avez d'autres fragments avec des vues de contenu pour lesquels fitsSystemWindows est défini sur true, l'indicateur sera potentiellement ignoré. Si possible, envisagez de faire en sorte que votre conteneur de fragments contienne le drapeau fitsSystemWindows. Sinon, ajoutez manuellement un remplissage. 

5
Maurycy

J'ai eu pas mal de problèmes avec ça aussi ... J'ai vu toutes les réponses ici. Malheureusement, aucun d’entre eux ne réglait mon problème 100% du temps . SystemBarConfig ne fonctionne pas toujours, car il ne détecte pas la barre sur certains périphériques . sont stockés à l'intérieur de la fenêtre.

        Rect insets = new Rect();
        Window window = getActivity().getWindow();
        try {
            Class clazz = Class.forName("com.Android.internal.policy.impl.PhoneWindow");
            Field field = clazz.getDeclaredField("mDecor");
            field.setAccessible(true);
            Object decorView = field.get(window);
            Field insetsField = decorView.getClass().getDeclaredField("mFrameOffsets");
            insetsField.setAccessible(true);
            insets = (Rect) insetsField.get(decorView);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

C'est comment les obtenir. Apparemment, il y aura une bonne méthode pour obtenir ces incrustations dans Android L, mais entre-temps, cela pourrait être une bonne solution.

2
Pasquale Anatriello

J'ai rencontré le même problème. Quand je remplace Fragment. Le 'fitsSystemWindows' ne fonctionne pas. 

J'ai corrigé par code ajouter à votre fragment

@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    AndroidUtil.runOnUIThread(new Runnable() {
        @Override
        public void run() {
            ((ViewGroup) getView().getParent()).setFitsSystemWindows(true);
        }
    });
}
0
user1198954

Combiné avec @BladeCoder answer, j'ai créé la classe FittedFrameLayout qui fait deux choses:

  • il n'ajoute pas de rembourrage pour lui-même
  • il balaie toutes les vues à l'intérieur de son conteneur et ajoute un remplissage pour celles-ci, mais s'arrête sur le calque le plus bas (si l'indicateur fitssystemwindows est trouvé, il n'analysera pas l'enfant plus en profondeur, mais à la même profondeur ou au-dessous).

    public class FittedFrameLayout extends FrameLayout {
        private Rect insets = new Rect();
    
        public FittedFrameLayout(Context context) {
            super(context);
        }
    
        public FittedFrameLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @TargetApi(Build.VERSION_CODES.Lollipop)
        public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        protected void setChildPadding(View view, Rect insets){
            if(!(view instanceof ViewGroup))
                return;
    
            ViewGroup parent = (ViewGroup) view;
            if (parent instanceof FittedFrameLayout)
                ((FittedFrameLayout)parent).fitSystemWindows(insets);
            else{
                if( ViewCompat.getFitsSystemWindows(parent))
                    parent.setPadding(insets.left,insets.top,insets.right,insets.bottom);
                else{
                    for (int i = 0, z = parent.getChildCount(); i < z; i++)
                        setChildPadding(parent.getChildAt(i), insets);
                }
            }
        }
    
        @Override
        protected boolean fitSystemWindows(Rect insets) {
            this.insets = insets;
            for (int i = 0, z = getChildCount(); i < z; i++)
                setChildPadding(getChildAt(i), insets);
    
            return true;
        }
    
        @Override
        public void addView(View child, int index, ViewGroup.LayoutParams params) {
            super.addView(child, index, params);
            setChildPadding(child, insets);
        }
    }
    
0
Krzysztof Zgondek

J'ai résolu cette question en 4.4

if(test){
    Log.d(TAG, "fit true ");
    relativeLayout.setFitsSystemWindows(true);
    relativeLayout.requestFitSystemWindows();
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}else {
    Log.d(TAG, "fit false");
    relativeLayout.setFitsSystemWindows(false);
    relativeLayout.requestFitSystemWindows();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
0
Kejie Yuan