Tworzenie ekranu preferencji z obsługą paska narzędzi (v21)


116

Miałem problem z używaniem nowego paska narzędzi Material Design w bibliotece pomocy na ekranie Preferencje.

Mam plik settings.xml jak poniżej:

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

Ciągi są zdefiniowane w innym miejscu.


stackoverflow.com/a/27455363/2247612 Ta odpowiedź ma idealne rozwiązanie dla biblioteki pomocy technicznej
harishannam

Odpowiedzi:


110

Znajdź repozytorium GitHub: tutaj


Trochę późno na imprezę, ale to jest moje rozwiązanie, którego używam jako obejścia, aby nadal używać 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 :

przykład


UPDATE (zgodność z piernikami):

Zgodnie z komentarzami urządzenia Gingerbread zwracają wyjątek NullPointerException w tej linii:

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

NAPRAWIĆ:

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

Wszelkie problemy z powyższym daj mi znać!


AKTUALIZACJA 2: OBEJŚCIE BARWIENIA

Jak wskazano w wielu notatkach deweloperskich, PreferenceActivitynie obsługuje barwienia elementów, jednak używając kilku wewnętrznych klas MOŻESZ to osiągnąć. Dzieje się tak do momentu usunięcia tych klas. (Działa przy użyciu appCompat support-v7 v21.0.3).

Dodaj następujące importy:

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;

Następnie nadpisz onCreateViewmetodę:

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

przykład 2


AppCompat 22.1

AppCompat 22.1 wprowadził nowe przyciemniane elementy, co oznacza, że ​​nie ma już potrzeby wykorzystywania klas wewnętrznych, aby osiągnąć ten sam efekt, co podczas ostatniej aktualizacji. Zamiast tego postępuj zgodnie z tym (nadal nadrzędnym 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;
}

EKRANY PREFERENCYJNE ZAGNIEŻDŻONE

Wiele osób ma problemy z włączaniem paska narzędzi do zagnieżdżonego, <PreferenceScreen />jednak znalazłem rozwiązanie! - Po wielu próbach i błędach!

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

Powodem tego PreferenceScreenjest to, że są one oparte na oknach opakowujących, więc musimy uchwycić układ okna dialogowego, aby dodać do niego pasek narzędzi.


Cień paska narzędzi

Zgodnie z projektem importowanie Toolbarnie pozwala na elewację i cieniowanie w urządzeniach starszych niż 21, więc jeśli chcesz mieć elewację na swoim Toolbar, musisz owinąć ją w 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>

Nie zapominając o dodaniu biblioteki obsługi projektowania jako zależności w build.gradlepliku:

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

Zbadałem zgłoszony problem nakładania się i nie mogę go odtworzyć.

Pełny kod używany jak powyżej daje następujące wyniki:

wprowadź opis obrazu tutaj

Jeśli czegoś brakuje, daj mi znać za pośrednictwem tego repozytorium, a zbadam sprawę.


daje wyjątek nullpointer w pierniku, korzeń ma wartość null .. jakiekolwiek rozwiązanie?
Qlimax

