Создание экрана настроек с панелью инструментов поддержки (v21)

116

У меня возникли проблемы с использованием новой панели инструментов Material Design в библиотеке поддержки на экране настроек.

У меня есть файл settings.xml, как показано ниже:

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

Строки определены в другом месте.

Джеймс Кросс
источник
stackoverflow.com/a/27455363/2247612 В этом ответе есть идеальное решение для библиотеки поддержки
harishannam

Ответы:

110

Пожалуйста, найдите репозиторий GitHub: здесь


Немного поздно для вечеринки, но это мое решение, которое я использую для продолжения использования 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 :

пример


ОБНОВЛЕНИЕ (Совместимость с пряниками):

Согласно комментариям, Gingerbread Devices возвращают исключение NullPointerException в этой строке:

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

Сообщите мне о любых проблемах с вышеуказанным!


ОБНОВЛЕНИЕ 2: ВОЗМОЖНОЕ РЕШЕНИЕ ПО ОКРАШИВАНИЮ

Как указано во многих заметках разработчика PreferenceActivity, не поддерживает тонирование элементов, однако, используя несколько внутренних классов, вы МОЖЕТЕ добиться этого. Пока эти классы не будут удалены. (Работает с использованием appCompat support-v7 v21.0.3).

Добавьте следующий импорт:

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;

Затем переопределите 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 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:

пример 2


AppCompat 22.1

AppCompat 22.1 представил новые тонированные элементы, что означает, что больше нет необходимости использовать внутренние классы для достижения того же эффекта, что и при последнем обновлении. Вместо этого следуйте этому (все еще приоритетному 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;
}

Вложенные экраны предпочтений

Многие люди испытывают проблемы с включением Панели инструментов во вложенную, <PreferenceScreen />однако я нашел решение !! - После долгих проб и ошибок!

Добавьте в свой 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();
        }
    });
}

Причина, по которой PreferenceScreenэто такая неприятность, заключается в том, что они основаны на диалоге-оболочке, поэтому нам нужно зафиксировать макет диалога, чтобы добавить к нему панель инструментов.


Тень панели инструментов

По дизайну, импорт Toolbarне допускает возвышения и затенения на устройствах до версии 21, поэтому, если вы хотите, чтобы у вас было повышение, Toolbarвам нужно обернуть его в 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>

Не забудьте добавить библиотеку поддержки дизайна в качестве зависимости в build.gradleфайл:

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

Я исследовал проблему перекрытия, о которой сообщалось, и не могу воспроизвести ее.

Полный код, используемый, как указано выше, дает следующее:

введите описание изображения здесь

Если мне что-то не хватает, дайте мне знать через это репо, и я займусь расследованием.

