google / flexbox-layout

Flexbox for Android
Apache License 2.0
18.24k stars 1.79k forks source link

Recyclerview jumps to the first item while scrolling #569

Open komar-tgm opened 3 years ago

komar-tgm commented 3 years ago

Issues and steps to reproduce

The Recyclerview jumps back to the starting item while or after scrolling

The Recyclerview uses a FlexBoxLayoutManager with: FlexDirection.ROW FlexWrap.NOWRAP JustifyContent.FLEX_START AlignItems.FLEX_END

(maybe when adapter.notifyDataSetChanged() is called, but why?)

Expected behavior

If I scroll the Recyclerview stays on that position and does not jump back to the start

Version of the flexbox library

'com.google.android:flexbox:2.0.1'

Chamana commented 2 years ago

Facing the same issue. Have you found any solution for the above issue @komar-tgm?

Makwana619 commented 2 years ago

any. help here ? same issue @Chamana @komar-tgm

Chamana commented 2 years ago

Hi @Makwana619 This is happening whenever there is any view that updates data dynamically. For ex: We have a timer and every second the text view gets updated, it will scroll to first as it redraws the recycler view to adjust the height with the newly updated text. This is because setText() method internally calls onMeaure.

onishchenko-belkacar commented 2 years ago

same issue too(

FranciellyM commented 1 year ago

Same issue in 3.0.0

jnavarro98 commented 4 months ago

@Chamana A workaround is to set a fixed size to the TextView that is being updated, like 30dp width 30dp height, and the list won't be reacreated. This is because the TextView won't call onMeasure if it has a fixed size.

You can also set the fixed size dynamically (remember to set the layoutparams you had in your xml) so you can still have the benefits of not setting the fixed dp in the xml, letting the responsability to calculate the size to the views.

private fun setFixedSize() {
    binding.tvHours.layoutParams = ConstraintLayout.LayoutParams(
        binding.tvHours.width,
        binding.tvHours.height
    )
    binding.tvHours.updateLayoutParams<ConstraintLayout.LayoutParams> {
        startToStart = rootView.id
        bottomToBottom = rootView.id
    }
    binding.tvMinutes.layoutParams = ConstraintLayout.LayoutParams(
        binding.tvHours.width,
        binding.tvHours.height
    )
    binding.tvMinutes.updateLayoutParams<ConstraintLayout.LayoutParams> {
        startToEnd = binding.tvHoursLabel.id
        bottomToBottom = rootView.id
        marginStart = 16
    }
    binding.tvSeconds.layoutParams = ConstraintLayout.LayoutParams(
        binding.tvHours.width,
        binding.tvHours.height
    )
    binding.tvSeconds.updateLayoutParams<ConstraintLayout.LayoutParams> {
        startToEnd = binding.tvMinutesLabel.id
        bottomToBottom = rootView.id
        marginStart = 16
    }
}

Then in a countdown timer, like in my case in the first tick you can set the fixed size:

var hasBeenUpdated = false
countDownTimer = object : CountDownTimer(millisInFuture, ONE_SECOND) {

        override fun onTick(millisUntilFinished: Long) {
            if (millisUntilFinished < ONE_HOUR) {
                setLastMinutesUI()
            }

            if(!hasBeenUpdated) {
                setFixedSize()
                hasBeenUpdated = true
            }

            val values = millisUntilFinished.countdownValues

            binding.tvHours.text = values.hours
            binding.tvMinutes.text = values.minutes
            binding.tvSeconds.text = values.seconds
        }

        override fun onFinish() {
            onFinishListener()

            binding.tvHours.text = "00"
            binding.tvMinutes.text = "00"
            binding.tvSeconds.text = "00"

            this.cancel()
        }
    }.start()