简体   繁体   中英

ViewPager with unknown or dynamic number of pages

I am trying to develop a Survey application for Android. In the requirements, the client is asking for a swipe effect between the questions. I used a ViewPager with FragmentPagerAdapter and everything works fine.

The problem comes when they require a Tree Decision System . In other words, when the surveyed person select an answer, I should lead him to a question according on which one is defined in that answer.

树决策系统

As you can see in the image, before answer the first question, I can't load the second page because I don't know which will be the question to load.

Also I can't know the number of pages that should I return in the getCount method, only when the user responds, I can know if there's one more page or not, and which should be its content.


I tried many solution posted over there, but the most important, or at least was logic for me. Is to set the count as the known pages number, and when the user select an answer, I tried to change the count and call notifyDataSetChanged method, but all what I get, is to change the number of pages dynamically, but not the content.

The scenario is:

  • In the first question, I set the count to 1, so the user can't swipe to the next page because it's unknown.

  • When the user select an answer, I change the count to 2 and load the next question. Everything OK!

  • If the user back to the first question and change his answer, I tried to destroy or change the content of the second page. But in this case, the notifyDataSetChanged doesn't replace the Fragment .

I know that I am asking a strange and difficult behavior, but I want to know if someone has to do the same thing and could find the right solution.

Sorry for don't post any code, but after so many intents, my code becomes ugly and I do revert in VCS.

You could try a linked list for your data items. Here's a quick example of how that might look.

var viewPager: ViewPager? = null

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_dynamic_view_pager, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewPager = view.findViewById(R.id.viewPager)
    viewPager?.apply {
        adapter = MyAdapter(childFragmentManager, this).also {
            it.initialDataItem = buildDataItems()
        }
    }

}

fun buildDataItems(): DataItem {
    val item0 = DataItem(0).also {
        it.title = "What can I help you with today?"
    }
    val item1 = DataItem(1).also {
        it.title = "Technical Problem"
    }
    val item2 = DataItem(2).also {
        it.title = "Ordering Problem"
    }
    val item3 = DataItem(3).also {
        it.title = "Is your power light on?"
    }
    val item4 = DataItem(4).also {
        it.title = "Have you received your order?"
    }
    val item5 = DataItem(5).also {
        it.title = "New content node"
    }
    val item6 = DataItem(6).also {
        it.title = "New content node"
    }

    item0.yesItem = item1
    item0.noItem = item2
    item1.yesItem = item3
    item2.yesItem = item4
    item3.yesItem = item5
    item3.noItem = item6

    return item0
}

data class DataItem(
    var id: Int = 0
) {
    var title: String = ""
    var yesItem: DataItem? = null
    var noItem: DataItem? = null
    var answer: Answer = Answer.UNANSWERED
}

enum class Answer {
    UNANSWERED,
    YES,
    NO
}

class MyAdapter(fm: FragmentManager, val viewPager: ViewPager) : FragmentPagerAdapter(fm) {
    var initialDataItem: DataItem = DataItem()


    override fun getItem(position: Int): Fragment {
        var index = 0
        var dataItem: DataItem? = initialDataItem
        while (index < position) {
            when (dataItem?.answer) {
                Answer.YES -> dataItem = dataItem.yesItem
                Answer.NO -> dataItem = dataItem.noItem
                else -> {}
            }
            index ++
        }

        return DetailFragment(dataItem) {
            dataItem?.answer = if (it) Answer.YES else Answer.NO
            notifyDataSetChanged()
            viewPager.setCurrentItem(position + 1, true)
        }
    }

    override fun getCount(): Int {
        var count = 1
        var dataItem: DataItem? = initialDataItem
        while (dataItem?.answer != Answer.UNANSWERED) {
            when (dataItem?.answer) {
                Answer.YES -> dataItem = dataItem.yesItem
                Answer.NO -> dataItem = dataItem.noItem
                else -> {}
            }
            count++
        }

        return count
    }
}

class DetailFragment(val dataItem: DataItem?, val listener: ((answeredYes: Boolean) -> Unit)) : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_viewpager_detail, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.findViewById<TextView>(R.id.title)?.text = dataItem?.title
        view.findViewById<Button>(R.id.yesButton)?.setOnClickListener {
            listener(true)
        }
        view.findViewById<Button>(R.id.noButton)?.setOnClickListener {
            listener(false)
        }
    }
}

Note: You will want to refine this a bit to handle error cases and reaching the end of the tree. Also, you'll want to use a better method for injecting data into the detail fragment - this is just for illustration purposes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM