Jak dodać pasek akcji z biblioteki wsparcia do PreferenceActivity?


128

Kompatybilność z paskiem akcji została dodana do biblioteki obsługi w wersji 18. Posiada ona teraz ActionBarActivityklasę do tworzenia działań z paskiem akcji na starszych wersjach Androida.

Czy jest jakiś sposób na dodanie paska akcji z biblioteki wsparcia do PreferenceActivity?

Wcześniej korzystałem z ActionBarSherlock i ma SherlockPreferenceActivity.



1
Właśnie zredagowałem moją odpowiedź i znalazłem działającą implementację fragmentu preferencji opartą na support-v4, która świetnie działa z ActionBarActivity. Sam tego używam.
Ostkontentitan

Jeśli chcesz, możesz skorzystać z mojego rozwiązania: github.com/AndroidDeveloperLB/MaterialStuffLibrary
programista Androida

Odpowiedzi:


128

EDYCJA: W appcompat-v7 22.1.0 Google dodał klasę abstrakcyjną AppCompatDelegate jako delegata, którego można użyć do rozszerzenia obsługi AppCompat na dowolną aktywność.

Użyj tego w ten sposób:

...
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
...

public class SettingsActivity extends PreferenceActivity {

    private AppCompatDelegate mDelegate;

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

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

    public ActionBar getSupportActionBar() {
        return getDelegate().getSupportActionBar();
    }

    public void setSupportActionBar(@Nullable Toolbar toolbar) {
        getDelegate().setSupportActionBar(toolbar);
    }

    @Override
    public MenuInflater getMenuInflater() {
        return getDelegate().getMenuInflater();
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().addContentView(view, params);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        getDelegate().onPostResume();
    }

    @Override
    protected void onTitleChanged(CharSequence title, int color) {
        super.onTitleChanged(title, color);
        getDelegate().setTitle(title);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        getDelegate().onConfigurationChanged(newConfig);
    }

    @Override
    protected void onStop() {
        super.onStop();
        getDelegate().onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();
    }

    public void invalidateOptionsMenu() {
        getDelegate().invalidateOptionsMenu();
    }

    private AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, null);
        }
        return mDelegate;
    }
}

Nigdy więcej hakowania. Kod pobrany z AppCompatPreferenceActivity.java .


w odpowiedzi umieść niezbędne fragmenty kodu. aby uniknąć potencjalnego problemu z uszkodzonym łączem.
Yohanes Khosiawan 许先汉

dzięki, to działa dla mnie - zmieniłbym nowy LinearLayout w pierwszym wywołaniu zawyżania na:(ViewGroup) getWindow().getDecorView().getRootView()
ahmedre

2
@petrsyn To faktycznie działa. Zajrzyj tutaj i tutaj, aby dowiedzieć się, jak powinieneś to zrobić.
Ľubomír Kučera

1
@swooby Możesz cierpieć z powodu tego błędu.
Ľubomír Kučera

1
Ok, więc gdzie umieścić pasek narzędzi w xml? Otrzymuję wyjątek NullPointerException
Martin Erlic

78

Obecnie nie ma sposobu, aby to osiągnąć za pomocą AppCompat. Otworzyłem błąd wewnętrznie.


6
Dzięki @Chris. Będzie miło mieć tę funkcję.
Pablo

12
@Chris, czy masz jakiś pomysł, kiedy możemy się spodziewać, PreferenceActivityże zostaniemy dodani do ActionBarCompat?
imbryk

3
Myślę, że jest bardzo mało prawdopodobne, aby ta funkcja została zaimplementowana. Liczba urządzeń ze starszymi wersjami Androida (<4.0) jest obecnie mniejsza niż 30%, a liczba ta spada z każdym miesiącem.
Roman

1
To zostało wprowadzone prawie rok temu i nie ma rozwiązania?
Someone Somewhere,

1
wtf wciąż czekam ??
Broak

24

Udało mi się stworzyć obejście podobne do tego, którego używa Sklep Google Play. Link do oryginalnej odpowiedzi

Znajdź repozytorium GitHub: tutaj


Bardzo podobny do twojego własnego kodu, ale dodano XML, aby umożliwić ustawienie tytułu:

Kontynuacja użytkowania 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):

Jak wskazano tutaj , Gingerbread Devices zwracają 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 deweloperów, 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 wykonaj następujące czynności (nadal nadrzędne 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żonych <PreferenceScreen />s, 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 Design Support 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ę.


1
Świetny! Działa jak urok :)
Tobi

Dlaczego to nie działa dla zagnieżdżonego PreferenceScreen, ale tylko dla głównego PreferenceScreen?
Tobi

@Tobi Zaktualizowałem swoją odpowiedź, aby rozwiązać zagnieżdżony problem i wyjaśniłem dlaczego. :)
David Passmore

Dzięki! Twoja rada "AppCompat 22.1" załatwiła mi sprawę :) Bardzo przydatne!
Martin Pfeffer

1
dlaczego PreferenceActivity takie upierdliwe używanie ??? ma zaoszczędzić czas. Równie dobrze mógłbym wykonywać regularne czynności i samodzielnie układać wszystkie ustawienia w układzie liniowym. Fuuuuck!
Someone Somewhere,

12

Znaleziono implementację PreferenceFragment opartą na fragmencie support-v4:

https://github.com/kolavar/android-support-v4-preferencefragment

