I have a ListView within my project. It has many elements, and it uses a custom adapter, since its populated dynamically from a rails server.
I want to change the content of a ListItem when the item is longpressed. In order to achieve this, I have 2 layouts inside the ListItem, with one visible and one hidden.
The issue is that when I longpress an item, the layout changes (As expected), but other ListItems are also affected, and changed in the same way. This appear to occur once for every 5 items, and I cant figure out why.
This is the LongClickListener I'm using, it is located inside de GetView method on the custom adapter:
View v = convertView;
if (v == null){
LayoutInflater vi =
(LayoutInflater)getActivity().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_item, null);
}
final LinearLayout placeInfo =
(LinearLayout) v.findViewById(R.id.list_item_info);
final RelativeLayout placeBrief =
(RelativeLayout)v.findViewById(R.id.list_item_brief);
v.setOnLongClickListener(new OnLongClickListener(){
#Override public boolean onLongClick(View v) {
placeInfo.setVisibility(View.GONE);
placeBrief.setVisibility(View.VISIBLE);
return false;
}});
I would appreciate any help, many thanks in advance.
ListViews recycle Views, so you only have a few views for all of your items. You're directly changing one of these view instances to switch between the info|brief. What you need is to save the status of the info|brief flag for the affected position somewhere else (e.g. a list of positions that should be "briefs" in the adapter). That way when you come back into getView() you can display the right one.
Related
I am inflating layout in view in a for loop and then adding views in Array like:
ArrayList<View> views = new ArrayList();
for (Result datalist : arraylist){
View view = layoutinflater.inflate(R.layout.viewlayout, null);
/*View modification code here....
.............
.............
.............*/
views.add(view);
};
The problem is when i get views from arraylist even by index like views.get(i) it returning only last view. I want to get all views to be able to work on all views.
Here is the code where i am getting views from arrays.
for (View v : views) {
// if (insertPoint != null) {
// if (views != null) {
// if (rel_leftright != null) {
Rect scrollBounds = new Rect();
insertPoint.getHitRect(scrollBounds);
if (v.getLocalVisibleRect(scrollBounds)) {
rel_leftright.setVisibility(GONE);
} else {
rel_leftright.setVisibility(VISIBLE);
}
// }
// }
// }
}
But this code returning only working on last view in array and i need to act on all views.
Please Help me to get out of this. Thanks!!!
First I am missing something:
- Why are you trying to work with the views (/*View modification code here.... ) before you have created all of them? First craete them in the loop and the iterate over views Array and there call the modification code.
- Are you sure you don't want to pass a parent?
View view = layoutinflater.inflate(R.layout.viewlayout, null);
Even if you don't want to be attached you can call:
View view = layoutinflater.inflate(R.layout.viewlayout, parent, false);
And in general it is not possible to fill ArrayList with values and not to find them... Please log the size of datalist, then the size of views. The most probably should match.
And also please note that by the line: /*View modification code here.... not all views are created. You first need to finish the loop.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(resource, null);
}
ImageView imageView;
imageView = (ImageView) convertView.findViewById(R.id.ivGallery);
for(HospitalModel.Images images: hospitalModelList.get(position).getImagesList()) {
Glide.with(getContext()).load(images).into(imageView);
}
return convertView;
}
// image URLs are stored in string ArrayList . I defined getter and setter for array list but still I don't know how to use get method for showing ArrayList images dynamically in ListView
Extend the class from BaseAdapter
Override getCount() method and return here the total amount of images you need to show.
In the getView load with Glide only ONE url (remove the for loop), the getView method will be called N times to show in the list the "total" amount of images you returned in the getCount method, the position parameter in the getView method will run from 0 to (total - 1).
You should map 1 position to 1 url, maybe you will need to change the way you access the objects that contain the urls.
Okay so first: stop using ListView, use RecyclerView.
Next, you need to override getItemCount(). You can either choose to do this using a for loop:
int count = 0;
for(HospitalModel.Images images: hospitalModelList.get(position).getImagesList()) {
count += images.size();
}
Or, what is likely preferable, flatten your model object into just what this adapter actually cares about (which is the image URL strings). Something like this:
ArrayList<String> imageUrls = new ArrayList<String>();
for(HospitalModel model : hospitalModelList) {
imageUrls.addAll(model.getImagesList().getImageUrls());
}
Then pass in the imageUrls ArrayList to the Adapter instead. You should only need to flatten this when the model is updated (whether that's initialization or changed). When that occurs, use the notify... methods on the adapter.
After that, it's just a simple Glide.with(getContext()).load(imageUrls.get(position)).into(imageView); inside getView (or onBindViewHolder if you're using a RecyclerView, which I HIGHLY recommend).
How can I retrieve the View at the position X in a ListView? I dont want to inflate a new one, just retrieve the cell visible in the screen for change some parameters programmatically
Since views in ListView are re-used/re-cycled. There is no direct way of getting a view reference from the ListView.
If you want to access a view you need to extend ArrayAdapter and then override getView. There you should call the super.getView and write your own custom code.
If we you really need to control more than try extending BaseAdapter or CursorAdapter.
Found a dirty solution:
You should be able of identify each row generated. For example adding a TextView with visibility=gone and writing a unique value there when generating (or recycling the row)
In the listactivity call to getListView.setSelection(position) to the desired cell
Survey the listview list for the row (until displayed)
lv=getListView();
for (int i=0;i <lv.getChildCount();i++){
if (((TextView)lv.findViewById(R.id.my_hidden_textview)).getText.equals(mykey)){
// view found
} else {
// schedule another survey "soon"
}
}
For the schedule you can use something like:
final int RETRY_DELAY=100;
new Handler(){
public void handleMessage(Message msg){
if (msg.what<0) return; //something went wrong and retries expired
lv=getListView();
for (int i=0;i <lv.getChildCount();i++){
if (((TextView)lv.findViewById(R.id.my_hidden_textview)).getText.equals(mykey)){
//result = lv.findViewById(R.id.my_hidden_textview);
} else {
this.sendEmptyMessageDelayed(msg.what-1,RETRY_DELAY);
}
}
}
}.sendEmptyMessageDelayed(10,RETRY_DELAY);
As I said is a very ugly solution but it works
I didn't clearly understand your problem. But to what I've understood I would suggest you use a frame layout within a linear layout. You can use another frame layout to do your manipulations.
I went through tutorials and searched, but still I can't understand how the,
getView(int position, View convertView, ViewGroup arg2)
method works when extends BaseAdapter to create a custom listView in my android application. Therefore I cant Edit the Custom list view exactly I want.
I need to know when this method invokes and the meanings of the parameters.
If someone can explain the following method its great. Thanks
#Override
public View getView(int position, View convertView, ViewGroup arg2)
{
ViewHolder holder;
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null)
{
convertView = inflater.inflate(R.layout.listitem_row, null);
holder = new ViewHolder();
holder.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
holder.txtViewDescription = (TextView) convertView.findViewById(R.id.textView2);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.txtViewTitle.setText(title[position]);
holder.txtViewDescription.setText(description[position]);
return convertView;
}
getView() is called when you call setAdapter in your code. After that when you move focus over list or select any item or you call notifyDataSetChanged() , you get call in getView().
Position - The position of the item within the adapter's data set of the item whose view we want.
convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type
The ViewGroup - that this view will eventually be attached to.
getView() : As mentioned in specs getView method displays the data at the specified position. So, when you setAdapter and when you scroll your listView getView method will be called.
The method you copied here is a part of EfficientAdapter to optimize your ListView performance and along with optimization you used ViewHolder pattern.
Copied from Specs : With little more explanation
position :The position of the item within the adapter's data set of the item whose view we want.
convertView: The old view to reuse, if possible. Note: You should check that this
view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).
So, in above method when you are doing the following thing you are reusing your convertView.
if (convertView == null){
....
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
And by doing following thing you are avoiding lookup (findViewById), thats what the good thing about ViewHolder pattern
holder.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
parent : The parent that this view will eventually be attached to
Edited
Question : How many times getView is called and how many convertView will be created ?
Answer: Lets take an example of Efficeint Adapter from ApiDemos. If your screen showing 10 Rows, then,
convertView Count : 10 + 1 = 11 (10 Rows what you are seeing on screen, one extra to show scrolling effect). That means statements in if(convertView == null){...} block will be called only 11 times.
getView Count: Initially count will be 10, but when you start scrolling count keep on increasing. getView called every time to update data.
Note: This is only true for getView method mentioned in question.
Here is the description of getView() parameters:
int position - the position of view in the list;
View convertView - IMHO, this is the most difficult parameter for understanding. At the beginning of list's work convertView = null. But when you start to scroll it down, when an item of list (which is instance of View) is hidden, it is stored in memory like convertView. This trick allows you not to create a new item when you scroll your list back, but use convertView stored in memmory. So the first item of list which becomes the convertView is the item at 0 position. Remember, that when you scroll your ListView down (from 0 position to bigger), convertView is situated on the top and on the bottom if you scroll the ListView up.
ViewGroup arg2 - this is your ListView (this class is derived from ViewGroup).
ViewHolder is the pattern, that makes comfortable communication with list's items. You make this object as item's tag and can use them for for indirect interaction with list's item, because it refers on item's fields (View.setTag(holder)).
getView() method is called every time when Android need to draw another one list's item.
Any questions?
Is there a better way to create a custom list, for my code here seems to make for a less-responsive list. I can't use any of the standard Android lists because I need two ListView's in a ScrollView.
setContentView(R.layout.alertsview);
inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
topRows = new ArrayList<View>();
topLayout = (LinearLayout)findViewById(R.id.topListLayout);
topLayout.removeAllViews();
topRows.clear();
List<Camera> camList = new ArrayList<Camera>(SitesOrchestrator.getInstance().currentSite.cameraList);
for (int i = 0; i < camList.size(); i++)
{
Camera cam = camList.get(i);
View row = inflater.inflate(R.layout.cameraalertrow, null);
TextView name = (TextView)row.findViewById(R.id.cameraNameAlerts);
CheckBox chk = (CheckBox)row.findViewById(R.id.camAlertChk);
name.setText(cam.name);
chk.setChecked(cam.alertsEnabled);
chk.setOnCheckedChangeListener(this);
String index = Integer.toString(i);
chk.setTag(index);
topRows.add(row);
topLayout.addView(row);
}
If you need to lists in the same layout, you should create your own Adapter (derive from base adapter might be good), and, suppose you have two arraylist:
ArrayList<TypeA> typeAList;
ArrayList<TypeB> typeBList;
#Override
int getViewTypeCount(){ return 2; } // means you have two different views from it
#Override
int getItemViewType(int position){
if (position>typeAList.size()) return 1;
return 0;
}
getView(int pos, View convertView, ViewGroup parent){
// Check the pos
if (getItemViewType(pos) == 0){
// Inflate view and bind for type A
}
else{
// Inflate view and bind for type B
}
}
In general, having two list view vertically is not encouraged in Android, but putting both of your content to one list do the trick. I also have a tutorial about this, though it is done in MVVM with Android-Binding.
Moreover, adding Views one by one to ScrollView to mimic the ListView would, of course, be very inefficient. The way Android ListView works (which should different from desktop frameworks) is with recycling views. Suppose you are scrolling up, once the child is scroll out of sight, the listview will recycle the topmost one and place it to the bottom, and the possibly recycled view will pass as convertView in the above getView code. Inflating is considered to be quite expensive process, and multiple child views are memory consuming as well, that's the reason why your code, compare to standard ListView, is very inefficient.