使用可更新的片段构建ViewPager

时间:2019-02-03 17:37:31

标签: java android android-fragments kotlin android-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)
}

我们也在{p}中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
}

取决于队列是否为空。

(本文底部的完整片段代码。)

那么这个设置怎么了?让我们来看看我们的三个用户故事

案例1:用户启动应用程序

将还原队列并显示第一项,无论队列是否为空,它都能正确执行此操作。很好。

案例2:用户位于不同的片段中,并在片段之间来回切换

如果队列中有项目,那么在某一时刻,队列片段将只显示一个空的文本视图。

如果队列为空,它将始终正确显示NO CONTENT视图。

案例3:用户打开了队列片段,并按下了删除按钮

片段出队,但视图未更新(仍然显示当用户使用新项目切换到时队列队列显示为空时显示的元素,此时片段正确显示了NO CONTENT视图。 / p>

是的,不是很好。

我将尽一切帮助。

完整的片段代码:

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
    }
}

0 个答案:

没有答案