首页 > 解决方案 > 我不断收到错误消息“指定的孩子已经有父母。您必须先在孩子的父母上调用 removeView() (Android)

问题描述

我正在开发一个日历应用程序,它在由 DayViewFragment 膨胀的 DayView 对象上显示日历事件。

以下是 fragment_day_view 布局的片段:

<?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <com.linkedin.android.tachyon.DayView
            android:id="@+id/dayView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="@dimen/large_padding"
            app:dividerHeight="@dimen/divider_height"
            app:endHour="@integer/end_hour"
            app:eventMargin="@dimen/small_padding"
            app:halfHourDividerColor="@color/half_hour_divider"
            app:halfHourHeight="@dimen/half_hour_height"
            app:hourDividerColor="@color/hour_divider"
            app:hourLabelMarginEnd="@dimen/large_padding"
            app:hourLabelWidth="@dimen/hour_label_width"
            app:startHour="@integer/start_hour"
            />
    </ScrollView>

下面是使用上述布局的 DayViewFragment 的内容:

 override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        mainView = inflater.inflate(R.layout.fragment_day_view, container, false)
        //return inflater.inflate(R.layout.fragment_day_view, container, false)
        return mainView
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
        onDayChange()
    }

方法 initView() 和 onDayChange() 用于从 xml 布局初始化我的视图,并在数据更改时更新视图的内容。

下面是 initView() 和 onDayChang() 的代码片段:

private fun initView() {

        binding = FragmentDayViewBinding.bind(mainView)
        scrollView = binding.scrollView
        dayView = binding.dayView

        // Inflate the layout for this fragment
        with(day){
            set(Calendar.HOUR_OF_DAY, 0)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }
        val config = Config.newInstance(this.requireContext())
        dateFormatter = config.dateFormat
        timeFormatter = config.timeFormat

        val hour = day.clone() as Calendar
        val hourLabelViews = mutableListOf<View>()

        for (i in dayView.startHour.rangeTo(dayView.endHour)){
            hour.set(Calendar.HOUR_OF_DAY, i)

            val hourLabelView: TextView = layoutInflater.inflate(R.layout.hour_label, dayView, false) as TextView
            hourLabelView.text = timeFormatter.format(hour.time)
            hourLabelViews.add(hourLabelView)
            Log.d(TAG, "{hourLabelView at $i is: ${hourLabelView.text}}")
        }
        Log.d(TAG, "hourLabelViews has size: ${hourLabelViews.size}")

        dayView.setHourLabelViews(hourLabelViews)


    }

