Do I need multiple viewHolders for ExpandableListView?

隐身守侯 提交于 2019-12-05 05:54:36

问题


Am I using this viewholder wrong? I'm getting an NPE on line 165. Is there an obvious reason why that I'm missing? Do I need a group viewholder and a child viewholder if I'm using expandablelistview? I marked line 165 to try to make it easier on the eyes.

Thanks a lot

my expandablelistview that's getting the NPE:

public class MyExpandableListAdapter extends BaseExpandableListAdapter {

    private Context mContext;
    private ArrayList<ContactNameItems> mListDataHeader;
    private ArrayList<Boolean> phoneNumberCheckStates;
    private ArrayList<String> selectedNumbers;

    private HashMap<String, List<ContactPhoneItems>> mListDataChild;

    private ViewHolder mViewHolder;

    public MyExpandableListAdapter(Context context,
            ArrayList<ContactNameItems> listDataHeader,
            HashMap<String, List<ContactPhoneItems>> listDataChild,
            ArrayList<String> listOfNumbers) {

        mContext = context;
        mListDataHeader = listDataHeader;
        mListDataChild = listDataChild;
        selectedNumbers = listOfNumbers;
    }

    @Override
    public int getGroupCount() {
        return mListDataHeader.size();
    }

    @Override
    public ContactNameItems getGroup(int groupPosition) {
        return mListDataHeader.get(groupPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {

        String contactName = getGroup(groupPosition).getName();
        Bitmap contactImage = getGroup(groupPosition).getImage();

        if (convertView == null) {

            LayoutInflater inflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.contact_name_item, null);

            mViewHolder = new ViewHolder();

            mViewHolder.mContactName = (TextView) convertView
                    .findViewById(R.id.lblListHeader);

            mViewHolder.mContactImage = (ImageView) convertView
                    .findViewById(R.id.ivContactPhoto);

            convertView.setTag(mViewHolder);
        } else {

            mViewHolder = (ViewHolder) convertView.getTag();
        }

        if (contactImage != null) {
            mViewHolder.mContactImage.setImageBitmap(contactImage);

        } else {
            mViewHolder.mContactImage.setImageResource(R.drawable.default_contact);
        }

        mViewHolder.mContactName.setText(contactName);

        return convertView;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return mListDataChild.get(mListDataHeader.get(groupPosition).getName())
                .size();
    }

    @Override
    public ContactPhoneItems getChild(int groupPosition, int childPosition) {
        return mListDataChild.get(mListDataHeader.get(groupPosition).getName())
                .get(childPosition);
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public View getChildView(int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {

        String numberText = getChild(groupPosition, childPosition).getNumber();
        String typeText = getChild(groupPosition, childPosition).getPhoneType();

        final int mGroupPosition = groupPosition;

        if (convertView == null) {

            LayoutInflater inflater = (LayoutInflater) this.mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.contact_detail_item, null);

            mViewHolder = new ViewHolder();

            mViewHolder.mPhoneNumber = (TextView) convertView
                    .findViewById(R.id.tv_phone_number);

            mViewHolder.mPhoneType = (TextView) convertView
                    .findViewById(R.id.tv_phone_type);

            mViewHolder.mCheckBox = (CheckBox) convertView
                    .findViewById(R.id.checkBox);

            convertView.setTag(mViewHolder);

        } else {

            mViewHolder = (ViewHolder) convertView.getTag();
        }

        mViewHolder.mPhoneNumber.setText(numberText);
        mViewHolder.mPhoneType.setText(typeText);

        phoneNumberCheckStates = new ArrayList<Boolean>();

        for (int i = 0; i < mListDataChild.size(); i++) {

            phoneNumberCheckStates.add(false);
        }

        if (phoneNumberCheckStates.get(childPosition)) {
            mViewHolder.mCheckBox.setChecked(true);
        } else {
            mViewHolder.mCheckBox.setChecked(false);
        }

        mViewHolder.mCheckBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

/*this is line 165*/    if (mViewHolder.mCheckBox.isChecked()) {  /*this is line 165*/
                    phoneNumberCheckStates.set(childPosition, true);

                    selectedNumbers.add(mListDataChild
                            .get(mListDataHeader.get(mGroupPosition).getName())
                            .get(childPosition).getNumber());

                } else {
                    phoneNumberCheckStates.set(childPosition, false);

                    selectedNumbers.remove(mListDataChild
                            .get(mListDataHeader.get(mGroupPosition).getName())
                            .get(childPosition).getNumber());
                }
            }
        });

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return false;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    public ArrayList<Boolean> getCheckedNumbers() {

        return phoneNumberCheckStates;
    }

    public ArrayList<String> getSelectedNumbers() {

        return selectedNumbers;
    }

    private class ViewHolder {

        TextView mContactName;
        TextView mPhoneNumber;
        TextView mPhoneType;
        ImageView mContactImage;
        CheckBox mCheckBox;
    }
}

if it helps, here's the Log:

01-25 04:34:31.695: E/AndroidRuntime(7074): FATAL EXCEPTION: main
01-25 04:34:31.695: E/AndroidRuntime(7074): java.lang.NullPointerException
01-25 04:34:31.695: E/AndroidRuntime(7074):     at com.psesto.journeysend.contactpicker.MyExpandableListAdapter$1.onClick(MyExpandableListAdapter.java:165)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.view.View.performClick(View.java:4204)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.widget.CompoundButton.performClick(CompoundButton.java:100)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.view.View$PerformClick.run(View.java:17355)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.os.Handler.handleCallback(Handler.java:725)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.os.Handler.dispatchMessage(Handler.java:92)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.os.Looper.loop(Looper.java:137)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at android.app.ActivityThread.main(ActivityThread.java:5041)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at java.lang.reflect.Method.invoke(Method.java:511)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
01-25 04:34:31.695: E/AndroidRuntime(7074):     at dalvik.system.NativeStart.main(Native Method)

回答1:


You have one ViewHolder reference in your Adapter for all your Views. This makes no sense because every View in the List has its own instance of the ViewHolder which you get by View.getTag().

You could set an int[] with the positions you need as a Tag of CheckBox

    int[] positions = new int[2];
    positions[0] = childPosition;
    positions[1] = groupPosition;
    mViewHolder.mCheckBox.setTag(positions);

and in onClick()

    CheckBox box = (CheckBox) v;
    int[] posTag = (int[]) v.getTag();

Then you have the CheckBox for the state and the positions for the rest




回答2:


I accepted Towlie288's answer because it pointed me in the right direction. Here's the code change that made everything work:

public class MyExpandableListAdapter extends BaseExpandableListAdapter {

    private Context mContext;
    private ArrayList<ContactNameItems> mListDataHeader;
    private ArrayList<String> selectedNumbers;

    private HashMap<String, List<ContactPhoneItems>> mListDataChild;

    private ChildViewHolder childViewHolder;
    private GroupViewHolder groupViewHolder;

    public MyExpandableListAdapter(Context context,
            ArrayList<ContactNameItems> listDataHeader,
            HashMap<String, List<ContactPhoneItems>> listDataChild,
            ArrayList<String> listOfNumbers) {

        mContext = context;
        mListDataHeader = listDataHeader;
        mListDataChild = listDataChild;
        selectedNumbers = listOfNumbers;

    }

    @Override
    public int getGroupCount() {
        return mListDataHeader.size();
    }

