Erstellen eines Einstellungsbildschirms mit der Support-Symbolleiste (v21)

116

Ich hatte Probleme bei der Verwendung der neuen Material Design-Symbolleiste in der Support-Bibliothek auf einem Einstellungsbildschirm.

Ich habe eine settings.xml-Datei wie folgt:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/AddingItems"
        android:key="pref_key_storage_settings">

        <ListPreference
            android:key="pref_key_new_items"
            android:title="@string/LocationOfNewItems"
            android:summary="@string/LocationOfNewItemsSummary"
            android:entries="@array/new_items_entry"
            android:entryValues="@array/new_item_entry_value"
            android:defaultValue="1"/>

    </PreferenceCategory>
</PreferenceScreen>

Die Zeichenfolgen werden an anderer Stelle definiert.

James Cross
quelle
stackoverflow.com/a/27455363/2247612 Diese Antwort hat eine perfekte Lösung für Support Library
Harishannam

Antworten:

110

Das GitHub Repo finden Sie hier


Ein bisschen spät zur Party, aber dies ist meine Lösung, die ich verwende, um weiterhin zu verwenden PreferenceActivity:

settings_toolbar.xml :

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/actionBarSize"
    app:navigationContentDescription="@string/abc_action_bar_up_description"
    android:background="?attr/colorPrimary"
    app:navigationIcon="?attr/homeAsUpIndicator"
    app:title="@string/action_settings"
    />

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
        Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
        root.addView(bar, 0); // insert at top
        bar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }

}

Result :

Beispiel


UPDATE (Lebkuchen-Kompatibilität):

Gemäß den Kommentaren geben Lebkuchengeräte NullPointerException in dieser Zeile zurück:

LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();

FIX:

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        Toolbar bar;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
            bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
            root.addView(bar, 0); // insert at top
        } else {
            ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
            ListView content = (ListView) root.getChildAt(0);

            root.removeAllViews();

            bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
            

            int height;
            TypedValue tv = new TypedValue();
            if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
                height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
            }else{
                height = bar.getHeight();
            }

            content.setPadding(0, height, 0, 0);

            root.addView(content);
            root.addView(bar);
        }

        bar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

Alle Probleme mit den oben genannten lassen Sie es mich wissen!


UPDATE 2: TINTING WORKAROUND

Wie in vielen Entwicklungsnotizen erwähnt, wird PreferenceActivitydas Abtönen von Elementen nicht unterstützt. Mit einigen internen Klassen können Sie dies jedoch erreichen. Das ist, bis diese Klassen entfernt werden. (Funktioniert mit appCompat support-v7 v21.0.3).

Fügen Sie die folgenden Importe hinzu:

import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintSpinner;

Überschreiben Sie dann die onCreateViewMethode:

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    // Allow super to try and create a view first
    final View result = super.onCreateView(name, context, attrs);
    if (result != null) {
        return result;
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        // If we're running pre-L, we need to 'inject' our tint aware Views in place of the
        // standard framework versions
        switch (name) {
            case "EditText":
                return new TintEditText(this, attrs);
            case "Spinner":
                return new TintSpinner(this, attrs);
            case "CheckBox":
                return new TintCheckBox(this, attrs);
            case "RadioButton":
                return new TintRadioButton(this, attrs);
            case "CheckedTextView":
                return new TintCheckedTextView(this, attrs);
        }
    }

    return null;
}

Result:

Beispiel 2


AppCompat 22.1

AppCompat 22.1 führte neue getönte Elemente ein, sodass die internen Klassen nicht mehr verwendet werden müssen, um den gleichen Effekt wie beim letzten Update zu erzielen. Folgen Sie stattdessen diesem (immer noch überschreibenden onCreateView):

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    // Allow super to try and create a view first
    final View result = super.onCreateView(name, context, attrs);
    if (result != null) {
        return result;
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        // If we're running pre-L, we need to 'inject' our tint aware Views in place of the
        // standard framework versions
        switch (name) {
            case "EditText":
                return new AppCompatEditText(this, attrs);
            case "Spinner":
                return new AppCompatSpinner(this, attrs);
            case "CheckBox":
                return new AppCompatCheckBox(this, attrs);
            case "RadioButton":
                return new AppCompatRadioButton(this, attrs);
            case "CheckedTextView":
                return new AppCompatCheckedTextView(this, attrs);
        }
    }

    return null;
}

NESTED PREFERENCE SCREENS

Viele Leute haben Probleme mit der Aufnahme der Symbolleiste in eine verschachtelte, <PreferenceScreen />aber ich habe eine Lösung gefunden !! - Nach viel Versuch und Irrtum!

