使用 viewpager 创建多个片段

Posted

技术标签:

【中文标题】使用 viewpager 创建多个片段【英文标题】:Multiple Fragments getting created using a viewpager 【发布时间】:2021-10-07 11:57:44 【问题描述】:

当这个特定片段打开时,列表第二项的数据(问题模型)被膨胀到 ui 并且生命周期方法也被调用两次。

我也进行了滑动刷新以进行检查。刷新后对应的数据就来了。

如果我滑动到第 3 或第 4 位置并在一段时间后返回第一页,那么正确的数据也会膨胀。

寻呼机适配器类

import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.iisc.englishgyani.fragments.*
import com.iisc.englishgyani.models.topic_details.PracticeModel

class TopicPracticeAdapter(var fragmentManager: FragmentManager, var practiceList: ArrayList<PracticeModel>, var topic_name: String, var topicId: Int): FragmentPagerAdapter(fragmentManager) 

    private val TAG = TopicPracticeAdapter::class.java.simpleName

    init 
        Log.d(TAG, "TopicPracticeAdapter: $practiceList")
        Log.d(TAG, "topic_name: $topic_name")
    

    override fun getCount() = practiceList.size

    override fun getItem(position: Int): Fragment 
        return when 
            practiceList.get(position).type.equals("text") -> 
                FragmentTutorialText.newInstance(practiceList.get(position).tutorialModel, topic_name)
            
            practiceList.get(position).type.equals("video") -> 
                FragmentTutorialVideo.newInstance(practiceList.get(position).tutorialModel, topic_name)
            
            practiceList.get(position).type.equals("link") -> 
                FragmentTutorialDoc.newInstance(practiceList.get(position).tutorialModel, topic_name)
            
            practiceList.get(position).type.equals("MCQ") -> 
                FragmentMCQ.newInstance(practiceList.get(position).questionModel, topicId)
            
            practiceList.get(position).type.equals("FIB") -> 
                FragmentMCQ.newInstance(practiceList.get(position).questionModel, topicId)
            
            practiceList.get(position).type.equals("DND") -> 
                return FragmentDragAndDrop().newInstance(practiceList.get(position).questionModel, topicId)
            
            practiceList.get(position).type.equals("MTF") -> 
                FragmentMatchTheFollowing.newInstance(practiceList.get(position).questionModel, topicId)
            
            else -> 
                FragmentTutorialText.newInstance(practiceList.get(position).tutorialModel, topic_name)
            
        
    


FragmentDragAndDrop


import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import com.google.android.flexbox.*
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.iisc.englishgyani.adapters.SentenceAdapter
import com.iisc.englishgyani.adapters.WordsAdapter
import com.iisc.englishgyani.callbacks.DropListener
import com.iisc.englishgyani.databinding.FragmentDragAndDropBinding
import com.iisc.englishgyani.interfaces.NextFragment
import com.iisc.englishgyani.models.answer.AnswerResponse
import com.iisc.englishgyani.models.topic_details.QuestionModel
import com.iisc.englishgyani.network.ApiService
import com.iisc.englishgyani.network.ApiUtils
import com.iisc.englishgyani.utils.Constants.Companion.showNextFragment
import com.iisc.englishgyani.utils.NetworkHelper
import com.iisc.englishgyani.utils.ProgressDialogHelper
import okhttp3.MediaType
import okhttp3.RequestBody
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER

private const val ARG_QUESTION_MODEL = "questionModel"
private const val ARG_TOPIC_ID = "topicId"

private val words = ArrayList<String>()
private val sentence = mutableListOf<String>()

class FragmentDragAndDrop : Fragment() 

    private lateinit var nextFragment: NextFragment
    private var _binding: FragmentDragAndDropBinding? = null
    private val binding get() = _binding!!

    private var selectedWord = ""
    private val TAG = FragmentDragAndDrop::class.java.simpleName

    // TODO: Rename and change types of parameters
    private var questionModel: QuestionModel? = null
    private var topicId: Int? = null

    private var mAPIService: ApiService? = null
    var loader: ProgressDialogHelper? = null

    override fun onAttach(context: Context) 
        super.onAttach(context)
        try 
            nextFragment = context as NextFragment
         catch (e: ClassCastException) 
            throw ClassCastException((context as Activity).localClassName
                    + " must implement OnButtonClickListener")
        
        Log.d(TAG, "onAttach: ")
    

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate: ")
        mAPIService = ApiUtils.getApiService()
        loader = context?.let 
            ProgressDialogHelper(it)
        
    

    private fun setUI()

        Log.d(TAG, "setUI showNextFragment: $showNextFragment")

