Как оживить расширение и откат CardView внутри скважины RecyclerView (с точки зрения производительности и визуальности)? - PullRequest
0 голосов
/ 10 февраля 2020

Мне удалось создать автоматическое обновление макета c. У меня есть список CardView, который отображается внутри RecyclerView. При нажатии на CardView указанное CardView будет расширяться, и другие потенциально расширенные CardView будут втягиваться. Мне удалось сделать это:

  • Поддерживая переменную, которая содержит положение CardView, которое должно быть раскрыто внутри моего RecyclerView адаптера
  • Обновление указанной переменной, когда щелчок происходит внутри любого из CardView
  • Если еще один CardView расширен, уберите предыдущий CardView и разверните только что нажатый CardView

Код Выполнение вышеуказанного можно увидеть ниже:

// Inside RecyclerView.Adapter
private var expandedViewHolder = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val cardView = LayoutInflater.from(parent.context)
        .inflate(R.layout.recyclerview_cardview, parent, false) as CardView
    val holder = ViewHolder(cardView)

    cardView.setOnClickListener {
        notifyItemChanged(expandedPosition)
        expandedPosition = if (expandedPosition == holder.adapterPosition) -1 else holder.adapterPosition
        notifyItemChanged(expandedPosition)
    }
    return holder
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.headline.text = dataset[position].name
    holder.desc.text = dataset[position].desc
    if (position == expandedPosition) {
        holder.desc.visibility = View.VISIBLE
    }
    else {
        holder.desc.visibility = View.GONE
    }
}

Как видно, я заставляю RecyclerView повторно вызвать onBindViewHolder, используя notifyItemChanged(). Внутри onBindViewHolder видимость описания CardView будет соответствующим образом изменена. onBindViewHolder автоматически перерисовывает aws новый макет с автоматическим c переходом / анимацией.

Однако существует небольшая проблема с отводом, когда CardView расширяется, а другой отводится, как показано ниже ( обратите внимание, что расширение и убирание одного CardView не создает проблем):

Sample GIF

Если вы внимательно посмотрите, после отвода текст частично виден, когда Высота CardView полностью убрана. В идеале я хочу, чтобы текст был невидим (альфа установлен на 0) намного быстрее (например, 10 мс). Таким образом, мои вопросы таковы:

  • Как плавно убрать CardView, одновременно расширяя еще CardView и , регулируя продолжительность альфа-значения? (Проблема, как видно из рисунка, заключается в том, что анимация визуально непривлекательна.)
  • Есть ли лучший способ добиться простой анимации, которую я хочу, кроме использования notifyItemChanged, который повторно запускает onBindViewHolder?

Вот более четкое представление о том, что я имею в виду, когда видимость текста частично видима:

Clearer Picture

1 Ответ

1 голос
/ 10 февраля 2020

Окей, есть несколько хитрых случаев с анимацией видимости. Например, вы можете посмотреть этот пост. Анимация макета не работает при первом запуске

android:animateLayoutChanges="true"

animateLayoutChanges достаточно в большинстве случаев. Но в recyclerView разработчик должен восстановить предыдущее состояние viewHolder, поскольку recyclerView повторно использует держатели вида, поэтому, если вы не проверяете реальное состояние держателя вида в соответствии с положением, это может быть неправильно (например, в двух разных положениях используется один и тот же держатель, и одно из них расширено а другой развалился). Теперь появилась новая проблема, которая заключается в том, что если вы используете анимационные изменения макета, то нет простого способа развернуть или свернуть текст описания без анимации. Вы можете попробовать отключить, включить переход родительского макета, но он не работает для меня.

Другим способом является изменение высоты textViews 0 на TextView.getHeight () или наоборот, но в этом случае вы не знаете высоту textViews, поскольку ее высота равна 0, и вы не хотите расширять ее, если пользователь не щелкнет по ней. Но вы можете найти желаемую высоту TextView. Это рабочий код для меня. Пожалуйста, дайте мне знать, если это вам поможет.

class Adapter : RecyclerView.Adapter<Adapter.Holder>() {

    private var expandedHolderPosition = -1

    private var itemList = emptyList<Item>()

    private var getViewHolder: (position: Int) -> Holder? = { null }

    override fun getItemCount() = itemList.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val inflatedView = parent.inflate(R.layout.item, false)
        return Holder(inflatedView)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val currentItem = itemList[position]
        holder.bindItem(currentItem)
    }

    fun updateList(items: List<Item>) {
        this.expandedHolderPosition = -1
        this.itemList = items
        this.notifyDataSetChanged()
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        getViewHolder = { position ->
            recyclerView.findViewHolderForAdapterPosition(position) as? Holder
        }
    }

    inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        private val textTitle = itemView.textTitle
        private val textDescription = itemView.textDescription
        private var desiredTextViewHeight = 0
        private val isExpanded get() = adapterPosition == expandedHolderPosition


        fun bindItem(item: Item) {

            textTitle.text = item.title.toString()

            textDescription.setText(item.description)

            textDescription.post {

                desiredTextViewHeight = with(textDescription) {
                    lineHeight * lineCount + layout.bottomPadding - layout.topPadding
                }

                if (isExpanded) {
                    showDescription(false)
                } else {
                    hideDescription(false)
                }
            }

            itemView.setOnClickListener {

                val position = adapterPosition

                //Its in open state close it.
                if (isExpanded) {
                    expandedHolderPosition = -1
                    hideDescription(true)
                } else {
                    getViewHolder(expandedHolderPosition)?.hideDescription(true)
                    showDescription(true)
                    expandedHolderPosition = position
                }
            }
        }

        private fun hideDescription(animate: Boolean) {
            logDebug("hideDescription")
            if (animate) {
                changeHeightWithAnimation(desiredTextViewHeight, 0)
            } else {
                updateDescriptionHeight(0)
            }
        }

        private fun showDescription(animate: Boolean) {
            logDebug("showDescription")
            if (animate) {
                changeHeightWithAnimation(0, desiredTextViewHeight)
            } else {
                updateDescriptionHeight(desiredTextViewHeight)
            }
        }

        private fun changeHeightWithAnimation(from: Int, to: Int) {
            val animator = ValueAnimator.ofInt(from, to)
            animator.duration = 300
            animator.addUpdateListener { animation: ValueAnimator ->
                updateDescriptionHeight(animation.animatedValue as Int)
            }
            animator.start()
        }

        private fun updateDescriptionHeight(newHeight: Int) {
            textDescription.updateLayoutParams<ViewGroup.LayoutParams> {
                height = newHeight
            }
        }

    }
}

data class Item(val title: Int, @StringRes val description: Int = R.string.lorem)

Файл макета для пользовательского элемента.

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/textTitle"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:gravity="center_vertical"
            tools:text="Title" />

        <TextView
            android:id="@+id/textDescription"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:gravity="center_vertical"
            tools:layout_height="wrap_content"
            tools:text="Description" />

    </LinearLayout>

</com.google.android.material.card.MaterialCardView>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...