こんにちは、 id:numanuma08 です。 最近、recyclerview-selectionを利用する機会がありました。recyclerview-selectionのドキュメントには、Viewの選択状態はsetActivated
を使って変更するべきと書かれています。
In Adapter#onBindViewHolder, set the "activated" status on view. Note that the status should be "activated" not "selected". See View.html#setActivated for details.
しかし、Viewには似たような用途っぽいメソッドにsetSelected
も用意されています。なぜsetActivated
を使うべきなのか調べました。
リファレンスを参照する
何はなくともリファレンスを参照します。まずはsetSelected
から。
Changes the selection state of this view. A view can be selected or not. Note that selection is not the same as focus. Views are typically selected in the context of an AdapterView like ListView or GridView; the selected view is the view that is highlighted.
ビューの選択状態を変更します。ビューを選択することもしないこともできます。選択とフォーカスは同じでないことに注意しましょう。ビューは典型的にはListViewやGridViewといったAdapterViewのコンテキストの中で選択されます。
なんだか、選択状態を表現するのにsetSelected
でも良さそうな雰囲気ですが。次はsetActivated
のドキュメントを読みます。
Changes the activated state of this view. A view can be activated or not. Note that activation is not the same as selection. Selection is a transient property, representing the view (hierarchy) the user is currently interacting with. Activation is a longer-term state that the user can move views in and out of. For example, in a list view with single or multiple selection enabled, the views in the current selection set are activated. (Um, yeah, we are deeply sorry about the terminology here.) The activated state is propagated down to children of the view it is set on.
ビューの有効状態を変更します。ビューを有効化することもしないこともできます。有効化は選択と違うことに気をつけてください。選択は一時的なプロパティで、現在ユーザーが操作しているビュー(階層)を表します。有効化は長期的な状態でユーザーがビューを出し入れ可能です。例えば単一または複数選択が可能なリストビューでは現在選択されているセットのビューが有効です。(あー、専門用語で本当にごめんなさい)。有効状態は子どもの階層のビューに伝達されます。
謝られてしまいました。ただ、どうやらリスト中の要素を選んで操作する場合、setActivated
が適しているように思えます。一時的な状態、長期的な状態という表現から推察するに次のような使い分けができるかと思います。
- isSelected: リスト中の要素を長押しなどで選択している状態
- isActivated: リスト中の要素をタップしてチェックボックスがチェックされた状態
また、ビュー階層についても言及がありsetSelected
は単一の階層でsetActivated
は子どもの階層も含みます。やはり、リスト要素タップ後にチェックボックスをチェックしたり背景を変えるなどはsetActivated
で設定された値を参照するのが正しそうです。
ソースコードを眺める
リファレンスを参照した結果どうやらrecyclerview-selectionでsetAcitvated
を使うのが正しそうと分かりました。ここで、実際の実装がどうなっているかView.java
のソースコードを眺めて確認します。まずはsetSelected
から。
public void setSelected(boolean selected) { //noinspection DoubleNegation if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) { mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0); if (!selected) resetPressedState(); invalidate(true); refreshDrawableState(); dispatchSetSelected(selected); if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } else { notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } }
特にこれといった処理は行われていないように思えます。mPrivateFlags
というViewの様々な状態を格納するプロパティの更新を行ったあと、invalidate
で描画の更新、その後はアクセシビリティ関連の通知を発行しています。
次はsetActivated
を見ます。
public void setActivated(boolean activated) { //noinspection DoubleNegation if (((mPrivateFlags & PFLAG_ACTIVATED) != 0) != activated) { mPrivateFlags = (mPrivateFlags & ~PFLAG_ACTIVATED) | (activated ? PFLAG_ACTIVATED : 0); invalidate(true); refreshDrawableState(); dispatchSetActivated(activated); } }
こちらも特にこれと言った処理はありません。mPrivateFlags
の更新と描画の更新です。
イマイチこれと言った差分もないので次はViewGroup
を見ます。ViewGroup
ではdispatchSetSelected
とdispatchSetActivated
を実装してselected/activatedが変更されたときの処理が実装されていました。
@Override public void dispatchSetSelected(boolean selected) { final View[] children = mChildren; final int count = mChildrenCount; for (int i = 0; i < count; i++) { children[i].setSelected(selected); } } @Override public void dispatchSetActivated(boolean activated) { final View[] children = mChildren; final int count = mChildrenCount; for (int i = 0; i < count; i++) { children[i].setActivated(activated); } }
同じやないか・・・。selected/activatedともにViewGroup
で管理している子ビューにその変更が通知されています。ドキュメントの内容と違う・・・。
もうちょっと具体的な実装を探すためにTextView
の実装を見てみます。TextView
はsetSelected
が実装されていますが、setActivated
は実装されていませんでした。
public void setSelected(boolean selected) { boolean wasSelected = isSelected(); super.setSelected(selected); if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (selected) { startMarquee(); } else { stopMarquee(); } } }
marqueeするんかい。ellipsize
にmarquee
が設定されておりかつ一文が一行に収まらないときテキストが横スクロールします。
Android SDK内の実装ではこれと言ったselected/activatedの使い分けがはっきりとしませんでした。
他にもxmlのリソースファイルも調べたのですが、SDK内で定義されているクラスや仕組みの中にactivated
を利用したものはありませんでした。
まとめ
recyclerview-selectionで利用するためView.setSelected
とView.setActivated
の違いを調べました。ドキュメントを読むとsetActivated
を利用するのが正しいそうですが、SDK内部の実装は特にこれと言った使い分けはされてないようでした。したがって実装上はsetActivated
とsetSelected
のどちらを利用しても大きな問題は起こらないかもしれません。しかし、ドキュメントにあるように選択と有効化は別のものと定義されています。そのため、利用するデバイスや環境によっては動作に違いが生じるかもしれません。しっかりと選択、有効化の違いを意識して実装を行いたいです。