//        if(showNextFragment) 
//
//            showNextFragment = false

            activity?.actionBar?.title = questionModel?.qid.toString()

            Log.d(TAG, "setUI backStackEntryCount: $activity?.supportFragmentManager?.backStackEntryCount")

            binding.fragmentSwipe.isRefreshing = false
            arguments?.let 
                questionModel = it.getParcelable(ARG_QUESTION_MODEL)
                topicId = it.getInt(ARG_TOPIC_ID)
            

            Log.d(TAG, "setUI: $questionModel")
            Log.d(TAG, "setUI: $topicId")
            Log.d(TAG, "setUI qid: $questionModel?.qid")

            binding.fragmentSwipe.setOnRefreshListener 
                setUI()
            

            words.clear()
            questionModel?.choices?.forEach 
                words.add(it)
            

            val sentenceAdapter = SentenceAdapter 
                selectedWord = it
            .apply 
                submitList(sentence)
            

            val wordsAdapter = WordsAdapter 
                selectedWord = it
            .apply 
                submitList(words)
            

            val layoutManager = FlexboxLayoutManager(context, FlexDirection.ROW, FlexWrap.WRAP).apply 
                justifyContent = JustifyContent.FLEX_START
                alignItems = AlignItems.FLEX_START
            

//        layoutManager.orientation = GridLayoutManager.VERTICAL

            binding.tvInst.text = questionModel?.desc
            binding.tvQues.text = questionModel?.question?.get(0)?.question

            binding.rvSentence.layoutManager = layoutManager

            binding.rvSentence.adapter = sentenceAdapter

            binding.rvSentence.setOnDragListener(
                    DropListener 
                        Log.d(TAG, "setUI selectedWord: $selectedWord")
                        Log.d(TAG, "setUI sentenceAdapter: $sentenceAdapter.currentList")
                        Log.d(TAG, "setUI wordsAdapter: $wordsAdapter.currentList")
                        wordsAdapter.removeItem(selectedWord)
                        if (!sentence.contains(selectedWord)) 
                            sentenceAdapter.addItem(selectedWord)
                        
//                    sentenceAdapter.notifyDataSetChanged()
//                    wordsAdapter.notifyItemRemoved(words.indexOf(selectedWord))
                    
            )
            binding.rvWords.layoutManager = FlexboxLayoutManager(context, FlexDirection.ROW, FlexWrap.WRAP).apply 
                justifyContent = JustifyContent.FLEX_START
                alignItems = AlignItems.FLEX_START
            

            binding.rvWords.adapter = wordsAdapter

            binding.rvWords.setOnDragListener(
                    DropListener 
                        sentenceAdapter.removeItem(selectedWord)
                        wordsAdapter.addItem(selectedWord)
                    
            )

            binding.cardSubmit.setOnClickListener 
                val selectedAnswer = StringBuffer()
                sentenceAdapter.currentList.forEach 
//                selectedAnswer.commonPrefixWith(" ", sentenceAdapter.currentList.indexOf(it) == sentenceAdapter.currentList.size)
                    selectedAnswer.append("$it ")
                
                Log.d(TAG, "setUI:$selectedAnswer.lastIndex")
                Log.d(TAG, "setUI:$selectedAnswer.length")
                Log.d(TAG, "setUI:$selectedAnswer.substring(0, selectedAnswer.lastIndex)")
                val choice = ArrayList<String>()
                choice.add(selectedAnswer.substring(0, selectedAnswer.lastIndex))
                validate(choice)
            
//        

    

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

        Log.d(TAG, "onCreateView: ")
        _binding = FragmentDragAndDropBinding.inflate(inflater, container, false)
        return binding.root
    

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

        setUI()

        Log.d(TAG, "onViewCreated: ")

        Log.d(TAG, "onViewCreated questionModel: $questionModel")
        Log.d(TAG, "onViewCreated topicId: $topicId")

    

    private fun validate(choice: ArrayList<String>?) 

        // req -> topic_id, qid, type, category, email_id, question array -> question, user_answer
        // res <- question array, type

        try 
            if (NetworkHelper.isNetworkAvailable(context!!)) 
                submitAnswer(choice)
             else 
                Toast.makeText(context, "Connect to internet", Toast.LENGTH_SHORT).show()
            
         catch (e: Exception) 
            e.printStackTrace()
        

    

    private fun submitAnswer(choice: ArrayList<String>?) 

        loader?.showProgressDialog()

        val jsonObject = JSONObject()
        val question = JSONArray()
//        question.put(questionModel?.question?.get(0)?.question?.let 
//            choice?.let  choice ->
//                QAPairModel(it, choice.get(0))
//            
//        )

        val jsonObjectQuestion = JSONObject()
        jsonObjectQuestion.put("question", questionModel?.question?.get(0)?.question)
        jsonObjectQuestion.put("user_answer", choice?.get(0))

        question.put(jsonObjectQuestion)

        try 
            val account = GoogleSignIn.getLastSignedInAccount(context)
            account?.let 
                jsonObject.put("topic_id", topicId)
                jsonObject.put("type", questionModel?.type)
                jsonObject.put("qid", questionModel?.qid)
                jsonObject.put("email_id", account.email)
                jsonObject.put("category", "grammer")
                jsonObject.put("question", question)
            

         catch (e: JSONException) 
            e.printStackTrace()
        
        Log.d(TAG, "submitAnswer: json object $jsonObject")

        val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toString())

        Log.d(TAG, "onResponse RequestBody: $body")

        mAPIService = ApiUtils.getApiService()
        mAPIService?.answer(body)?.enqueue(object : Callback<AnswerResponse> 
            override fun onResponse(call: Call<AnswerResponse>, response: Response<AnswerResponse>) 
                Log.d(TAG, "onResponse: $response.body()")
                if (response.isSuccessful) 
                    loader?.hideProgressDialog()
                    try 
                        Toast.makeText(context, response.body()?.message, Toast.LENGTH_SHORT).show()
                        nextFragment.showNextFragment()
                     catch (e: Exception) 
                        e.printStackTrace()
                    
                
            

            override fun onFailure(call: Call<AnswerResponse>, t: Throwable) 
                loader?.hideProgressDialog()
                Log.d(TAG, "onFailure: $t.message")
            

        )

    

//    companion object 
//        /**
//         * Use this factory method to create a new instance of
//         * this fragment using the provided parameters.
//         *
//         * @param questionModel Parameter 1.
//         * @param topicId Parameter 2.
//         * @return A new instance of fragment FragmentDragAndDrop.
//         */
//        // TODO: Rename and change types and number of parameters
//        @JvmStatic
//        fun newInstance(questionModel: QuestionModel?, topicId: Int) =
//                FragmentDragAndDrop().apply 
//                    arguments = Bundle().apply 
//                        putParcelable(ARG_QUESTION_MODEL, questionModel)
//                        putInt(ARG_TOPIC_ID, topicId)
//                    
//                
//    


    fun newInstance(questionModel: QuestionModel?, topicId: Int) =
            FragmentDragAndDrop().apply 
                arguments = Bundle().apply 
                    putParcelable(ARG_QUESTION_MODEL, questionModel)
                    putInt(ARG_TOPIC_ID, topicId)
                
            

    override fun onPause() 
        super.onPause()
        Log.d(TAG, "onPause: ")
    

    override fun onDestroy() 
        super.onDestroy()
        Log.d(TAG, "onDestroy: ")
    

    override fun onResume() 
        super.onResume()
        Log.d(TAG, "onResume: ")
        setUI()
    

    override fun onDestroyView() 
        super.onDestroyView()
        Log.d(TAG, "onDestroyView: ")
    

    override fun onStop() 
        super.onStop()
        Log.d(TAG, "onStop: ")
    



【问题讨论】:

【参考方案1】:

默认情况下,片段的左侧和右侧被预加载到 ViewPager 中。它不允许设置小于 1 的“离屏页面限制”,但在 Viewpager 2 中,您可以将最小离屏页面限制设置为零。所以只会加载当前片段。

viewPager2.setOffscreenPageLimit(0);

【讨论】:

没用。实际上,正在生成 2 个片段实例和 2 个独立的生命周期。但是第二个项目的数据都进来了。

以上是关于使用 viewpager 创建多个片段的主要内容,如果未能解决你的问题,请参考以下文章

在android中使用viewpager的带有多个片段的SearchView

在 ViewPager 的多个选项卡中使用单个片段

对 ViewPager 的多个选项卡布局使用单个片段类

anko的基本viewPager示例

在 TabLayout 和 ViewPager2 中执行异步任务后更新具有相同布局的多个片段

在 ViewPager 中使用视图创建动态片段