简体   繁体   中英

Android Studio RecyclerView androidx.constraintlayout.widget.ConstraintLayout cannot be cast to android.widget.TextView

I just started learning Android apps in Android Studio using Kotlin Programming. When I want to create a simple RecyclerView example. I encounter bugs when using the recycler view adapter.

I have followed few tutorials online but the bug still exists in the code. I think the bug comes from the GameFragment.kt where it needs to set the layout manager (the below is the code related to initializing the recycler view).

val adapter = GameAdapter()
binding.recyclerView.adapter = adapter

val linearLayoutManager:LinearLayoutManager = LinearLayoutManager(activity)
linearLayoutManager.orientation = LinearLayoutManager.VERTICAL
binding.recyclerView.layoutManager = linearLayoutManager

I tried to print log messages to see which method is executed, the result shows that the adapter is initialized twice and onCreateViewHolder is executed once, but onBindViewHolder never executed. Inside LinearLayoutManager, I tried to put "this", "activity", "RequiredActivity", "this as context", "activity.context", but none of the above works. Does anyone know how I can fix this problem?


The error log generated

    java.lang.ClassCastException: androidx.constraintlayout.widget.ConstraintLayout cannot be cast to android.widget.TextView
        at com.jasonpwy.simplecountdowntimer.game.GameAdapter.onCreateViewHolder(GameAdapter.kt:31)
        at com.jasonpwy.simplecountdowntimer.game.GameAdapter.onCreateViewHolder(GameAdapter.kt:12)
        at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7078)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1855)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at androidx.appcompat.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:530)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:789)
        at android.view.View.layout(View.java:21946)
        at android.view.ViewGroup.layout(ViewGroup.java:6260)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3086)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2596)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1727)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7612)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1029)
        at android.view.Choreographer.doCallbacks(Choreographer.java:852)
        at android.view.Choreographer.doFrame(Choreographer.java:787)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1014)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:359)
        at android.app.ActivityThread.main(ActivityThread.java:7407)

game_fragment.xaml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    <data>
        <variable
            name="gameViewModel"
            type="com.example.simplecountdowntimer.game.GameViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/constraint_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <EditText
            android:id="@+id/activity_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:ems="10"
            android:hint="@string/activity_name"
            android:inputType="textPersonName"
            app:layout_constraintBaseline_toBaselineOf="@+id/timer_text"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/submit_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="20dp"
            android:text="@string/submit"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/activity_name" />

        <TextView
            android:id="@+id/timer_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="32dp"
            android:layout_marginEnd="16dp"
            android:fontFamily="@font/roboto"
            android:text="@{gameViewModel.currentTimeString}"
            android:textSize="28sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/activity_name"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/submit_btn"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

GameFragment.kt

package com.example.simplecountdowntimer.game

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.jasonpwy.simplecountdowntimer.R
import com.jasonpwy.simplecountdowntimer.databinding.GameFragmentBinding

class GameFragment : Fragment() {

    private lateinit var viewModel : GameViewModel

    private lateinit var binding: GameFragmentBinding

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

        // Inflate view and obtain an instance of the binding class
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.game_fragment,
                container,
                false
        )

        viewModel = ViewModelProvider(this).get(GameViewModel::class.java)

        binding.lifecycleOwner = this
        binding.gameViewModel = viewModel

        val adapter = GameAdapter()
        binding.recyclerView.adapter = adapter

        binding.submitBtn.setOnClickListener {
            Log.i("Test", "It runs")
            viewModel.addText(binding.activityName.text.toString())
            viewModel.CountDown()
        }

        val linearLayoutManager:LinearLayoutManager = LinearLayoutManager(activity)
        linearLayoutManager.orientation = LinearLayoutManager.VERTICAL
        binding.recyclerView.layoutManager = linearLayoutManager

        viewModel.ListData.observe(viewLifecycleOwner, Observer {
            it?.let {
                adapter.data = it
            }
        })
        return binding.root

    }
}

GameAdapter.kt

