はじめに
FiNCのAndroidエンジニア兼Bisonの南里です。
AdventCalendarってなんで師走の忙しい時期なんでしょうね?
それでは参りましょう。
まずはこちらをご覧ください
— 南里勇気 (@neonankiti) December 22, 2016
ラインの自動再生を録画してみました。
佐野ひなこかわいいですね。
色々なSNSでよくみる自動再生。
実際に実装している例があまり見受けられなかったので、考えてみました。
機能要件
- Wifiで自動再生(モバイルで自動再生はユーザーアンライクです)
- 自動再生はスクリーン内に入った際に行う。
- 複数ある場合は、占有面積が大きい1つのみを再生
1. Wifi部分の実装
ConnectivityManagerというクラスがあります。
このクラスは、ネットワークの状態、変化に関して責務を負うクラスです
現在のネットワークがwifiであることを取得するには、以下のコードを書きます。
// ネットワーク状態の取得
private static NetworkInfo getNetworkInfo(@NonNull Context context) {
ConnectivityManager connectivityManager
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return connectivityManager.getActiveNetworkInfo();
}
// Wifiステータス
public static boolean isWifi(@NonNull Context context) {
return getNetworkType(context) == ConnectivityManager.TYPE_WIFI;
}
簡単ですね。
2. 自動再生はスクリーン内に入った際に行う
3. 複数ある場合は、占有面積が大きい1つのみを再生
方針は、RecyclerView.OnScrollListerで画面上に表示されている各コンテンツ(ViewHolder)のpositionを取得し、動画を始めるべきpositionかどうか判断します。
動画を始めるべきpositionであれば開始し、そうでなければそのコンテンツは動画を止めます。
ReyclerViewを利用する際には、そのイベントリスナーとして、RecyclerView.OnScrollListener
を利用します。
コンテンツが画面内にあるかどうかは取得できるのですが、コンテンツが再生すべき動画であることを特定するには、それぞれのコンテンツごとの座標を取得して、計算する必要があります。
簡単にスクリーン内にあるコンテンツを特定するためにまず、以下のようなinterfaceを定義します。
そして、OnScrollListener内でinsideScreenを呼び出してあげます。
interface OnPositionListener {
void insideScreen(int startPosition);
}
// positionのlisterはMapで持ちます。
private HashMap<Integer, OnCompletelyVisiblePositionListener> positionListeners = new HashMap<>();
public void setOnPositionListener(
int position, OnPositionListener positionListener) {
this.positionListeners.put(position, positionListener);
}
insideScreen内での実装では、スクリーン上の全てのコンテンツ(ViewHolder)に対して、startPositionを渡してあげます。
ViewHolderのgetAdapterPosition()と比較して、startPositionであれば、VideoView(ExoPlayerが最近の流行りですかね)をstart()するという形です。
private final RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
insideScreen(getMovieStartPosition(recyclerView));
}
};
@Override
public void insideScreen(int startPosition) {
for (Map.Entry<Integer, OnCompletelyVisiblePositionListener> e : onCompletelyVisiblePositionListeners.entrySet()) {
e.getValue().insideScreen(startPosition);
}
}
画面の占有面積を取得する部分
private int getMovieStartPosition(RecyclerView recyclerView) {
// 最初のコンテンツの取得
int firstVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
// 最後のコンテンツの取得
int lastVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();
// コンテンツの大きさにより、画面上に表示されるコンテンツの数は異なる可能性がある。
if (lastVisibleItemPosition - firstVisibleItemPosition == 0) {
return firstVisibleItemPosition;
}
// 画面上に表示される最初と最後のアイテムのpositionの差分が1以上のとき、表示するべきコンテンツ一つを特定する必要がある。
float ratio = 0.0f;
int startPosition = firstVisibleItemPosition;
// コンテンツ分for文を回す。
for (int i = 0; i < lastVisibleItemPosition - firstVisibleItemPosition + 1; i++) {
// positionのadapterを取得する
RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(firstVisibleItemPosition + i);
// findViewHolderForAdapterPosition could return null.
if (holder == null) {
continue;
}
// 各コンテンツのTop, Bottomの座標を取得する
int topPosition = holder.itemView.getTop() < 0 ? 0 : holder.itemView.getTop();
int bottomPosition = holder.itemView.getBottom() > displaymetrics.heightPixels
? displaymetrics.heightPixels
: holder.itemView.getBottom();
// 画面縦の比率を取得して、コンテンツごとに占有面積を比較。
float pointedRatio = (bottomPosition - topPosition) / (float) displaymetrics.heightPixels;
if (pointedRatio > ratio) {
ratio = pointedRatio;
startPosition = firstVisibleItemPosition + i;
}
}
return startPosition;
}
上記で、タイムライン上において、動画を再生するべきpositionを特定したので、あとはViewHolder.getAdapterPosition()の値と比較して、再生するべきコンテンツなのか判断してあげてください。
まとめ
Line, Twitter, Facebook, Vineなどで自動再生どう実装しているんだろうなー。と考えていたので、実際に実装してみるといい勉強になりました。
もっといい実装あれば、教えて下さい。