Создание ViewPager с обновляемыми фрагментами

Пытался заставить это работать последние пару дней и был бы очень признателен за помощь.

Я хочу просто: у меня есть очередь элементов, и я хочу отобразить пользователю следующий элемент в этой очереди.

А пока давайте представим, что есть только один тип контента: текст.

Следовательно, очередь может находиться только в двух состояниях.

  • он может быть пустым
  • он может иметь текстовый элемент

Поскольку идея состоит в том, чтобы позже поддерживать больше типов контента, я хочу, чтобы мой «QueueFragment» имел ViewPager, с помощью которого я могу обменивать одно представление на другое.

Это ВСЕ фрагмент очереди

  • пейджер представления, содержащий фрагмент представления содержимого
  • плавающая кнопка удаления, используемая для исключения из очереди

Итак, есть только три пользовательских истории:

  • пользователь открывает приложение
    • фрагмент отображается
    • фрагмент отображает текущий элемент или представление без содержимого, если текущего элемента нет
  • приложение уже запущено, но пользователь находится на другом фрагменте
    • пользователь переключается на фрагмент
    • фрагмент отображает текущий элемент или представление без содержимого, если текущего элемента нет
  • фрагмент уже отображается
    • пользователь нажимает кнопку удаления
    • удаление фрагментов из очереди
    • фрагмент отображает новый текущий элемент или представление без содержимого, если текущего элемента нет

Мой вопрос прост:

КАК мне получить фрагмент, чтобы обновить его?

Вот моя текущая попытка:

Поскольку мы поддерживаем только текст, я могу обойтись одним макетом представления контента:

<?xml version = "1.0" encoding = "utf-8"?>
<android.widget.LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:orientation = "vertical">

    <TextView
        android:id = "@+id/content"
        android:layout_width = "match_parent"
        android:layout_height = "match_parent"
        android:layout_weight = "1"
        android:textAlignment = "center"
        android:gravity = "center"
        tools:text = "PLACEHOLDER" />
</android.widget.LinearLayout>

И мы создаем два экземпляра для этого...

Один с жестко закодированной строкой, появляющейся, когда очередь пуста

class NoContentView : AbstractContentView() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val fragmentView = inflater.inflate(R.layout.fragment_process_component_text_view, container, false)
        fragmentView.content.text = Html.fromHtml(getString(R.string.process_queueIsEmpty))
        return fragmentView
    }

    override fun displayThought(thought: IThoughtWithContent) {
        throw IllegalStateException("this view cannot display any thoughts")
    }
}

и один с устанавливаемым содержимым

class TextContentView:AbstractContentView(){
    @Volatile private var displayedData:String? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_process_component_text_view, container, false)
        displayedData?.let { view.content.text = it }
        return view
    }

    /**The idea here behind setting [displayedData] is as follows:
       if the view has already been created, [content] will not be null
                                       and we can set its text directly
       otherwise, the content will be set in [onCreateView]
    */
    override fun displayThought(thought: IThoughtWithContent) {
        displayedData = String(thought.data, Charsets.UTF_8)
        content?.text = displayedData
    }
}

где

import android.support.v4.app.Fragment    
abstract class AbstractContentView:Fragment(){
    abstract fun displayThought(thought:IThoughtWithContent)
}

и

interface IThoughtWithContent: IThought {
    val type : IContentType
    val data : ByteArray

    val timestampCreated:Date
}

Теперь мне сказали, что FragmentStatePagerAdapter должен всегда возвращать новый экземпляр вместо кэширования фрагментов для повторного использования.

Таким образом, текущая идея состоит в том, чтобы сообщить адаптеру данные, которые он должен отображать, и обновлять фрагменты по мере их возврата:

class ProcessFragmentStatePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
    companion object {
        private const val NOF_VIEWS_AVAILABLE = 2

        const val EMPTY = 0
        const val TEXT = 1
    }

    override fun getCount(): Int { return NOF_VIEWS_AVAILABLE }

    override fun getItemPosition(`object`: Any): Int {
        return PagerAdapter.POSITION_NONE
    }

    @Volatile var nextThought: IThoughtWithContent? = null

    override fun getItem(position: Int): Fragment {
        return when(position){
            EMPTY -> {
                nextThought = null
                NoContentView()
            }
            TEXT -> {
                val fragment = TextContentView()
                nextThought?.let { fragment.displayThought(it) }
                fragment
            }
            else -> throw IllegalArgumentException("unexpected fragment position: $position")
        }
    }
}

Переходим к фрагменту нашей очереди...

<?xml version = "1.0" encoding = "utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:orientation = "vertical">


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

        <android.support.v4.view.ViewPager
            android:id = "@+id/view_container"
            android:layout_width = "match_parent"
            android:layout_height = "match_parent" />
    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:id = "@+id/btn_delete"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:layout_gravity = "end|bottom"
        android:layout_margin = "16dp"
        android:src = "@drawable/ic_checkmark" />

</android.support.design.widget.CoordinatorLayout>

опять же, очень простое расположение.

И сам фрагмент тоже очень простой:

