I created a rich StickyHeader that meets special UI requirements.
You can translate this page into English.
はじめに
アプリのUI要件として以下のような画面の要望をリクエストされたことはないでしょうか。
- 画面内に非常に多くのViewが存在する
- 画面は上下スクロールする
- 画面の一部のセルは画面上部に張り付く(StickyHeader)
- 張り付いたセルにボタンなどを配置しても反応する
いわゆるStickeyHeaderです。StickyHeaderのサンプルはググればいくつも出てきますが、このUI要件を完全に満たしたものは私が探した限り見つけることはできなかった。
ないものは作るしかない。ということで作成したのですが、同じような悩みを抱えている人はきっといらっしゃるはず。と思い、汎用性をもたせるように改良してサンプルプログラムを公開させていただこうと思った次第です。
GitHub
こちら
(アプリの要件にあわせて自由に修正していただきたいのでライブラリ形式ではなく、ソースをコピペして使って頂くような想定をしています)
簡単な使い方
MainActivityを斜め読みすれば使い方がすぐに理解できるようにしています。(改良せず使うだけであればlibstickeyheaderモジュールの中を解析する必要はありません)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
getStickyHeaderFragment().bind(
// Note: Specify here a list of all the Fragments that make up the screen.
listOf(
P1CellFragment.newInstance(),
P2CellFragment.newInstance(),
// .... (omit)
)
)
}
private fun getStickyHeaderFragment() =
binding.stickyHeaderFragment.getFragment<StickyHeaderListFragment>()
}
StickyHeaderListFragmentを画面に配置し、bind()を呼ぶだけです。
P1CellFragment, P2CellFragmentは、リスト上に表示される個々のセルです。これらのFragmentはIStickyHeaderListCellを実装する必要があります。
interface IStickyHeaderListCell {
fun isStickyHeader(): Boolean
fun fragment() : Fragment
}
isStickyHeader ... そのセルをStickyHeaderとして使用する場合はtrue。普通のセルとして使用する場合はfalse
fragment ... thisを返します
百聞は一見に如かず。ソース見た方が早いと思います
動作確認(Animation Gif)
(作成中)
仕組み
非常にシンプルな実装です。
まず、普通のScrollViewで画面を表示。その上にStickyHeaderとして表示するセルを重ねているだけです。
また、セルの高さが変化したときに、Stickyなセルも追従するようにしています。(サンプルのChange View Sizeボタンを押下)。これはセルの中にExpandViewなどを使用できるようにするための対応です。
あとがき
このUI要件に潜む罠として一番悩んだところは、試行錯誤した結果、結局RecyclerViewは使えなかったということでした。
なぜなら、非常に多くのViewが1つのセルに存在するからです。表示領域外から表示領域内にセルが移動したとき、RecyclerViewはcacheの上限設定を変えることはできますが、リバインドは不可避です。スクロールのたびにリバインドが発生するとViewの多さのためカクついてしまい、製品として使い物になりませんでした。(当時、RecyclerViewで作成したStickeyHeaderは全部捨てて書き直した苦い経験があります)
かといって、スクロール時にリバインドが発生しないListViewで実装する気にもならず、それならLinearLayoutで十分だよね?と思いこの形になりました。
このような手戻りが開発の現場で起こらないように、少しでもお役にたてれば幸いです。