Android

웹뷰 스크롤 전파

그란. 2021. 4. 3. 15:23

CoordinatorLayout -> ViewPager ->  Fragment 가 웹뷰를 가지고 있다.

그러면 웹뷰를 스크롤하면 ViewPager의

app:layout_behavior="@string/appbar_scrolling_view_behavior"

이 동작하지 않는다. (단지 웹뷰만 스크롤 될뿐이다.)

 

그래서 웹뷰가 스크롤될때 상황에 따라 상위 뷰에 전파하기 가 필요하다

 

아래의 코드는 NestedScrollWebView 라이브러리를 커스텀 했다 (NestedScrollingChild3 로 변형하여 ) 

 

웹뷰 프레그먼트 레이아웃

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">

        <kr.beamview.view.widget.NestedScrollWebView
            android:id="@+id/wv_product_description_html"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

</FrameLayout>

 

class NestedScrollWebView @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.webViewStyle
) : WebView(context, attrs, defStyleAttr), NestedScrollingChild3 {
    private val mScrollConsumed = IntArray(2)
    private val mScrollOffset = IntArray(2)
    private var mLastMotionY = 0
    private var mVelocityTracker: VelocityTracker? = null
    private val mMinimumVelocity: Int
    private val mMaximumVelocity: Int
    private val mScroller: OverScroller
    private var mLastScrollerY = 0
    private val mChildHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this)

    init {
        isNestedScrollingEnabled = true
        mScroller = OverScroller(getContext())
        val configuration: ViewConfiguration = ViewConfiguration.get(getContext())
        mMinimumVelocity = configuration.scaledMinimumFlingVelocity
        mMaximumVelocity = configuration.scaledMaximumFlingVelocity
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        offsetInWindow: IntArray?,
        type: Int,
        consumed: IntArray
    ) {
        mChildHelper.dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow, type, consumed
        )
    }

    private fun initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain()
        }
    }

    private fun recycleVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker!!.recycle()
            mVelocityTracker = null
        }
    }

    private fun fling(velocityY: Int) {
        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH)
        mScroller.fling(
            scrollX, scrollY,  // start
            0, velocityY,  // velocities
            0, 0, Int.MIN_VALUE, Int.MAX_VALUE,  // y
            0, 0
        )
        mLastScrollerY = scrollY
        ViewCompat.postInvalidateOnAnimation(this)
    }

    override fun computeScroll() {
        super.computeScroll()
        if (mScroller.computeScrollOffset()) {
            val x = mScroller.currX
            val y = mScroller.currY
            val dy = y - mLastScrollerY
            if (dy != 0) {
                val scrollY = scrollY
                var dyUnConsumed = 0
                var consumedY = dy
                if (scrollY == 0) {
                    dyUnConsumed = dy
                    consumedY = 0
                } else if (scrollY + dy < 0) {
                    dyUnConsumed = dy + scrollY
                    consumedY = -scrollY
                }
                if (!dispatchNestedScroll(
                        0, consumedY, 0, dyUnConsumed, null,
                        ViewCompat.TYPE_NON_TOUCH
                    )
                ) {
                }
            }

            // Finally update the scroll positions and post an invalidation
            mLastScrollerY = y
            ViewCompat.postInvalidateOnAnimation(this)
        } else {
            // We can't scroll any more, so stop any indirect scrolling
            if (hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
                stopNestedScroll(ViewCompat.TYPE_NON_TOUCH)
            }
            // and reset the scroller y
            mLastScrollerY = 0
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        initVelocityTrackerIfNotExists()
        val vtev = MotionEvent.obtain(event)
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                mLastMotionY = event.rawY.toInt()
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
                mVelocityTracker!!.addMovement(vtev)
                mScroller.computeScrollOffset()
                if (!mScroller.isFinished) {
                    mScroller.abortAnimation()
                }
            }
            MotionEvent.ACTION_UP -> {
                val velocityTracker = mVelocityTracker
                velocityTracker!!.computeCurrentVelocity(1000, mMaximumVelocity.toFloat())
                val initialVelocity = velocityTracker.yVelocity.toInt()
                if (abs(initialVelocity) > mMinimumVelocity) {
                    fling(-initialVelocity)
                }
                stopNestedScroll()
                recycleVelocityTracker()
            }
            MotionEvent.ACTION_CANCEL -> {
                stopNestedScroll()
                recycleVelocityTracker()
            }
            MotionEvent.ACTION_MOVE -> {
                val y = event.rawY.toInt()
                val deltaY = mLastMotionY - y
                if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
                    vtev.offsetLocation(0f, mScrollConsumed[1].toFloat())
                }
                mLastMotionY = y
                val scrollY = scrollY
                var dyUnconsumed = 0
                if (scrollY == 0) {
                    dyUnconsumed = deltaY
                } else if (scrollY + deltaY < 0) {
                    dyUnconsumed = deltaY + scrollY
                    vtev.offsetLocation(0f, -dyUnconsumed.toFloat())
                }
                mVelocityTracker!!.addMovement(vtev)
                val result = super.onTouchEvent(vtev)
                if (dispatchNestedScroll(
                        0,
                        deltaY - dyUnconsumed,
                        0,
                        dyUnconsumed,
                        mScrollOffset
                    )
                ) {
                }
                return result
            }
            else -> {
            }
        }
        return super.onTouchEvent(vtev)
    }

    override fun setNestedScrollingEnabled(enabled: Boolean) {
        mChildHelper.isNestedScrollingEnabled = enabled
    }

    override fun isNestedScrollingEnabled(): Boolean {
        return mChildHelper.isNestedScrollingEnabled
    }

    override fun startNestedScroll(axes: Int): Boolean {
        return mChildHelper.startNestedScroll(axes)
    }

    override fun startNestedScroll(axes: Int, type: Int): Boolean {
        return mChildHelper.startNestedScroll(axes, type)
    }

    override fun stopNestedScroll() {
        mChildHelper.stopNestedScroll()
    }

    override fun stopNestedScroll(type: Int) {
        mChildHelper.stopNestedScroll(type)
    }

    override fun hasNestedScrollingParent(): Boolean {
        return mChildHelper.hasNestedScrollingParent()
    }

    override fun hasNestedScrollingParent(type: Int): Boolean {
        return mChildHelper.hasNestedScrollingParent(type)
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
        dyUnconsumed: Int, offsetInWindow: IntArray?
    ): Boolean {
        return mChildHelper.dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow
        )
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
        dyUnconsumed: Int, offsetInWindow: IntArray?, type: Int
    ): Boolean {
        return mChildHelper.dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow, type
        )
    }

    override fun dispatchNestedPreScroll(
        dx: Int,
        dy: Int,
        consumed: IntArray?,
        offsetInWindow: IntArray?
    ): Boolean {
        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
    }

    override fun dispatchNestedPreScroll(
        dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?,
        type: Int
    ): Boolean {
        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
    }

    override fun dispatchNestedFling(
        velocityX: Float,
        velocityY: Float,
        consumed: Boolean
    ): Boolean {
        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed)
    }

    override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean {
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY)
    }

    companion object {
        private val TAG = NestedScrollWebView::class.java.simpleName
    }
}

'Android' 카테고리의 다른 글

상품리스트 - 좋아요 - 툴팁 - 트러블 슈팅  (0) 2021.06.05
비동기 함수 - Rx로 변경하기  (0) 2021.05.14
Map vs FlatMap  (0) 2021.04.01
Data class + DiffUtil 이슈  (0) 2021.03.25
Jetpack Navigation Component 활용하기  (0) 2020.11.22