AppCompatActivity＋ListActivity的なものを自作してみた

こんにちは。

# 🐀動機

Androidアプリを開発するとなると、厄介なのがその開発するアプリが対応する古いバージョンのAndroid端末への考慮。たとえば、いにしえのAndroidでは[ActionBar](https://developer.android.com/training/appbar/index.html?hl=ja)が存在していませんでした。
この「ActionBarが表示できない古いバージョンのAndroid端末[^1]でも、私が作るアプリでは、ActionBarを出したい」などのような要件を叶えてくれるのが、Support Libraryです。

[^1]: 言い換えると、「古いAPI LevelではActionBarというクラスが無い（無かった）」というかんじです。

そこで今回私がやりたいことは、「**Support Libraryの力でActionBarを出している画面が一覧表示するアプリ**を作りたい」です。かつ、「**ActionBar上で、アクションボタン[^2]も表示したい**」のです。こんな画面です。

[^2]: アクションボタンのことを、「メニューアイテム」とか「メニューアクション」などとも呼称するようです。

![dbadfc5c-b9cd-84bc-69a1-2d2760bc0e3f.png](https://qiita-image-store.s3.amazonaws.com/0/100612/426c77af-fe94-271a-e1c4-5d21c2a4def4.png)


ActionBarの右にアクションボタン![d11ddcbd-f4d0-148e-3608-d347e076a96c.png](https://qiita-image-store.s3.amazonaws.com/0/100612/6635d8b4-1296-c158-6ede-787d2bf314c0.png)も配置したいのです。

# 🐂Support Library

数多あるAndroidのライブラリーの中でも、ダントツに利用されている、否、もはや必須と言っても過言ではないのが、[Support Library](https://developer.android.com/topic/libraries/support-library/features.html?hl=ja)です。

今回は、[android.support.v7.app.ActionBar](https://developer.android.com/reference/android/support/v7/app/ActionBar.html?hl=ja)クラスを使用したい[^3]ので、数種類あるSupport Libraryの中でも、「[v7 appcompat ライブラリ](https://developer.android.com/topic/libraries/support-library/features.html?hl=ja#v7-appcompat)」を使用します。

このActionBarを出せるようにしてくれるActivityは、android.support.v7.app.AppCompatActivityなわけです。が、このAppCompatActivityって、Android Studioで新規にプロジェクトを作成するウィザードで、メインActivityを「Empty Activity」で選択したりすると、このサブクラスをお膳立てしてくれます。それだけ”AppCompatActivityを使うのは、当たり前だのクラッカー”だということでしょう。

[^3]: [android.app.ActionBar](https://developer.android.com/reference/android/app/ActionBar.html)の方ではないですよ。

# 🐅開発環境

ざっくり言うと、WindowsでAndroid Studio 3.0でJavaです。Kotlinは使いません。

> Android Studio 3.0
> Build #AI-171.4408382, built on October 21, 2017
> JRE: 1.8.0_152-release-915-b01 amd64
> JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
> Windows 10 10.0

エミュレーターは、API level 27です。

# 🐇さて、困った

そういえば、私がやりたいことは、「一覧表示」なので、	Activityは[android.app.ListActivity](https://developer.android.com/reference/android/app/ListActivity.html)のサブクラスにしたい。

しかし。

（ActionBarを表示すべく）AppCompatActivityのサブクラスにしたい。

しかし。

Javaは多重継承を許していない。extendsできるクラスは1個だけ。

さて、困った。:disappointed_relieved:

# 🐉公式開発者サイトのサンプルを漁ってみる

[公式開発者サイトのサンプル集](https://developer.android.com/samples/index.html)にイイのありましたよ。「[CustomChoiceList](https://github.com/googlesamples/android-CustomChoiceList/)」です。チーズ:cheese:一覧アプリです。ちなみに私は[CŒUR DE LION](https://www.coeurdelion.com/)のCamembertチーズが好きです。

このサンプル「CustomChoiceList」はこんなかんじです（ところどころ略しています）。ざっと見ていきましょう。

## 🐍サンプルのActivity

Activityは、普通にListActivityのサブクラスとして定義されていました。

```java:MainActivity.java
public class MainActivity extends ListActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);
        setListAdapter(new MyAdapter());
    }

    /**
     * A simple array adapter that creates a list of cheeses.
     */
    private class MyAdapter extends BaseAdapter {
        // 略
    }
}
```

カスタマイズしたアダプターはネストクラスで定義してありました。

## 🐎サンプルのレイアウトリソースファイル

ListActivityで一覧表示をする際のセオリーとして、枠と一行のレイアウトリソースファイルが用意されていました。

まずは、枠のレイアウトリソースファイルです。

```xml:sample_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:targetApi="HONEYCOMB"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:showDividers="middle"
    android:divider="?android:dividerHorizontal">

    <ListView android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:paddingLeft="@dimen/page_margin"
        android:paddingRight="@dimen/page_margin"
        android:scrollbarStyle="outsideOverlay"
        android:choiceMode="multipleChoice" />
</LinearLayout>
```

ListViewのid属性値は、定番の`android:id="@android:id/list"`でした。

アダプターに埋め込んでもらう一行レイアウトには、カスタムビューグループが使われていました。

```xml:list_item.xml
<com.example.android.customchoicelist.CheckableLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="8dp"
    android:paddingRight="8dp"
    android:minHeight="?android:listPreferredItemHeight"
    android:gravity="center_vertical">

    <TextView android:id="@android:id/text1"
        android:duplicateParentState="true"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textAppearance="?android:textAppearanceMedium"
        android:textColor="@color/hideable_text_color" />

    <ImageView android:src="@drawable/ic_hideable_item"
        android:duplicateParentState="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp" />

</com.example.android.customchoicelist.CheckableLinearLayout>
```

## 🐏サンプルのカスタムビューグループ

一行レイアウトで使われていたカスタムビューグループは、LinearLayoutのサブクラスでした。

```java:CheckableLinearLayout.java
public class CheckableLinearLayout extends LinearLayout implements Checkable {
    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};

    private boolean mChecked = false;

    public CheckableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean isChecked() {
        return mChecked;
    }

    public void setChecked(boolean b) {
        if (b != mChecked) {
            mChecked = b;
            refreshDrawableState();
        }
    }

    public void toggle() {
        setChecked(!mChecked);
    }

    @Override
    public int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }
}
```

ということで、サンプル「CustomChoiceList」は特に奇を衒ったものではありませんでした。

<details><summary>ただ、Cheeses.javaは気合入っています。</summary>
<pre>
public class Cheeses {
    public static final String[] CHEESES = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
    };
}
</pre>
</details>

## 🐒ActionBar上で、アクションボタン（メニューアイテム）も表示するべく

このサンプルに、res/menuディレクトリと、メニューリソースファイルを追加しておきます。

```xml:res/menu/action_button.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_button1"
        android:icon="@android:drawable/ic_menu_info_details"
        android:title="info"
        app:showAsAction="always" />
</menu>
```

どうしても表示したいので、`app:showAsAction="always"`にしておきます。文字列リソースファイルを使っていなくてスミマセン。

サンプルのActivityに、onCreateOptionsMenuとonOptionsItemSelectedメソッドを追記しておきます。

```java:MainActivity.java
package com.example.android.customchoicelist;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends ListActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);
        setListAdapter(new MyAdapter());
    }

    /** このメソッドは私が付け加えた */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.action_button, menu);
        return true;
    }

    /** このメソッドは私が付け加えた */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Toast.makeText(getApplicationContext(), "インフォメーション", Toast.LENGTH_SHORT).show();
        return true;
    }

    /**
     * A simple array adapter that creates a list of cheeses.
     */
    private class MyAdapter extends BaseAdapter {
        // 略
    }
}
```

ListActivityのサブクラスのままです。

ちなみに、レイアウトリソースファイル（枠の方も、一行の方も）は改変していません。

さて、実行してみますと、おお！ActionBarも表示されている一覧画面が出ました！:smile:
ActionBarの領域を表示しているのは、スタイルリソースファイルで以下のような定義がなされていることに起因しているようです。

```xml:res/values-21/base-template-styles.xml
<resources>
    <!-- Activity themes -->
    <style name="Theme.Base" parent="android:Theme.Material.Light">
    </style>
</resources>
```

API level 27のエミュレーターなので、API level 21以上から提供された`android:Theme.Material.Light`のおかげで、ActionBarらしく表示されています。もし、API level 21未満11以上であるならば、`android:Theme.Holo.Light`などにしておきましょう（見栄えは変わってしまいますが）。

![8b7caaba-8c1d-ccba-6838-fb36c701392a.png](https://qiita-image-store.s3.amazonaws.com/0/100612/7c40fc06-209e-4a7f-c3ef-f46e27148aac.png)

でも、あ...あれあれ？アクションボタンが...?:unamused:

![8f69a744-857e-8cf2-42c4-8b96d03d3bbf.png](https://qiita-image-store.s3.amazonaws.com/0/100612/eaa2fe37-661e-62c3-7f55-0a3c268bbf08.png)

アイコンが表示されていない。押してみます。

![2f7c7860-955b-f810-73aa-fb0ec760da29.png](https://qiita-image-store.s3.amazonaws.com/0/100612/08feb9dc-4ce8-5e43-eceb-99cfb5240818.png)

えーえーえー！:scream:
`app:showAsAction="never"`扱いされてる！:confounded:
`@android:drawable/ic_menu_info_details`のアイコンが表示されない！:weary:
OverFlow（オーバーフロー）の中に格納されてしまっている！:sob:

`android:Theme.Holo.Light`にしても、同様です。

![7f435dcf-681b-9545-edfb-e8a7d1a93d5e.png](https://qiita-image-store.s3.amazonaws.com/0/100612/88581fe5-0b1f-86be-9eaf-00f0fced22e3.png)

# 🐓自力でなんとかしようとすることに決意しました

このサンプルにおいて、`app:showAsAction`を`always`にしてあるのに`never`扱いにしている~~犯人捜し~~仕様探しをするよりも、自力で**AppCompatActivity＋ListActivity的なものを自作すること**を決意しました。

どう設計しようかと考察してみました。

Javaは多重継承は不可なので、ListActivityは捨てることにしました。となると、ListActivityの[onListItemClick](https://developer.android.com/reference/android/app/ListActivity.html#onListItemClick(android.widget.ListView, android.view.View, int, long))メソッドも諦めざるを得ないのですが...否、諦めたくない。負けないで、もう少し。

それが、この抽象クラスを定義した所以です。

```java:AppCompatListActivity.java
package jp.co.casareal.appcompatlistactivity;

import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

/**
 * AppCompatActivityにListActivityの機能を付加したような基底クラス
 */
public abstract class AppCompatListActivity extends AppCompatActivity {

    protected final class ListOnItemClickListener implements AdapterView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            onListItemClick((ListView) parent, view, position, id);
        }
    }

    /**
     * ListActivityのonListItemClickメソッドを模した抽象メソッド
     */
    protected abstract void onListItemClick(ListView l, View v, int position, long id);
}
```

[AdapterView.OnItemClickListener](https://developer.android.com/reference/android/widget/AdapterView.OnItemClickListener.html)インタフェースは、API level 1から存在しているリスナーです。

ListActivityのonListItemClickメソッドを模した抽象メソッドを定義しました。仮引数名まで真似しておきました。

## 🐕いざ、ActionBarにアクションボタンが出ている一覧画面を作る

この抽象クラスを継承します。

```java:MainActivity.java
package jp.co.casareal.appcompatlistactivity;

import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends AppCompatListActivity {
    final static String[] ETO = {"ねずみ", "うし", "とら", "うさぎ", "たつ", "へび", "うま", "ひつじ", "さる", "とり", "いぬ", "いのしし"};

    private ListView listView;
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.list_view);
        listView.setOnItemClickListener(new ListOnItemClickListener());
    }

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

        adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, ETO);
        listView.setAdapter(adapter);
    }

    /** これがやりたかった */
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        String item = (String) adapter.getItem(position);
        Toast.makeText(this, "あなたの干支：" + item, Toast.LENGTH_LONG).show();
    }

    /** これもやりたかった */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.action_button, menu);
        return true;
    }

    /** これもやりたかった */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Toast.makeText(getApplicationContext(), "インフォメーション", Toast.LENGTH_SHORT).show();
        return true;
    }
}
```

レイアウトリソースファイルはこちら。

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

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light" />

</LinearLayout>
```

`<ListView>`のidは、定数`@android:id/list`ではなく、任意の値にします。

# 🐖まとめ

- 公式開発者サイトで提供されているサンプルでは、アクションバーのアイコンは`app:showAsAction="always"`にしてても、なぜか`never`扱いになって表示される。
- AppCompatActivity＋（ListActivity的な）onListItemClickメソッドを持つ抽象クラスを自作した。
    - AdapterView.OnItemClickListenerインタフェースの存在に気付いた。
- けど、ListViewのidは定数`@android:id/list`ではなく、任意の値に設定し、プログラムの方でfindViewByIdしてsetOnItemClickListenerしなければならない面倒臭さはある。

このやり方がベストプラクティスなのか自分では判断できないのですが、今後このような局面のときにはこの抽象クラスを使い回せばいいかなぁ、と思う次第です。

以上です。