consp1racy / android-support-preference

Android Preferences according to Material design specs
Apache License 2.0
331 stars 49 forks source link

OnBindViewHolder findViewById returns always a null value #71

Closed goldmont closed 7 years ago

goldmont commented 7 years ago

Hi,

after I have upgraded to 25.3.1 support libraries, my App crashes when I try to get a LinearLayout using the findViewById method in the onBindViewHolder. The returned View is always null even if it's not. I don't know why this happens but I can confirm that the code worked like a charm using 23.2.0 support libraries.

This is my custom CheckBoxPreference:

public class CustomCheckBoxPreference extends CheckBoxPreference {

    private PreferenceFactory.Padding mPaddingType;

    public CustomCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public CustomCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CustomCheckBoxPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomCheckBoxPreference(Context context) {
        super(context);
    }

    @Override
    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);
        LinearLayout mPreferenceContainer = (LinearLayout) holder.findViewById(R.id.preference_container);
        LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
        mParams.setMargins(0, 20, 0, 0);
        mPreferenceContainer.setLayoutParams(mParams);
    }

}

This is my custom preference layout (It is the same as yours. I've only wrapped it with a LinearLayout and a CardView):

<LinearLayout
    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:id="@+id/preference_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingStart="16dp"
    android:paddingEnd="16dp">

    <android.support.v7.widget.CardView
        android:id="@+id/cardView_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        app:cardElevation="@dimen/card_view_elevation"
        app:cardCornerRadius="@dimen/card_view_corner_radius"
        app:cardPreventCornerOverlap="false">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?selectableItemBackground"
            android:baselineAligned="false"
            android:clipToPadding="false"
            android:gravity="center_vertical"
            android:minHeight="?listPreferredItemHeightSmall"
            android:paddingLeft="?listPreferredItemPaddingLeft"
            android:paddingRight="?listPreferredItemPaddingRight"
            tools:ignore="RtlHardcoded">

            <FrameLayout
                android:id="@+id/icon_frame"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="12dp"
                android:layout_marginLeft="-4dp"
                android:layout_marginRight="12dp"
                android:layout_marginStart="-4dp"
                android:minWidth="48dp"
                android:visibility="gone"
                tools:visibility="visible">

                <ImageView
                    android:id="@android:id/icon"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:maxHeight="48dp"
                    android:maxWidth="48dp"
                    android:scaleType="fitStart"
                    android:visibility="gone"
                    tools:ignore="ContentDescription"
                    tools:visibility="visible"/>

            </FrameLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:paddingBottom="16dp"
                android:paddingTop="16dp">

                <TextView
                    android:id="@android:id/title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:ellipsize="marquee"
                    android:textAppearance="?textAppearanceListItem"/>

                <TextView
                    android:id="@android:id/summary"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?asp_textAppearanceListItemSecondary"
                    android:textColor="?android:textColorSecondary"/>

            </LinearLayout>

            <LinearLayout
                android:id="@android:id/widget_frame"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginEnd="-16dp"
                android:layout_marginRight="-16dp"
                android:clipToPadding="false"
                android:gravity="end|center_vertical"
                android:orientation="vertical"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"/>

        </LinearLayout>

    </android.support.v7.widget.CardView>

</LinearLayout>

My goal is to get the LinearLayout whose ID equals to preference_container in order to set a Margin to it. Hoping in your help!

consp1racy commented 7 years ago

Hi, please send a minimal sample app where you're able to reproduce the issue. Right now I'm not sure what I need to know so I best have everything at hand.

goldmont commented 7 years ago

Good morning! Here you are: https://www.dropbox.com/s/8ph0pd1r1zivpkx/Peppe130Sample.zip?dl=0

consp1racy commented 7 years ago

Source of android.support.v7.preference.Preference:

public void onBindViewHolder(PreferenceViewHolder holder) {
    holder.itemView.setOnClickListener(mClickListener);
    holder.itemView.setId(mViewId);
    ....
}

where mViewId is

private int mViewId = 0;

assignable by Preference.setViewId(int).

I haven't found where it's used. Haven't look that hard either :D


  1. Don't find root by ID, root is holder.itemView.
  2. Don't put margins on direct children of RecyclerView. Put padding on parent of CardView or put margins on the CardView.
  3. Use FrameLayout instead of Linearlayout when the CardView is the only child.

and on unrelated note

  1. Don't prefix local variables with m as in LinearLayout mPreferenceContainer or LinearLayout.LayoutParams mParams. M stands for member, these are local variables not class members.

Now that this is fixed, you'll have to work on a custom divider implementation because AppCompat divider is full bleed and doesn't respect your margin.

In fact you shouldn't need to extend a Preference class just to work with space around the views. Create an ItemDecoration and override getItemOffsets. Let me know if you get stuck on this.


EDIT: Parent of CardView should have android:clipChildren="false" andandroid:clipToPadding="false"` to draw the shadow properly. Maybe even the RecyclerView shouldsetClipChildren(false)` because the shadow is cut off now.

goldmont commented 7 years ago

It works fine now! Thank you again for all the help you are giving me!