はじめに
なんでSwipeRefreshLayoutを使わなかったの?うん。使いたかったよ?とっても使いたかったよ?pull to refreshがとっても楽に実装できるし、デザインガイドにもあってるし、Androidの標準装備だし使いたかったよ?(しつこい
なんで使わなかったかって言うと、お客さんにデザインが違うよって言われたから。まぁ、正確にはまだ言われてないんだけど、 SwipeRefreshLayout を使うとデザインが違うからお客さんと交渉をしなくてはならないらしく、面倒くさいなぁって思ったからw
ということで初めて引っ張って更新処理を自作した。
ただこれから載せる実装はとてもとても汎用性がないものなので参考程度に見てもらえたら嬉しいです。
実装
レイアウト
ScrollViewで引っ張って更新処理を作成した。プログレス部分にマイナスマージンを指定して、上のView部分にめり込ませるという荒業w
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/pullRefreshImageView"
android:layout_width="?android:attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize"
android:layout_gravity="center"
android:layout_marginTop="-56dp"
android:padding="14dp"
app:lottie_loop="true"
app:lottie_rawRes="@raw/loading" />
</ScrollView>
プログレス部分にlottie使ってるのは気にしちゃいけない。lottieって何よ?ってなるから一応GitHubのリンクだけおいて置きます。素敵アニメーションが作れるライブラリです。
コード
ScrollViewのタッチイベントでScrollViewが一番上にいるときに下方向にスワイプしたらScrollViewのMarginTopをスワイプ分増やすっていう作戦。もりもりずらしています。
//
var scrollY: Int = 0
var oldMoveY: Float = 0f
var newMoveY: Float = 0f
var isRefresing = false
private fun initPullRefresh() {
scrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
this.scrollY = scrollY
}
scrollView.setOnTouchListener { v, event ->
if (scrollY > 0 || isRefresing) return@setOnTouchListener false
val marginTop = resources.getDimensionPixelSize(R.dimen.margin_56dp)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
oldMoveY = event.y
}
MotionEvent.ACTION_MOVE -> {
newMoveY = event.y - oldMoveY
pullRefreshImageView.progress = newMoveY / 1000
if (newMoveY > 0) {
setPullRefreshImageViewMargin(newMoveY.toInt() - marginTop)
return@setOnTouchListener true
}
}
MotionEvent.ACTION_UP -> {
oldMoveY = 0f
// プログレスが全部表示されていたら更新する
if (newMoveY > marginTop) {
setPullRefreshImageViewMargin(0, true, newMoveY.toInt() - marginTop)
isRefresing = true
pullRefreshImageView.playAnimation()
// API通信処理とか
} else {
setPullRefreshImageViewMargin(marginTop * -1, true, newMoveY.toInt() - marginTop)
}
}
}
return@setOnTouchListener false
}
}
マージンでもりもりずらしているメソッド。
else文で何をやっているかって言うと、更新処理後にプログレス部分を消す際に何も気にせずにマイナスマージンを指定するといきなりプログレス部分がいきなり消えて目が痛かったので対処した。
やってることは似非アニメーション。アニメーションでやろうかなって思ったんだけど、なんか面倒になったのでHandlerで定期実行してアニメーションさせてやったw
//
val handler = Handler()
var runnableCode: Runnable? = null
private fun setPullRefreshImageViewMargin(marginTop: Int, isHide: Boolean = false, nowMargin: Int = 0) {
if (!isHide) {
val layoutParams = pullRefreshImageView.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = marginTop
pullRefreshImageView.layoutParams = layoutParams
} else {
var margin = nowMargin
handler.removeCallbacks(runnableCode)
runnableCode = object : Runnable {
override fun run() {
margin -= if (marginTop == 0) {
60
} else {
30
}
if (margin <= marginTop) {
setPullRefreshImageViewMargin(marginTop)
return
}
setPullRefreshImageViewMargin(margin)
handler.postDelayed(this, 1)
}
}
handler.post(runnableCode)
}
}
結果
失敗談
この状態にたどり着くために結構苦労した。。。挫折しかけたw
失敗その1
最初はScrollViewをMarginではなくtranslationYでズラしてた。一見うまく行っているようには見えるんだけど、下にスワイプすると ScrollView のタッチイベントでタッチしている箇所もずれて行って(Yが100,500,100みたいに交互に来る)、画面がかなりガクガクした。
失敗その2
その1の失敗を踏まえ、FrameLayoutの上にScrollViewを乗っけてFrameLayoutのタッチイベントでScrollViewを制御しようと考えた。
結果、 ScrollView のタッチイベントが勝ってしまって、FrameLayoutまでタッチイベントが伝播してくれなかった。なにか方法はあるんだろうけど、わからなかったので断念。
さいごに
Android標準の部品を使えるようだったら使ったほうが良いと思う。こういうのは自作するべきじゃない感がとってもした。。。