Twoje rozwiązanie działa świetnie. ale jest problem z tym podejściem, infact bez rozszerzania ActionBarActivity, które jest obowiązkowe (z dokumentacji), aby uzyskać motyw materiału na <5.0, colorAccent (tylko dla przykładu) nie jest stosowany do pól wyboru w urządzeniach <5.0. Wydaje się to bolesne. Może muszę usunąć aktywność preferencji i użyć układu liniowego, aby zasymulować ekran preferencji, w przeciwnym razie nie widzę sposobu na użycie motywu materiału w urządzeniach od poziomu API od 8 do 21. Fragment preferencji to "only"> 11 :(
andQlimax

1
@andQlimax Zaktualizowałem odpowiedź o rozwiązanie problemu z zabarwieniem
David Passmore

3
@DavidPassmore Dla mnie lista preferencji nakłada się na pasek narzędzi
Shashank Srivastava

1
@ShashankSrivastava Jest to odzwierciedlone, jeśli używasz Androida 6, pracuję nad rozwiązaniem tego problemu. Dziękuję za aktualizację.
David Passmore

107

Możesz użyć PreferenceFragment, jako alternatywy dla PreferenceActivity. Oto Activityprzykład pakowania :

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

A oto plik układu (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>

I na koniec PreferenceFragment:

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

Mam nadzieję, że to komuś pomoże.


39
Próbowałem tego podejścia. Problem polega na tym, że nie wyświetla paska narzędzi na ekranach preferencji dzieci.
Madhur Ahuja

2
Myślę, że mówi o PreferenceScreen osadzonym w głównym pliku XML preferencji.
Lucas S.

5
Podobało mi się to podejście, ale niestety nie zadziała, jeśli docelowy api jest mniejszy niż API 11
midhunhk

12
To zadziała z żadnym. Praktycznie wydaje się, że nie ma sposobu na tworzenie zagnieżdżonych ekranów preferencji zaprojektowanych pod kątem materiałów, opartych na narzędziach. Jeśli użyjesz ActionBarActivitydo uzyskania paska narzędzi i powiązanych funkcji, nie będzie onBuildHeaders()możliwości zastąpienia ani faktycznej obsługi preferencji w działaniu. Jeśli używasz starego PreferenceActivity, nie masz paska narzędzi i powiązanych funkcji (tak, możesz mieć Toolbarukład i, ale nie możesz wywołać setSupportActionBar(). Tak więc, z nagłówkami preferencji lub zagnieżdżonymi ekranami preferencji, wydaje się, że utknęliśmy.
Gábor

1
Zgadzam się z komentarzem Gabora. To rozwiązanie ogólnie nie działa. Jest lepszy poniżej z emulującym paskiem narzędzi (bez ActionBar, ale kogo to obchodzi), a także nowa biblioteka wsparcia wydana z AppCompatDelegate na pokładzie.
Eugene Wechsler

48

Całkowicie nowa aktualizacja.

Po pewnych eksperymentach wydaje mi się, że znalazłem działające rozwiązanie AppCompat 22.1+ dla zagnieżdżonych ekranów preferencji.

Po pierwsze, jak wspomniano w wielu odpowiedziach (w tym w jednej tutaj), musisz użyć nowego AppCompatDelegate. Skorzystaj z AppCompatPreferenceActivity.javapliku z demonstracji pomocy ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompivityatPreva ) z niego lub skopiuj odpowiednie funkcje do własnych PreferenceActivity. Tutaj pokażę pierwsze podejście:

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

Towarzyszący układ jest raczej prosty i zwykły ( 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>

Same preferencje są zdefiniowane jak zwykle ( 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>

Do tego momentu nie ma żadnej różnicy w stosunku do rozwiązań w sieci. Właściwie możesz tego użyć, nawet jeśli nie masz zagnieżdżonych ekranów, żadnych nagłówków, tylko jeden ekran.

Używamy wspólnego PreferenceFragmentdla wszystkich głębszych stron, różniących się extraparametrami w nagłówkach. Każda strona będzie miała oddzielny XML ze wspólnym PreferenceScreenwnętrzem ( xml/settings_page1.xmli in.). Fragment używa tego samego układu co ćwiczenie, łącznie z paskiem narzędzi.

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

Na koniec krótkie podsumowanie tego, jak to faktycznie działa. Nowa AppCompatDelegatepozwala nam korzystać z dowolnej aktywności z funkcjami AppCompat, nie tylko z tych, które wykraczają poza działania faktycznie w AppCompat. Oznacza to, że możemy zamienić stare dobre PreferenceActivityw nowe i jak zwykle dodać pasek narzędzi. Od tego momentu możemy trzymać się starych rozwiązań dotyczących ekranów preferencji i nagłówków, bez odstępstwa od istniejącej dokumentacji. Jest tylko jedna ważna kwestia: nie używaj onCreate()w ćwiczeniu, ponieważ doprowadzi to do błędów. Posługiwać sięonBuildHeaders() do wszystkich operacji, takich jak dodawanie paska narzędzi.

Jedyną prawdziwą różnicą jest to, że to właśnie sprawia, że ​​działa z zagnieżdżonymi ekranami, ponieważ możesz zastosować to samo podejście do fragmentów. Możesz użyć ich w onCreateView()ten sam sposób, nadmuchując własny układ zamiast systemowego, dodając pasek narzędzi w taki sam sposób jak w ćwiczeniu.


2
Co za świetne, małe obejście! Jest to jedyne rozwiązanie, które znalazłem, które pokaże pasek narzędzi materiału na potomnym ekranie PreferenceScreen. Dobra robota, sir.
String

Używam zasobu z biblioteki appcompat dla ikony up:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully

Myślę, że dzięki temu rozwiązaniu pasek narzędzi będzie przewijał się z zawartością, prawda? Ponieważ jest to tylko element w wewnętrznym ListView.
tasomaniac

Nie z tym zaktualizowanym, nowym rozwiązaniem. Działa zgodnie z oczekiwaniami.
Gábor,

O dziwo, to rozwiązanie nie wydaje się rozpoznawać PreferenceFragmentCompatzamiast PreferenceFragment. Skonfigurowanie preference-headerz, xmlns:app="http://schemas.android.com/apk/res-auto" a następnie app:fragmentzamiast android:fragmentnie ładuje żadnego nowego ekranu ustawień wstępnych. Więc masz problemy z kompatybilnością wsteczną ... sugestiami?
fattire

18

Jeśli chcesz użyć PreferenceHeaders, możesz użyć następującego podejścia:

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>

Możesz tutaj użyć dowolnego układu, po prostu upewnij się, że dostosowałeś go również w kodzie Java.

I na koniec twój plik z nagłówkami (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>

root.addView (pasek narzędzi); czemu? root.addView (toolbarContainer);
Crossle Song

Ups, przegapiłem zmienną podczas zmiany nazwy, naprawiłem ją.
Sven Dubbeld

1
Doskonała odpowiedź. Kluczem jest tutaj android.R.id.content, biorąc pod uwagę, że zamiast tego przekazywaliśmy z ListViewz android.R.id.listdla samej listy preferencji (i nadal robimy, jeśli używamy metody bez fragmentów, bez nagłówka).
davidcsb

2
Myślę, że lepiej sprawdzić kod Androida, aby zobaczyć, czego potrzebuje, zamiast majstrować przy jego widokach (usuń / dodaj widoki, które ma). Myślę, że w ten sposób jest bezpieczniej. Proponuję sprawdzić plik „preference_list_content”.
programista Androida

2
To najlepsza odpowiedź w tym wątku. Autor tego artykułu rozszerzył go do pełnej implementacji referencyjnej, z której korzystałem. W rzeczywistości jest to jedyne działające rozwiązanie do obsługi zaawansowanych preferencji w Twojej aplikacji.
Eugene Wechsler

17

Wraz z wydaniem biblioteki obsługi systemu Android 22.1.0 i nowej AppCompatDelegate, tutaj można znaleźć ładną próbkę implementacji PreferenceActivity z obsługą materiałów z kompatybilnością wsteczną.

Aktualizacja Działa również na zagnieżdżonych ekranach.

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


1
Och, to wspaniała wiadomość! Wydaje się więc, że rozwiązania oparte na „rozszerzeniu PreferenceActivity” są lepsze niż te oparte na „rozszerzeniu ActionBarActivity” w tej nowej perspektywie.
Eugene Wechsler

1
@EugeneWechsler Tak, rzeczywiście, ActionBarActivity jest teraz przestarzałe.
MrBrightside

Czy to rozwiązanie działa również na ekranach zagnieżdżonych? Czy jest lepszy przykład?
Tomas

@Tomas Jeszcze nie próbowałem, ale powinno działać również na zagnieżdżonych ekranach. Jeśli działa dla Ciebie, powiedz nam, proszę.
MrBrightside

Wielkie dzięki ! Pracuję dla mnie na Galaxy Nexusie (4.3) i na emulatorze z zagnieżdżonymi ekranami (Lollipop).
Tim Autin

6

Chociaż powyższe odpowiedzi wydają się skomplikowane, jeśli chcesz szybko naprawić rozwiązanie, aby korzystać z paska narzędzi Toolbar z obsługą API 7 i jednocześnie rozszerzać PreferenceActivity, otrzymałem pomoc z tego projektu poniżej.

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

6

Ja też szukaliśmy rozwiązania dodając v7 pasek oporowy ( API 25 ) do AppCompatPreferenceActivity (który jest automatycznie utworzony przez AndroidStudio podczas dodawania SettingsActivity). Po przeczytaniu kilku rozwiązań i wypróbowaniu każdego z nich, starałem się wyświetlić wygenerowane przykłady PreferenceFragment również z paskiem narzędzi.

Zmodyfikowane rozwiązanie, które się sprawdziło, pochodziło z „ Gabora ”.

Jednym z zastrzeżeń, z jakimi się spotkałem, były pożary „onBuildHeaders” tylko raz. Jeśli obrócisz urządzenie (takie jak telefon) na bok, widok zostanie odtworzony, a PreferenceActivity pozostanie ponownie bez paska narzędzi, jednak PreferenceFragments zachowa swoje.

Próbowałem użyć „onPostCreate”, aby wywołać „setContentView”, podczas gdy to działało w celu odtworzenia paska narzędzi po zmianie orientacji, PreferenceFragments był wtedy renderowany jako pusty.

To, co wymyśliłem, wykorzystuje prawie każdą wskazówkę i odpowiedź, jaką mogłem przeczytać na ten temat. Mam nadzieję, że inni też uznają to za przydatne.

Zaczniemy od Java

Najpierw w (wygenerowanym) AppCompatPreferenceActivity.java zmodyfikowałem 'setSupportActionBar' w następujący sposób:

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

Po drugie , utworzyłem nową klasę o nazwie AppCompatPreferenceFragment.java (jest to aktualna nieużywana nazwa, chociaż może tak nie pozostać!):

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

To jest część odpowiedzi Gabora, która zadziałała.

Ostatnio , aby uzyskać konsystencję musimy dokonać pewnych zmian 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);
        }
    }
}

Część kodu została pominięta ze względu na zwięzłość. Kluczowe składniki tutaj to „ onAttachedFragment ”, „ onPostCreate ”, a „GeneralPreferenceFragment” rozszerza teraz niestandardowy „ AppCompatPreferenceFragment ” zamiast PreferenceFragment.

Podsumowanie kodu : Jeśli fragment jest obecny, fragment wstrzykuje nowy układ i wywołuje zmodyfikowaną funkcję setSupportActionBar. Jeśli fragmentu nie ma, SettingsActivity wstrzykuje nowy układ w „onPostCreate”

Teraz przejdźmy do XML (bardzo proste):

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>

Wynik końcowy :

Ustawienia Aktywność

GeneralPreferenceFragment


Wygląda obiecująco, ale dla mnie nie działa. imgur.com/lSSVCIo (emulator Pixel C).
Thomas Vos

Github Link dla leniwych
Martin Sing

5

Mam nowe (prawdopodobnie schludniejsze) rozwiązanie, które wykorzystuje AppCompatPreferenceActivitypróbki z Support v7. Mając ten kod w ręku, stworzyłem własny układ zawierający pasek narzędzi:

<?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>

Następnie w moim AppCompatPreferenceActivityzmieniłem, setContentViewaby utworzyć nowy układ i umieściłem dostarczony układ w moim 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);
}

