google / flexbox-layout

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

Scrollbar disappears sometimes during scrolling #524

Open semenoh opened 4 years ago

semenoh commented 4 years ago

Issues and steps to reproduce

I don't really know what exact conditions cause this. I have a few screens and it depends on items in the list and/or device (most probably it has something to deal with dimensions of items and screen combined) Here is a video recording of how it behaves. As you can see it disappears while scrolling up and then appears at the very top of the screen.

It reproduces on this screen every time at the same range. One more important thing is that the screen should be scrolled to the very bottom, otherwise it does not disappears.

But here is another video from the different device where it disappears right after user starts scrolling.

Expected behavior

The scrollbar handle is visible during all the scrolling duration.

Version of the flexbox library

1.1.0 and 2.0.0

Link to code

Layout

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="20dp"
        android:paddingEnd="20dp"
        android:clipToPadding="false"
        android:scrollbars="vertical"
        android:scrollbarSize="3dp"
        android:scrollbarStyle="outsideOverlay"
        android:fadeScrollbars="false"
        android:scrollbarAlwaysDrawVerticalTrack="true"
        app:layoutManager="com.google.android.flexbox.FlexboxLayoutManager"
        tools:listitem="@layout/personalization_item"
        />

RecyclerView setup:

        list.apply {
            setHasFixedSize(false)
            layoutManager = FlexboxLayoutManager(activity)

            adapter = mAdapter
            setPadding(paddingLeft, paddingTop, paddingRight, dip(184))
        }

Adapter and ViewHolders :

data class Item(
        val data: PersonalizationItem,
        var isSelected: Boolean
)

class PersonalizationAdapter(
        private val checkedChangeListener: (Item) -> Unit,
        private val title: CharSequence? = null,
        private val initiator: PersonalizationActivity.Initiator
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {

    companion object {
        private const val TYPE_TITLE = 0
        private const val TYPE_ITEM = 1
        private const val TYPE_TITLE_SIMPLE = 2
    }

    private var mData: List<Item>? = null
    private val mFilter: CustomFilter = CustomFilter(this)
    private val mFilteredList = ArrayList<Item>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            TYPE_TITLE -> SecondaryPageAdapter.TitleViewHolder(parent, title ?: "")
            TYPE_TITLE_SIMPLE -> SimpleTitleViewHolder(parent, title ?: "")
            TYPE_ITEM -> ItemViewHolder(parent)
            else -> throw Error()
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ItemViewHolder -> {
                val item = if (title == null) mFilteredList[position] else mFilteredList[position - 1]
                (holder as? ItemViewHolder)?.bindView(item)
            }
            else -> {}
        }
    }

    override fun getItemCount(): Int = if (title == null) mFilteredList.size else mFilteredList.size + 1

    override fun getItemViewType(position: Int): Int {
        return if (title != null && position == 0) {
            if (initiator == PersonalizationActivity.Initiator.ONBOARDING) {
                TYPE_TITLE
            } else {
                TYPE_TITLE_SIMPLE
            }
        } else TYPE_ITEM
    }

    fun swapData(data: List<Item>) {
        mData = data
        mFilteredList.clear()
        mFilteredList.addAll(data)
        notifyDataSetChanged()
    }

    fun isEmpty(): Boolean = mData?.isEmpty() ?: true

    override fun getFilter(): Filter = mFilter

    @Suppress("UNUSED_ANONYMOUS_PARAMETER")
    inner class ItemViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(parent.inflate(R.layout.personalization_item)) {
        private val mCheck: AppCompatCheckBox = itemView.findViewById(R.id.cbFollow)
        private var item: Item? = null

        init {
            mCheck.setOnCheckedChangeListener { compoundButton, checked ->
                item?.let {
                    it.isSelected = !it.isSelected
                    checkedChangeListener.invoke(it)
                }
            }
        }

        internal fun bindView(item: Item) {
            this.item = null
            mCheck.text = item.data.title
            mCheck.isChecked = item.isSelected
            this.item = item
        }

    }

    private inner class CustomFilter constructor(private val mAdapter: PersonalizationAdapter) : Filter() {

        override fun performFiltering(constraint: CharSequence): FilterResults {
            mFilteredList.clear()
            val results = FilterResults()
            if (constraint.isEmpty()) {
                mFilteredList.addAll(mData ?: emptyList())
            } else {
                val filterPattern = constraint.toString().toLowerCase().trim { it <= ' ' }
                mData?.filterTo(mFilteredList) { it.data.title.toLowerCase().contains(filterPattern) }
            }
            results.values = mFilteredList
            results.count = mFilteredList.size
            return results
        }

        override fun publishResults(constraint: CharSequence, results: FilterResults) {
            this.mAdapter.notifyDataSetChanged()
        }
    }

    class SimpleTitleViewHolder(parent: ViewGroup, title: CharSequence): RecyclerView.ViewHolder(parent.inflate(R.layout.onbording_title_item)) {
        init {
            (itemView as TextView).apply {
                @Suppress("DEPRECATION")
                text = Html.fromHtml(title.toString()).toString()
                textSizeDimen = R.dimen.personalization_list_header_text_size
                textColorResource = R.color.masterDarkGrey
            }
        }
    }
}

Item layout:

<?xml version="1.0" encoding="utf-8"?>
<CheckBox
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/cbFollow"

    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:layout_marginTop="16dp"
    android:layout_marginBottom="16dp"
    android:layout_marginStart="12dp"
    android:layout_marginEnd="12dp"
    android:background="@drawable/personalization_item_bg"
    android:button="@drawable/ic_checkmark"
    android:clickable="true"
    android:ellipsize="end"
    android:focusable="true"

    android:lines="1"
    android:minHeight="40dp"
    android:paddingStart="6dp"
    android:paddingTop="8dp"

    android:paddingEnd="20dp"
    android:paddingBottom="8dp"
    android:textColor="#de000000"
    android:textSize="16sp"

    tools:checked="true"
    tools:ignore="RtlSymmetry"
    tools:text="name"
    />

Title layout:

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:textSize="23sp"
    android:textStyle="normal"
    android:textColor="@color/masterBlack"
    app:fontFamily="@font/custom_font_family"
    android:typeface="serif"

    android:layout_width="match_parent"
    android:layout_height="wrap_content"

    android:layout_marginStart="16dp"
    android:layout_marginEnd="16dp"
    android:layout_marginTop="32dp"
    android:layout_marginBottom="16dp"

    tools:text="Nesten der. Du kan også tilpasse andre lokale samfunnsinteresser"
    />

Sorry for such a noisy code. I can not sare a repo so I extracted some snippets that should help. If there is something missing please let me know in a comment.

cs-alexhuiculescu commented 4 years ago

You could also post:

semenoh commented 4 years ago

target and compile sdk versions are 28 The first video is Pixel 2 XL running Android 10 The second video is SGS 8 or 9 not sure about sdk version