Дэвид Пассмор
источник
это дает исключение nullpointer в имбирном прянике, корень равен нулю .. любое решение?
andQlimax
ваше решение отлично работает. но есть проблема с этим подходом, фактически без расширения ActionBarActivity, который является обязательным (из документации) для получения темы материала на <5.0, colorAccent (просто для примера) не применяется к флажкам в устройствах <5.0. Это кажется настоящей болью ... возможно, мне нужно удалить активность предпочтений и использовать линейный макет для имитации экрана предпочтений, иначе я не вижу способа использовать тему материала на устройствах с уровнями api с 8 по 21. Фрагмент предпочтения - "only"> 11 :(
andQlimax 03
1
@andQlimax Я обновил свой ответ решением проблемы с тонировкой
Дэвид Пассмор,
3
@DavidPassmore: для меня список предпочтений перекрывается на панели инструментов
Шашанк Шривастава
1
@ShashankSrivastava Это отражено, если вы используете Android 6, я работаю над решением этой проблемы. Спасибо за обновление.
Дэвид Пассмор
107

Вы можете использовать PreferenceFragmentв качестве альтернативы PreferenceActivity. Итак, вот Activityпример упаковки :

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

А вот и файл макета (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>

И наконец PreferenceFragment:

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

Я надеюсь, что это поможет кому-то.

Джеймс Кросс
источник
39
Я пробовал этот подход. Проблема в том, что он не отображает панель инструментов на экранах дочерних настроек.
Мадхур Ахуджа
2
Я думаю, он говорит о PreferenceScreen, встроенном в XML-файл корневых предпочтений.
Лукас С.
5
Мне понравился подход, но, к сожалению, он не будет работать, если целевой api меньше API 11
midhunhk 02
12
Он не будет работать ни с одним из них. На практике, похоже, нет никакого способа создать материально спроектированные, снабженные инструментами, вложенные экраны предпочтений. Если вы используете ActionBarActivityдля получения панели инструментов и связанных функций, onBuildHeaders()переопределения и фактической поддержки предпочтений в действии не будет. Если вы используете старую версию PreferenceActivity, у вас нет панели инструментов и связанных функций (да, у вас может быть Toolbarи макет, но вы не можете вызывать его setSupportActionBar(). Итак, с заголовками предпочтений или вложенными экранами предпочтений мы, похоже, застряли.
Габор
1
Я согласен с комментарием Габора. Это решение вообще не работает. Лучше один ниже с эмуляцией панели инструментов (без ActionBar, но кого это волнует), а также новая библиотека поддержки, выпущенная с AppCompatDelegate на борту.
Юджин Векслер
48

Совершенно новое обновление.

Поэкспериментировав, я, кажется, нашел рабочее решение AppCompat 22.1+ для вложенных экранов предпочтений.

Во-первых, как упоминается во многих ответах (включая один здесь), вам нужно будет использовать новый AppCompatDelegate. Либо использовать AppCompatPreferenceActivity.javaфайл поддержки демоса ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java ) и просто продлить из него или скопируйте соответствующие функции в свои собственные PreferenceActivity. Я покажу здесь первый подход:

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

Соответствующий макет довольно простой и привычный ( 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>

Сами предпочтения определяются как обычно ( 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>

До этого момента реальной разницы с решениями в сети нет. Фактически, вы можете использовать это, даже если у вас нет вложенных экранов, нет заголовков, только один экран.

Мы используем общий PreferenceFragmentдля всех более глубоких страниц, различающийся extraпараметрами в заголовках. Каждая страница будет иметь отдельный XML с общим PreferenceScreenвнутренним ( xml/settings_page1.xmlи др.). Фрагмент использует тот же макет, что и действие, включая панель инструментов.

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

Наконец, краткое описание того, как это на самом деле работает. Новое AppCompatDelegateпозволяет нам использовать любые действия с функциями AppCompat, а не только те, которые выходят из действий, которые фактически находятся в AppCompat. Это означает, что мы можем превратить старый добрый PreferenceActivityв новый и добавить панель инструментов как обычно. С этого момента мы можем придерживаться старых решений в отношении экранов предпочтений и заголовков без каких-либо отклонений от существующей документации. Есть только один важный момент: не используйте onCreate()в действии, потому что это приведет к ошибкам. использованиеonBuildHeaders() для всех операций, таких как добавление панели инструментов.

Единственная реальная разница в том, что заставляет его работать с вложенными экранами, это то, что вы можете использовать тот же подход с фрагментами. Вы можете использовать их так onCreateView()же, увеличивая свой собственный макет вместо системного, добавляя панель инструментов так же, как и в упражнении.

Габор
источник
2
Какой замечательный обходной путь! Это единственное найденное мной решение, которое будет отображать панель инструментов материала на потомке PreferenceScreen. Молодец, сэр.
String
Я использую ресурс из библиотеки appcompat для значка вверх:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully
Я думаю, что с этим решением панель инструментов будет прокручиваться вместе с содержимым, верно? Потому что это просто элемент во внутреннем ListView.
tasomaniac
Только не с этим обновленным, новым решением. Это работает, как и ожидалось.
Gábor
Как ни странно, это решение не распознает PreferenceFragmentCompatвместо PreferenceFragment. Настройка preference-headerwith, xmlns:app="http://schemas.android.com/apk/res-auto" а затем app:fragmentвместо android:fragmentне загружает новый экран prefs. Итак, есть проблемы с обратной совместимостью ... предложения?
fattire
18

Если вы хотите использовать PreferenceHeaders, вы можете использовать следующий подход:

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

}

макет / 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>

Вы можете использовать здесь любой макет, который вам нравится, просто убедитесь, что вы также настроили его в коде Java.

И напоследок ваш файл с заголовками (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 (панель инструментов); Зачем? root.addView (toolbarContainer);
Crossle Song
Упс, пропустил переменную при переименовании, исправил.
Sven Dubbeld
1
Отличный ответ. Ключевым моментом здесь является то android.R.id.content, что мы раньше передавали ListViewwith android.R.id.listдля самого списка предпочтений (и все еще делаем это, если вместо этого используем способ без фрагментов и заголовков).
davidcsb
2
Я думаю, что лучше проверить код Android, чтобы увидеть, что ему нужно, вместо того, чтобы возиться с его представлениями Hirerchy (удалить / добавить представления, которые у него есть). Я думаю, так безопаснее. Я предлагаю проверить файл "preference_list_content".
разработчик Android
2
Это лучший ответ в этой теме. Автор этой статьи расширил ее до полной эталонной реализации, которую я использовал. Фактически, это единственное рабочее решение для поддержки сложных настроек в вашем приложении.
Юджин Векслер
17

С выпуском библиотеки поддержки Android 22.1.0 и нового AppCompatDelegate здесь вы можете найти хороший образец реализации PreferenceActivity с материальной поддержкой и обратной совместимостью.

Обновить Он работает и на вложенных экранах.

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

Мистер Брайтсайд
источник
1
О, это отличные новости! Таким образом, кажется, что решения, основанные на «расширении PreferenceActivity», лучше, чем решения на «расширении ActionBarActivity» в этой новой перспективе.
Юджин Векслер
1
@EugeneWechsler Да, действительно, ActionBarActivity устарел.
MrBrightside
Работать с этим решением также на вложенных экранах? Есть лучший пример?
Tomas
@Tomas Я еще не пробовал, но он должен работать и на вложенных экранах. Если у вас работает, сообщите нам, пожалуйста.
MrBrightside
Большое спасибо ! У меня работает на Galaxy Nexus (4.3) и на эмуляторе с вложенными экранами (леденец).
Тим Атин
6

Хотя приведенные выше ответы кажутся сложными, если вам нужно быстрое решение для использования панели инструментов с поддержкой API 7 и выше при расширении PreferenceActivity, я получил помощь от этого проекта ниже.

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
источник
6

Я тоже искал решение для добавления панели инструментов поддержки v7 ( API 25 ) в AppCompatPreferenceActivity (который автоматически создается AndroidStudio при добавлении SettingsActivity). Прочитав несколько решений и опробовав каждое из них, я изо всех сил пытался получить сгенерированные примеры PreferenceFragment для отображения на панели инструментов.

Модифицированное решение, которое вроде как сработало, было от " Gabor ".

Одно из предостережений, с которым я столкнулся, было то, что onBuildHeaders срабатывает только один раз. Если вы повернете устройство (например, телефон) на бок, представление воссоздается, и PreferenceActivity снова остается без панели инструментов, однако PreferenceFragments сохранит свою.

Я попытался использовать onPostCreate для вызова setContentView, хотя это работало для воссоздания панели инструментов при изменении ориентации, тогда PreferenceFragments были бы отображены пустыми.

То, что я придумал, использует практически все советы и ответы, которые я мог прочитать по этой теме. Я надеюсь, что и другие сочтут это полезным.

Начнем с Java

Сначала в (сгенерированном) AppCompatPreferenceActivity.java я изменил setSupportActionBar следующим образом:

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

Во-вторых , я создал новый класс с именем AppCompatPreferenceFragment.java (это текущее неиспользуемое имя, хотя оно может и не оставаться таким!):

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

Это сработала часть ответа Габора.

Наконец , для согласованности нам нужно внести некоторые изменения в 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);
        }
    }
}

