為さねば成らぬ

retia.verno@gmail.com

prerenderを使うとエラーが出る

最近ちょっとReact Rails触ってる。 まずはServer Side Renderingやろうとprerenderをonにしてみたのだけど、エラーが出る。

Encountered error "#<ExecJS::ProgramError: TypeError: Cannot read property 'toLowerCase' of undefined>"

指定していたcomponent名が間違っていました。わかんねぇ・・・

デバッグ実行がやたら遅かった

f:id:verno3632:20171212091549p:plain

デバッグ実行(debug buildではない)すると重くなるという事象が発生していた。最初のActivityが起動し終える前にANRと判定されるくらい重い。

いろいろいじったがkotlin-gradle-pluginのverを1.1.60から変えたら直った。

別のプロジェクトでやってみたら1.1.60でも問題ないから何か他のライブラリとの相性が悪かったと思うのだが、謎。

すでに1.2.0出てるからいいのだが・・・

ver.3.7.10公開しました

不具合修正。

ブックマーク解除できない

ブックマークIDが適切に取得できていませんでした。

小説本文のルビ変換

[[rb:漢字 > ふりがな]]と記述されている部分を漢字《ふりがな》とルビ変換していましたが、判定ルールが間違っていたため1行に2箇所以上ルビを使うとおかしくなっていました。

外部からURLを開くとログイン画面で止まってしまう

外部URLの判定ロジックがおかしくなってました。



今月はちょっと頑張ります。

現在発生中の不具合に関して

どうやら古いAPIが使用できなくなったようで、以上の通信ができず、機能が使えなくなっています。 特に小説本文見れないのは痛すぎるなー

範囲がちょっと大きいので、時間かかりそうです。

3.7.2リリースしました。

すごい久々の更新。ログインできない不具合と自分のブックマーク一覧で未分類が出なくなってたのを直しました。

http://www.vernobox.com/pxviewer/downloads/

一歩進んだ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構造の画面について

  1. MasterのActivity(=以下 M)のList(Grid)にてitemを選択
  2. 選択されたitemがDetailのActivity(以下D)に表示
  3. Detail画面にてViewPagerでitemを移動
  4. 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変更に伴うものです。

全面的に変更されてしまいました。 f:id:verno3632:20161026184830p:plain

PxViewerで取得するデータは基本的にPC用ページから抜き出していました。 UIが変わるとページの内部の構成も変わってしまうので、その処理の部分を全面的に書き換えなければいけません。

はっきり言うと以前一部APIが使えなくなって大改修したときより影響範囲でかいです。 verno3632.hatenablog.com

ということで今回も修正するのに時間かかりそうです。 ゆっくり待っててね。