private fun onEventsChange() {
        // The day view needs a list of event views and a corresponding list of event time ranges
        val eventViews: MutableList<View> = ArrayList<View>()
        val eventTimeRanges: MutableList<DayView.EventTimeRange> =  ArrayList<DayView.EventTimeRange>()
        //Querying all events for this day needs the day start and end time in millis
        //start of this day
        val start: Calendar = day.clone() as Calendar
        start.apply {
            set(Calendar.HOUR_OF_DAY, 0)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }

        //end of day
        val end: Calendar = day.clone() as Calendar
        end.apply {
            set(Calendar.HOUR_OF_DAY, 23)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }
        val time1 = day.clone() as Calendar
        time1.apply {
            set(Calendar.HOUR_OF_DAY, 14)
            set(Calendar.MINUTE, 30)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }

        val time2 = day.clone() as Calendar
        time2.apply {
            set(Calendar.HOUR_OF_DAY, 15)
            set(Calendar.MINUTE, 30)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }

        val time3 = day.clone() as Calendar
        time3.apply {
            set(Calendar.HOUR_OF_DAY, 10)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }

        val time4 = day.clone() as Calendar
        time4.apply {
            set(Calendar.HOUR_OF_DAY, 11)
            set(Calendar.MINUTE, 30)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }

        /** Dummy events start and end days represented in milliseconds **/
        val startHour = time1.get(Calendar.HOUR_OF_DAY)
        val startMinute = time1.get(Calendar.MINUTE)
        val totalStartMinute = time1.get(Calendar.HOUR_OF_DAY).times(60) .plus(time1.get(Calendar.MINUTE)) //870
        val totalEndMinute = time2.get(Calendar.HOUR_OF_DAY).times(60).plus(time2.get(Calendar.MINUTE)) //930
        val duration =  totalEndMinute.minus(totalStartMinute) //60

        val totalStartMillis = totalStartMinute.times(60).times(1000).toLong() //522 * 10^5
        val totalEndMillis = totalEndMinute.times(60).times(1000).toLong() //558 ^5

        Log.d(TAG, "totalStartMinute: $totalStartMinute")
        Log.d(TAG, "totalEndMinute: $totalEndMinute")
        Log.d(TAG, "duration: $duration")

        val startHour1 = time3.get(Calendar.HOUR_OF_DAY) //10
        val startMinute1 = time3.get(Calendar.MINUTE) //00
        val totalStartMinute1 = time3.get(Calendar.HOUR_OF_DAY).times(60) .plus(time3.get(Calendar.MINUTE)) //600
        val totalEndMinute1 = time4.get(Calendar.HOUR_OF_DAY).times(60).plus(time4.get(Calendar.MINUTE)) //690
        val duration1 =  totalEndMinute1.minus(totalStartMinute1) //90

        val totalStartMillis1 = totalStartMinute1.times(60).times(1000).toLong() //360 x 10^5
        val totalEndMillis1 = totalEndMinute1.times(60).times(1000).toLong() //414 x 10^5

        Log.d(TAG, "totalStartMinute1: $totalStartMinute1")
        Log.d(TAG, "totalEndMinute1: $totalEndMinute1")
        Log.d(TAG, "duration1: $duration1")

        /** start and end day represented in milliseconds **/
        val startDayMinute = start[Calendar.HOUR_OF_DAY].times(60).plus(start[Calendar.MINUTE])
        val startDayMillis = startDayMinute.times(60).times(1000).toLong()  //00

        val endDayMinute = end[Calendar.HOUR_OF_DAY].times(60).plus(start[Calendar.MINUTE])
        val endDayMillis = endDayMinute.times(60).times(1000).toLong() // 82, 800, 000

        Log.d(TAG, "startDayMillis: $startDayMillis ")
        Log.d(TAG, "endDayMillis: $endDayMillis ")


        /** Format time in milliseconds **/

        //val events = arrayListOf<Event>(Event("Money Heist", "Bank of spain", totalStartMillis, totalEndMillis, startHour, startMinute, duration))

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: SharedViewModel by viewModels()

        //Log.d(TAG, "Events has size:${eventsList.size}")
//            Log.d(TAG, "Events size ${listEvents.size}")
//            for(event in listEvents) {
//                Log.d(TAG, "Event Title: ${event.title}") //Go for walk
//                Log.d(TAG, "Event Location: ${event.location}") //Neighborhood
//                Log.d(TAG, "Event start_time in Millis: ${event.start_time}") //360 x 10^5
//                Log.d(TAG, "Event end_time in Millis:  ${event.end_time}") //414 x 10^5
//                Log.d(TAG, "Event startHour: ${event.start_hour}") //10
//                Log.d(TAG, "Event startMinute: ${event.start_minute}") //00
//                Log.d(TAG, "Event Duration: ${event.duration}") //90
//            }

        //Query All Events of today
         model.queryAll(startDayMillis, endDayMillis).observe(this, androidx.lifecycle.Observer { events ->


             Collections.sort(
                 events,
                 Comparator<Event> { e1, e2 ->
                     if (e1.start_hour < e2.start_hour) -1
                     else if (e1.start_hour == e2.start_hour)
                         if (e1.start_minute < e2.start_minute) -1
                         else if (e1.start_minute == e2.start_minute) 0
                         else 1 else 1

                 })

             //Reclaim all of the existing views so we can reuse them if needed
             val recycled = dayView.removeEventViews()
             var remaining: Int = recycled?.size ?: 0


             for (event in events) {

                 // Try to recycle an existing event view if there are enough left, otherwise inflate a new one
                 val eventView = if (remaining > 0) recycled!![--remaining] else layoutInflater.inflate(
                     R.layout.event,
                     dayView,
                     false)

                 //When an event is clicked, go to update destination
                 eventView.run {
                     findViewById<TextView>(R.id.event_title)?.text = event.title
                     findViewById<TextView>(R.id.event_location)?.text = event.location
                     setBackgroundResource(R.color.kk_primary_dark)
                     setOnClickListener {
                         val action = DayViewFragmentDirections.actionDayViewDestToUpdateDest(event)
                         requireActivity().findNavController(R.id.nav_host_fragment).navigate(action)
                     }
                 }
                 eventViews.add(eventView)

                 // The day view needs the event time ranges in the start minute/end minute format,
                 // so calculate those here
                 val startMinute = event.start_minute
                 val endMinute = event.duration + startMinute
                 eventTimeRanges.add(EventTimeRange(startMinute, endMinute))

             }
             // Update the day view with the new events
             dayView.setEventViews(eventViews, eventTimeRanges) // <-- Here is where the Logcat points me to!

         })

无论应用程序第一次在哪里运行,都没有问题,但再次运行时,日志中的崩溃输出如下:

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        at android.view.ViewGroup.addViewInner(ViewGroup.java:3967)
        at android.view.ViewGroup.addView(ViewGroup.java:3817)
        at android.view.ViewGroup.addView(ViewGroup.java:3758)
        at android.view.ViewGroup.addView(ViewGroup.java:3731)
        at com.linkedin.android.tachyon.DayView.setEventViews(DayView.java:222)
        at com.project.kamitkalenda.DayViewFragment$onEventsChange$7.onChanged(DayViewFragment.kt:272)
        at com.project.kamitkalenda.DayViewFragment$onEventsChange$7.onChanged(DayViewFragment.kt:27)

请问有什么想法吗?

标签: androidkotlin

解决方案


在这一行 // Try to recycle an existing event view if there are enough left, otherwise inflate a new one val eventView = if (remaining > 0) recycled!![--remaining] else layoutInflater.inflate( R.layout.event, dayView, false)

同时膨胀您dayView作为父级传递的视图,这实际上是将视图添加到 dayView。稍后当您调用时setEventViews(),它会尝试在内部将视图添加到不同的 viewGroup,从而导致崩溃。我不确定dayView您的布局是什么或如何,但为了防止这种崩溃,您可以传递 null 而不是dayView在膨胀时传递。


推荐阅读