package com.jasonpwy.simplecountdowntimer.game

import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.simplecountdowntimer.R

class GameAdapter() : RecyclerView.Adapter<GameAdapter.TextItemViewHolder>() {
    init{
        Log.i("Temp", "Created")
    }
    var data = listOf<String>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
        val item = data[position]
        holder.view.text = item
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
        val textView = LayoutInflater.from(parent.context).inflate(R.layout.game_fragment, parent, false)
        return TextItemViewHolder(textView as TextView)
    }

    override fun getItemCount(): Int {
        return data.size
    }

    class TextItemViewHolder(val view: TextView): RecyclerView.ViewHolder(view)
}

GameViewModel.kt

package com.example.simplecountdowntimer.game

import android.os.CountDownTimer
import android.text.format.DateUtils
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel

class GameViewModel : ViewModel(){
    companion object {

        // These represent different important times
        // This is when the game is over
        const val DONE = 0L
        // This is the number of milliseconds in a second
        const val ONE_SECOND = 1000L
        // This is the total time of the game
        const val COUNTDOWN_TIME = 10000L

    }

    private lateinit var timer : CountDownTimer
    private var _currentTime = MutableLiveData<Long>()
    val currentTime : LiveData<Long>
        get() = _currentTime

    val currentTimeString = Transformations.map(currentTime) { time ->
        DateUtils.formatElapsedTime(time)
    }

    private val _storedText = MutableLiveData<String>()
    val storeText : LiveData<String>
        get() = _storedText;

    private val _ListData = MutableLiveData<List<String>>()
    val ListData : LiveData<List<String>>
        get() = _ListData

    init{
        _storedText.value = ""
        _ListData.value = listOf()
        _ListData.value = _ListData.value?.plus("Testing only")
    }

    fun CountDown(){
        Log.i("Test", "Count Down Start")
        _currentTime.value = COUNTDOWN_TIME
        timer = object : CountDownTimer(COUNTDOWN_TIME, ONE_SECOND) {

            override fun onTick(millisUntilFinished: Long) {
                _currentTime.value = (millisUntilFinished/ONE_SECOND)
            }

            override fun onFinish() {
                _currentTime.value = DONE
            }
        }
        timer.start()
    }

    fun addText(message: String){
        _storedText.value += message
        _storedText.value += "\n"
        _storedText.value?.let { Log.i("Test", it) }
        _ListData.value = _ListData.value?.plus("Testing only")
    }
}

Your error message is pretty self explanatory. A TextView cannot be cast to a ConstraintLayout .

When you're inflating your view in onCreateViewHolder you get a View which is the root of the layout you're inflating ( game_fragment ), and the root is a ConstraintLayout . So when you write return TextItemViewHolder(textView as TextView) you get an error because a ConstraintLayout isn't a TextView .

You'll have to use findViewById to get the TextView from the view that was inflated, something like the code below

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.game_fragment, parent, false)
    val textView: TextView = view.findViewById(R.id.<id of your text view>)
    return TextItemViewHolder(textView as TextView)
}

I found out that I misunderstood the meaning of the recycler view. I finally can solve the problem by changing the recycler view adapter and add one more layout to the project.

I uploaded the project to github:https://github.com/Jason627592/SimpleAndroidRecyclerViewExample

Updated version of recyclerview adapter:

package com.example.simplecountdowntimer.game

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.jasonpwy.simplecountdowntimer.R
import kotlinx.android.synthetic.main.recyclerview_row.view.*

class TextItemViewHolder(view: View): RecyclerView.ViewHolder(view){
    var content: TextView = view.stored_message
}

class GameAdapter() : RecyclerView.Adapter<TextItemViewHolder>() {
    var data = listOf<String>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
        val item = data[position]
        holder.content.text = item
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_row, parent, false)
        return TextItemViewHolder(view)
    }

    override fun getItemCount(): Int {
        return data.size
    }
}

recyclerview_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp">

    <TextView
        android:id="@+id/stored_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        tools:text="Testing" />

</LinearLayout>

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