Некоторый код был исключен из упражнения для краткости. Ключевыми компонентами здесь являются « onAttachedFragment », « onPostCreate » и то, что «GeneralPreferenceFragment» теперь расширяет пользовательский « AppCompatPreferenceFragment » вместо PreferenceFragment.

Сводка кода : если фрагмент присутствует, фрагмент внедряет новый макет и вызывает измененную функцию setSupportActionBar. Если фрагмент отсутствует, SettingsActivity вставляет новый макет в onPostCreate

Теперь перейдем к XML (очень просто):

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>

Конечный результат :

SettingsActivity

GeneralPreferenceFragment

SilverX
источник
Выглядит многообещающе, но мне не подходит. imgur.com/lSSVCIo (эмулятор Pixel C).
Thomas Vos
Github Link для ленивых
Мартин Синг
5

У меня есть новое (возможно, более аккуратное) решение, в котором используются AppCompatPreferenceActivityобразцы из Support v7. С помощью этого кода я создал свой собственный макет с панелью инструментов:

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

Затем в моем AppCompatPreferenceActivityя изменил, setContentViewчтобы создать свой новый макет, и поместил предоставленный макет в свой 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);
}

Затем я просто расширяю AppCompatPreferenceActivity, позволяя мне вызывать setSupportActionBar((Toolbar) findViewById(R.id.toolbar))и расширять элементы меню на панели инструментов. И все это при сохранении преимуществ PreferenceActivity.