    @Override
    public ContactNameItems getGroup(int groupPosition) {
        return mListDataHeader.get(groupPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {

        String contactName = getGroup(groupPosition).getName();
        Bitmap contactImage = getGroup(groupPosition).getImage();

        if (convertView == null) {

            LayoutInflater inflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.contact_name_item, null);

            groupViewHolder = new GroupViewHolder();

            groupViewHolder.mContactName = (TextView) convertView
                    .findViewById(R.id.lblListHeader);

            groupViewHolder.mContactImage = (ImageView) convertView
                    .findViewById(R.id.ivContactPhoto);

            convertView.setTag(groupViewHolder);
        } else {

            groupViewHolder = (GroupViewHolder) convertView.getTag();
        }

        if (contactImage != null) {
            groupViewHolder.mContactImage.setImageBitmap(contactImage);

        } else {
            groupViewHolder.mContactImage
                    .setImageResource(R.drawable.default_contact);
        }

        groupViewHolder.mContactName.setText(contactName);

        return convertView;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return mListDataChild.get(mListDataHeader.get(groupPosition).getName())
                .size();
    }

    @Override
    public ContactPhoneItems getChild(int groupPosition, int childPosition) {
        return mListDataChild.get(mListDataHeader.get(groupPosition).getName())
                .get(childPosition);
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public View getChildView(int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {

        String numberText = getChild(groupPosition, childPosition).getNumber();
        String typeText = getChild(groupPosition, childPosition).getPhoneType();

        final int mGroupPosition = groupPosition;

        if (convertView == null) {

            LayoutInflater inflater = (LayoutInflater) this.mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.contact_detail_item, null);

            childViewHolder = new ChildViewHolder();

            childViewHolder.mPhoneNumber = (TextView) convertView
                    .findViewById(R.id.tv_phone_number);

            childViewHolder.mPhoneType = (TextView) convertView
                    .findViewById(R.id.tv_phone_type);

            childViewHolder.mCheckBox = (CheckBox) convertView
                    .findViewById(R.id.checkBox);

            childViewHolder.mCheckBox.setOnCheckedChangeListener(checkListener);

            convertView.setTag(childViewHolder);

        } else {

            childViewHolder = (ChildViewHolder) convertView.getTag();
        }

        childViewHolder.mPhoneNumber.setText(numberText);
        childViewHolder.mPhoneType.setText(typeText);

        ContactPhoneItems cpi = getChild(mGroupPosition, childPosition);

        childViewHolder.mCheckBox.setTag(cpi);
        childViewHolder.mCheckBox.setChecked(cpi.getSelected());

        // for managing the state of the boolean
        // array according to the state of the
        // CheckBox

        childViewHolder.mCheckBox
                .setOnClickListener(new View.OnClickListener() {

                    String contactNumber = mListDataChild
                            .get(mListDataHeader.get(mGroupPosition).getName())
                            .get(childPosition).getNumber();

                    public void onClick(View v) {

                        boolean isChecked = ((CheckBox) v).isChecked();

                        if (isChecked) {

                            selectedNumbers.add(contactNumber);

                        } else {

                            selectedNumbers.remove(contactNumber);
                        }

                        getChild(mGroupPosition, childPosition).setSelected(isChecked);
                        notifyDataSetChanged();
                    }
                });

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return false;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    public ArrayList<String> getSelectedNumbers() {

        return selectedNumbers;
    }

    public final class GroupViewHolder {

        TextView mContactName;
        ImageView mContactImage;
    }

    public final class ChildViewHolder {

        TextView mPhoneNumber;
        TextView mPhoneType;
        CheckBox mCheckBox;
    }

    OnCheckedChangeListener checkListener = new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {

            ContactPhoneItems c = (ContactPhoneItems) buttonView.getTag();
            c.setSelected(isChecked);
        }
    };
}



回答3:


This happens if a Child's view is reusing a Group's view, also the ViewHolder of it. Obviously, it cannot find mCheckBox in line 165, because it has not been set.
Simply adding a flag in ViewHolder, to check whether it is a Child's ViewHolder could solve your problem. No need to have two kinds of ViewHolder here.
Hope is helps



来源:https://stackoverflow.com/questions/21345972/do-i-need-multiple-viewholders-for-expandablelistview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!