Twitter cardとOpen graphのデータを取ってくる
Twitterカードのデータは個々のwebページのmetaタグなので、ページをフェッチしてhtmlを解析すればよい。AndroidではXmlPullParserを使えばお手軽にhtmlを解析できる。
ちなみに、上のサイトには
- KXmlParser via XmlPullParserFactory.newPullParser().
- ExpatPullParser, via Xml.newPullParser().
などと書いてあるが少なくともM(API Level 23)ではXml.newPullParser()
でも返ってくるのはKXmlPullParser
である様子。
ここでの肝はXmlPullParser#setFeature(Xml.FEATURE_RELAXED, true)
をすること。これがないと閉じてないmetaタグやlinkタグのせいでheadの閉じタグを読み込むところで例外がでる。文法的にゆるふわなところをいい感じに無視してくれるフラグということだと思う。しかし、これをもってしてもhtml 4.0のドキュメントタイプ宣言部分をパースできないようなので、XmlPullParserに食わせる前に削っておくなどの配慮が必要である(追記:冷静に考えたらそんなわけないじゃんとなった。doctype宣言が適切でないのが原因のようす)。また、上のサイトではXmlPullParser#require()
でpullparserの状態をチェックしているが、このメソッドは大文字小文字を厳格にチェックするようなのでオススメしない。
あとはhttpクライアントのリダイレクトを有効にしておくことぐらいだろうか。OkHttp3はデフォルトではリダイレクトが有効になっていた。
twitter cardに対応していないサイトでも、open graph protocolに従ったデータを書いているサイトもある。これも合わせて読み込むようにしておくと、カードを作れるサイトの幅が広がるので良い。instagramとか。
カードの準備をすることによってgoo.glとかで圧縮されたurlの展開を先んじて行えるので、モバイルでのリダイレクト遅すぎ問題を解決できる。androidならintentでアプリを直接開くこともできる。
RecyclerViewの中の要素をshared elementsにしてアニメーションする
今作っているツイッタークライアントは、タイムラインのユーザアイコンがタップされたらユーザ情報Activityに遷移するのだが、そのときにタップされたユーザアイコンが移動してユーザ情報Activityのユーザアイコンに重なるアニメーションを実装した。現時点ではAndroid 5.0 (API level 21)以上で有効。
まずは遷移元(タイムライン)と先(ユーザ情報)のActivityにWindow.requestFeature()
する。
// setContentView()の前に行う if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); }
遷移元と先のビューにユニークなtransitionName
をつける。RecyclerView
の中の要素にtransitionName
をつけるときはアニメーションさせようとする直前にセットしておかないとユニークにならないようで、カスタムビューの中でinflateした後にセットするとうまくいかなかった(タイムライン→ユーザ情報はうまくいくが、そこからbackボタンで戻るときにアニメーションの起点が右下とか左下になったりした)。クリックされたビューがアニメーションするので、クリックリスナの中でやってしまっている。
userIcon.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { ViewCompat.setTransitionName(view, "user_icon"); } });
ActivityOption
を渡してstartActivity()
する。同様に上のコードのクリックリスナの中でやっている。
Intent intent = new Intent(context, UserInfoActivity.class); ActivityCompat.startActivity(this, intent, ActivityOptionsCompat.makeSceneTransitionAnimation(this, userIcon, "user_icon").toBundle());
UserInfoActivity.start(Activity, View)
みたいなユーティリティメソッドの中でやってしまうのもありかもしれない。
public static Intent createIntent(Context context) { return new Intent(context, UserInfoActivity.class); } public static void start(Activity activity, View userIcon) { final Intent intent = createIntent(activity.getApplicationContext()); ViewCompat.setTransitionName(userIcon, "user_icon"); ActivityCompat.startActivity(activity, intent, ActivityOptionsCompat.makeSceneTransitionAnimation(activity, userIcon, "user_icon").toBundle()); }
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
の中でImageView
にsetOnClickListener
してから返したりして難を逃れたりしていたが、次のようにも書けた。
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を引っ張り出すスワイプ」でも、指を離したときにクリックイベントがなぜか発生してしまうので、独自実装したタッチリスナを同様に渡したほうがよい。