3.7.2リリースしました。
すごい久々の更新。ログインできない不具合と自分のブックマーク一覧で未分類が出なくなってたのを直しました。
一歩進んだActivity Transitionの実装
この投稿はAndroid その3 Advent Calendar 2016の20日目の記事です。
背景
Android 5.0からActivity Transitionが追加され、画面遷移のアニメーションをカスタマイズできるようになりました。 Activity/Fragment Transitionsのつかいかたを書いたのもアドベントカレンダーで、当時はAndrdoi5.0が出たばかりで実端末にはあまり乗っていない状態だったのを覚えています。 それから2年。Activity Transition対応の機種がおよそ6割になりました。( Dashboard | Android Developers )
Transitionの仕方にもいろいろありますが、よりリッチなアニメーションを見せることができるのがSharedElementです。 これによりActivity間のViewを連続したものとして見せるようなことが可能になっています。
本記事では自分のアプリにて実装したTransitionについて書きます。Activity Transitionの基本的なところはこちらをご参考ください。
このアプリはいわゆるMasterDetail構造で、Detail側がViewPagerになっています。 やりたいことは、MasterとDetailを行き来する際に同じ要素はアニメーションで連続させて見せることです。
問題となるのがDetailから戻るときで、MasterDetail構造の画面について
- MasterのActivity(=以下 M)のList(Grid)にてitemを選択
- 選択されたitemがDetailのActivity(以下D)に表示
- Detail画面にてViewPagerでitemを移動
- BackボタンでMasterに戻る
以上のようにActivityTransitionを設定していると M=>D は問題なくいくのですが、D=>M はうまくいきません。というのもD=>Mの際にsharedされる要素の組み合わせはデフォルトではM=>Dで設定した組み合わせと同じものであるので、3.で要素を移動したことによりSharedElementを動的に変更してあげなければいけないためです。
サンプルコードはこちらから。 今回はActivityのみで実装しましたが、Fragmentでも同様に使えます。
以下ポイントとなる部分を解説していきます。
Transitionのタイミングの調整
///DetailActivity supportPostponeEnterTransition(); viewPager.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { viewPager.getViewTreeObserver().removeOnPreDrawListener(this); supportStartPostponedEnterTransition(); return true; } });
Transitionが行われる際には遷移後ActivityのSharedElementのViewが生成されていなければいけませんし、大きさ・位置が決定していなければ正しくアニメーションしません。Viewの状態に合わせてタイミングを調整します。
まずActivity DのonCreate()
でsupportPostponeEnterTransition()
することにより、自動で始まってしまう遷移アニメーションを中断します。
ViewはViewTreeObserver
で監視し、描写が開始される直前でアニメーションが開始するようにsupportStartPostponedEnterTransition()
を実行します。
今回のサンプルではViewPagerを使っているのですが、子ViewがinstantiateItem()
で生成するのを待つために使っています。
ここでpostponeしたらstartするのを忘れないでください。 startは何かのlistener中に記述し呼ばれるまで待つ、ということが多いですが、条件によってイベントが発行されずstartしない、ということがままあります。
SharedElementの対応付け
遷移前後のSharedElementのViewの対応は、通常だとActivityOptionsで設定します。これをIntent以外で設定する場合はonMapSharedElements(String, Map<String,View>)
が有効です。第二引数にSharedElementsに使われるMapが渡されてくるので設定します。以下通常の遷移と戻る遷移について見ていきます。
Master => Detail
通常の遷移では特別なことはやっていません。DのSharedElementにtransitionName
を設定して、MでActivityOptionを使ってIntentを投げるだけです。
//DetailActivity @Override public View instantiateItem(ViewGroup container, int position){ ImageView imageview = new ImageView(container.getContext()); imageview.setImageResource(ItemListActivity.image_drawables[position]); imageview.setTag("item_"+position); ViewCompat.setTransitionName(imageview, "item_"+position); container.addView(imageview); return imageview; }
//MasterActivity ActivityCompat.startActivityForResult(MasterActivity.this, intent, REQUEST_CODE_POSITION, options1.toBundle());
(Master) <= Detail
戻る遷移に際して、Activity D側ではSharedElementをsetEnterSharedCallback()
で設定します。
//DetailActivity setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { int position = viewPager.getCurrentItem(); View v = viewPager.findViewWithTag("item_"+position); sharedElements.clear(); sharedElements.put("item_"+position, v); } });
MapのsharedElements
にはActivity起動時に渡されてきた、つまり通常遷移でのSharedElementが入っているので、一度sharedElements.clear()
でリセットした上で新たに設定し直しています。
Master <= (Detail)
戻ってくる際のTransitionには、Activity MではonActivityReenter()
が呼ばれます。ここにTransitionに関するコードを書いていきます。
ここの遷移では、Detailで表示されている要素がリストに収束するようにアニメーションします。しかしDetail画面で表示されている要素が移動している場合、Master画面のリストにそれが表示されていない場合があります。そのときにはTransitionを中止し、リストがスクロールして要素の準備ができてからTransitionを再開します。 スクロールしなくて済む場合はSharedElementとして変数に入れておきます。
//MasterActivity //表示されていない時はスクロールしてからTransitionを開始 if(position < manager.findFirstVisibleItemPosition() || position > manager.findLastVisibleItemPosition()){ supportPostponeEnterTransition(); recyclerView.scrollToPosition(position); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if(position >= manager.findFirstVisibleItemPosition() && position <= manager.findLastVisibleItemPosition()){ sharedElement = recyclerView.getChildAt(position - manager.findFirstVisibleItemPosition()).findViewById(R.id.content); recyclerView.removeOnScrollListener(this); supportStartPostponedEnterTransition(); } } }); //表示されているときは設定するだけ }else{ sharedElement = recyclerView.getChildAt(position - manager.findFirstVisibleItemPosition()).findViewById(R.id.content); }
取得したsharedElemennt
についてはDetail側と同様にCallbackでMapするようにします。この時設定するのはExitSharedElement()
です。
Activityが消えるExit
は戻ってくるときのReenter
と対応しているためです。
//MasterActivity setExitSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { sharedElements.clear(); sharedElements.put("item_"+position, sharedElement); setExitSharedElementCallback(new SharedElementCallback() { }); } });
Tips
以下自分が実装する際にハマったポイントです。
デバッグ
うまく動かない時は、既出のSharedElementCallbackを設定し、SharedElementが正しく設定されているかのぞいて見るといいです。
またWindow.getSharedElementEnterTransition()
でTransitionのstartなどのイベントを拾えるので使ってみるとよいかもしれません。
階層の深いView
階層の深いViewをSharedElementに設定すると、Viewの生成が追い付かないのか時々Crashします。なるべく無駄なViewを作らず、設定するViewはなるべく親Viewのほうにするべきでしょう。
まとめ
本記事ではActivity Transitionについて、SharedElementを動的に設定する方法について書きました。これにより、よりわかりやすい遷移アニメーションが可能になりました。
みなさんもどんどんActivity Transition使っていきましょう。
現在発生中の不具合に関して
10月に入ったあたりから、PxViewerの一部・全機能が使用できない不具合が発生しております。 これはPixiv側のUI変更に伴うものです。
全面的に変更されてしまいました。
PxViewerで取得するデータは基本的にPC用ページから抜き出していました。 UIが変わるとページの内部の構成も変わってしまうので、その処理の部分を全面的に書き換えなければいけません。
はっきり言うと以前一部APIが使えなくなって大改修したときより影響範囲でかいです。 verno3632.hatenablog.com
ということで今回も修正するのに時間かかりそうです。 ゆっくり待っててね。
ver3.7.0 リリースしました
https://www.vernobox.com/pxviewer/downloads/PxViewer_3_7_0.apk
4/24 追記 バグ修正版出しました
https://www.vernobox.com/pxviewer/downloads/PxViewer_3_7_1.apk
機能追加
ブックマークボタンの追加
イラスト・小説作品画面の下部ボタンに、ブックマークボタンを入れました。以前は一番右のメニューから選べました。 ブックマークしてるかどうかがボタンのアイコンでわかるようになってます。
ユーザー画面のデザイン変更
ユーザーの画面にて、アイコンとフォローボタンを出すようにしました。 "どの"ユーザーの情報を見てるかちょっとわかりずらかったので。。
小説画面タップで上下バーを消す
ActionBarとBottomBarで表示域が小さいという声が以前からありましたので、 小説画面については本文タップで表示・非表示を切り替えられるようにしました。
スクロールに伴うアニメーションの追加
作品一覧でスクロールすると上のバーが引っ込んだり出てきたりします。 完全にヤリタカッタダケ−
PxViewer ver3.6.2リリースしました
https://www.vernobox.com/pxviewer/downloads/PxViewer_3_6_2.apk
機能追加
画面遷移アニメーションのON/OFF
Android 5以上に限り、設定からアニメーションのON/OFFを切り替えられます。 Android 5.0.xでアニメーション時にクラッシュする不具合があるので、それを避ける場合にもOFFにしてください。
不具合修正
Android6.0で画像保存できない
新しいパーミッションモデルへ対応しました。初回に書き込み権限の許可を求められます。 そこで許可すると画像保存できるようになります。
小説の件数が表示されない
小説の投稿日時が表示されない
画面遷移アニメーション時のクラッシュ
PxViewer ver3.6.0 リリースしました。
http://www.vernobox.com/pxviewer/downloads/PxViewer_3_6_0.apk
http://www.vernobox.com/pxviewer/downloads/PxViewer_3_6_1.apk
3連休前に出すつもりだったけどログイン周りしちゃってて手こずった。
機能追加
ユーザー作品のタグ別表示
自分のブックマーク作品はタグ別に表示できていましたが、各ユーザーの投稿作品もタグ別に表示できるようにしました。 初見のユーザーさんの作品の傾向分かるし思ったより便利です。
一覧-詳細画面間のアニメーション(Android 5.0以上)
対応できる端末が増えたので1年前に投稿したのをようやく実装しました。
一覧と作品画面を行き来する際にわかりやすくなるかと思います。
ログインエラーを詳細に表示
今までログイン時には成功したか失敗したかしか出ませんでした。 pixivのログインには複数回失敗するとアカウントロックされるため、詳細を表示されるようにしました 特に
- セキュリティチェックが必要です。一度ブラウザからログインしてください。
が出た場合にはID・パスワードが合っていても続けるとロックされます。 一度通常のブラウザからpixivにログインして、それからPxViewerにてログインを行ってください。
画像保存のデフォルトディレクトリの変更
Androidのバージョンによりますが、これまではアプリの領域がデフォルトでした。つまりPxViewerをアンインストールすると保存した画像も削除するはめに。。 Picturesフォルダの下に作るようにしました。
不具合修正
外部から作品が開けない
他ブラウザのURLから作品URLを開こうとした時、ログインしておらず作品が開けないことがありましたので修正。
Android 2.3対応
タブレット対応をした際に入れたコードが2.3に対応しておらず、一度サポートからはずしていました。 対応できたので再度復活させてます。Google Play だとこういうのできないですね! (minSdkVersionを下げられない) メモリ不足でクラッシュとかはごめんなさい
強調/非表示タグが動作しない
イラストに対するタグフィルターは、プレミアム会員向けの機能ですが、 "プレミアム会員である"という情報がうまくとれずに動作しないことがありました。
apkサイズ縮小
Proguard入れたのでアプリケーションのサイズ減ってます。 Libraryのproguardと出てくるエラーのクラスををごりごり-keepいれればいいので思ったより楽でした。