You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用View Binding配置RecyclerView失败问题求助

问题:使用View Binding时RecyclerView无法工作,findViewById则正常

使用findViewById可以正常让RecyclerView加载数据,但改用View Binding后,RecyclerView完全无响应——onCreateViewHolder和onBindViewHolder从未被调用,还会出现错误提示No adapter attached; skipping layout,界面上看不到任何数据。虽然findViewById可用,但想改用性能更优的View Binding,已经卡了好几天,求问题原因。

可用代码

val tdList: RecyclerView = findViewById(R.id.td_list);
tdList.layoutManager = LinearLayoutManager(this)
tdList.setHasFixedSize(true);
tdList.adapter = reportAdapter

不可用代码

tdBinding.tdList.layoutManager = LinearLayoutManager(this)
tdBinding.tdList.setHasFixedSize(true);
tdBinding.tdList.adapter = reportAdapter

完整代码

DurationsReport.kt

package com.funkytwig.tasktimer

import android.database.Cursor
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.funkytwig.tasktimer.databinding.ActivityDurationsReportBinding
import com.funkytwig.tasktimer.databinding.TaskDurationsBinding
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

private const val TAG = "DurationsReportXX"

enum class SortColumns { NAME, DESCRIPTION, START_DATE, DURATION }

class DurationsReport : AppCompatActivity() {
    private val reportAdapter by lazy { DurationsRVAdapter(this, null) }
    var databaseCursor: Cursor? = null
    var sortOrder = SortColumns.NAME
    private val selection = "${DurationsContract.Columns.START_TIME} BETWEEN ? AND ?"
    private var selectionArgs = arrayOf("0", "1559347199")

    private lateinit var binding: ActivityDurationsReportBinding
    private lateinit var tdBinding: TaskDurationsBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        val func = "onCreate"
        Log.d(TAG, func)
        super.onCreate(savedInstanceState)

        binding = ActivityDurationsReportBinding.inflate(layoutInflater)
        tdBinding = TaskDurationsBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        Log.d(TAG, "$func: Setup adapter")

        val tdList: RecyclerView = findViewById(R.id.td_list);
        tdList.layoutManager = LinearLayoutManager(this)
        tdList.setHasFixedSize(true);
        tdList.adapter = reportAdapter

        // This does not work
        // tdBinding.tdList.layoutManager = LinearLayoutManager(this)
        // tdBinding.tdList.setHasFixedSize(true);
        // tdBinding.tdList.adapter = reportAdapter

        loadData()
    }

    private fun loadData() {
        val func = "loadData"
        Log.d(TAG, func)
        val order = when (sortOrder) {
            SortColumns.NAME -> DurationsContract.Columns.NAME
            SortColumns.DESCRIPTION -> DurationsContract.Columns.DESCRIPTION
            SortColumns.START_DATE -> DurationsContract.Columns.START_DATE
            SortColumns.DURATION -> DurationsContract.Columns.DURATION
        }
        Log.d(TAG, "order=$order")
        GlobalScope.launch {
            val cursor = application.contentResolver.query(
                DurationsContract.CONTENT_URI, null, selection, selectionArgs, order
            )
            Log.d(TAG, "$func: cursor.count=${cursor?.count}")
            databaseCursor = cursor
            reportAdapter.swapCursor(cursor)?.close()
        }
    }
}

DurationsRVAdapter.kt

package com.funkytwig.tasktimer

import android.content.Context
import android.database.Cursor
import android.text.format.DateFormat
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.funkytwig.tasktimer.databinding.TaskDurationItemsBinding
import java.util.Locale
import java.lang.IllegalStateException

private const val TAG = "DurationsRVAdapterXX"

