Пытался заставить это работать последние пару дней и был бы очень признателен за помощь.
Я хочу просто: у меня есть очередь элементов, и я хочу отобразить пользователю следующий элемент в этой очереди.
А пока давайте представим, что есть только один тип контента: текст.
Следовательно, очередь может находиться только в двух состояниях.
Поскольку идея состоит в том, чтобы позже поддерживать больше типов контента, я хочу, чтобы мой «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
}
}
@DanielNugent, что интересно, получилось, спасибо. Таким образом, это не проблема «мы не можем кэшировать фрагменты», а скорее «ЭТО конкретный метод не может использовать кэшированные фрагменты». Спасибо. Вы собираетесь сделать ответ из вашего комментария? В противном случае я просто удалю вопрос завтра, но я все же хотел, чтобы вы знали, что я ценю ваш комментарий.




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