2015年10月23日金曜日

[コラム] Android6.0「テキスト選択」の新仕様とFloating Action Mode


Android6.0 Marshmallow (マシュマロ)では、テキスト選択(Text Selection)の動作が仕様変更となりました。これまでは、テキスト選択時に画面上部のアクションバー(Action Bar)という部分に、コピー、貼り付け、切り取り、などのメニューが表示されていましたが、今回の仕様変更によってアクションバーへの表示はなくなり、選択した文字列のすぐ近くにメニューバーが表示されるようになりました。これによって、ユーザーはテキスト選択時に、選んだ文字列からあまり視線を離すことなく、メニューを選ぶことができるようになります。今回は、そのテキストビューの変更とバックグラウンド機能として導入されたフローティングアクションバー(Floating Action Bar)について簡単に紹介したいと思います。


■仕様変更となったテキスト選択時のアクションバー


以前のテキスト選択時の動作は、画像1のように、画面上部に「Text selection」というタイトルが表示され、その横にメニューのアイコンが並ぶという形でした。

画像1 [テキスト選択時(Android API Lv 22)]

Android 6.0(API LV 23)では、画像2のような動きに変わりました。

画像2 [テキスト選択時(Android API Lv 23)]

プログラムの方ですが、開発者は何もしなくても、テキストビューを画面に配置しただけで、デフォルトでフローティングアクションモードとしてテキスト選択時の動きをしてくれます。実際、画像1と2のプログラムは、図1のように同じコードで、コンパイルするAPIレベルを22と23に変えただけです。

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

        TextView textView = (TextView) findViewById(R.id.sample_text);
        textView.setText(R.string.sample_text);
        textView.setTextIsSelectable(true);

    }

もちろん、図2のようにレイアウトXMLに記載してもOKです。

    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textIsSelectable="true"
        android:text="@string/sample_text"
        />

この背後にある仕組みについて言います。

アクションバーへのメニューの表示やメニュー選択時の動きは、アクションモード(Action Mode)という仕組みで実現されています。テキストが選択された場合、テキストビューは、アクションバーにメニューを表示するようアクションモードに処理を依頼します(図3)。

図3 [アクションモード]

 このようにして、テキスト選択時の動作は実現されていますが、テキスト選択時のメニューバーの表示位置の変更にあたって、このアクションモードという仕組みに手が入りました。具体的には、アクションモードにフローティング(浮かんでいる)という新たな種別が追加になりました。このフローティングタイプのアクションモードを動かすと、対象となる部品のすぐそばにメニューバーを表示することになります。一方、これまでのアクションバーへの表示は、プライマリ(基本的な、主な)という種別として新たに呼ばれるようになり、アクションモードを利用する場合には、この2種類を選べるようになったというわけです。

いろいろ試してみましたが、テキストビューをプライマリのアクションモードとして簡単に設定しなおす方法はなさそうです。プロジェクトによっては、これまでアクションバー(画面上部)に表示されていたメニューが、アンドロイドのバージョンを6.0にあげたことにより、テキストのそばに表示されるようになってしまい、仕様と違う動きになって困るところも出てくるかもしれません。将来的にはどちらも選べるようになってほしいですね。

■Floating Action Mode を使ってみる


実際に、フローティングアクションモード(Floating Action Mode)を使って何か作ってみます。

例えば、テキストを並べたリストに対して、フローティングアクションモードを使ってみましょう。
メニューバーに並べる項目は、追加、削除、共有の三つにしておきます。

[実行の様子1]

サンプルプログラムは、複数選択のリストビューに対して、アイテムをクリックしたときにフローティングアクションバーを表示するようにしています。そのとき、アクションバーを表示する場所が、選択したアイテムに被らないように、選択したアイテムをちょうど覆う矩形領域を計算してアクションモードに渡しています。[実行の様子1]の青い部分が、その矩形領域です。

では、コードの要点の説明です。

アクションモードを使用する場合は、ユーザーの何らかのアクションに対して、アクションされたViewのstartActionModeメソッドを呼ぶ必要があります。


    private class CustomItemClickedListener implements AdapterView.OnItemClickListener {

        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id) {
            ListView listView = (ListView)parent;
            boolean hasChecked = false;
            Rect rect = new Rect();
            Rect sumRect = null;
            int firstVisiblePosition = listView.getFirstVisiblePosition();
            for (int i = 0; i < listView.getChildCount(); i++) {
                TextView textView = (TextView) listView.getChildAt(i);
                if (listView.isItemChecked(firstVisiblePosition + i)) {
                    hasChecked = true;
                    textView.getGlobalVisibleRect(rect);
                    if (sumRect != null) {
                        sumRect.union(rect);
                    }
                    else {
                        sumRect = new Rect(rect);
                    }
                }
            }

            mSumRect = getRectRelTo(sumRect, listView);

            if (hasChecked) {
                showSelectRegion(mSumRect);
                // MEMO: startActionModeは、呼び出しスレッドとは別のスレッドでActionModeのコールバックを呼び出す
                mActionMode = listView.startActionMode(new CustomActionModeCallback(), ActionMode.TYPE_FLOATING);
            }
            else {
                showSelectRegion(null);
                if (mActionMode != null) {
                    mActionMode.finish();
                }
            }
        }
    }