Fügen Sie Folgendes hinzu SettingsActivity:

@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
    super.onPreferenceTreeClick(preferenceScreen, preference);

    // If the user has clicked on a preference screen, set up the screen
    if (preference instanceof PreferenceScreen) {
        setUpNestedScreen((PreferenceScreen) preference);
    }

    return false;
}

public void setUpNestedScreen(PreferenceScreen preferenceScreen) {
    final Dialog dialog = preferenceScreen.getDialog();

    Toolbar bar;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        LinearLayout root = (LinearLayout) dialog.findViewById(android.R.id.list).getParent();
        bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
        root.addView(bar, 0); // insert at top
    } else {
        ViewGroup root = (ViewGroup) dialog.findViewById(android.R.id.content);
        ListView content = (ListView) root.getChildAt(0);

        root.removeAllViews();

        bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);

        int height;
        TypedValue tv = new TypedValue();
        if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
            height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
        }else{
            height = bar.getHeight();
        }

        content.setPadding(0, height, 0, 0);

        root.addView(content);
        root.addView(bar);
    }

    bar.setTitle(preferenceScreen.getTitle());

    bar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dialog.dismiss();
        }
    });
}

Der Grund dafür PreferenceScreenist, dass sie als Wrapper-Dialogfeld basieren. Daher müssen wir das Dialogfeld-Layout erfassen, um die Symbolleiste hinzuzufügen.


Symbolleistenschatten

Durch das Importieren von Designs ist das ToolbarAufheben und Abschatten in Geräten vor Version 21 nicht möglich. Wenn Sie also eine Höhe auf Ihrem Gerät haben möchten, müssen ToolbarSie diese in Folgendes einschließen AppBarLayout:

settings_toolbar.xml ::

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

   <android.support.v7.widget.Toolbar
       .../>

</android.support.design.widget.AppBarLayout>

Nicht zu vergessen, dass Sie die Design Support-Bibliothek als Abhängigkeit in der build.gradleDatei hinzufügen :

compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'

Android 6.0

Ich habe das gemeldete überlappende Problem untersucht und kann das Problem nicht reproduzieren.

Der oben verwendete vollständige Code erzeugt Folgendes:

Geben Sie hier die Bildbeschreibung ein

Wenn mir etwas fehlt, lass es mich bitte über dieses Repo wissen und ich werde es untersuchen.

David Passmore
quelle
es gibt nullpointer Ausnahme in Gingebread, root ist null..jede Lösung?
Qlimax
Ihre Lösung funktioniert hervorragend. Es gibt jedoch ein Problem mit diesem Ansatz, infact ohne Erweiterung von ActionBarActivity, das (aus der Dokumentation) obligatorisch ist, um das Materialthema auf <5.0 zu erhalten. colorAccent (nur um ein Beispiel zu erstellen) wird nicht auf Kontrollkästchen in Geräten <5.0 angewendet. Dies scheint ein echtes Problem zu sein. Vielleicht muss ich die Präferenzaktivität entfernen und ein lineares Layout verwenden, um einen Präferenzbildschirm zu simulieren. Andernfalls sehe ich keine Möglichkeit, Materialthemen in Geräten von API-Level 8 bis 21 zu verwenden. Präferenzfragment ist "nur"> 11 :(
andQlimax
1
@andQlimax Ich habe meine Antwort mit einer Lösung für das Farbproblem aktualisiert
David Passmore
3
@ DavidPassmore für mich überlappt die Präferenzliste auf der Symbolleiste
Shashank Srivastava
1
@ShashankSrivastava Dies spiegelt sich wider, wenn Sie Android 6 verwenden. Ich arbeite an einer Lösung dafür. Danke für das Update.
David Passmore
107

Sie können a PreferenceFragmentals Alternative zu verwenden PreferenceActivity. Hier ist das ActivityBeispiel für die Verpackung :

public class MyPreferenceActivity extends ActionBarActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pref_with_actionbar);

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(uk.japplications.jcommon.R.id.toolbar);
        setSupportActionBar(toolbar);

        getFragmentManager().beginTransaction().replace(R.id.content_frame, new MyPreferenceFragment()).commit();
    }
}

Und hier ist die Layoutdatei (pref_with_actionbar):

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="@dimen/action_bar_height"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:theme="@style/ToolbarTheme.Base"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_below="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

Und zum Schluss die PreferenceFragment:

