Created
September 21, 2023 03:32
-
-
Save kyawhtut-cu/510dff986790a7df0f9be38ef3dd150e to your computer and use it in GitHub Desktop.
A lightweight tableView container helper based on recyclerView, Tiny and fast, Easy to use, No intrusion into business code. Java version https://github.com/crosswall/EasyTableScrollHelper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
tools:context=""> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<TextView | |
android:id="@+id/tv_name" | |
android:layout_width="120dp" | |
android:layout_height="30dp" | |
android:ellipsize="end" | |
android:gravity="center" | |
android:maxLines="2" | |
android:text="名称" | |
android:textColor="#333333" | |
android:textSize="17sp" | |
android:textStyle="bold" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<View | |
android:id="@+id/dividerOne" | |
android:layout_width="0.5dp" | |
android:layout_height="0dp" | |
android:background="#333333" | |
app:layout_constraintBottom_toBottomOf="@id/tv_name" | |
app:layout_constraintStart_toEndOf="@id/tv_name" | |
app:layout_constraintTop_toTopOf="@id/tv_name" /> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="0dp" | |
android:layout_height="30dp" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toEndOf="@id/dividerOne" | |
app:layout_constraintTop_toTopOf="parent"> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:id="@+id/headerScrollView" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent"> | |
<TextView | |
android:id="@+id/tv_value1" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="最新价" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value2" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="涨跌幅" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value1" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value3" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="换手率" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value2" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value4" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="总市值" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value3" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value5" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="年初至今" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value4" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value6" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="成交量" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value5" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
<View | |
android:id="@+id/divider" | |
android:layout_width="0dp" | |
android:layout_height="0.5dp" | |
android:background="#333333" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toBottomOf="@id/tv_name" /> | |
<androidx.recyclerview.widget.RecyclerView | |
android:id="@+id/recycler_view" | |
android:layout_width="0dp" | |
android:layout_height="0dp" | |
android:orientation="vertical" | |
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toBottomOf="@id/divider" | |
tools:listitem="@layout/item_table" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
tools:context=""> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content"> | |
<TextView | |
android:id="@+id/tv_name" | |
android:layout_width="120dp" | |
android:layout_height="60dp" | |
android:ellipsize="end" | |
android:gravity="center" | |
android:maxLines="2" | |
android:text="神秘代码" | |
android:textColor="#333333" | |
android:textSize="17sp" | |
android:textStyle="bold" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<View | |
android:id="@+id/divider" | |
android:layout_width="0.5dp" | |
android:layout_height="0dp" | |
android:background="#333333" | |
app:layout_constraintBottom_toBottomOf="@id/tv_name" | |
app:layout_constraintStart_toEndOf="@id/tv_name" | |
app:layout_constraintTop_toTopOf="@id/tv_name" /> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="0dp" | |
android:layout_height="60dp" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toEndOf="@id/divider" | |
app:layout_constraintTop_toTopOf="parent"> | |
<androidx.constraintlayout.widget.ConstraintLayout | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:tag="table_scroll_container" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent"> | |
<TextView | |
android:id="@+id/tv_value1" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="value 1" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value2" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="value 2" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value1" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value3" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="value 3" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value2" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value4" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="value 4" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value3" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value5" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="value 5" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value4" | |
app:layout_constraintTop_toTopOf="parent" /> | |
<TextView | |
android:id="@+id/tv_value6" | |
android:layout_width="100dp" | |
android:layout_height="0dp" | |
android:gravity="center" | |
android:text="value 6" | |
android:textColor="#999999" | |
android:textSize="14sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintStart_toEndOf="@id/tv_value5" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
<TextView | |
android:id="@+id/view_other" | |
android:layout_width="0dp" | |
android:layout_height="wrap_content" | |
android:background="#B0FFEB3B" | |
android:gravity="center" | |
android:padding="20dp" | |
android:text="隐藏/显示区域" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> | |
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.SuppressLint | |
import android.os.Bundle | |
import android.view.LayoutInflater | |
import android.view.ViewGroup | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.core.view.isVisible | |
import androidx.recyclerview.widget.DividerItemDecoration | |
import androidx.recyclerview.widget.RecyclerView | |
import com.dindinn.dashboard.databinding.ActivityTableBinding | |
import com.dindinn.dashboard.databinding.ItemTableBinding | |
class TableActivity : AppCompatActivity() { | |
private lateinit var vb: ActivityTableBinding | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
vb = ActivityTableBinding.inflate(layoutInflater) | |
setContentView(vb.root) | |
vb.recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) | |
val adapter = Adapter() | |
adapter.setData(getDemoDataList()) | |
TableScrollHelper.attachToRecyclerView( | |
vb.recyclerView, | |
adapter, | |
vb.headerScrollView | |
) | |
} | |
private fun getDemoDataList(): List<DemoData> { | |
return (0..160).map { | |
DemoData( | |
it % 3 == 0, | |
"神秘代码 $it" | |
) | |
} | |
} | |
private data class DemoData( | |
var expanded: Boolean, | |
var name: String, | |
) | |
private class Adapter : RecyclerView.Adapter<Adapter.ViewHolder>() { | |
private var itemList = mutableListOf<DemoData>() | |
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter.ViewHolder { | |
return ViewHolder( | |
ItemTableBinding.inflate( | |
LayoutInflater.from(parent.context), | |
parent, | |
false | |
) | |
) | |
} | |
override fun onBindViewHolder(holder: ViewHolder, position: Int) { | |
holder.bind(itemList[position]) | |
} | |
override fun getItemCount(): Int { | |
return itemList.size | |
} | |
@SuppressLint("NotifyDataSetChanged") | |
fun setData(list: List<DemoData>) { | |
itemList.addAll(list) | |
notifyDataSetChanged() | |
} | |
class ViewHolder( | |
private val vb: ItemTableBinding | |
) : RecyclerView.ViewHolder(vb.root) { | |
fun bind(data: DemoData) { | |
vb.tvName.text = data.name | |
vb.viewOther.isVisible = data.expanded | |
vb.viewOther.setOnClickListener { | |
data.expanded = false | |
vb.viewOther.isVisible = false | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.SuppressLint | |
import android.content.Context | |
import android.util.AttributeSet | |
import android.view.MotionEvent | |
import android.view.View | |
import android.view.ViewGroup | |
import android.view.ViewGroup.LayoutParams | |
import android.widget.HorizontalScrollView | |
import android.widget.OverScroller | |
import androidx.recyclerview.widget.LinearLayoutManager | |
import androidx.recyclerview.widget.RecyclerView | |
import timber.log.Timber | |
import java.lang.reflect.Field | |
import kotlin.math.max | |
object TableScrollHelper { | |
private const val TAG = "TableScrollHelper" | |
fun <VB : RecyclerView.ViewHolder> attachToRecyclerView( | |
recyclerView: RecyclerView?, | |
adapter: RecyclerView.Adapter<VB>?, | |
headerScroll: ViewGroup?, | |
asyncScrollable: Boolean = true | |
) { | |
if (recyclerView == null) { | |
Timber.d("$TAG recyclerView not be null") | |
return | |
} | |
if (adapter == null) { | |
Timber.d("$TAG adapter not be null") | |
return | |
} | |
if (headerScroll == null) { | |
Timber.d("$TAG headerScroll not be null") | |
} | |
// add header scrollView | |
val parent = headerScroll?.parent as ViewGroup? | |
val scrollView = TableHorizonScrollView(recyclerView) | |
if (parent != null && headerScroll != null) { | |
val lp = headerScroll.layoutParams | |
parent.removeView(headerScroll) | |
scrollView.addView(headerScroll, lp) | |
parent.addView( | |
scrollView, | |
LayoutParams( | |
LayoutParams.WRAP_CONTENT, | |
LayoutParams.MATCH_PARENT | |
) | |
) | |
} | |
// init TableAdapterWrapper | |
val layoutManager = LinearLayoutManager(recyclerView.context) | |
layoutManager.orientation = LinearLayoutManager.VERTICAL | |
recyclerView.layoutManager = layoutManager | |
val wrapperAdapter = TableAdapterWrapper(recyclerView, adapter, scrollView, asyncScrollable) | |
recyclerView.adapter = wrapperAdapter | |
} | |
class TableHorizonScrollView : HorizontalScrollView { | |
companion object { | |
internal const val VIEW_TAG = "TableHorizonScrollView" | |
} | |
private var recyclerView: RecyclerView? = null | |
private var moveFlag: Boolean = false | |
private var scrollerField: Field? = null | |
private var overScrollX: Int = 0 | |
private var callback: OnScrollXCallback? = null | |
constructor(recyclerView: RecyclerView) : this(recyclerView.context) { | |
this.recyclerView = recyclerView | |
} | |
constructor(context: Context) : this(context, null) | |
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) | |
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( | |
context, | |
attrs, | |
defStyleAttr | |
) { | |
initView() | |
} | |
fun setOnScrollXCallback(callback: OnScrollXCallback) { | |
this.callback = callback | |
} | |
fun abortScroll() { | |
try { | |
val scroller = scrollerField?.get(this) as OverScroller? | |
if (scroller != null && !scroller.isFinished) { | |
scroller.abortAnimation() | |
} | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
/*override fun overScrollBy( | |
deltaX: Int, | |
deltaY: Int, | |
scrollX: Int, | |
scrollY: Int, | |
scrollRangeX: Int, | |
scrollRangeY: Int, | |
maxOverScrollX: Int, | |
maxOverScrollY: Int, | |
isTouchEvent: Boolean | |
): Boolean { | |
Timber.i( | |
"$VIEW_TAG overScrollBy scrollX: %d , maxOverScrollX: %d , scrollRangeX: %d , isTouchEvent: %s , deltaX: %d", | |
scrollX, | |
maxOverScrollX, | |
scrollRangeX, | |
isTouchEvent, | |
deltaX | |
) | |
callback?.scrollX(scrollX) | |
return super.overScrollBy( | |
deltaX, | |
deltaY, | |
scrollX, | |
scrollY, | |
scrollRangeX, | |
scrollRangeY, | |
maxOverScrollX, | |
maxOverScrollY, | |
isTouchEvent | |
) | |
}*/ | |
override fun onScrollChanged(scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) { | |
super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY) | |
callback?.scrollX(scrollX) | |
val rv = recyclerView ?: return | |
val childCount = rv.childCount | |
(0..childCount).mapNotNull { rv.getChildAt(it) }.forEach { view -> | |
val scrollView = view.findViewWithTag<TableHorizonScrollView>(VIEW_TAG) | |
if (scrollView != null /*&& scrollView != this && moveFlag*/) { | |
scrollView.scrollTo(scrollX, 0) | |
} | |
} | |
Timber.i( | |
"$VIEW_TAG onScrollChanged scrollX: %d , oldScrollX: %d , currentScrollX: %d , overScrollX: %d", | |
scrollX, | |
oldScrollX, | |
scrollX, | |
overScrollX | |
) | |
} | |
override fun dispatchTouchEvent(ev: MotionEvent): Boolean { | |
moveFlag = ev.actionMasked == MotionEvent.ACTION_DOWN || ev.actionMasked == MotionEvent.ACTION_MOVE | |
return super.dispatchTouchEvent(ev) | |
} | |
@SuppressLint("ClickableViewAccessibility") | |
override fun onTouchEvent(ev: MotionEvent): Boolean { | |
if (ev.actionMasked == MotionEvent.ACTION_DOWN) { | |
val rv = recyclerView ?: return super.onTouchEvent(ev) | |
val childCount = rv.childCount | |
(0..childCount).mapNotNull { rv.getChildAt(it) }.forEach { view -> | |
val scrollView = view.findViewWithTag<TableHorizonScrollView>(VIEW_TAG) | |
scrollView?.abortScroll() | |
} | |
callback?.abort() | |
} | |
return super.onTouchEvent(ev) | |
} | |
override fun fling(velocityX: Int) { | |
// super.fling(velocityX) | |
val rv = recyclerView ?: return | |
val childCount = rv.childCount | |
(0..childCount).mapNotNull { | |
rv.getChildAt(it) | |
}.forEach { view -> | |
val scrollView = view.findViewWithTag<TableHorizonScrollView>(VIEW_TAG) | |
if (scrollView != null) { | |
val width = scrollView.width - scrollView.paddingRight - scrollView.paddingLeft | |
val right = scrollView.getChildAt(0).width | |
val scroller: OverScroller? = try { | |
scrollerField?.get(scrollView) as OverScroller | |
} catch (e: IllegalAccessException) { | |
e.printStackTrace() | |
null | |
} | |
scroller?.let { | |
it.fling( | |
scrollView.scrollX, | |
scrollView.scrollY, | |
velocityX, | |
0, | |
0, | |
max(0, right - width), | |
0, | |
0, | |
width / 2, | |
0 | |
) | |
scrollView.postInvalidateOnAnimation() | |
} | |
} | |
} | |
} | |
private fun initView() { | |
tag = VIEW_TAG | |
overScrollMode = OVER_SCROLL_NEVER | |
try { | |
scrollerField = javaClass.superclass.getDeclaredField("mScroller") | |
scrollerField?.isAccessible = true | |
} catch (e: NoSuchFieldException) { | |
e.printStackTrace() | |
} | |
} | |
} | |
@Suppress("UNCHECKED_CAST") | |
class TableAdapterWrapper<VH : RecyclerView.ViewHolder>( | |
private val recyclerView: RecyclerView, | |
private val adapter: RecyclerView.Adapter<VH>, | |
private val tableHeaderView: TableHorizonScrollView, | |
private val asyncScrollable: Boolean | |
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | |
companion object { | |
private const val SCROLL_ROOT_CONTAINER = "table_scroll_container" | |
} | |
private var currentScrollX: Int = 0 | |
init { | |
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { | |
@SuppressLint("NotifyDataSetChanged") | |
override fun onChanged() { | |
super.onChanged() | |
notifyDataSetChanged() | |
} | |
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { | |
super.onItemRangeMoved(fromPosition, toPosition, itemCount) | |
notifyItemMoved(fromPosition, toPosition) | |
} | |
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { | |
super.onItemRangeChanged(positionStart, itemCount) | |
notifyItemRangeChanged(positionStart, itemCount) | |
} | |
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { | |
super.onItemRangeInserted(positionStart, itemCount) | |
notifyItemRangeInserted(positionStart, itemCount) | |
} | |
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { | |
super.onItemRangeRemoved(positionStart, itemCount) | |
notifyItemRangeRemoved(positionStart, itemCount) | |
} | |
}) | |
// recyclerView scroll callback | |
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { | |
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { | |
super.onScrolled(recyclerView, dx, dy) | |
if (!asyncScrollable) tableHeaderView.abortScroll() | |
// tableHeaderView.scrollTo(currentScrollX, 0) | |
val childCount = recyclerView.childCount | |
(0..childCount).mapNotNull { recyclerView.getChildAt(it) }.forEach { view -> | |
val scrollView: TableHorizonScrollView? = view.findViewWithTag( | |
TableHorizonScrollView.VIEW_TAG | |
) | |
if (scrollView != null) { | |
if (!asyncScrollable) scrollView.abortScroll() | |
// scrollView.scrollTo(currentScrollX, 0) | |
} | |
} | |
} | |
}) | |
// header scrollView callback | |
tableHeaderView.setOnScrollXCallback(object : OnScrollXCallback { | |
override fun scrollX(scrollX: Int) { | |
} | |
override fun abort() { | |
if (!asyncScrollable) recyclerView.stopScroll() | |
} | |
}) | |
} | |
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { | |
val holder = adapter.onCreateViewHolder(parent, viewType) | |
val targetView = holder.itemView.findViewWithTag<View>(SCROLL_ROOT_CONTAINER) | |
if (targetView != null) { | |
val targetParent = targetView.parent as ViewGroup | |
val lp = targetView.layoutParams | |
val scrollView = TableHorizonScrollView(recyclerView) | |
targetParent.removeView(targetView) | |
scrollView.addView(targetView, lp) | |
scrollView.setOnScrollXCallback(object : OnScrollXCallback { | |
override fun scrollX(scrollX: Int) { | |
currentScrollX = scrollX | |
tableHeaderView.scrollTo(scrollX, 0) | |
} | |
override fun abort() { | |
if (!asyncScrollable) recyclerView.stopScroll() | |
tableHeaderView.abortScroll() | |
} | |
}) | |
targetParent.addView( | |
scrollView, | |
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT) | |
) | |
} | |
return holder | |
} | |
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { | |
adapter.onBindViewHolder(holder as VH, position) | |
try { | |
holder.itemView.findViewWithTag<TableHorizonScrollView>( | |
TableHorizonScrollView.VIEW_TAG | |
)?.let { | |
syncScrollX(it, 0) | |
} | |
} catch (e: Exception) { | |
Timber.e("$TAG onBindViewHolder: %s", e.message) | |
} | |
} | |
override fun getItemCount(): Int { | |
return adapter.itemCount | |
} | |
private fun syncScrollX(scrollView: TableHorizonScrollView, delayMills: Long) { | |
scrollView.postDelayed({ | |
scrollView.scrollTo(currentScrollX, 0) | |
scrollView.postInvalidate() | |
if (scrollView.scrollX != currentScrollX) syncScrollX(scrollView, 30) | |
Timber.w("$TAG currentScrollX: %d, scrollView.scrollX: %d", currentScrollX, scrollView.scrollX) | |
}, delayMills) | |
} | |
} | |
interface OnScrollXCallback { | |
fun scrollX(scrollX: Int) | |
fun abort() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment