0

I have a custom view, CourseRaceButton, that extends LinearLayout and is to be displayed in a RecyclerView. The purpose of using a custom view is to embed a state control enum into each button separately, so that colours etc can be changed depending on which race or course has been selected.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/courseRace_width"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:layout_margin="@dimen/courseRace_margin"
    android:background="@drawable/courses_border"
    android:padding="@dimen/courseRace_padding"
    >

<TextView
    android:id="@+id/course_id"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone" />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="2">

    <TextView
        android:id="@+id/course_name"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:padding="@dimen/default_padding"
        android:text="Ascot"
        android:textColor="@color/brb_blue">

    </TextView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:weightSum="2">

        <TextView
            android:id="@+id/course_time"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="@dimen/default_padding"
            android:text="11:00"
            android:textColor="@color/brb_blue"/>

        <TextView
            android:id="@+id/course_MTP"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="right"
            android:padding="@dimen/default_padding"
            android:text="MTP 10"
            android:textColor="@color/brb_blue"/>

    </LinearLayout>
</LinearLayout>

This is the XML for the view. I have left the containing viewGroup as LinearLayout (and not my CourseRaceButton since it was causing inflation issues, and because I didn't need to do so in a previous Custom LinearLayout).

public class CourseRaceButton extends LinearLayout{

private ButtonStates state;

private TextView title;
private TextView time;
private TextView mtp;

public CourseRaceButton(Context context) {
    super(context);
    init();
}

public CourseRaceButton(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init();
}

public CourseRaceButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

public void init(){

    LayoutInflater.from(getContext()).inflate(
            R.layout.course_layout, this);

    title = findViewById(R.id.course_id);
    time = findViewById(R.id.course_time);
    mtp = findViewById(R.id.course_MTP);
    state = ButtonStates.UNSELECTED;

}

Here's my onBindViewHolder for the RecyclerView adapter:

@Override
public void onBindViewHolder(final holder holder, final int position) {
    holder.button.getTitle().setText(courses.get(position).name);

    if (!initialised) {
        holder.button.setState(ButtonStates.UNSELECTED);
        previousSelectedCourse = holder.itemView;
        currentCourse=courses.get(position);

        if (position == 0) {
            OnCourseClickListener.onCourseClick(currentCourse);
            holder.button.setState(ButtonStates.SELECTED);
            previousSelectedCourse = holder.itemView;
        }
        initialised = true;
    }

That previousSelectedCourse = holder.itemView should ideally be downcasted to my CourseRaceButton (more on this in a second)

Here's the OnClick:

@Override
    public void onClick(View v) {

        Log.e("TESTING ******"," ON TOUCH " );
        /** Sort out colours as we may have changed button */

        currentCourse = courses.get(this.getAdapterPosition());
        CourseRaceButton b = (CourseRaceButton) v;
        if(b.getState() == ButtonStates.SELECTED){
            return;

Now if click on a button, I get an error on this line

CourseRaceButton b = (CourseRaceButton) v;

java.lang.ClassCastException: android.widget.LinearLayout cannot be cast to com.ineda.terminal.CourseRaceButton

My CourseRaceButton extends LinearLayout, so therefore CourseRaceButton is a LinearLayout, so why can't I downcast the view to a CourseRaceButton? I assume v is a LinearLayout because that is how it is declared in the XML, but to change it to a CourseRaceButton gives me an infinite loop of errors (since obviously the inflater is trying to create the Layout, which references itself).

If I can't downcast the LinearLayout, I can't change its state Enum in order to update its colours.

There's probably a few ways around this and I'm open to any of them

Edit: Here's the onCreateViewHolder

public holder onCreateViewHolder(ViewGroup parent, int viewType) {
    View iv = LayoutInflater.from(parent.getContext()).inflate(R.layout.course_layout, parent, false);
    return new holder(iv);
}

By request, here is 1 loop of the infinite loop I get when I change the first LinearLayout in my course_layout.xml to my CourseRaceButton.

at com.ineda.terminal.CourseRaceButton.init(CourseRaceButton.java:41)
at com.ineda.terminal.CourseRaceButton.<init>(CourseRaceButton.java:31)
at java.lang.reflect.Constructor.newInstance0!(Native method) 
at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
at android.view.LayoutInflater.createView(LayoutInflater.java:645)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:787) 
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727)
at android.view.LayoutInflater.inflate(LayoutInflater.java:495)
- locked <0x05c7192a> (a java.lang.Object[])
at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
at android.view.LayoutInflater.inflate(LayoutInflater.java:377)
at com.ineda.terminal.CourseRaceButton.init(CourseRaceButton.java:41)
at com.ineda.terminal.CourseRaceButton.<init>(CourseRaceButton.java:31)
at java.lang.reflect.Constructor.newInstance0!(Native method)

10-19 15:00:41.792 17508-17508/ineda.com.genericterminal.ineda A/art: 
art/runtime/runtime.cc:422]   at 
java.lang.reflect.Constructor.newInstance(Constructor.java:430)
at android.view.LayoutInflater.createView(LayoutInflater.java:645)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:787)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727)
at android.view.LayoutInflater.inflate(LayoutInflater.java:495)
- locked <0x05c7192a> (a java.lang.Object[])
at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
at android.view.LayoutInflater.inflate(LayoutInflater.java:377)
Richardweber32
  • 168
  • 2
  • 16
  • Check v.getId() to make sure it is the correct view that you are receiving in the click event. Also, make sure you declared and not – Luis Oct 19 '17 at 13:43
  • @Luis Declare where? top of the XML for Course_Layout? As I said that creates an infinite loop of errors on inflating. – Richardweber32 Oct 19 '17 at 13:47
  • Could you post your layout file ? (not the custom view one, but the one you actually use it). About the erros when inflating, post them too. It will be easier to help – Luis Oct 19 '17 at 14:43
  • Post your `onCreateViewHolder` – Dimezis Oct 19 '17 at 14:43
  • @Luis the fact that I don't have one leads me to believe that may be the issue. No where in XML is my CourseRaceButton referenced. The layouts that are being used are the course_layout, for the rowItem of the RecyclerView and one for the fragment, which is just a RecyclerView and nothing more. – Richardweber32 Oct 19 '17 at 15:07

1 Answers1

1

When creating ViewHolder, you are inflating it from XML by doing this:

public holder onCreateViewHolder(ViewGroup parent, int viewType) {
    View iv = 
    LayoutInflater.from(parent.getContext()).inflate(R.layout.course_layout, 
    parent, false);
    return new holder(iv);
}

Obviously, Adapter then doesn't know anything about your custom View, because ViewHolder is created with regular LinearLayout as root.

So depending on your needs, you need either pass the instance of your custom View to the ViewHolder by instantiating it manually:

public holder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new holder(new CourseRaceButton(context));
}

Or you need to use your custom View as a root of your XML. But then you will need to fix a flaw in your logic - to not inflate your custom View by the same layout where your custom View is declared. That's why you are getting this infinite loop of exceptions.

Dimezis
  • 1,541
  • 11
  • 24
  • I modified the constructor for the holder to take CourseRaceButton, and changed CreateViewHolder in the way you suggested and the downcasting is working as intended. You're a star, thank you. – Richardweber32 Oct 19 '17 at 15:21