public static class MyPreferenceFragment extends PreferenceFragment{
    @Override
    public void onCreate(final Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings);
    }
}

Ich hoffe das hilft jemandem.

James Cross
quelle
39
Ich habe diesen Ansatz versucht. Das Problem ist, dass die Symbolleiste nicht in den Bildschirmen für untergeordnete Einstellungen angezeigt wird.
Madhur Ahuja
2
Ich denke, er spricht von PreferenceScreen, der in das XML der Stammeinstellungen eingebettet ist.
Lucas S.
5
Ich mochte den Ansatz, aber leider nicht funktionieren, wenn die Ziel-API kleiner als API 11 ist
Midhunhk
12
Es wird mit keinem funktionieren. Praktisch scheint es keine Möglichkeit zu geben, materiell gestaltete Bildschirme mit Symbolleisten und verschachtelten Einstellungen zu erstellen. Wenn Sie eine verwenden ActionBarActivity, um die Symbolleiste und die zugehörigen Funktionen abzurufen, gibt es keine onBuildHeaders()zu überschreibende und keine tatsächliche Präferenzunterstützung in der Aktivität. Wenn Sie die alte verwenden PreferenceActivity, verfügen Sie nicht über die Symbolleiste und die zugehörigen Funktionen (ja, Sie können eine Toolbarund ein Layout haben, aber Sie können nicht aufrufen setSupportActionBar(). Entweder mit Präferenzüberschriften oder verschachtelten Präferenzbildschirmen scheinen wir festzustecken.
Gábor
1
Ich stimme Gabors Kommentar zu. Diese Lösung funktioniert im Allgemeinen nicht. Es gibt besser einen Balg mit emulierender Symbolleiste (keine ActionBar, aber wen interessiert das?) Und auch eine neue Support-Bibliothek, die mit AppCompatDelegate an Bord veröffentlicht wurde.
Eugene Wechsler
48

Komplett neues Update.

Mit einigen Experimenten habe ich anscheinend die funktionierende AppCompat 22.1+ -Lösung für verschachtelte Einstellungsbildschirme gefunden.

Erstens müssen Sie, wie in vielen Antworten erwähnt (einschließlich einer hier), die neue verwenden AppCompatDelegate . Verwenden Sie entweder die AppCompatPreferenceActivity.javaDatei aus den Support-Demos ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/AppC ) oder kopieren Sie die relevanten Funktionen in Ihre eigenen PreferenceActivity. Ich werde hier den ersten Ansatz zeigen:

public class SettingsActivity extends AppCompatPreferenceActivity {

  @Override
  public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.settings, target);

    setContentView(R.layout.settings_page);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    ActionBar bar = getSupportActionBar();
    bar.setHomeButtonEnabled(true);
    bar.setDisplayHomeAsUpEnabled(true);
    bar.setDisplayShowTitleEnabled(true);
    bar.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
    bar.setTitle(...);
  }

  @Override
  protected boolean isValidFragment(String fragmentName) {
    return SettingsFragment.class.getName().equals(fragmentName);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        onBackPressed();
        break;
    }
    return super.onOptionsItemSelected(item);
  }
}

Das dazugehörige Layout ist recht einfach und üblich ( layout/settings_page.xml):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="0dp"
    android:orientation="vertical"
    android:padding="0dp">
  <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary"
      android:elevation="4dp"
      android:theme="@style/..."/>
  <ListView
      android:id="@id/android:list"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>
</LinearLayout>

Die Einstellungen selbst sind wie gewohnt definiert ( xml/settings.xml):

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
  <header
      android:fragment="com.example.SettingsFragment"
      android:summary="@string/..."
      android:title="@string/...">
    <extra
        android:name="page"
        android:value="page1"/>
  </header>
  <header
      android:fragment="com.example.SettingsFragment"
      android:summary="@string/..."
      android:title="@string/...">
    <extra
        android:name="page"
        android:value="page2"/>
  </header>
  ...
</preference-headers>

Bis zu diesem Zeitpunkt gibt es keinen wirklichen Unterschied zu Lösungen im Internet. Tatsächlich können Sie dies auch dann verwenden, wenn Sie keine verschachtelten Bildschirme, keine Überschriften, nur einen einzigen Bildschirm haben.

Wir verwenden eine gemeinsame PreferenceFragmentfür alle tieferen Seiten, die sich durch die extraParameter in den Kopfzeilen unterscheiden. Jede Seite hat ein separates XML mit einem gemeinsamen PreferenceScreenInneren (xml/settings_page1.xml et al.). Das Fragment verwendet dasselbe Layout wie die Aktivität, einschließlich der Symbolleiste.

