Положение элемента Android DataBinding в recyclerView

У меня есть фрагмент с ViewModel (так Mvvm), а в xml у меня есть 2 RecyclerView, у обоих есть собственный адаптер, использующий один и тот же список элементов, потому что я хочу видеть одни и те же данные в двух разных макетах.

Перед переключением на Mvvm я мог ссылаться на адаптер и recyclerView и мог достичь "getChildAdapterPosition"

Но сейчас это кажется совершенно невозможным. Я создал специальный BindingAdapter для привязки моего адаптера, CustomPageSnaperHelper и scrollListener. но теперь я не могу синхронизировать 2, чтобы отобразить один и тот же элемент, даже если я прокручиваю нижний или верхний.

Собираюсь опубликовать фрагмент кода, но не уверен, какой из них поможет больше:

@JvmStatic @BindingAdapter("adapter")
fun <T : RecyclerView.ViewHolder> setRvAdapter(rv: RecyclerView, adapter: RecyclerView.Adapter<T>?) {
    if (adapter != null) rv.adapter = adapter
}

@JvmStatic @BindingAdapter("pageSnapper")
fun setPageSnapper(rv: RecyclerView, cs: CustomSnapHelper?){
    cs?.attachToRecyclerView(rv)
}

@JvmStatic @BindingAdapter("rvScrollListener")
fun setRvScrollListener(rv: RecyclerView, sl: RecyclerView.OnScrollListener?){
    if (sl == null) return
    rv.addOnScrollListener(sl)
}

XML:

<androidx.recyclerview.widget.RecyclerView
        adapter = "@{vm.OCardAdapter}"
        pageSnapper = "@{vm.OCardPageSnaper}"
        rvScrollListener = "@{vm.OCardScrollListener}"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        android:orientation = "horizontal"
        app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toBottomOf = "@id/top_tabs"
        tools:listItem = "@layout/card_layout" />

    <androidx.recyclerview.widget.RecyclerView
        adapter = "@{vm.OCarouselAdapter}"
        pageSnapper = "@{vm.OCarouselPageSnaper}"
        rvScrollListener = "@{vm.OCarouselScrollListener}"
        android:layout_width = "wrap_content"
        android:layout_height = "200dp"
        android:background = "@color/colorWhite"
        android:orientation = "horizontal"
        app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toStartOf = "parent"
        tools:listItem = "@layout/carousel_layout"/>

Честно говоря, я мог бы вставить больше своего кода, но я не думаю, что это могло бы помочь больше.

val scrollListenerCard = object: RecyclerView.OnScrollListener(){
    var mScrolled = false
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
    }

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
    }
}
1
0
2 447
3

Ответы 3

Я не уверен, что хорошо понимаю вашу проблему, но если бы мне пришлось создать два rw-s, которые всегда прокручиваются в одну и ту же позицию с помощью databindig, я бы дал адаптеру A для прокрутки слушателя B в качестве параметра конструктора (и обратно тоже ), и таким образом я бы вызвал метод scrollTo () адаптера при срабатывании слушателя.

class MyScrollListener(private val recyclerView: RecyclerView): RecyclerView.OnScrollListener(){
        var mScrolled = false
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
        }
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            this.recyclerView.smoothScrollToPosition(newState)
        }

    }

    val scrollListenerCard = MyScrollListener(recyclerView)

Мой ScrollListener - это объект, который я построил. Я добавил их в конце вопроса, мне очень жаль, но я не знаю, как добавить к ним адаптер в качестве конструктора, не могли бы вы мне помочь?

Olivier labelle 10.12.2018 02:47

Обновил мой исходный ответ. Я был неправ, вам нужен rw вместо адаптера, но концепция та же.

guni 10.12.2018 14:19

Вы могли бы создать

@InverseBindingAdapter(attribute = "currentPosition")
public static int getCurrentPosition(final RecyclerView rv) {
return ((LinearLayoutManager) rv.getLayoutManager()).findFirstVisibleItemPosition();
}

@BindingAdapter(value = "currentPositionAttributeChanged")
public static void setListener(final RecyclerView rv, final InverseBindingListener l) {
final int prevPos = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();

        rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (dy == 0) {
                    return;
                }

                int newPosition = linearLayoutManager.findFirstVisibleItemPosition();
                if (previousPosition != newPosition) {
                    l.onChange();
                }
            }
        }); 
}

@BindingAdapter("currentPosition")
public void setCurrentPosition(final RecyclerView rv, final int pos) {
// no need to do anything here - setter required by databinding
}

Это даст вам доступ к позициям, или вы можете установить этих слушателей, получив доступ к привязкам внутри вашего фрагмента / активности и обновив оттуда вашу viewModel.

Спасибо за это! Прекрасно работает с несколькими настройками. Я сделал новый ответ в котлине на основе этого ниже.

Torkel Velure 13.08.2020 21:57

Основываясь на ответе Мирзы выше, я придумал это в kotlin, который отлично работает:

    @JvmStatic
    @InverseBindingAdapter(attribute = "currentPosition", event = "currentPositionAttributeChanged")
    fun getCurrentPosition(rv: RecyclerView): Int {
        return (rv.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
    }

    @JvmStatic
    @BindingAdapter(value = ["currentPositionAttributeChanged"])
    fun setListener(rv: RecyclerView, l: InverseBindingListener) {
        val layoutManager = (rv.layoutManager as LinearLayoutManager?)!!
        var prevPos = layoutManager.findFirstVisibleItemPosition()
        rv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (dy == 0) {
                    return
                }
                val newPos = layoutManager.findFirstVisibleItemPosition()
                if (prevPos != newPos) {
                    prevPos = newPos
                    l.onChange()
                }
            }
        })
    }

    @JvmStatic
    @BindingAdapter("currentPosition")
    fun setCurrentPosition(rv: RecyclerView, pos: Int) {
        (rv.layoutManager as LinearLayoutManager).scrollToPosition(pos)
    }

Я использую это так:

ViewModel имеет:

val currentPos = MutableLiveData<Int>()

А в макете есть:

<androidx.recyclerview.widget.RecyclerView
        android:id = "@+id/recyclerview"
        android:layout_width = "match_parent"
        android:layout_height = "match_parent"
        currentPosition = "@ = {viewModel.currentPos}"
        app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager">

Другие вопросы по теме