Następnie po prostu przedłużam AppCompatPreferenceActivity, pozwalając mi na wywoływanie setSupportActionBar((Toolbar) findViewById(R.id.toolbar))i zawyżanie elementów menu na pasku narzędzi. Wszystko to przy zachowaniu zalet pliku PreferenceActivity.


5

Zachowajmy tutaj prostotę i przejrzystość, bez naruszania żadnego wbudowanego układu

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

Dla mnie nie działa, root.getChildAt(0);wraca null.
Eido95

4

Podczas pracy nad tym znalazłem to proste rozwiązanie. Najpierw musimy stworzyć układ dla czynności związanych z ustawieniami.

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>

Upewnij się, że dodajesz widok listy z android:id="@android:id/list", w przeciwnym razie zostanie rzuconyNullPointerException

Następnym krokiem jest dodanie onCreatemetody (Zastąp) w aktywności ustawień

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

Upewnij się, że importujesz android.suppoer.v7.widget.Toolbar. Powinno to działać prawie we wszystkich interfejsach API powyżej 16 (Jelly Bean i nowszych)


1

Chciałbym kontynuować zaznaczone rozwiązanie Jamesa Crossa, ponieważ po tym pojawia się problem z zamykaniem tylko aktywnego zagnieżdżonego ekranu (PreferenceFragment), aby nie zamykać również SettingsActivity.