class DurationsRVAdapter(context: Context, private var cursor: Cursor?) :
    RecyclerView.Adapter<DurationsRVAdapter.DurationsViewHolder>() {

    inner class DurationsViewHolder(val bindings: TaskDurationItemsBinding) :
        RecyclerView.ViewHolder(bindings.root)

    private val dateFormat = DateFormat.getDateFormat(context)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DurationsViewHolder {
        Log.d(TAG, "onCreateViewHolder")
        val view =
            TaskDurationItemsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return DurationsViewHolder(view)

    }

    override fun onBindViewHolder(holder: DurationsViewHolder, position: Int) {
        val func = "onBindViewHolder"
        Log.d(TAG, "$func: position = $position")
        val cursor = cursor

        if (cursor != null && cursor.count != 0) {
            if (!cursor.moveToPosition(position)) {
                throw IllegalStateException("Couldn't move cursor to position $position")
            }
            val name = cursor.getString(cursor.getColumnIndex(DurationsContract.Columns.NAME))
            val description =
                cursor.getString(cursor.getColumnIndex(DurationsContract.Columns.DESCRIPTION))
            val startTime =
                cursor.getLong(cursor.getColumnIndex(DurationsContract.Columns.START_TIME))
            val totalDuration =
                cursor.getLong(cursor.getColumnIndex(DurationsContract.Columns.DURATION))
            val userDate =
                dateFormat.format(startTime * 1000)  // The database stores seconds, we need milliseconds
            val totalTime = formatDuration(totalDuration)

            holder.bindings.tdName.text = name
            holder.bindings.tdDescription?.text = description
            holder.bindings.tdStart.text = userDate
            holder.bindings.tdDuration.text = totalTime
        }
    }

    private fun formatDuration(duration: Long): String {
        // convert duration Long to hours:mins:secs String (can be > 24 hours so cant use dateFormat)
        val hours = duration / 3600
        val remainder = duration - hours * 3600
        val minutes = remainder / 60
        val seconds = remainder % 60
        return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
    }

    override fun getItemCount(): Int {
        val func = "getItemCount"
        val count = cursor?.count ?: 0
        Log.d(TAG, "$func: count=$count")
        return count
    }

    fun swapCursor(newCursor: Cursor?): Cursor? {
        val func = "swapCursor"
        Log.d(TAG, func)
        if (newCursor === cursor) return null

        val numItems = itemCount
        val oldCursor = cursor

        cursor = newCursor
        Log.d(TAG, "$func: cursor.count=${cursor?.count}")

        Log.d(TAG, "$func newCursor.count=${newCursor?.count}, oldCursor.cont=${oldCursor?.count}")

        if (newCursor != null) {
            Log.d(TAG, "$func notify the observers about the new cursor")
            // notify the observers about the new cursor
            this.notifyDataSetChanged()
            Log.d(TAG, "$func: notifyDataSetChanged")
        } else {
            Log.d(TAG, "$func Notify observer about lack of dataset")
            // Notify observer about lack of dataset, all of it from 0 to newItems,
            // i.e. whole range of records has gone
            this.notifyItemRangeChanged(0, numItems)
            Log.d(TAG, "$func: notifyItemRangeChanged(0, $numItems)")
        }
        return oldCursor
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        val func = "onAttachedToRecyclerView"
        Log.d(TAG, "$func: ${recyclerView.adapter.toString()}")
        super.onAttachedToRecyclerView(recyclerView)
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        val func = "onDetachedFromRecyclerView"
        Log.d(TAG, "$func: ${recyclerView.adapter.toString()}")
        super.onDetachedFromRecyclerView(recyclerView)
    }
}

task_durations.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    app:layout_constraintHorizontal_chainStyle="spread">

    <TextView
        android:id="@+id/td_name_heading"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:background="?attr/colorButtonNormal"
        android:padding="4dp"
        android:text="@string/td_text_name"
        android:textAlignment="viewStart"
        android:textStyle="bold"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="spread" />

    <TextView
        android:id="@+id/td_start_heading"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginEnd="4dp"
        android:background="?attr/colorButtonNormal"
        android:padding="4dp"
        android:text="@string/td_text_date"
        android:textAlignment="viewStart"
        android:textStyle="bold"
        app:layout_constraintBaseline_toBaselineOf="@+id/td_name_heading"
        app:layout_constraintEnd_toStartOf="@+id/td_duration_heading"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/td_name_heading" />

    <TextView
        android:id="@+id/td_duration_heading"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginEnd="4dp"
        android:background="?attr/colorButtonNormal"
        android:padding="4dp"
        android:text="@string/td_text_duration"
        android:textAlignment="viewStart"
        android:textStyle="bold"
        app:layout_constraintBaseline_toBaselineOf="@+id/td_start_heading"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/td_start_heading" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/td_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:scrollbars="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/td_name_heading"
        tools:listitem="@layout/task_duration_items" />
</androidx.constraintlayout.widget.ConstraintLayout>

task_duration_items.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:showIn="@layout/task_durations">

    <TextView
        android:id="@+id/td_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:padding="4dp"
        android:textAlignment="viewStart"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="spread"
        tools:text="@string/td_text_name" />

    <TextView
        android:id="@+id/td_start"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginEnd="4dp"
        android:padding="4dp"
        android:textAlignment="viewStart"
        app:layout_constraintBaseline_toBaselineOf="@+id/td_name"
        app:layout_constraintEnd_toStartOf="@+id/td_duration"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/td_name"
        tools:text="@string/td_text_date" />

    <TextView
        android:id="@+id/td_duration"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginEnd="4dp"
        android:padding="4dp"
        android:textAlignment="viewStart"
        app:layout_constraintBaseline_toBaselineOf="@+id/td_start"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/td_start"
        tools:text="@string/td_text_duration" />
</androidx.constraintlayout.widget.ConstraintLayout>

内容的提问来源于stack exchange,提问作者Ben Edwards

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:50:23