it-swarm.com.de

Android 4.4 - Transparente Status-/Navigationsleisten - passtSystemWindows/clipToPadding nicht durch Fragmenttransaktionen

Wenn Sie die durchscheinenden Status- und Navigationsleisten der neuen Android 4.4 KitKat-APIs verwenden, funktioniert die Einstellung von fitsSystemWindows="true" und clipToPadding="false" auf ListView zunächst. fitsSystemWindows="true" behält die Liste unter der Aktionsleiste und über der Navigationsleiste bei, clipToPadding="false" ermöglicht es der Liste, unter der transparenten Navigationsleiste zu blättern, und das letzte Element in der Liste wird so weit nach oben verschoben, dass die Navigationsleiste durchlaufen wird.

Wenn Sie jedoch den Inhalt durch eine andere Fragment durch eine FragmentTransaction ersetzen, verschwindet der Effekt von fitsSystemWindows und das Fragment wird unter der Aktionsleiste und der Navigationsleiste angezeigt.

Ich habe hier eine Codebase mit Demo-Quellcode und eine herunterladbare APK als Beispiel: https://github.com/afollestad/KitKat-transparency-demo . Um zu sehen, wovon ich spreche, öffnen Sie die Demo-App von einem Gerät, auf dem KitKat ausgeführt wird, tippen Sie auf ein Element in der Liste (wodurch eine weitere Aktivität geöffnet wird), und tippen Sie auf ein Element in der neuen geöffneten Aktivität. Das Fragment, das den Inhalt ersetzt, wird unter der Aktionsleiste angezeigt, und clipToPadding funktioniert nicht ordnungsgemäß (die Navigationsleiste deckt das letzte Element in der Liste ab, wenn Sie ganz nach unten scrollen).

Irgendwelche Ideen? Irgendwelche Klarstellung nötig? Ich habe die Vorher-Nachher-Screenshots meiner persönlichen App veröffentlicht, die für meinen Arbeitgeber entwickelt wurde. 

OneTwo

27
afollestad

Ich habe das Problem gelöst, indem ich die Bibliothek verwendete. Ich verwende die Farbe meiner durchscheinenden Statusleiste.

