Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix recyclerview fill all items #71

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}

Expand Down
5 changes: 3 additions & 2 deletions codeview/src/main/res/layout/layout_code_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">

<!-- Just let the root RelativeLayout determine the height of the code viewport.-->
<io.github.kbiakov.codeview.views.BidirectionalScrollView
android:id="@+id/v_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:fillViewport="true">

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_code_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
tools:listitem="@layout/item_code_line"/>

</io.github.kbiakov.codeview.views.BidirectionalScrollView>
Expand Down