[Android]SwipeRefreshLayoutを使わず引っ張って更新を作成した

スポンサーリンク

はじめに

なんで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のリンクだけおいて置きます。素敵アニメーションが作れるライブラリです。

GitHub - airbnb/lottie-android: Render After Effects animations natively on Android and iOS, Web, and React Native
Render After Effects animations natively on Android and iOS, Web, and React Native - airbnb/lottie-android

コード

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標準の部品を使えるようだったら使ったほうが良いと思う。こういうのは自作するべきじゃない感がとってもした。。。

Amazon.co.jp: Google Android グーグル アンドロイド アンドリュー・ベル ミニコレクタブル フィギュア/Andrew Bell Designer Toy Figure [並行輸入品] : おもちゃ
Amazon.co.jp: Google Android グーグル アンドロイド アンドリュー・ベル ミニコレクタブル フィギュア/Andrew Bell Designer Toy Figure : おもちゃ
タイトルとURLをコピーしました