the PRJ contains :
Frag_Settings which has the ExpandableListView, using a custom adpater I name it Frag_Settings_Adapter listAdapter.
the expandable groups are 5 and are static (aka predefined). Each of group accommodate a different view. One of these views has listview, which also implements a custom adapter (takes records from dbase)...
so lets see, what I have wrote for the moment, at expandable Frag_Settings_Adapter :
#Override
public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
LayoutInflater inflater = null;
switch (groupPosition) {
case 0:
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.frag_settings_row_detail_01_cities, null);
break;
case 1:
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.frag_settings_row_detail_02_categories, null);
//raise event to parent
if (listener_02_categories != null)
listener_02_categories.fill_with_data(convertView);
break;
}
return convertView;
}
when group is 2, inflate the view + raise an event to fragment with the view! (is this good?)
then at fragment, onActivityCreated, when setup expandable_listview :
listAdapter = new Frag_Settings_Adapter(getActivity(), listDataHeader, listDataChild);
listAdapter.setListener(new Fill_02_Categories_Listener() {
#Override
public void fill_with_data(View convertView) {
////////////////////////////////////////////////
//////////////listview 02 - categories
lstv = (ListView) convertView.findViewById(R.id.frag_settings_row_detail_categories_lstv);
Frag_Settings_Categories_LIST = new ArrayList<Frag_Settings_Categories>();
lstv.setOnItemClickListener(this);
lstv_adapter = new Frag_Settings_Categories_Adapter(getActivity(), Frag_Settings_Categories_LIST);
lstv.setAdapter(lstv_adapter);
CategoriesDatasource categories_datasource = new CategoriesDatasource(getActivity());
for (Categories d : categories_datasource.getAllCategoriess()) {
Frag_Settings_Categories_LIST.add(new Frag_Settings_Categories(d.getid(),d.getcategory_name(),false));
}
lstv.setAdapter(lstv_adapter);
}
});
// setting list adapter
expListView.setAdapter(listAdapter);
-this one working but I have somehow increase the height of expandable group 2 because now I can see only 1listview item... :(
-over all, is there any way to achieve this in more easier way?
-the method I follow is correct?
No way^ too many problems with that way....
I discover a manual solution, once as I said the views are static https://tsicilian.wordpress.com/2013/09/02/android-tips-expandablecollapsible-views/
Related
I finally have my ExpandableListView working (thanks to SO), but now I want to be able to toggle between two different childViews whenever I (short) click on a child item.
I have this snippet for the setOnChildClickListener, which currently resides in the onCreate section. I assume I have to move this to within the BaseExpandableListAdapter in order to be able to toggle the layout of the childView (or can I do this from within onCreate?)
maybe a picture can illustrate it best: If I click on the 'PPLT' child, I want it to expand to approx. double height and and more info (according to a different child xml layout file)
expListView = (ExpandableListView) findViewById(R.id.StockExpandList);
expListAdapter = new ExpandableListAdapter(PortfolioView.this, SubcategoryList, subgroupCollection);
expListView.setAdapter(expListAdapter);
expListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
public boolean onChildClick(ExpandableListView parent,
View v,int groupPosition, int childPosition, long id) {
final String selected = (String) expListAdapter.getChild(groupPosition, childPosition);
Toast.makeText(getBaseContext(), selected, Toast.LENGTH_SHORT).show();
return true;
}
});
I'm probably showing my ignorance on how a basic listview adapter works, but I assume this is where I add a condition that decides whether to use the default child_item layout or the other one I want to show up when I click on it (e.g. called child_expanded_item since it's supposed to be larger with more info in it)
public View getChildView(final int groupPosition, final int
childPosition,boolean isLastChild,View childView, ViewGroup parent)
{
LayoutInflater inflater = context.getLayoutInflater();
if (childView == null) {
childView = inflater.inflate(R.layout.child_item, null);
}
}
Or do I go crazy and do a nested ExpandableListView?
I'm an Android newbie. I want to create an expandable ListView where 1 row has custom info that has to be specified in the manin thread instead of being hardcoded in a layout.
I want to have an image and 2 textviews in it. I think I will need a custom layout file, but I'm not sure where to make a call for it in my code. Please assist.
What follows is my GropView fxn inside my custom adapter. I want case 0 to load a custom layout file
public View getGroupView(int groupPosition, boolean arg1, View convertView,
ViewGroup arg3) {
String laptopName = (String) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.group_item, null);
}
TextView item = (TextView) convertView.findViewById(R.id.laptop);
item.setTypeface(null, Typeface.BOLD);
item.setText(laptopName);
this.context = (Activity) context;
switch (groupPosition) {
case 0:
convertView.setBackgroundColor(this.context.getResources()
.getColor(R.color.dark_blue));
convertView.inflate(R.layout.first_row_layout, 0, arg3);
break;
case 1:
convertView.setBackgroundColor(this.context.getResources()
.getColor(R.color.purple));
break;
case 2:
convertView.setBackgroundColor(this.context.getResources()
.getColor(R.color.green));
break;
default:
break;
}
return convertView;
}
The position in your code where you define the layout for your group items is this:
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.group_item, null);
}
In your case you are using the layout R.layout.group_item for your group items. If you want to load different layouts for each group position u need to move this part of your code inside the switch-case. You have to be careful with images inside your ListView, more info about that here:
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
I have a class that extends BaseExpandableListAdapter :
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
Filter filter = (Filter) getChild(groupPosition, childPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.exp_list_child, null);
}
TextView tv = (TextView) convertView.findViewById(R.id.child_tv);
tv.setText(filter.toString());
tv = (TextView) convertView.findViewById(R.id.child_sub_tv);
tv.setText(filter.getType());
if(!filter.getType().endsWith("ExportPin"))
// I dont want to return antyhing
return convertView;
}
As you can see I create a TextView and everything but I don't want to return it if the type of the filter does not end with "ExportPin" (see if statement at the end).
I can't return null and I dont want to return an empty list item.
In my class I also have getChildrenCount(), getGroupCount() ...
Any ideas?
According to the official documentation, an Adapter should not be in charge of filtering the data :
An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.
You have to filter your data before putting them in your Cursor, then the Adapter will render the needed view to be display the data inside the ListView
Perhaps you should filter the data before passing it to the adapter, (or in adapter constructor) so that the adapter to use only the filtered already data.
I have an Activity, which simply consists in listing Pair<String, String> objects. I have a custom TextWithSubTextAdapter, which extends ArrayAdapter:
public View getView(int position, View convertView, ViewGroup parent)
{
View view;
if (convertView == null)
{
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.text_sub, null);
TextView tv = (TextView)view.findViewById(R.id.mainText);
tv.setText(mCategories.get(position).first);
TextView desc = (TextView)view.findViewById(R.id.subText);
desc.setText(Html.fromHtml(mCategories.get(position).second));
}
else
{
view = (View) convertView;
}
return view;
}
mCategories is an ArrayList<Pair<String, String>>
I then call lv.setAdapter(new TextSubTextAdapter(this, Common.physConstants));
As long as I have a limited set of elements, It works great, because I don't need to scroll. However, when I add enough elements, after scrolling, the items swap their positions, like this:
I suspect that this behavior is due to me calling mCategories.get(position). Because the Views are never kept in the background and Android regenerates them every time, I never get the same item, as position will rarely have the same value.
Is there a way to get a constant id, which could allow me to get items with fixed positions ? I tried to use getItemID, but I do not understand how to implement it.
Note: Every string comes from a strings.xml file. They are never compared, and instanciated once, at startup.
When you scroll your list Android dynamically reuses the Views which scroll out of the screen. These convertViews don't have the content which should be at this position yet. You have to set that manually.
View view;
if (convertView == null)
{
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.text_sub, null);
}
else
{
view = convertView;
}
TextView tv = (TextView)view.findViewById(R.id.mainText);
tv.setText(mCategories.get(position).first);
TextView desc = (TextView)view.findViewById(R.id.subText);
desc.setText(Html.fromHtml(mCategories.get(position).second));
return view;
I have a ListView, and I have added a header (with getListView().addHeaderView) that simply contains a TextEdit widget.
Then when I tap the TextEdit to start writting, the keyboard appears and it messes up the list!
If I tap everywhere else to hide the keyboard, the list messes up again!
I don't know why is this happening. I thought it was something related with the onConfigurationChanged method, but after implementing it (and adding the corresponding attribute in the manifest file) the problem persists.
How could I fix it? Why is Android messing up my list?
EDIT:
My list uses a custom adapter, this is the getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v != null) {
return v;
}
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_row, null);
ListTask list_item = items.get(position);
if (list_item != null) {
TextView item_name = (TextView) v.findViewById(R.id.item_name);
item_name.setText(list_item.getTitle());
}
return v;
}
The problem is not the value of my items, but their order. They are displayed in a different order when the keyboard appears, but the values are correct.
EDIT2:
Ok, I have changed my getView method with rekaszeru's suggestion and now it works as expected. But now I'm facing another problem: what if my items have two textviews?
Let's say the second textview is optional, and "Item 1" and "Item 3" have it, but "Item 2" does not, so it's initialized as a void String (length == 0).
The first time the list is displayed, it shows "Item1" and "Item 3" with their second textview, and "Item 2" without it. That's correct. But when the keyboard appears, the "Item 2" takes the second textview of another item and displays it!
This is the modified code I have right now:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
TextView item_optional_text = (TextView) convertView.findViewById(R.id.item_optional_text);
item_name.setText(list_item.getTitle());
// if the item has defined the optional text, make some room and display it
if (item_optional_text.isNotEmpty()) {
LayoutParams layout_params = (LayoutParams) item_name.getLayoutParams();
layout_params.topMargin = 10;
layout_params.height = -2; // -2: wrap_content
item_name.setLayoutParams(layout_params);
item_optional_text.setText(list_item.getOptionalText());
}
return convertView;
}
The isNotEmpty() does this in the Item class:
public boolean isNotEmpty() {
return this.optional_text.length() > 0;
}
Maybe it's too complex to understand in a written question. If so, I can make a short video showing the problem and my source code. Thanks in advance for your help guys.
Your row recycling is messed up. Android is not changing the order of the items, you are.
Right now, if you are passed a row to recycle, you return it without modification. This is a mistake. You are supposed to modify the contents of the row to reflect the data at the supplied position. The only piece of logic you can skip in this case is inflating a brand-new row.
Here is a free excerpt from one of my books that goes through all of this.
You should override the getView method in your ListAdapter implementation, and make sure that you always assign a new value to the view that you are returning (or at least always update it to contain the proper data).
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = inflater.inflate(R.layout.list_row, parent, false);
//set the necessary data in your TextViews, Checkboxes, etc...
return convertView;
}
If you don't inflate your item renderer, then you can instantiate it from code, like:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = new TextView([...]);
convertView.setText(textBasedOnYourData);
return convertView;
}
Edit
As #CommonsWare noted, attention should be payed to the recycling of your list item renderer. So instead of instantiating it every time, you should check whether it already exists or not, and update the underlying TextView afterwards.
So I'd suggest give a try to this slightly modified getView implementation:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
//the item should never be null, but just in case:
item_name.setText((list_item == null) ? "" : list_item.getTitle());
return convertView;
}