Ставим deleteButton.setOnClickListener {dequeue()}. dequeue удаляет текущий элемент из очереди и отображает следующий (или, по крайней мере, это то, что он должен делать):

private fun dequeue(thoughts: IThoughtSet? = null){
    val queue = thoughts ?: currentThoughts()
    queue.pop()
    displayNextThought(queue)
}

Мы также displayNextThought в

override fun onResume() {
    super.onResume()
    if (initialized)displayNextThought()
}

и

override fun setUserVisibleHint(isVisibleToUser: Boolean) {
    if (userVisibleHint && initialized) displayNextThought()
    super.setUserVisibleHint(isVisibleToUser)
}

displayNextThought звонит либо

private fun displayNoThought(){
    viewContainer.currentItem = VIEW_EMPTY
}

или

private fun displayTextThought(thought:Thought.ThoughtWithContent){
    val adapter = viewContainer.adapter as ProcessFragmentStatePagerAdapter
    adapter.nextThought = thought
    viewContainer.currentItem = VIEW_TEXT
}

в зависимости от того, пуста очередь или нет.

(Полный код фрагмента внизу этого поста.)

Так что же происходит с этой настройкой? Давайте пройдемся по нашим трем пользовательским историям

Case1: пользователь запускает приложение

Очередь восстанавливается и отображается первый элемент, и делает это правильно независимо от того, пуста очередь или нет. Здорово.

Case2: пользователь находится в другом фрагменте и переключается между фрагментами туда и обратно

Если в очереди есть элементы, то в какой-то момент фрагмент очереди просто отобразит пустое текстовое представление.

Если очередь пуста, она всегда будет корректно отображать представление НЕТ СОДЕРЖИМОГО.

Case3: у пользователя открыт фрагмент очереди и он нажимает кнопку удаления

Фрагмент удаляется из очереди, но представление не обновляется (по-прежнему показывает элемент, который он отображал, когда пользователь переключился на новый элемент, пока очередь не станет пустой, после чего фрагмент правильно отображает представление НЕТ СОДЕРЖИМОГО.

Так что да, не здорово.

Я был бы признателен за любую помощь, которую я могу получить.

Полный код фрагмента:

class ProcessFragment : Fragment() {
    companion object {
        private const val VIEW_EMPTY = ProcessFragmentStatePagerAdapter.EMPTY
        private const val VIEW_TEXT = ProcessFragmentStatePagerAdapter.TEXT
    }

    private lateinit var fragmentView:View
    private lateinit var viewContainer: ViewPager
    private lateinit var deleteButton: FloatingActionButton
    private var initialized = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        super.onCreateView(inflater, container, savedInstanceState)

        fragmentView = inflater.inflate(R.layout.fragment_process, container, false)
        viewContainer = fragmentView.view_container
        deleteButton = fragmentView.btn_delete

        setupViewPager(viewContainer)

        deleteButton.setOnClickListener {dequeue()}

        initialized = true

        return fragmentView
    }
    private fun setupViewPager(pager:ViewPager){
        pager.adapter = ProcessFragmentStatePagerAdapter(childFragmentManager)
    }

    override fun onResume() {
        super.onResume()
        if (initialized)displayNextThought()
    }


    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        if (userVisibleHint && initialized) displayNextThought()
        super.setUserVisibleHint(isVisibleToUser)
    }

    private fun currentThoughts():IThoughtSet{
        val um = DataConfig.getUserManager()
        val au = um.getActiveUser()
        return um.getThoughts(au)
    }

    private fun dequeue(thoughts: IThoughtSet? = null){
        val queue = thoughts ?: currentThoughts()
        queue.pop()
        displayNextThought(queue)
    }

    private fun displayNextThought(thoughts: IThoughtSet? = null){
        val queue = thoughts ?: currentThoughts()
        val peek = queue.peek()
        if (peek is Reply.OK && peek.result is IThoughtWithContent){
            when(peek.result.type){
                ContentType.Text -> displayTextThought(peek.result)
                else -> throw IllegalArgumentException("don't know how to handle content type ${peek.result.type}")
            }
        }
        else displayNoThought()
    }

    private fun displayNoThought(){
        viewContainer.currentItem = VIEW_EMPTY
    }

    private fun displayTextThought(thought:IThoughtWithContent){
        val adapter = viewContainer.adapter as ProcessFragmentStatePagerAdapter
        adapter.nextThought = thought
        viewContainer.currentItem = VIEW_TEXT
    }
}

Этот ответ должен вам помочь: stackoverflow.com/a/36504458/4409409

Daniel Nugent 03.02.2019 18:44

@DanielNugent, что интересно, получилось, спасибо. Таким образом, это не проблема «мы не можем кэшировать фрагменты», а скорее «ЭТО конкретный метод не может использовать кэшированные фрагменты». Спасибо. Вы собираетесь сделать ответ из вашего комментария? В противном случае я просто удалю вопрос завтра, но я все же хотел, чтобы вы знали, что я ценю ваш комментарий.

User1291 04.02.2019 21:27
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
2
33
0

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