public class SettingsFragment extends PreferenceFragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getActivity().setTheme(R.style...);

    if (getArguments() != null) {
      String page = getArguments().getString("page");
      if (page != null)
        switch (page) {
          case "page1":
            addPreferencesFromResource(R.xml.settings_page1);
            break;
          case "page2":
            addPreferencesFromResource(R.xml.settings_page2);
            break;
          ...
        }
    }
  }

  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View layout = inflater.inflate(R.layout.settings_page, container, false);
    if (layout != null) {
      AppCompatPreferenceActivity activity = (AppCompatPreferenceActivity) getActivity();
      Toolbar toolbar = (Toolbar) layout.findViewById(R.id.toolbar);
      activity.setSupportActionBar(toolbar);

      ActionBar bar = activity.getSupportActionBar();
      bar.setHomeButtonEnabled(true);
      bar.setDisplayHomeAsUpEnabled(true);
      bar.setDisplayShowTitleEnabled(true);
      bar.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
      bar.setTitle(getPreferenceScreen().getTitle());
    }
    return layout;
  }

  @Override
  public void onResume() {
    super.onResume();

    if (getView() != null) {
      View frame = (View) getView().getParent();
      if (frame != null)
        frame.setPadding(0, 0, 0, 0);
    }
  }
}

Zum Schluss noch eine kurze Zusammenfassung, wie das tatsächlich funktioniert. Mit der neuen Funktion AppCompatDelegatekönnen wir alle Aktivitäten mit AppCompat-Funktionen verwenden, nicht nur solche, die von den Aktivitäten in AppCompat ausgehen. Dies bedeutet, dass wir das gute alte PreferenceActivityin ein neues verwandeln und die Symbolleiste wie gewohnt hinzufügen können. Ab diesem Zeitpunkt können wir uns an die alten Lösungen für Einstellungsbildschirme und Überschriften halten, ohne von der vorhandenen Dokumentation abzuweichen. Es gibt nur einen wichtigen Punkt: Nicht onCreate()in der Aktivität verwenden, da dies zu Fehlern führen kann. Verwenden Sie diese Option onBuildHeaders()für alle Vorgänge wie das Hinzufügen der Symbolleiste.

Der einzige wirkliche Unterschied besteht darin, dass Sie mit verschachtelten Bildschirmen den gleichen Ansatz für die Fragmente verwenden können. Sie können sie auf onCreateView()die gleiche Weise verwenden, indem Sie Ihr eigenes Layout anstelle des Systemlayouts aufblasen und die Symbolleiste auf die gleiche Weise wie in der Aktivität hinzufügen.

Gábor
quelle
2
Was für eine tolle kleine Problemumgehung! Dies ist die einzige Lösung, die ich gefunden habe und die die Materialsymbolleiste auf einem untergeordneten PreferenceScreen anzeigt. Gut gemacht, Sir.
String
Ich benutze die Ressource aus der Appcompat-Bibliothek für das Up-Icon:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully
Mit dieser Lösung denke ich, dass die Symbolleiste mit dem Inhalt scrollen wird, oder? Weil es nur ein Element in der internen ListView ist.
Tasomaniac
Nicht mit dieser aktualisierten, neuen Lösung. Das funktioniert wie erwartet.
Gábor
Merkwürdigerweise ist diese Lösung nicht zu erkennen scheint PreferenceFragmentCompatstatt PreferenceFragment. Wenn Sie einen preference-headermit xmlns:app="http://schemas.android.com/apk/res-auto" und dann app:fragmentanstelle von android:fragmenteinrichten, wird kein neuer Einstellungsbildschirm geladen. Haben Sie also Probleme mit der Abwärtskompatibilität ... Vorschläge?
Fett
18

Wenn Sie PreferenceHeaders verwenden möchten, können Sie den folgenden Ansatz verwenden:

import android.support.v7.widget.Toolbar;

public class MyPreferenceActivity extends PreferenceActivity

   Toolbar mToolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
        LinearLayout content = (LinearLayout) root.getChildAt(0);
        LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.activity_settings, null);

        root.removeAllViews();
        toolbarContainer.addView(content);
        root.addView(toolbarContainer);

        mToolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar);
    }

    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }

    // Other methods

}

layout / activity_settings.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:theme="@style/AppTheme"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</LinearLayout>

Sie können hier jedes Layout verwenden, das Sie bevorzugen. Stellen Sie jedoch sicher, dass Sie es auch im Java-Code anpassen.

