Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Contact AppっぽいSticky Headerをつくる

Contact AppっぽいSticky Headerをつくる

2018/02/22に開催されたpotatotips #48での発表スライドです。

Yoshihiro WADA

February 22, 2018
Tweet

More Decks by Yoshihiro WADA

Other Decks in Programming

Transcript

  1. Contact App っぽい
    Sticky Header をつくる
    Yoshihiro Wada a.k.a. @e10dokup
    2018/02/22 @ potatotips #48

    View full-size slide

  2. 自己紹介
    Yoshihiro Wada

    CyberAgent, Inc. でいろいろやったりやらなかったり
    @e10dokup

    View full-size slide

  3. 作りたい物
    Android の Contact App や droidkaigi-conference-app
    みたいな 「名前一覧のアレ」

    View full-size slide

  4. つかうもの
    3FDZDMFS7JFX*UFN%FDPSBUJPO
    3 つのメソッドを Override する
    RecyclerView のアイテムに対して装飾をする adstract class
    divider の線を引いたりアイテム間のドラッグをしたり…
    HFU*UFN0GGTFUT 3FDUPVU3FDU
    7JFXWJFX 3FDZDMFS7JFXQBSFOU
    3FDZDMFS7JFX4UBUFTUBUF

    PO%SBX $BOWBTD 3FDZDMFS7JFXQBSFOU
    3FDZDMFS7JFX4UBUFTUBUF

    PO%SBX0WFS $BOWBTD 3FDZDMFS7JFXQBSFOU
    3FDZDMFS7JFX4UBUFTUBUF

    View full-size slide

  5. HFU*UFN0GGTFUT
    RecyclerView の各 Item の描画位置をずらすメソッド
    第一引数の outRect をいじることで描画位置をずらすことができる
    item の parent ごと弄るイメージなので item 側の layout XML
    は outRect の中で描画されることになる
    今回作りたい Sticky Header の場合だと left の座標を作りたいマージン分
    加えるだけで良さそう。

    View full-size slide

  6. PO%SBXPO%SBX0WFS
    Decoration の描画時 / 描画後に呼ばれるメソッド
    第一引数の c (Canvas) を操作することで装飾を描画できる
    この Canvas は RecyclerView 全体の Canvas で、 RecyclerView の再描画
    毎に 1 回だけ呼び出される
    今回作りたい Sticky Header の場合だと表示されている要素をループで
    見ながら頭文字を DESBX5FYU で然る場所に描画したら良さそう

    View full-size slide

  7. Canvas 上に文字を書く
    DBOWBTESBX5FYU

    'POU.FUSJDT
    指定した座標に指定した 'POU.FUSJDT で文字列を描画するメソッド
    指定した座標が中央ではないので補正してあげる必要がある
    5FYU1BJOU に対し色やフォントサイズを決定した後、
    HFU'POU.FUSJDT
    で取得する
    abcdfeghi
    (0,0)
    top ascent
    leading
    descent bottom
    ※原点は 5FYU1BJOUUFYU"MJHO1BJOU"MJHO-&'5 の場合

    View full-size slide

  8. ItemDecoration を実装する (下準備)
    リスト要素からラベルに表示したい文字 (大体頭文字) を取り出して渡す
    interface を用意しておく
    JOUFSGBDF$BMMCBDL\
    GVOHFU*OJUJBM QPTJUJPO*OU
    4USJOH
    ^
    ӊҮӉҮ JOUFSGBDF Әᇴ⸢әҽӽӔᖚӂ㮢6QQFS$BTF Ӓҵә⢰ᗍӁӐҮӔҮ㮣
    PCKFDU4UJDLZ-BCFM*UFN%FDPSBUJPO$BMMCBDL\
    PWFSSJEFGVOHFU*OJUJBM QPTJUJPO*OU
    4USJOH\
    SFUVSOJUFNTOBNF<>UP4USJOH

    ^
    ^
    ESPQXIJMF
    とか UP6QQFS$BTF
    を使うといい感じに @ を
    省いたり大文字小文字を揃えたりできそう

    View full-size slide

  9. ItemDecoration を実装する (初期化)
    初期化中に 'POU.FUSJDT と要素の margin、 ラベルの padding を
    設定する。 今回は Context を拾って resource から引く感じ
    JOJU\
    WBMSFTPVSDFDPOUFYUSFTPVSDFT
    5FYU1BJOU Ә⼀ᇰ
    UFYU1BJOUBQQMZ\
    UZQFGBDF5ZQFGBDF%&'"6-5
    JT"OUJ"MJBTUSVF
    UFYU4J[FSFTPVSDFHFU%JNFOTJPO 3EJNFOTUJDLZ@MBCFM@GPOU@TJ[F

    DPMPS$POUFYU$PNQBUHFU$PMPS DPOUFYU 3DPMPSQSJNBSZ

    UFYU"MJHO1BJOU"MJHO-&'5
    ^
    ൵ኃᓬӘᴀᇰ
    GPOU.FUSJDTUFYU1BJOUGPOU.FUSJDT
    MBCFM1BEEJOHSFTPVSDFHFU%JNFOTJPO1JYFM4J[F 3EJNFOMBCFM@QBEEJOH

    DPOUFOU.BSHJOSFTPVSDFHFU%JNFOTJPO1JYFM4J[F 3EJNFODPOUFOU@NBSHJO

    ^

    View full-size slide

  10. ItemDecoration を実装する (HFU*UFN0GGTFUT)
    init で得た contentMargin の分だけ outRect の left を増加させ、
    ラベルが入る場所を作る
    PWFSSJEFGVOHFU*UFN0GGTFUT PVU3FDU3FDU WJFX7JFX
    QBSFOU3FDZDMFS7JFX
    TUBUF3FDZDMFS7JFX4UBUF
    \
    TVQFSHFU*UFN0GGTFUT PVU3FDU WJFX QBSFOU TUBUF

    PVU3FDUMFGUDPOUFOU.BSHJO
    ^
    このままだとラベルを与えたい要素以外も全部左にマージンが入るので
    Callback.getInitial() に不正な値が入ったときはマージンを与えない等
    すると本来ラベル与えたい要素以外は画面前幅に描画される
    全幅

    View full-size slide

  11. ItemDecoration を実装する (PO%SBX)
    PWFSSJEFGVOPO%SBX D$BOWBT QBSFOU3FDZDMFS7JFX TUBUF3FDZDMFS7JFX4UBUF
    \
    TVQFSPO%SBX D QBSFOU TUBUF

    WBMUPUBM*UFN$PVOUTUBUFJUFN$PVOUᩈ✊ӁӐҮӵ஁⺙✛ᡔ
    WBMDIJME$PVOUQBSFOUDIJME$PVOUজⷭ⒈ҿӶӐҮӵ⺙✛ᡔ
    WBMGPOU$FOUFS GPOU.FUSJDTBTDFOUGPOU.FUSJDTEFTDFOU

    WBSQSFWJPVT*OJUJBM4USJOH
    WBSJOJUJBM
    GPS JJOVOUJMDIJME$PVOU
    \
    ⷭ⒈ҿӶӐҮӵ⺙✛ҶՑՁՓӼᝡ∈Ӄӣҷ⺙✛ҵᴀᇰӃӵ
    WBMWJFXQBSFOUHFU$IJME"U J

    WBMQPTJUJPOQBSFOUHFU$IJME"EBQUFS1PTJUJPO WJFX

    QSFWJPVT*OJUJBMJOJUJBM
    JOJUJBMDBMMCBDLHFU*OJUJBM QPTJUJPO

    JG 5FYU6UJMTJT&NQUZ JOJUJBM
    ]]QSFWJPVT*OJUJBMJOJUJBM
    DPOUJOVF
    ᝡ∈৴⡕Ә : ᏍᮏӼᴀᇰӁӐᝡ∈Ӄӵ㮢ᰪՂդԠ㮣
    ^
    ^

    View full-size slide

  12. ItemDecoration を実装する (PO%SBX)
    PWFSSJEFGVOPO%SBX D$BOWBT QBSFOU3FDZDMFS7JFX TUBUF3FDZDMFS7JFX4UBUF
    \
    TVQFSPO%SBX D QBSFOU TUBUF

    WBMUPUBM*UFN$PVOUTUBUFJUFN$PVOU ᩈ✊ӁӐҮӵ஁⺙✛ᡔ
    WBMDIJME$PVOUQBSFOUDIJME$PVOUজⷭ⒈ҿӶӐҮӵ⺙✛ᡔ
    WBMGPOU$FOUFS GPOU.FUSJDTBTDFOUGPOU.FUSJDTEFTDFOU

    WBSQSFWJPVT*OJUJBM4USJOH
    WBSJOJUJBM
    GPS JJOVOUJMDIJME$PVOU
    \
    ⷭ⒈ҿӶӐҮӵ⺙✛ҶՑՁՓӼᝡ∈Ӄӣҷ⺙✛ҵᴀᇰӃӵ
    WBMWJFXQBSFOUHFU$IJME"U J

    WBMQPTJUJPOQBSFOUHFU$IJME"EBQUFS1PTJUJPO WJFX

    QSFWJPVT*OJUJBMJOJUJBM
    JOJUJBMDBMMCBDLHFU*OJUJBM QPTJUJPO

    JG 5FYU6UJMTJT&NQUZ JOJUJBM
    ]]QSFWJPVT*OJUJBMJOJUJBM
    DPOUJOVF
    ᝡ∈৴⡕Ә : ᏍᮏӼᴀᇰӁӐᝡ∈Ӄӵ㮢ᰪՂդԠ㮣
    ^
    ^
    リストの前要素と initial が
    一致するときは描画しない
    描画するのは同じ initial の
    先頭の要素だけ

    View full-size slide

  13. ItemDecoration を実装する (実際の描画)
    ラベルを描画すべきと判定した要素について描画位置の Y 座標を決定して
    c.DrawText() で描画する
    ᇴ㗖Ә 'POU.FUSJDT ӘसႠᏍᮏ㮢నՂդԠҵӳᛝӍӐҷӉӊһ㮣
    WBMGPOU$FOUFS GPOU.FUSJDTBTDFOUGPOU.FUSJDTEFTDFOU

    ᇴ㗖Әᝡ∈௡ⅈ
    WBMWJFX$FOUFS WJFXUPQWJFXCPUUPN

    WBMWJFX.JEEMFWJFXIFJHIU
    WBSUFYU:.BUINBY WJFX.JEEMF WJFX$FOUFS
    GPOU$FOUFS
    JG QPTJUJPOUPUBM*UFN$PVOU
    \
    WBMOFYU*OJUJBMDBMMCBDLHFU*OJUJBM QPTJUJPO

    JG OFYU*OJUJBMJOJUJBMWJFX$FOUFSWJFX.JEEMF
    \
    UFYU:WJFX.JEEMFWJFX$FOUFS
    ^
    ^
    DESBX5FYU JOJUJBM MBCFM1BEEJOHUP'MPBU
    UFYU: UFYU1BJOU

    View full-size slide

  14. ItemDecoration を実装する (実際の描画)
    ラベルを描画すべきと判定した要素について描画位置の Y 座標を決定して
    c.DrawText() で描画する
    ᇴ㗖Ә 'POU.FUSJDT ӘसႠᏍᮏ㮢నՂդԠҵӳᛝӍӐҷӉӊһ㮣
    WBMGPOU$FOUFS GPOU.FUSJDTBTDFOUGPOU.FUSJDTEFTDFOU

    ᇴ㗖Әᝡ∈௡ⅈ
    WBMWJFX$FOUFS WJFXUPQWJFXCPUUPN

    WBMWJFX.JEEMFWJFXIFJHIU
    WBSUFYU:.BUINBY WJFX.JEEMF WJFX$FOUFS
    GPOU$FOUFS
    JG QPTJUJPOUPUBM*UFN$PVOU
    \
    WBMOFYU*OJUJBMDBMMCBDLHFU*OJUJBM QPTJUJPO

    JG OFYU*OJUJBMJOJUJBMWJFX$FOUFSWJFX.JEEMF
    \
    UFYU:WJFX.JEEMFWJFX$FOUFS
    ^
    ^
    DESBX5FYU JOJUJBM MBCFM1BEEJOHUP'MPBU
    UFYU: UFYU1BJOU

    まず先頭要素の View 中央に
    Y 座標をとるようにする
    A ascent
    descent
    FontMetrics 原点から
    実際の中央への補正
    もやっておく

    View full-size slide

  15. 各 initial の先頭要素か画面最上部に張り付くようにラベルが描画される

    View full-size slide

  16. ItemDecoration を実装する (実際の描画)
    ラベルを描画すべきと判定した要素について描画位置の Y 座標を決定して
    c.DrawText() で描画する
    ᇴ㗖Ә 'POU.FUSJDT ӘसႠᏍᮏ㮢నՂդԠҵӳᛝӍӐҷӉӊһ㮣
    WBMGPOU$FOUFS GPOU.FUSJDTBTDFOUGPOU.FUSJDTEFTDFOU

    ᇴ㗖Әᝡ∈௡ⅈ
    WBMWJFX$FOUFS WJFXUPQWJFXCPUUPN

    WBMWJFX.JEEMFWJFXIFJHIU
    WBSUFYU:.BUINBY WJFX.JEEMF WJFX$FOUFS
    GPOU$FOUFS
    JG QPTJUJPOUPUBM*UFN$PVOU
    \
    WBMOFYU*OJUJBMDBMMCBDLHFU*OJUJBM QPTJUJPO

    JG OFYU*OJUJBMJOJUJBMWJFX$FOUFSWJFX.JEEMF
    \
    UFYU:WJFX.JEEMFWJFX$FOUFS
    ^
    ^
    DESBX5FYU JOJUJBM MBCFM1BEEJOHUP'MPBU
    UFYU: UFYU1BJOU

    次の initial が迫ってきたらラベル
    を上に逃がすように view の中央が
    上にずれた分だけ補正する

    View full-size slide

  17. 次の initial の要素が来ると画面外に押し出されるアニメーションが実現できる

    View full-size slide

  18. 最後に
    実は DroidKaigi 公式アプリでは座標計算が適当だったので修正しました
    ↓一応コードが通しで見れるサンプルはこちら↓
    https://github.com/e10dokup/StickyHeaderSample
    簡単な算数が必要だが場合分けさえしっかり考えれば実現は楽
    スピーカー一覧画面をよーく見るとラベルが中央からずれているはず

    View full-size slide