読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Android】InfoWindowの拡張とFragmentの追加(和歌山トイレマップで遊んでみる 7)

はじめに

今回は地図上に置いたマーカーをタッチした時に表示されるInfoWindowに表示する情報を増やしたのと、Fragmentで画面を追加した時の諸々について書いてみます。

InfoWindow

以前はマーカーをタッチすると、タイトルとして名称だけが表示される内容でした。
今回は住所と利用可能時間を追加してみます。

Snippetの追加と課題

public void addMarker(String strToiletName, double dblLatitude, double dblLongitude, String strSnippet){
    if (map != null) {
            // 表示したマップにマーカーを追加する.
            map.addMarker(new MarkerOptions().position(
                    new LatLng(dblLatitude, dblLongitude))
                    .title(strToiletName)
                    // strSnippet: 住所\n利用可能時間
                    .snippet(strSnippet));
    }
}

上記のように、MarkerOptionsにtitleだけでなくsnippetを渡してやることで、表示する情報を増やすことができます。

しかし、このままだとsnippetは改行できず、かつWindowのサイズが内容量に合わせて変更できない、という問題がありました。

Viewを設定する

InfoWindowAdapterというインターフェイスを継承したアダプタークラスを作成することで、InfoWindowsをカスタマイズすることができます。

Windowや中に追加するテキストなどのLayoutを指定して、GoogleMapにセットします。

layout_marker_window.xml

< ?xml version="1.0" encoding="utf-8"? >
< LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@android:color/white"
    android:padding="10dp" >

    < TextView
        android:id="@+id/marker_infowindow_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:ellipsize="end"
        android:singleLine="true"
        android:textColor="#000000"
        android:textSize="14dp"
        android:textStyle="bold" / >

    < TextView
        android:id="@+id/marker_infowindow_snippet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:singleLine="false"
        android:textColor="#808080"
        android:textSize="14dp"/ >
< /LinearLayout >
  • wrap_contentでWindowのサイズを指定すると文字がWindowギリギリの位置まで表示されるので、Paddingを指定しています。

ToiletInfoWindowViewer.java

package jp.searchwakayamatoilet;

import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;

public class ToiletInfoWindowViewer implements GoogleMap.InfoWindowAdapter {
    private final View infoWindowView;
    public ToiletInfoWindowViewer(Activity newActivity){
        infoWindowView = newActivity.getLayoutInflater().inflate(R.layout.layout_marker_window, null);
    }
    @Override
    public View getInfoWindow(Marker marker) {
        // TextViewにTitle, Snippetをセットする.
        ((TextView) infoWindowView.findViewById(R.id.marker_infowindow_title))
                .setText(marker.getTitle());
        ((TextView) infoWindowView.findViewById(R.id.marker_infowindow_snippet))
                .setText(marker.getSnippet());

        return infoWindowView;
    }
    @Override
    public View getInfoContents(Marker marker) {
        return null;
    }
}

GoogleMapへのセットはこんな感じで行っています。

LocationAccesser.java

~省略~
((SupportMapFragment) fragmentActivity.getSupportFragmentManager().findFragmentById(R.id.map))
                .getMapAsync(new OnMapReadyCallback() {
                    @Override
                    public void onMapReady(GoogleMap googleMap) {
                        try{
                            map = gMap;
                            map.setMyLocationEnabled(true);
                            map.setInfoWindowAdapter(new ToiletInfoWindowViewer(fragmentActivity));
                            // 和歌山県庁に移動.
                            map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(34.22501, 135.1678), 9));
                            // GoogleMap.OnMyLocationButtonClickListener - onMyLocationButtonClick().
                            map.setOnMyLocationButtonClickListener(() -> {
                                locationAccesser.moveToMyLocation(fragmentActivity);
                                return false;
                            });
                            presenter.loadCsvData(true);
                        }catch(SecurityException ex){
                            // TODO: error handling.
                            Log.d("SWT Error", ex.getLocalizedMessage());
                        }
                    }
                });
                ~省略~

あとは先ほどと同じように地図上にMarkerを置くときにTitleとSnippetを指定してやればOKです。

f:id:mslGt:20160209013224p:plain

Fragmentの追加

アプリについての説明などを載せるページを追加することにしました。

まずはToolbarに項目を追加します。

menu_main.xml