[コード1]は、リストのアイテムをクリックしたときの処理です。

74行目~91行目で表示されている選択項目を覆う矩形領域を求めています。ついでに、選択項目があるかどうかもここでチェックしています。ここで求めた矩形領域は、コード2の方で使用します。

93行目から103行目では、項目が選択されている場合は、上で求めた矩形領域を表示し、リストビューに対して、startActionModeメソッドをTYPE_FLOATINGで呼び出しています。アクションモードのタイプを指定しないタイプのstartActionModeメソッドは、APIレベル11のときから存在していましたが、タイプを指定するstartActionModeメソッドは、APIレベル23から追加されました。startActionModeは、ActionModeオブジェクトを返します。クリックした結果、選択項目がなくなった場合に、このActionModeのfinish()メソッドを呼び出して、フローティングアクションバーを消しています。矩形領域もついでに消しています。showSelectionRegionメソッドは、筆者が作ったプライベートメソッドで、計算でもとめた矩形領域を青い透過色で可視化するためにあります。実際には必要ではありません。

次のコードは、ActionModeのコールバックの実装です。

    private class CustomActionModeCallback extends ActionMode.Callback2 {

        @Override
        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
            outRect.set(mSumRect);
            mSumRect = null;
        }

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            menu.add(INSERT);
            menu.add(DELETE);
            menu.add(SHARE);
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            if (item.getTitle().equals(INSERT)) {
                insertNewItem();
            }
            else if (item.getTitle().equals(DELETE)) {
                deleteSelection();
            }
            else if (item.getTitle().equals(SHARE)) {
                shareSelections();
            }
            mode.finish();
            showSelectRegion(null);
            return true;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
        }
    }
[コード2]で実装しているのは、startActionModeメソッドの引数に渡しているActionModeのコールバッククラスです。以前のActionModeのコールバックであるActionMode.Callbackは、インターフェースクラスでしたが、APIレベル23で追加されたこのActionMode.Callback2は、ActionMode.Callbackを実装するクラスとなっています。Callback2クラスは、

public void onGetContentRect(ActionMode mode, View view, Rect outRect)

というメソッドが実装されています。第一引数は、表示しているアクションモードが渡ってきます。第2引数のviewは、startActionModeが呼ばれたViewです。このプログラムの場合は、ListViewがわたってきます。第3引数は、アウト引数で、ここには領域を設定して返してあげます。この領域というのは、フローティングアクションバーがその周りに表示される領域のことです。ここはプログラマーが任意に指定することができます。ActionMode.Callback2のデフォルトの実装では、outRectには、viewの表示領域がそのまま渡されます。今回のプログラムの場合は、アイテムクリック時に計算した矩形領域をここで渡しています(111行目)。

ActionMode.Callback2のその他のメソッドは、ActionMode.Callbackから受け継いだもので、APIレベル23で追加されたメソッドではないので、ここでの説明は省きます。

細かい説明をだいぶ省きましたが、フローティングアクションモードは、矩形領域を計算して、onGetContentRectでアクションモードへ渡してやることがポイントとなります。どんな実装でもこの矩形領域を計算する部分が面倒になりそうです。

■まとめ


簡単ではありますが、Android 6.0 で導入されたテキスト選択の新仕様と、フローティングアクションモードの使い方をご紹介しました。まとめるとこんな感じです:

  • テキストビューは、テキストのそばにメニューが表示され便利になりましたが、以前のように画面上部には表示できなくなりました。
  • 開発者は、アクションバーを使う場合、画面上部のプライマリーにするのか、フローティングにするのかを考えないといけなくなりました。
  • フローティングアクションバーを使う場合は、矩形領域の計算で苦労すると思われます。
冒頭の写真は、いま流行のマシュマロコーヒーです。
今回のマシュマロ・アンドロイドでは、コーヒーに浮かぶマシュマロのような機能が一つ追加されたというわけです。

140 180 Action Mode , Android , Android 6.0 , Floating , Marshmallow , Text Selection

記載されている会社名、および商品名等は、各社の商標または登録商標です。

0 コメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...