I am making a menu which includes a custom ExpandableListView adapter. Despite trying to match my code as close to the API examples and any other examples I've seen online (including multiple closely related SO questions), I still can't get it working.
I know the adapter is being used because the group view is being shown (which is made from an xml). Clicking on the group item also calls "getGroupView", but the code never runs "getChildView", "getChild", or "getChildId".
I've even went through the Android-15 source code to find out what I might have done wrong, but nothing odd has came up.
= Base Activity
public class SettingsM extends FragmentActivity
{
static Context context;
ViewPager mViewPager;
CollectionPagerAdapter mDemoCollectionPagerAdapter;
//ColorPicker picker;
SharedPreferences preferences;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
context = this;
// Set up action bar.
final ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setTitle("Particle emitter settings");
// get the preferences for this screen
preferences = this.getSharedPreferences("base_world", 0);
// tab holder
setContentView(R.layout.tabmenu_holder);
mViewPager = (ViewPager) findViewById(R.id.pager);
mDemoCollectionPagerAdapter = new CollectionPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mDemoCollectionPagerAdapter);
}
public class CollectionPagerAdapter extends FragmentStatePagerAdapter
{
public CollectionPagerAdapter(FragmentManager fm)
{
super(fm);
}
@Override
public Fragment getItem(int i)
{
Fragment fragment = new TabFragment();
Bundle args = new Bundle();
if(preferences != null)
{
if(i == 0)
{// world
args.putBoolean("world", true);
} else
{// emitter
args.putBoolean("world", false);
args.putInt("emitter", i-1);
}
}
fragment.setArguments(args);
return fragment;
}
@Override
public int getCount()
{
return 100;
}
@Override
public CharSequence getPageTitle(int position)
{
if(position == 0)
{
return "World";
} else
{
return "Emitter #" + position;
}
}
}
public static class TabFragment extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Bundle args = getArguments();
View rootView = new TextView(context);// use a textview as the default
if(args.getBoolean("world"))
{
rootView = inflater.inflate(R.layout.m_backdrop, container, false);
// add face drop-down
ExpandableListView face_list = (ExpandableListView) rootView.findViewById(R.id.FaceList);
if(face_list != null)
{
face_list.setAdapter(new ExpandableFaceList(context));
face_list.setOnGroupClickListener(new OnGroupClickListener()
{
@Override
public boolean onGroupClick(ExpandableListView parent,
View v, int groupPosition, long id)
{
Log.i("FaceList", "Clicked:" + groupPosition);
return false;
}
});
}
} else
{
return rootView;
}
return rootView;
}
}
}
= Expandable List view Adapter
public class ExpandableFaceList extends BaseExpandableListAdapter implements ExpandableListAdapter
{
public Context context;
private LayoutInflater inflator;
private float mDensity = 1f;
private boolean bShowSw = true;
private ColorPickerMenuView cp;
private Switch sw;
public ExpandableFaceList(Context context)
{
this.context = context;
this.inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mDensity = context.getResources().getDisplayMetrics().density;
}
@Override
public int getGroupCount()
{
return 1;
}
@Override
public int getChildrenCount(int groupPosition)
{
return 6;// because there are 6 faces to a cube
}
// list views
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent)
{
View v = convertView;
TextView tv;
if(v == null)
{
v = inflator.inflate(R.layout.exlist_head, parent, false);
tv = (TextView) v.findViewById(R.id.exList_Title);
if(tv != null)
tv.setText("Face Colors");
tv = (TextView) v.findViewById(R.id.exList_Summary);
String s_text = "The color for each face, click to ";
s_text += ((isExpanded)? "collaspe": "expand");
if(tv != null)
tv.setText(s_text);
} else
{
tv = (TextView) v.findViewById(R.id.exList_Summary);
String s_text = "The color for each face, click to ";
s_text += ((isExpanded)? "collaspe": "expand");
if(tv != null)
tv.setText(s_text);
parent.invalidate();
}
return v;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
View v = convertView;
if(v == null)
{
if(bShowSw)
{
v = (LinearLayout) inflator.inflate(R.layout.face_info_sw, parent, false);
} else
{
v = (LinearLayout) inflator.inflate(R.layout.face_info, parent, false);
}
}
switch(childPosition)
{
case 0:
v.setTag("Front");
break;
case 1:
v.setTag("Back");
break;
case 2:
v.setTag("Left");
break;
case 3:
v.setTag("Right");
break;
case 4:
v.setTag("Top");
break;
case 5:
v.setTag("Bottom");
break;
}
Log.i("ELV", "Pos:" + childPosition);
cp = (ColorPickerMenuView) v.findViewById(R.id.face_color);
cp.setTitle((String) v.getTag());
cp.setOnClickListener(new OnClickListener() {
public void onClick(View v) { UpdateData(v); }
});
if(bShowSw)
{
sw = (Switch) v.findViewById(R.id.face_sw);
sw.setOnClickListener(new OnClickListener() {
public void onClick(View v) { UpdateData(v); }
});
}
return v;
}
private void UpdateData(View v) { }
@Override
public Object getGroup(int groupPosition)
{
return groupPosition;
}
@Override
public Object getChild(int groupPosition, int childPosition)
{
return "Child:" + groupPosition + "." + childPosition;
}
@Override
public long getGroupId(int groupPosition)
{
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition)
{
return childPosition;
}
@Override
public boolean hasStableIds()
{
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition)
{
return true;
}
}
= Activity's xml view
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.PagerTitleStrip android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
= m_backdrop.xml (menu for the backdrop setting)
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:app="http://schemas.android.com/apk/res/com.zyphronics.aquafinger">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/Title_GeneralSettings_text"
style="?android:attr/listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dip"
android:text="General Settings" />
<ExpandableListView
android:id="@+id/FaceList"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="@id/Title_GeneralSettings_text" >
</ExpandableListView>
</RelativeLayout>
</ScrollView>
= face_info.sw.xml (The face_info.xml is the same, but without the switch)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Switch
android:id="@+id/face_sw"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:tag="face_sw"
android:text="Enabled?"
android:textOff="No"
android:textOn="Yes" />
<com.zyphronics.AF.Controls.colorpicker.ColorPickerMenuView
android:id="@+id/face_color"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:tag="face_color" />
</LinearLayout>
Even without the "ColorPickerMenuView" part in the face_info_sw.xml, the child view is still not being called. I've also tried having the Relative Layout by itself, instead of in a scroll view, but this prevented further added items from being scrolled to (and also made custom controls have weird sizes).
Any help to solve this would be great. If anyone needs a packaged source of this (eclipse compatible), as in the comments and I'll reply with a link.
The Problem is, your ExpandableListView is in a ScrollView. This is most of the times the result of bad layouts, and you should try to avoid it.
However sometimes it is the only solution (that's actually not true but other solutions might be out of ones scope). Your solution also works but is a bit complex. Instead write a custom ExpandableListView like this:
public class ExpandExpandableListView extends ExpandableListView{
boolean expanded = true;
public ExpandExpandableListView(Context context) {
super(context);
}
public ExpandExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}else{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
and in your m_backdrop.xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
...
<RelativeLayout
...
<com.package.name.to.class.ExpandExpandableListView
android:id="@+id/FaceList"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="@id/Title_GeneralSettings_text" >
</ExpandableListView>
</RelativeLayout>
</ScrollView>
That's already it. It should work as expected.
Bummer that no one was able to answer this...but, luckly I was able find a solution for this problem so others out there can add expandable list views to their fragments too.
I found the solution while checking out the source code for the spinner (because I wanted to make a spinner with custom entries). The key point in the code was in it's "onMeasure" when it measures all of it's children...well, up to a pre-defined limit of 15, but measuring the children is the main point.
= Solution = When creating your adapter, you need to pass in your parent view. This is key because it's the only link you have to the view from the adapter (only easy way).
public Context context;
public ExpandableListView parent;
private LayoutInflater inflator;
// Only measure this many items to get a decent max height.
private static final int MAX_ITEMS_MEASURED = 15;
public ExpandableFaceList(Context context, final ExpandableListView parent)
{
this.context = context;
this.parent = parent;
this.inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mDensity = context.getResources().getDisplayMetrics().density;
}
Next, in your "getGroupView", because that seems to be the only thing which is commonly called, you'll need to add the following code:
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent)
{
if(convertView != null)
{
int nH = 0;
if(isExpanded)
{
nH = measureChildrenHeight(groupPosition);
} else
{
nH = convertView.getMeasuredHeight();
}
parent.getLayoutParams().height = (int) (nH * mDensity);
parent.invalidate();
}
return convertView;
}
The key part in the above code is the "measureChildrenHeight", the "convertView.getMeasuredHeight()" is just to restore the ELV when collapsed. The measure children code is as follows:
int measureChildrenHeight(int groupPosition)
{
int height = 0;
View itemView = null;
LinearLayout viewGroup = new LinearLayout(context);
viewGroup.setOrientation(LinearLayout.VERTICAL);
final int widthMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
final int heightMeasureSpec =
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// Make sure the number of items we'll measure is capped. If it's a huge data set
// with wildly varying sizes, oh well.
int start = 0;//Math.max(0, getSelectedItemPosition());
final int end = Math.min(getChildrenCount(groupPosition), start + MAX_ITEMS_MEASURED);
final int count = end - start;
start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
for (int i = start; i < end; i++)
{
itemView = getChildView(groupPosition, i, (i+1 == end), itemView, viewGroup);
if (itemView.getLayoutParams() == null)
{
itemView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
itemView.measure(widthMeasureSpec, heightMeasureSpec);
if(i+1 != end)
height += itemView.getMeasuredHeight();//Math.max(height, itemView.getMeasuredHeight());
}
return height;
}
Note: I had to adjust the code to exclude the last item's height because it was adding extra space below the last item. If you have children of different height, there might be trouble. I still get some empty space below the children/ group header, but it's excusable (unlike the previous amount, which was 3-4 children tall).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With