Bryan
источник
5

Давайте сохраним здесь простоту и чистоту, не нарушая встроенного макета.

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();
        }
    });
}
Самуил
источник
У меня не вышло, root.getChildAt(0);возвращается null.
Eido95
4

Я нашел это простое решение, работая над этим. Сначала нам нужно создать макет для настройки активности.

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>

Убедитесь, что вы добавили представление списка android:id="@android:id/list", иначе оно выдастNullPointerException

Следующим шагом будет добавление onCreateметода (Override) в ваши настройки.

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

Убедитесь, что вы импортируете android.suppoer.v7.widget.Toolbar. Это должно работать почти во всех API выше 16 (Jelly Bean и выше).

Apurva
источник
1

Я хотел бы продолжить отмеченное решение Джеймса Кросса, так как после этого возникает проблема закрытия только активного вложенного экрана (PreferenceFragment), чтобы не закрывать и SettingsActivity.

На самом деле он работает на всех вложенных экранах (поэтому я не понимаю решение Габора, которое я безуспешно пробовал, ну, оно работает до определенного момента, но это беспорядок из нескольких панелей инструментов), потому что, когда пользователь щелкает вспомогательный экран предпочтений , изменяется только фрагмент (см. <FrameLayout android:id="@+id/content_frame" .../>), а не панель инструментов, которая остается всегда активной и видимой, но необходимо реализовать настраиваемое поведение, чтобы соответственно закрыть каждый фрагмент.

В SettingsActivityрасширяющем основном классе ActionBarActivityдолжны быть реализованы следующие методы. Обратите внимание, что приватный setupActionBar()вызывается изonCreate()

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

Для заголовка выбранного вложенного экрана вы должны получить ссылку на свою панель инструментов и установить соответствующий заголовок с помощью toolbar.setTitle(R.string.pref_title_general);(например).

Нет необходимости реализовывать getSupportActionBar()во всем PreferenceFragment, так как при каждой фиксации изменяется только представление фрагмента, а не панель инструментов;

Нет необходимости создавать поддельный класс ToolbarPreference для добавления в каждый файл preference.xml (см. Ответ Габора).

Davideas
источник
1

Вот созданная мной библиотека, основанная на коде AOSP, который добавляет оттенки как в настройки, так и в диалоги, добавляет панель действий и поддерживает все версии из API 7:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary

разработчик Android
источник
Судя по коду, он не работает для вложенных настроек ...?
Тим Рэй
@TimRae Я не уверен, что проверил то, о чем вы говорите. Пожалуйста, объясните, что вы имеете в виду. Какой именно сценарий вы пытаетесь использовать?
разработчик Android
Если у вас есть PreferenceScreenвнутри , PreferenceScreenкак этого
Тим Rae
Никогда не использовал такую ​​штуку. читая документы:: developer.android.com/reference/android/preference/… , я вижу, что это может помочь при переходе между экранами. Вы говорите, я тоже должен это добавить? Я это проверю . Спасибо. Кроме того, пожалуйста, используйте Github в следующий раз для таких вещей (запросы и проблемы).
разработчик Android
Да, это полезно, когда у вас слишком много настроек для одного экрана ... В этой ветке уже есть несколько мест, где люди упоминают вложенные экраны, поэтому я думаю, что это подходящее место для комментариев
Тим Рэй
1

Что ж, это все еще проблема для меня сегодня (18 ноября 2015 г.). Я пробовал все решения из этой ветки, но я не смог решить две основные вещи:

  • Вложенные экраны настроек появились без панели инструментов
  • В настройках не было вида материала на устройствах до Lollipop

В итоге я создал библиотеку с более сложным решением. По сути, мне пришлось внутренне применить стили к настройкам, если мы используем устройство до Lollipop, и я также обрабатывал вложенные экраны с помощью настраиваемого фрагмента (восстанавливая всю вложенную иерархию с помощью ключа PreferenceScreen ).

Вот эта библиотека: https://github.com/ferrannp/material-preferences

И если вас интересует исходный код (слишком длинный, чтобы размещать его здесь), это в основном его ядро: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ Java / COM / ФНП / materialpreferences / PreferenceFragment.java

Ферран Негре
источник