Właściwie to działa na wszystkich zagnieżdżonych ekranach (więc nie rozumiem rozwiązania Gábor, które wypróbowałem bez powodzenia, działa do pewnego momentu, ale jest to bałagan wielu pasków narzędzi), ponieważ kiedy użytkownik kliknie ekran preferencji podrzędnych , tylko fragment jest zmieniany (patrz <FrameLayout android:id="@+id/content_frame" .../>) nie pasek narzędzi, który pozostaje zawsze aktywny i widoczny, ale należy zaimplementować niestandardowe zachowanie, aby odpowiednio zamknąć każdy fragment.

W klasie głównej, SettingsActivityktóra rozszerza ActionBarActivity, należy zaimplementować następujące metody. Zauważ, że prywatny setupActionBar()jest wywoływany zonCreate()

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

Aby uzyskać tytuł wybranego zagnieżdżonego ekranu, powinieneś pobrać odniesienie do swojego paska narzędzi i ustawić odpowiedni tytuł za pomocątoolbar.setTitle(R.string.pref_title_general); (na przykład).

Nie ma potrzeby wdrażaniagetSupportActionBar() we wszystkich PreferenceFragment, ponieważ przy każdym zatwierdzeniu zmienia się tylko widok fragmentu, a nie pasek narzędzi;

