Android Support Library, ловим баг

На developer.android.com появилась новая версия Support Library — android.suport.v7. В неё добавлена поддержка ActionBar для API level 7, то есть, для Android 2.1 и выше. Попробуем воспользоваться этой возможностью.

Не будем останавливаться подробно, где взять и как включить библиотеку android-support-v7-appcompat в проект. Напомню только, что для того, чтобы заставить ActionBar корректно отображаться, необходимо тему приложения или того Activity, где будет использоваться  этот элемент интерфейса, унаследовать от Theme.AppCompat. Кроме того, нам понадобится Fragment API, поэтому понадобится еще android.support.v4. Если устанавливать библиотеки стандартными средствами Android SDK Tools, то после установки их можно обнаружить в [SDK-ROOT]/extras/android/support.

Итак, библиотеки подключены. Создаём нашу Activity, базовым классом здесь выступает ActionBarActivity:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener;

public class MainActivity extends ActionBarActivity implements TabListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActionBar ab = getSupportActionBar();
        ab.setHomeButtonEnabled(false);
        ab.setTitle(null);
        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        ab.addTab(ab.newTab()
                .setIcon(android.R.drawable.ic_menu_manage)
                .setTabListener(this));
        ab.addTab(ab.newTab()
                .setIcon(android.R.drawable.ic_menu_mapmode)
                .setTabListener(this));
       
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
// Interface realizing methods are here
...
// end class declaration
}
Объект ActionBar, необходимый для дальнейшей работы, получаем при помощи метода getSupportActionBar(). Режим навигации выставляем в NAVIGTION_MODE_TABS. Не забываем о реализации интерфейса ActionBar.TabListener — в нашем примере это делает всё тот же класс MainActivity.

Теперь определим реакцию на нажатие вкладок на ActionBar. Методы интерфейса ActionBar.TabListener:

public class MainActivity extends ActionBarActivity implements TabListener {
...
// Interface realizing methods are here
    @Override
    public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
        // TODO Auto-generated method stub
       
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        String clz = null;
        switch (tab.getPosition()) {
        case 0:
            clz = MainFragment.class.getName();
            break;
        case 1:
            clz = MapFragment.class.getName();
            break;
        default:
            break;
        }
        if(clz != null) {
            Fragment f = Fragment.instantiate(this, clz);
            ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, f);
        }
    }

    @Override
    public void onTabUnselected(Tab arg0, FragmentTransaction arg1) {
        // TODO Auto-generated method stub
       
    }
// end class declaration.
}

В качестве первого параметра вызова Fragment.replace() используется константа, определённая в библиотеке — контейнер содержимого, находящегося под ActionBar
Классы, реализующие фрагменты, могут выглядеть, например, так:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MainFragment extends Fragment {
   
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
        View v = inflater.inflate(R.layout.fragment_main, container, false);
        return v;
       
    }
}

Запустив это приложение на Android 2.2, мы увидим наш ActionBar:


Попробуем запустить на Android 4.0. Приложение сваливается с ошибкой, а в логе мы видим:
java.lang.IllegalArgumentException: No view found for id 0x7f080014 (your.package.here:id/action_bar_activity_content) for fragment MainFragment

(реальное имя пакета в сообщении заменено)
то есть View с ID = action_bar_activity_content, которое мы хотим использовать в качестве контейнера, отсутствует.
Решение — создать свой контейнер. Кладем в каталог res/layout нашего проекта файл activity_main.xml:

<framelayout
   android:id="@android:id/tabcontent"
   android:layout_height="match_parent"
   android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
</framelayout>

а код метода OnCreate() изменяем:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar ab = getSupportActionBar();
        ab.setHomeButtonEnabled(false);
        ab.setTitle(null);
        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        ab.addTab(ab.newTab()
                .setIcon(android.R.drawable.ic_menu_manage)
                .setTabListener(this));
        ab.addTab(ab.newTab()
                .setIcon(android.R.drawable.ic_menu_mapmode)
                .setTabListener(this));
       
    }

Посмотрите: появился вызов setContentView() с параметром — id нашего layout'а.
Метод onTabSelected() тоже чуть-чуть изменится:

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        String clz = null;
        switch (tab.getPosition()) {
        case 0:
            clz = MainFragment.class.getName();
            break;
        case 1:
            clz = MapFragment.class.getName();
            break;
        default:
            break;
        }
        if(clz != null) {
            Fragment f = Fragment.instantiate(this, clz);
            ft.replace(android.R.id.tabcontent, f);
        }
    }

В качестве контейнера будет выступать наш FrameLayout, который мы объявили в activity_main.xml.
Вот теперь всё в порядке.