diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt index ad5c0b1..693bbba 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt @@ -1,12 +1,14 @@ package io.github.kbiakov.codeview.views import android.content.Context +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.View.MeasureSpec.makeMeasureSpec import android.widget.HorizontalScrollView -import io.github.kbiakov.codeview.dpToPx +import io.github.kbiakov.codeview.R /** * @class BidirectionalScrollView @@ -18,67 +20,65 @@ import io.github.kbiakov.codeview.dpToPx */ class BidirectionalScrollView : HorizontalScrollView { - private var currentX = 0 - private var currentY = 0 - private var isMoved = false - constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + private lateinit var codeContentRv: RecyclerView + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + codeContentRv = findViewById(R.id.rv_code_content) + } + override fun dispatchTouchEvent(event: MotionEvent): Boolean { - when (event.action) { - MotionEvent.ACTION_DOWN -> { - currentX = event.rawX.toInt() - currentY = event.rawY.toInt() - return super.dispatchTouchEvent(event) - } - MotionEvent.ACTION_MOVE -> { - val deltaX = Math.abs(currentX - event.rawX) - val deltaY = Math.abs(currentY - event.rawY) - scroll(event) + super.dispatchTouchEvent(event) + // consumed by this ViewGroup. + return true + } - val movedOnDistance = dpToPx(context, 2) - if (deltaX > movedOnDistance || deltaY > movedOnDistance) { - isMoved = true - } - } - MotionEvent.ACTION_UP -> { - if (!isMoved) { - return super.dispatchTouchEvent(event) - } - isMoved = false - } - MotionEvent.ACTION_CANCEL -> { - isMoved = false - } - } + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + super.onInterceptTouchEvent(ev) + // let touch event be stolen(intercepted) here. return true } - private fun scroll(event: MotionEvent) { - val x2 = event.rawX.toInt() - val y2 = event.rawY.toInt() - val posX = currentX - x2 - val posY = currentY - y2 - scrollBy(posX, posY) + override fun onTouchEvent(ev: MotionEvent?): Boolean { + // call recyclerview's and its onTouchEvent here + // to support cross-direction scrolling like 'scroll' method does before. (eg: 45° left top to right down) - currentX = x2 - currentY = y2 + // first, force child recyclerview handles vertical scrolling. + codeContentRv.onTouchEvent(ev) + // then handle horizontal scrolling here. + super.onTouchEvent(ev) + // finally mark this event is handled. + return true } override fun measureChild(child: View, parentWidthMeasureSpec: Int, parentHeightMeasureSpec: Int) { val zeroMeasureSpec = makeMeasureSpec(0) - child.measure(zeroMeasureSpec, zeroMeasureSpec) + // let the child RecyclerView know the actual height. + child.measure(zeroMeasureSpec, parentHeightMeasureSpec) } override fun measureChildWithMargins( - child: View, - parentWidthMeasureSpec: Int, widthUsed: Int, - parentHeightMeasureSpec: Int, heightUsed: Int + child: View, + parentWidthMeasureSpec: Int, widthUsed: Int, + parentHeightMeasureSpec: Int, heightUsed: Int ) = with(child.layoutParams as MarginLayoutParams) { + val widthMeasureSpec = makeMeasureSpec(leftMargin + rightMargin, MeasureSpec.UNSPECIFIED) - val heightMeasureSpec = makeMeasureSpec(topMargin + bottomMargin, MeasureSpec.UNSPECIFIED) + + /** + * Let the child RecyclerView know the actual height again. + * Because [RecyclerView.LayoutManager.mHeightMode] is determined by MeasureSpec mode in [RecyclerView.onMeasure]. + * If gives a [MeasureSpec.UNSPECIFIED] here, mHeightMode will be 0 and [LinearLayoutManager.LayoutState.mInfinite] will be true. + * It will cause [LinearLayoutManager.fill] iterates all items in adapter instead of remaining space allowed, + * which blocks main thread for a long time if there are many items in adapter, + */ + val heightMode = MeasureSpec.getMode(parentHeightMeasureSpec) + val parentHeight = MeasureSpec.getSize(parentHeightMeasureSpec) + val heightMeasureSpec = makeMeasureSpec(parentHeight + topMargin + bottomMargin, heightMode) child.measure(widthMeasureSpec, heightMeasureSpec) } diff --git a/codeview/src/main/res/layout/layout_code_view.xml b/codeview/src/main/res/layout/layout_code_view.xml index ab0491f..956d691 100644 --- a/codeview/src/main/res/layout/layout_code_view.xml +++ b/codeview/src/main/res/layout/layout_code_view.xml @@ -5,16 +5,17 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> +