Und schließlich Ihre Datei mit Headern (xml / pref_headers.xml)

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">

    <header
        android:fragment="com.example.FirstFragment"
        android:title="@string/pref_header_first" />
    <header
        android:fragment="com.example.SecondFragment"
        android:title="@string/pref_header_second" />

</preference-headers>
Sven Dubbeld
quelle
root.addView (Symbolleiste); Warum? root.addView (toolbarContainer);
Crossle Song
Hoppla, beim Umbenennen eine Variable verpasst, wurde sie behoben.
Sven Dubbeld
1
Hervorragende Antwort. Der Schlüssel hier ist android.R.id.content, wenn man bedenkt wir früher ein weitergeben ListViewmit android.R.id.listder Präferenzliste selbst (und immer noch tun , wenn das Fragment lose Verwendung headerless, Art und Weise) statt.
Davidcsb
2
Ich denke, es ist besser, den Code von Android zu überprüfen, um zu sehen, was es braucht, als mit seinen Ansichten herumzuspielen (Ansichten entfernen / hinzufügen, die es hat). Ich denke, dass es auf diese Weise sicherer ist. Ich schlage vor, die Datei "priority_list_content" auszuchecken.
Android-Entwickler
2
Dies ist die beste Antwort in diesem Thread. Der Autor dieses Artikels hat ihn auf die vollständige Referenzimplementierung erweitert, die ich verwendet habe. Tatsächlich ist dies die einzige funktionierende Lösung zur Unterstützung anspruchsvoller Einstellungen in Ihrer App.
Eugene Wechsler
17

Mit der Veröffentlichung der Android Support Library 22.1.0 und des neuen AppCompatDelegate finden Sie hier ein schönes Beispiel für eine Implementierung der PreferenceActivity mit Materialunterstützung mit Abwärtskompatibilität.

Update Es funktioniert auch auf verschachtelten Bildschirmen.

https://android.googlesource.com/platform/development/+/marshmallow-mr3-release/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java

MrBrightside
quelle
1
Oh, das sind großartige Neuigkeiten! Daher scheinen Lösungen, die auf "PreferenceActivity erweitern" basieren, in dieser neuen Perspektive besser zu sein als Lösungen, die auf "ActionBarActivity erweitern" basieren.
Eugene Wechsler
1
@EugeneWechsler Ja, ActionBarActivity ist jetzt veraltet.
MrBrightside
Arbeiten Sie diese Lösung auch auf verschachtelten Bildschirmen? Gibt es ein besseres Beispiel?
Tomas
@Tomas Ich habe es noch nicht versucht, aber es sollte auch auf verschachtelten Bildschirmen funktionieren. Wenn es für Sie funktioniert, teilen Sie uns dies bitte mit.
MrBrightside
Vielen Dank ! Ich arbeite für mich auf einem Galaxy Nexus (4.3) und auf dem Emulator mit verschachtelten Bildschirmen (Lollipop).
Tim Autin
6

Obwohl die obigen Antworten ausführlich erscheinen PreferenceActivity, habe ich Hilfe von diesem Projekt erhalten , wenn Sie eine schnelle Lösung für die Verwendung der Symbolleiste mit Support-API 7 und höher während der Erweiterung benötigen.

https://github.com/AndroidDeveloperLB/ActionBarPreferenceActivity

activity_settings.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/app_theme_light"
    app:popupTheme="@style/Theme.AppCompat.Light"
    app:theme="@style/Theme.AppCompat" />

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/padding_medium" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

SettingsActivity.java

public class SettingsActivity extends PreferenceActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_settings);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

    addPreferencesFromResource(R.xml.preferences);

    toolbar.setClickable(true);
    toolbar.setNavigationIcon(getResIdFromAttribute(this, R.attr.homeAsUpIndicator));
    toolbar.setTitle(R.string.menu_settings);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            finish();
        }
    });

}

private static int getResIdFromAttribute(final Activity activity, final int attr) {
    if (attr == 0) {
        return 0;
    }
    final TypedValue typedvalueattr = new TypedValue();
    activity.getTheme().resolveAttribute(attr, typedvalueattr, true);
    return typedvalueattr.resourceId;
}
}
midhunhk
quelle
6

Auch ich habe nach einer Lösung gesucht, um die v7- Support-Symbolleiste ( API 25 ) zur AppCompatPreferenceActivity hinzuzufügen (die von AndroidStudio beim Hinzufügen einer SettingsActivity automatisch erstellt wird). Nachdem ich mehrere Lösungen gelesen und jede ausprobiert hatte, bemühte ich mich, die generierten PreferenceFragment-Beispiele auch mit einer Symbolleiste anzuzeigen.

