fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

ViewPagerで表示するviewのclickがひろえないなと思ったら

Twitter公式クライアントの画像プレビューに限りなく近いものを作りたくて、ViewPagerにわたすFragmentの中でImageViewを作って、OnClickListenerをセットしてクリックイベントをとろうと思って次のように書いたのだが、だめだった。

@Override
public View onCreateView(LayoutInflater inflater,
                           @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  super.onCreateView(inflater, container, savedInstanceState);
  return new ImageView(getContext());
}

@Override
public void onStart() {
  super.onStart();
  getView().setOnClickListener(new View.OnClickListener(View view) {
    // immersiveな状態を解除したりUIを表示したりする
  });
}

stackoverflowで解決策を探していると、PagerAdapter.instantiateItem()をオーバライドしてその中でクリックリスナやタッチリスナをセットしろというアドバイスがあったので、onCreateViewの中でImageViewsetOnClickListenerしてから返したりして難を逃れたりしていたが、次のようにも書けた。

private ImageView imageView;

@Override
public View onCreateView(LayoutInflater inflater,
                           @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  super.onCreateView(inflater, container, savedInstanceState);
  this.imageView = new ImageView(getContext());
  return this.imageView;
}

@Override
public void onStart() {
  super.onStart();
  this.imageView.setOnClickListener(new View.OnClickListener(View view) {
    // immersiveな状態を解除したりUIを表示したりする
  });
}

onStartでセットしてonStopで解除すれば生存期間が若干短くなるのでこっちの方がいいかなと思いました(こなみ)。

ただ、これだとimmersive modeから復帰するための「システムUIを引っ張り出すスワイプ」でも、指を離したときにクリックイベントがなぜか発生してしまうので、独自実装したタッチリスナを同様に渡したほうがよい。

SYSTEM_UI_FLAG_IMMERSIVEとSYSTEM_UI_FLAG_IMMERSIVE_STICKYとの違い

API Level 19からは全画面表示をしたいときにどっちかのフラグをセット(View.setSystemUiVisibility())することで没入感をより高めることができる。

SYSTEM_UI_FLAG_HIDE_NAVIGATIONSYSTEM_UI_FLAG_FULLSCREENとを合わせてセットすることで、システムUIが画面外に押し出されている状態になる。外にあるUIを引っ張り出すように(上か下エッジからスワイプ)すると表示される。二つのフラグの違いは次の通り。

  • SYSTEM_UI_FLAG_IMMERSIVE: システムUIが引っ張り出されたら、システムUIが消えた状態に戻らない
  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY: システムUIが引っ張り出されたあと、しばらくするとまたシステムUIが引っ込む

IMMERSIVEの方を使い、View#setOnSystemUiVisibilityChangeListener()にリスナをセットすることでシステムUIの見え方が変わったイベントを受け取れる。このリスナの中で独自のUIを表示させるようにして、時間とか特定の操作で再びimmersiveな状態に遷移させれば良い。

システムUIが無いレイアウトに設定するためのフラグも一緒にセットしてやればカクついたりしない。詳しくは公式リファレンスを。

developer.android.com

カスタムビューに独自のスタイル属性を定義する

メディアのサムネイルを同じ大きさで横一列に並べるコンテナクラスを作ったので、中にいれるサムネイルの数をレイアウトリソースで定義できるようにした。

res/values/attr.xmldeclare-styleableを追加する

<resources>
    <declare-styleable name="MyCustomView">
        <attr name="thumbCount" format="integer" />
    </declare-styleable>
</resources>

layoutリソースファイルで次のように使う

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <com.freshdigitable.MyCustomView
        android:id="@+id/custom_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:thumbCount="4"
        />
</LinearLayout>

独自のネームスペースを定義しようとしたら、「gradleプロジェクトではapp:.../res-autoを使え」的なことを言われた気がする。

カスタムビューのコンストラクタで設定した値を受け取る

(色々と省略している)

public class MyCustomView extends View {
  private final int thumbCount;

  public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);
    try {
      this.thumbCount = a.getInt(R.styleable.MyCustomView_thumbCount, 0);
    } finally {
      a.recycle();
    }
  }
}