android - 我不断收到错误消息“指定的孩子已经有父母。您必须先在孩子的父母上调用 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)
请问有什么想法吗?
解决方案
在这一行
// 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
在膨胀时传递。
推荐阅读
- python - 脚本 Python 连接并运行查询 RMAN (ORACLE) 命令
- ios - 如何使用swift 4在ios中使用Material Components实现多行文本字段
- amazon-web-services - 用于不同实例和相同端口的 aws 负载均衡器
- typo3 - Typo3 9.5:如何在 Fluid 中渲染某些页面的某些 colPos 的内容?
- c# - 查找 C# 类和 JSON 文件之间的差异
- javascript - 如何抑制来自 PhantomJS 的错误消息?
- java - 在 Spring boot 中动态配置 DataSourceBuilder url
- c# - Kentico:使用 TreeProvider.SelectNodes 对象检索内容时如何解析 Widget 的内容
- azure - 在 Azure 函数中设置 cors 标头
- javascript - 无法一步自动点击浏览器