Eine modifizierte Lösung, die irgendwie funktionierte, war von " Gabor ".

Eine der Vorbehalte, mit denen ich konfrontiert war, war, dass 'onBuildHeaders' nur einmal feuerte. Wenn Sie ein Gerät (wie ein Telefon) zur Seite drehen, wird die Ansicht neu erstellt und die PreferenceActivity wird wieder ohne Symbolleiste angezeigt. Die PreferenceFragments behalten jedoch ihre.

Ich habe versucht, mit 'onPostCreate' 'setContentView' aufzurufen, während dies dazu diente, die Symbolleiste neu zu erstellen, wenn sich die Ausrichtung änderte. PreferenceFragments wurde dann leer gemacht.

Was ich mir ausgedacht habe, nutzt fast jeden Tipp und jede Antwort, die ich zu diesem Thema lesen konnte. Ich hoffe, andere finden es auch nützlich.

Wir werden mit Java beginnen

Zuerst in (der generierten) AppCompatPreferenceActivity.java habe ich 'setSupportActionBar' wie folgt geändert:

public void setSupportActionBar(@Nullable Toolbar toolbar) {
    getDelegate().setSupportActionBar(toolbar);
    ActionBar bar = getDelegate().getSupportActionBar();
    bar.setHomeButtonEnabled(true);
    bar.setDisplayHomeAsUpEnabled(true);
}

Zweitens habe ich eine neue Klasse mit dem Namen AppCompatPreferenceFragment.java erstellt (es ist derzeit ein nicht verwendeter Name, obwohl dies möglicherweise nicht so bleibt!):

abstract class AppCompatPreferenceFragment extends PreferenceFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_settings, container, false);
        if (view != null) {
            Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar_settings);
            ((AppCompatPreferenceActivity) getActivity()).setSupportActionBar(toolbar);
        }
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        View frame = (View) getView().getParent();
        if (frame != null) frame.setPadding(0, 0, 0, 0);
    }
}

Dies ist der Teil von Gabors Antwort, der funktioniert hat.

Last , Konsistenz gelangen müssen wir einige Änderungen machen SettingsActivity.java :

public class SettingsActivity extends AppCompatPreferenceActivity {