Nie ma potrzeby tworzenia fałszywej klasy ToolbarPreference w celu dodania jej do każdego pliku preferencji.xml (zobacz odpowiedź Gábora).


1

Oto biblioteka, którą stworzyłem, która jest oparta na kodzie AOSP, który dodaje odcienie zarówno do preferencji, jak i okien dialogowych, dodaje pasek akcji i obsługuje wszystkie wersje z API 7:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary


Patrząc na kod, nie działa to w przypadku zagnieżdżonych preferencji ...?
Tim Rae

@TimRae Nie jestem pewien, czy przetestowałem, o czym mówisz. Proszę wyjaśnij, co masz na myśli. Jaki dokładnie scenariusz próbujesz wykorzystać?
programista Androida

Kiedy masz coś PreferenceScreentakiego PreferenceScreenjak ten
Tim Rae

Nigdy czegoś takiego nie używałem. czytając dokumentację:: developer.android.com/reference/android/preference/… , widzę, że może to pomóc w przechodzeniu między ekranami. Mówisz, że powinienem to dodać? Sprawdzę to . Dziękuję Ci. Następnym razem prosimy o skorzystanie z Github w takich przypadkach (prośby i problemy).
programista Androida

Tak, jest to przydatne, gdy masz zbyt wiele preferencji dla jednego ekranu ... W tym wątku jest już kilka miejsc, w których ludzie wspominają o zagnieżdżonych ekranach, więc myślę, że tutaj jest odpowiednie miejsce na komentarz
Tim Rae,

1

Cóż, to nadal jest dla mnie problemem dzisiaj (18 listopada 2015). Wypróbowałem wszystkie rozwiązania z tego wątku, ale były dwie główne rzeczy, których nie mogłem rozwiązać:

  • Zagnieżdżone ekrany preferencji pojawiły się bez paska narzędzi
  • Preferencje nie miały wyglądu materiału na urządzeniach sprzed wersji Lollipop

Skończyło się więc na stworzeniu biblioteki z bardziej skomplikowanym rozwiązaniem. Zasadniczo musiałem wewnętrznie zastosować style do preferencji, jeśli używamy urządzenia sprzed wersji Lollipop, a także obsługiwałem zagnieżdżone ekrany przy użyciu niestandardowego fragmentu (przywracając całą zagnieżdżoną hierarchię przy użyciu klawisza PreferenceScreen ).

Biblioteka to ta: https://github.com/ferrannp/material-preferences

A jeśli interesuje Cię kod źródłowy (zbyt długi, aby go tutaj umieścić), to jest w zasadzie jego sedno: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ java / com / fnp / materialpreferences / PreferenceFragment.java

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.