< menu 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" tools:context=".MainActivity" >
    < item android:id="@+id/searchview"
        android:title="@string/action_search"
        android:icon="@mipmap/ic_search_black_24dp"
        app:showAsAction="always"
        app:actionViewClass="android.support.v7.widget.SearchView" / >
    < item android:id="@+id/update_button"
        android:title="@string/action_update"
        android:icon="@mipmap/ic_cached_black_24dp"
        app:showAsAction="always"/>
    
    < item android:id="@+id/show_about_button"
        android:title="@string/action_about"
        app:showAsAction="collapseActionView" / >
< /menu >

次にタップされた時にページ(Fragment)を表示する処理を行います。

MainActivity.java

~省略~
_toolbar.getMenu().findItem(R.id.show_about_button).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener(){
        @Override
        onMenuItemClick(MenuItem item){
            // aboutページの表示.
            aboutAppFragment = new AboutAppFragment();
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.main_container, aboutAppFragment);
            // 端末のBackキーでFragmentからこのページに戻るようにする.
            transaction.addToBackStack(null);
            transaction.commit();
            return true;
        }
    });
~省略~
  • R.id.main_containerはMainActivityのLayoutに設定しています。

Homeボタンの追加

端末のBackキーに加えて、Toolbar上にも戻るボタンを追加して、そこからも戻れるようにしたいところ。

AppCompatActivityを使ってHomeボタンを表示してみました。

f:id:mslGt:20160209013241p:plain

AboutAppFragment.java

~省略~
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.fragment_about_app, container, false);
    Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);

    AppCompatActivity activity = (AppCompatActivity) getActivity();
    // ToolbarでActionBarの機能を利用可能にする.
    activity.setSupportActionBar(toolbar);
    // Homeボタンの表示.
    activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    activity.getSupportActionBar().setHomeButtonEnabled(true);
    // タイトルは非表示にする.
    activity.getSupportActionBar().setDisplayShowTitleEnabled(false);

    // Inflate the layout for this fragment
    return view;
}
~省略~
  • ToolbarはMainActivityと同じものを使用しています。

失敗

実は上記のコードをそのまま実行しようとするとアプリがクラッシュしてしまいます。

このFragmentを呼び出しているMainActivity.javaがFragmentActivityを継承していたためです。
どうするべ、とググッてみたところ、実はHomeボタンの表示に使用しているAppCompatActivityは、FragmentActivityが継承していることがわかりました。

そのため、単純にFragmentActivityではなくAppCompatActivityを継承するよう変更するだけでこの問題は解決しました。便利ですね。

なお、このHomeボタンが押されたときは「onOptionsItemSelected(MenuItem item)」が呼ばれますが、FragmentではなくこのFragmentを呼び出しているMainActivityのものが呼ばれます。
コードを見れば納得なのですが、しばらくなぜメソッドが呼ばれないのか考えてしまいました...orz

アニメーション

さて、無事ページ遷移はできるようになりましたが、ページ遷移時に何もアニメーションが付いていないのは少しさびしい気がします。

Fragmentのページを開くときはメニュー(Aboutボタン)が左に消える動きをするアニメーションがあるためまだあまり気になりませんが、 Fragmentを消してMainActivityに戻るときはちょっと気になります。

というわけでアニメーションを付けてみることにします。

AboutAppFragment.java

~省略~
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 5.0以上の場合はFragmentを閉じるときにアニメーションを実行する.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
        this.setupWindowAnimations();
    }

}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setupWindowAnimations() {
    // EnterTransitionを早く開始する.
    this.setAllowEnterTransitionOverlap(true);
    Fade fade = new Fade();
    fade.setDuration(500);
    // Toolbar上のHomeボタンを押したときにアニメーションを実行する.
    this.setExitTransition(fade);
    // 端末のBackキーを押したときにアニメーションを実行する.
    this.setReturnTransition(fade);
}
~省略~
  • フェードアウト表現を行う「Fade」は、Android ver.5.0以降でのみ使用可能です。
  • アニメーションの実行開始までの時間に関係しているのか、「this.setAllowEnterTransitionOverlap(true);」を実行しないとアニメーションしているようには見えませんでした。

課題

上記ではFragmentを閉じるときのみアニメーションでフェードアウト表現を行っています。

同様にFragmentを表示するときに「setEnterTransition」を使うことでフェードインの表現が可能だと思うのですが、実際にやってみてもアニメーションが有効になっているようには見えませんでした。

これについては原因と対処法がわかればまたこのブログにでも書こうかと思います。

終わりに

今回追加したページに必要な内容を追加すれば、一旦機能の追加は終了として次はテストをやっていきたいと思います。
JUnit以外にも色々あるようなので、どれにするかから迷いそうですね。

参考

InfoWindow

Fragment

アニメーション