    boolean mAttachedFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mAttachedFragment = false;
        super.onCreate(savedInstanceState);
    }

    @Override
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }

    @Override
    public void onAttachFragment(Fragment fragment) {
        mAttachedFragment = true;
        super.onAttachFragment(fragment);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        //if we didn't attach a fragment, go ahead and apply the layout
        if (!mAttachedFragment) {
            setContentView(R.layout.activity_settings);
            setSupportActionBar((Toolbar)findViewById(R.id.toolbar_settings));
        }
    }

    /**
     * This fragment shows general preferences only. It is used when the
     * activity is showing a two-pane settings UI.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static class GeneralPreferenceFragment extends AppCompatPreferenceFragment {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            addPreferencesFromResource(R.xml.pref_general);
            setHasOptionsMenu(true);

            bindPreferenceSummaryToValue(findPreference("example_text"));
            bindPreferenceSummaryToValue(findPreference("example_list"));
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == android.R.id.home) {
                startActivity(new Intent(getActivity(), SettingsActivity.class));
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
}

Einige Codes wurden der Kürze halber aus der Aktivität herausgelassen. Die Schlüsselkomponenten hier sind ' onAttachedFragment ', ' onPostCreate ' und das 'GeneralPreferenceFragment' erweitert jetzt das benutzerdefinierte ' AppCompatPreferenceFragment ' anstelle von PreferenceFragment.

Codeübersicht : Wenn ein Fragment vorhanden ist, fügt das Fragment das neue Layout ein und ruft die geänderte Funktion 'setSupportActionBar' auf. Wenn das Fragment nicht vorhanden ist, fügt SettingsActivity das neue Layout in 'onPostCreate' ein.

Nun zum XML (sehr einfach):

activity_settings.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        layout="@layout/app_bar_settings"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

app_bar_settings.xml :

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".SettingsActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.NoActionBar.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar_settings"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.NoActionBar.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_settings" />

</android.support.design.widget.CoordinatorLayout>

content_settings.xml :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".SettingsActivity"
    tools:showIn="@layout/app_bar_settings">

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

Endergebnis :

EinstellungenAktivität

GeneralPreferenceFragment

SilverX
quelle
Sieht vielversprechend aus, funktioniert aber bei mir nicht. imgur.com/lSSVCIo (Pixel C-Emulator).
Thomas Vos
Github Link für die Faulen
Martin Sing
5

Ich habe eine neue (möglicherweise übersichtlichere) Lösung, die die Beispiele AppCompatPreferenceActivityaus dem Support v7 verwendet. Mit diesem Code habe ich mein eigenes Layout erstellt, das eine Symbolleiste enthält:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:fitsSystemWindows="true" tools:context="edu.adelphi.Adelphi.ui.activity.MainActivity">

    <android.support.design.widget.AppBarLayout android:id="@+id/appbar"
        android:layout_width="match_parent" android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar android:id="@+id/toolbar"
            android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

    <FrameLayout android:id="@+id/content"
        android:layout_width="match_parent" android:layout_height="match_parent"/>

</android.support.design.widget.CoordinatorLayout>

Dann habe AppCompatPreferenceActivityich in meinem geändert setContentView, um ein neues Layout zu erstellen, und das bereitgestellte Layout in mein FrameLayout:

@Override
public void setContentView(@LayoutRes int layoutResID) {
    View view = getLayoutInflater().inflate(R.layout.toolbar, null);
    FrameLayout content = (FrameLayout) view.findViewById(R.id.content);
    getLayoutInflater().inflate(layoutResID, content, true);
    setContentView(view);
}

Dann erweitere ich einfach AppCompatPreferenceActivity, damit ich auch setSupportActionBar((Toolbar) findViewById(R.id.toolbar))Menüelemente in der Symbolleiste aufrufen und aufblasen kann. Alles unter Beibehaltung der Vorteile von a PreferenceActivity.

Bryan
quelle
5

Lassen Sie es uns hier einfach und sauber halten, ohne ein eingebautes Layout zu beschädigen

import android.support.design.widget.AppBarLayout;
import android.support.v4.app.NavUtils;
import android.support.v7.widget.Toolbar;

private void setupActionBar() {
    Toolbar toolbar = new Toolbar(this);

    AppBarLayout appBarLayout = new AppBarLayout(this);
    appBarLayout.addView(toolbar);

    final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
    final ViewGroup window = (ViewGroup) root.getChildAt(0);
    window.addView(appBarLayout, 0);

    setSupportActionBar(toolbar);

    // Show the Up button in the action bar.
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
    });
}
Samuel
quelle
Hat bei mir nicht funktioniert, root.getChildAt(0);kehrt zurück null.
Eido95
4

Ich habe diese einfache Lösung gefunden, als ich daran gearbeitet habe. Zuerst müssen wir ein Layout für die Einstellungsaktivität erstellen.

activity_settings.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.my.package">

    <android.support.v7.widget.Toolbar
        android:id="@+id/tool_bar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="@dimen/appbar_elevation"
        app:navigationIcon="?attr/homeAsUpIndicator"
        app:navigationContentDescription="@string/abc_action_bar_up_description"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <ListView
        android:id="@android:id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tool_bar" />

</RelativeLayout>

Stellen Sie sicher, dass Sie eine Listenansicht mit hinzufügen android:id="@android:id/list", da sie sonst ausgelöst wirdNullPointerException

Der nächste Schritt ist das Hinzufügen (Überschreiben) der onCreateMethode in Ihrer Einstellungsaktivität

Settings.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_settings);
    Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
    toolbar.setTitle(R.string.action_settings);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            finish();
        }
    });
}

Stellen Sie sicher, dass Sie importieren android.suppoer.v7.widget.Toolbar. Dies sollte auf allen APIs über 16 (Jelly Bean und höher) funktionieren.

Apurva
quelle
1

Ich möchte die markierte Lösung von James Cross fortsetzen, da danach das Problem besteht, nur den aktiven verschachtelten Bildschirm (PreferenceFragment) so zu schließen, dass die SettingsActivity nicht ebenfalls geschlossen wird.

Eigentlich funktioniert es auf allen verschachtelten Bildschirmen (daher verstehe ich die Lösung von Gábor, die ich erfolglos ausprobiert habe, nicht. Nun, es funktioniert bis zu einem bestimmten Punkt, aber es ist ein Durcheinander mehrerer Symbolleisten), weil der Benutzer auf einen Bildschirm mit Untereinstellungen klickt Es wird nur das Fragment geändert (siehe <FrameLayout android:id="@+id/content_frame" .../>), nicht die Symbolleiste, die immer aktiv und sichtbar bleibt. Es sollte jedoch ein benutzerdefiniertes Verhalten implementiert werden, um jedes Fragment entsprechend zu schließen.

In der Hauptklasse , SettingsActivitydie sich ActionBarActivitydie folgenden Methoden sollten umgesetzt werden. Beachten Sie, dass private von setupActionBar()aufgerufen wirdonCreate()

private void setupActionBar() {
    Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
    //Toolbar will now take on default Action Bar characteristics
    setSupportActionBar(toolbar);
    getSupportActionBar().setHomeButtonEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case android.R.id.home:
        onBackPressed();
        return true;
    }
    return super.onOptionsItemSelected(item);
}

@Override
public void onBackPressed() {
    if (getFragmentManager().getBackStackEntryCount() > 0) {
        getFragmentManager().popBackStackImmediate();
        //If the last fragment was removed then reset the title of main
        // fragment (if so the previous popBackStack made entries = 0).
        if (getFragmentManager().getBackStackEntryCount() == 0) {
            getSupportActionBar()
                .setTitle(R.string.action_settings_title);
        }
    } else {
        super.onBackPressed();
    }
}

Für den Titel des ausgewählten verschachtelten Bildschirms sollten Sie die Referenz Ihrer Symbolleiste abrufen und den entsprechenden Titel mit toolbar.setTitle(R.string.pref_title_general);(zum Beispiel) festlegen .

Das PreferenceFragment muss nichtgetSupportActionBar() in allen implementiert werden, da bei jedem Commit nur die Ansicht des Fragments geändert wird, nicht die Symbolleiste.

Es ist nicht erforderlich , eine gefälschte ToolbarPreference-Klasse zu erstellen, um sie in jeder Einstellungen.xml hinzuzufügen (siehe Gábors Antwort).

Davideas
quelle
1

Hier ist eine Bibliothek, die ich erstellt habe und die auf AOSP-Code basiert, der sowohl den Einstellungen als auch den Dialogen Farbtöne hinzufügt, eine Aktionsleiste hinzufügt und alle Versionen von API 7 unterstützt:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary

Android-Entwickler
quelle
Wenn Sie sich den Code ansehen, funktioniert er nicht für verschachtelte Einstellungen ...?
Tim Rae
@ TimRae Ich bin nicht sicher, ob ich getestet habe, wovon du sprichst. Bitte erklären Sie, was Sie meinen. Was ist das Szenario, das Sie genau verwenden möchten?
Android-Entwickler
Wenn Sie ein im PreferenceScreenInnern ein PreferenceScreenwie dieser
Tim Rae
Ich habe so etwas noch nie benutzt. Lesen Sie die Dokumente :: developer.android.com/reference/android/preference/… . Ich sehe, dass dies beim Wechseln zwischen Bildschirmen hilfreich sein kann. Sie sagen, ich sollte es auch hinzufügen? Ich werde es mir ansehen . Danke dir. Verwenden Sie Github auch das nächste Mal für solche Dinge (Anfragen und Probleme).
Android-Entwickler
Ja, es ist nützlich, wenn Sie zu viele Einstellungen für einen einzelnen Bildschirm haben ... Es gibt bereits mehrere Stellen in diesem Thread, an denen verschachtelte Bildschirme erwähnt werden. Ich denke, hier ist ein geeigneter Ort, um Kommentare abzugeben
Tim Rae
1

Nun, das ist heute (18. November 2015) immer noch ein Problem für mich. Ich habe alle Lösungen aus diesem Thread ausprobiert, aber es gab zwei Hauptprobleme, die ich nicht lösen konnte:

  • Verschachtelte Einstellungsbildschirme wurden ohne Symbolleiste angezeigt
  • In den Einstellungen wurde der Material-Look auf Pre-Lollipop-Geräten nicht angezeigt

Also habe ich eine Bibliothek mit einer komplizierteren Lösung erstellt. Grundsätzlich musste ich Stile intern auf die Einstellungen anwenden, wenn wir ein Pre-Lollipop-Gerät verwenden, und ich habe auch die verschachtelten Bildschirme mit einem benutzerdefinierten Fragment behandelt (Wiederherstellung aller verschachtelten Hierarchien unter Verwendung des PreferenceScreen- Schlüssels ).

Die Bibliothek ist diese: https://github.com/ferrannp/material-preferences

Und wenn Sie sich für den Quellcode interessieren (zu lang, um ihn hier zu veröffentlichen), ist dies im Grunde der Kern davon: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ java / com / fnp / materialpreferences / PreferenceFragment.java

Ferran Negre
quelle