Mit der SystemBarConfig-Klasse von ArchiveTarTint (wie hier https://github.com/jgilfelt/SystemBarTint#systembarconfig ) können Sie Insets abrufen, die ich als Auffüllung der Liste in jedem Fragment zusammen mit der Verwendung von clipToPadding="false" verwendet habe. auf der Liste.

Ich habe Details darüber, was ich in diesem Beitrag getan habe: 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

Ich hatte gestern mit dem gleichen Problem zu kämpfen. Nach langem Nachdenken fand ich eine elegante Lösung für dieses Problem.

Zuerst sah ich die Methode requestFitSystemWindows() auf ViewParent und versuchte, sie in der onActivityCreated() des Fragments aufzurufen (nachdem das Fragment an die Ansichtshierarchie angehängt wurde), aber leider hatte es keine Auswirkungen. Ich würde gerne ein konkretes Beispiel für die Verwendung dieser Methode sehen.

Dann fand ich eine ordentliche Problemumgehung: Ich erstellte eine benutzerdefinierte FitsSystemWindowsFrameLayout, die ich als fragment-Container in meinen Layouts verwende, als Drop-In-Ersatz für eine klassische FrameLayout. Sie speichert die Fensterinsets, wenn fitSystemWindows() vom System aufgerufen wird. Anschließend wird der Aufruf erneut an sein untergeordnetes Layout (das Fragment-Layout) weitergeleitet, sobald das Fragment hinzugefügt/angehängt wird.

Hier ist der vollständige Code:

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

Ich denke, das ist viel einfacher und robuster als Hacks, die versuchen, die Größe der UI-Elemente zu bestimmen, indem sie auf verborgene Systemeigenschaften zugreifen, die sich im Laufe der Zeit ändern können, und dann die Auffüllung manuell auf die Elemente anwenden.

12
BladeCoder

Okay, das ist unglaublich komisch. Ich bin gerade vor kurzem mit dem gleichen Problem konfrontiert, außer dass meine Soft-Tastatur beinhaltet. Es funktioniert zunächst, aber wenn ich Fragment-Transaktion hinzufüge, funktioniert der Android:fitsSystemWindows="true" nicht mehr. Ich habe die Lösung hier ausprobiert, keiner von ihnen hat für mich gearbeitet.

Hier ist mein Problem: enter image description here

Anstatt meine Ansicht zu verkleinern, drückt sie meine Ansicht nach oben und das ist das Problem.

Ich hatte jedoch Glück und stolperte versehentlich in eine Antwort, die für mich funktionierte!

Hier ist es also:

Zuallererst ist mein App-Theme: Theme.AppCompat.Light.NoActionBar (wenn dies relevant ist, vielleicht ist es Android, ist komisch).

Maurycy wies hier auf etwas sehr Interessantes, deshalb wollte ich testen, was er als wahr oder falsch meinte. Was er gesagt hat, war auch in meinem Fall wahr ... WENN Sie dieses Attribut zu Ihrer Aktivität im Android-Manifest Ihrer App hinzufügen:

enter image description here

Sobald Sie hinzugefügt haben: 

Android:windowSoftInputMode="adjustResize" 

nach Ihrer Fragment-Transaktion wird Android:fitsSystemWindows="true" nicht mehr ignoriert!

Ich ziehe es jedoch vor, dass Sie Android:fitsSystemWindows="true" NICHT im Root-Layout Ihres Fragments aufrufen. Eine der größten Stellen, an denen dieses Problem auftritt, ist der Ort, an dem Sie EditText oder eine ListView haben. Wenn Sie wie ich in dieser Situation stecken, setzen Sie Android:fitsSystemWindows="true" im Child des Root-Layouts wie folgt:

enter image description here

Ja, diese Lösung funktioniert auf allen Lollipop- und Pre-Lollipop-Geräten.

Und hier ist der Beweis: enter image description here

Die Größe wird geändert, anstatt das Layout nach oben zu drücken .. So hoffentlich habe ich jemandem geholfen, der sich auf demselben Boot befindet wie ich.

Danke euch allen!

8
Yoosuf

Ein Kopf für einige Leute, die in dieses Problem geraten. 

Eine wichtige Information mit der Methode fitSystemWindows, die einen Großteil der Arbeit erledigt:

Das Durchlaufen dieser Funktion in der Hierarchie erfolgt zuerst in der Tiefe. Das Gleiche Das Content-Insets-Objekt wird in der Hierarchie weitergegeben, also alle Änderungen Die dazugehörigen Ansichten werden von allen folgenden Ansichten (einschließlich potenziell oben in der Hierarchie) angezeigt, da dies ein Durchlauf der ersten Tiefe ist. Die erste Ansicht, die true zurückgibt, bricht die gesamte Traversierung ab.

Wenn Sie also andere Fragmente mit Inhaltsansichten haben, für die passtSystemWindows auf true gesetzt ist, wird das Flag möglicherweise ignoriert. Ich würde in Betracht ziehen, dass Ihr Fragment-Container das FitsSystemWindows-Flag enthält, wenn dies möglich ist. Andernfalls fügen Sie das Auffüllen manuell hinzu. 

5
Maurycy

Ich habe auch ziemlich viel damit zu kämpfen gehabt ... Ich habe alle Antworten hier gesehen. Leider löste keiner von ihnen mein Problem zu 100% der Zeit. Die .BarConfig funktioniert nicht immer, da der Balken auf einigen Geräten nicht erkannt wird. Ich habe mir den Quellcode angesehen und die Einfügungen gefunden werden im Fenster gespeichert.

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

So bekommen Sie sie. Anscheinend gibt es in Android L eine nette Methode, um diese Insets zu erhalten, aber in der Zwischenzeit könnte dies eine gute Lösung sein.

2

Ich bin auf das gleiche Problem gestoßen. Wenn ich Fragment ersetze. .__ Die 'passtSystemWindows' funktioniert nicht. 

Ich habe den Code durch Hinzufügen zu Ihrem Fragment behoben

@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

Zusammen mit der @BladeCoder-Antwort habe ich die FittedFrameLayout-Klasse erstellt, die zwei Dinge tut:

  • es fügt keine Auffüllung für sich selbst hinzu
  • es scannt alle Ansichten in seinem Container und fügt eine Auffüllung für sie hinzu, stoppt jedoch auf der untersten Ebene (wenn die Flagge fitssystemwindows gefunden wird, wird das Kind nicht tiefer gescannt, aber immer noch auf derselben Tiefe oder darunter).

    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

Ich habe diese Frage in 4.4 gelöst

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