Edycja: właśnie go przetestowałem i działa świetnie!


@Konstantin, czy możesz dodać przykładowy kod? Ściągnąłem go, zmieniłem import z android.preference.PreferenceFragment na android.support.v4.preference.PreferenceFragment i widzę, że dodał kilka nagłówków na środku ekranu, ale nie ActionBar na górze
Gavriel

Nie dodaje paska akcji, który jest zadaniem działania. Niestety nie mam pod ręką samplecode, ale powinien on działać podobnie do: developer.android.com/reference/android/preference/…
Ostkontentitan

3

Integracja PreferenceActivityz ABC nie jest możliwa, przynajmniej dla mnie. Wypróbowałem dwie możliwości, które mogłem znaleźć, ale żadna nie działała:

Opcja 1:

ActionBarPreferenceActivityrozciąga się PreferenceActivity. Kiedy to robisz, zostajesz ograniczony ActionBarActivityDelegate.createDelegate(ActionBarActivity activity). Musisz również wdrożyć, ActionBar.Callbacksktóre nie jest dostępne

Opcja 2:

ActionBarPreferenceActivityrozciąga się ActionBarActivity. Takie podejście wymaga przepisywania zupełnie nowy PreferenceActivity, PreferenceManagera może być PreferenceFragmentco oznacza, że muszą mieć dostęp do ukrytych klas jak com.android.internal.util.XmlUtils Rozwiązaniem tego problemu może pochodzić tylko od Google DEVS wdrożenia ActionBarWrapper, które mogą być dodawane do każdej działalności.

Jeśli naprawdę potrzebujesz aktywności preferencyjnej, na razie radzę ActionBarSherlock.

Jednak udało mi się to tutaj zaimplementować .


1
Per4.0 powoduje awarię. Rozwidliłem to i poprawiłem. gist.github.com/0e9fe2119b901921b160
TeeTracker

Sprawdź moje rozwiązanie. Nie używa żadnego obejścia stackoverflow.com/a/25603448/1276636
Sufian

To niczego nie rozwiązuje! Mam nadzieję, że rozumiesz problem
Maxwell Weru

3

Tło problemu:

PO chce wiedzieć, w jaki sposób możemy umieścić MenuItemsw ActionBarod PreferenceActivitywstępnego Honeycomb ponieważ biblioteka wsparcie Android ma błąd, który nie może do tego dopuścić.

Moje rozwiązanie:

Znalazłem znacznie czystszy sposób, niż już proponowano, na osiągnięcie celu (i znalazłem go w Android Docs ):

android:parentActivityName

Nazwa klasy logicznego elementu nadrzędnego działania. Nazwa w tym miejscu musi być zgodna z nazwą klasy nadaną atrybutowi android: name odpowiedniego elementu.

System odczytuje ten atrybut, aby określić, które działanie powinno zostać uruchomione, gdy użytkownik naciśnie przycisk W górę na pasku akcji. System może również wykorzystać te informacje do zsyntetyzowania stosu działań z TaskStackBuilder.

Aby obsługiwać poziomy API 4-16, możesz również zadeklarować działanie nadrzędne za pomocą elementu, który określa wartość dla „android.support.PARENT_ACTIVITY”. Na przykład:

<activity
    android:name="com.example.app.ChildActivity"
    android:label="@string/title_child_activity"
    android:parentActivityName="com.example.myfirstapp.MainActivity" >
    <!-- Parent activity meta-data to support API level 4+ -->
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value="com.example.app.MainActivity" />
</activity>

Teraz zrób to, co normalnie zrobiłbyś w swoim onOptionsItemSelected(). Ponieważ jest częścią Android Docs, nie ma żadnych skutków ubocznych.

Miłego kodowania. :)

Aktualizacja:

To rozwiązanie nie działa już, jeśli celujesz w Lollipopa. Jeśli używasz AppCompat, ta odpowiedź jest tym, czego powinieneś szukać.


W jaki sposób pomaga to uzyskać pasek akcji w działaniu Preferencje?
Frozen Crayon

@ArjunU. proste rozwiązanie - wypróbuj i wymyśl to.
Sufian

@ArjunU. Problem polegał na tym, PreferencesActivityże nie mieli żadnego sposobu na umieszczenie przedmiotów ActionBar, zwłaszcza przycisku Wstecz. Moja odpowiedź to dobre rozwiązanie.
Sufian

Dzięki za głos przeciw. Jakieś wyjaśnienie, jak mogę poprawić swoją odpowiedź lub co było nie tak?
Sufian

1
@Sufian Dzięki za link do mojej odpowiedzi, cieszę się, że pomaga to wszystkim:)
David Passmore

1

Udało mi się uzyskać android.app.Actionbarza pomocą getActionBar(). Na początku zwróciło wartość null ... potem przeszedłem do manifestu i zmieniłem motyw na:

android:theme="@style/Theme.AppCompat"

Wtedy znów mogłem mieć pasek akcji. Zakładam, że to zadziała tylko na niektórych poziomach kompilacji. Więc możesz chcieć sprawdzić numer kompilacji lub sprawdzić, czy zwrócona wartość jest pusta.

Dla mnie będzie dobrze, ponieważ aplikacja, nad którą pracuję, jest przeznaczona dla ICS/4.0+.


Oto prostsze rozwiązanie, które nie wykorzystuje żadnych obejść stackoverflow.com/a/25603448/1276636
Sufian

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.