Cause onClick of a ListView row to offer editing options - android

I'd like to have the effect of clicking on a row in a ListView and offering buttons to do basic editing with what I'm displaying in said row.
Now I don't mean to change a textview into an editable-textview exactly, rather something that just offers buttons like "edit" and "delete" for example. This could be done with a popup but I'm trying to avoid this, I want these action buttons to be replacing the displayed content of that row.
At first I figured it could just have two layouts for inflating into rows. One "active" and one normal. On click it would just return the different layout for the row clicked and have a marker to indicate which row was the currently selected one. First ran into issues I didn't expect with the inflated views being recycled as a listview is scrolled up & down. Fine, ok, so I made an extension of BaseAdapter so I could do my own thing with getView(). Well I managed to get it to correctly allow recycling of views (and not reusing the "active" one where it wasn't suppose to be) however I see no way to make it refresh / reload the alternate layout except when scrolling said row off screen and then back on. Seems there's no way to force getView() to actually happen unless a row leaves the screen and comes back.
So it's a two headed question. One is there a way to make a single row swap out inflated views while it's being displayed? And two maybe my method of doing this is a backwards way to accomplish what I want and is there a better way?
Thank you for your time!
public View getView(int position, View convertView, ViewGroup parent) {
View v;
Boolean activeExists = false;
if(convertView!=null && convertView.getTag()==(String) "active"){
activeExists=true; }
if (((position==activeFlag && activeExists==true) || (position!=activeFlag && activeExists==false)) && convertView!=null) {
v = convertView;
} else if(position==activeFlag && activeExists==false){
v = inflater.inflate(rowlayoutActive, parent, false);
v.setTag((String) "active");
} else if (position!=activeFlag && activeExists==true) {
v = inflater.inflate(rowlayout, parent, false);
} else {
v = inflater.inflate(rowlayout, parent, false);
}
bindView(position, v);
return v;
}
(Outside of this getView I have (int) activeFlag to remember which one is the current "selected" and I have my own version of bindView which doesn't really differ that much from normal. The boolean is a quick mark for already inflated views to keep the wrong one from going to the wrong row when recycled.)

Great question, and part of the answer might be that you consider the MVC model. Strictly speaking, you should modify the underlying data to cause a change in the UI which suggests adding perhaps a boolean to your data "isEditing" or similar, set it with the click then calling notifyDataSetChanged. In getView, you would test that boolean to use the appropriate layout.

Related

android listview retain last viewed list item view before recycling

I'm using a list view, and I'm using the View holder pattern and recycling views. All working great and as expected. The list scroll smoothly and I'm quite happy with the implementation, besides one thing.
I would like to retain the last 3 views that were scroll off the screen before I get their views to recycle. I would like to do that in order to achieve the following. I have images in my list and I'm loading them asynchronously and they have some fade in animation. It all good as long the user scroll to new items, but I would like that in case that the user scroll back again, that the images would be populated already and won't be loaded again. Of course that I don't want all the previous view to save, but the last few items (lets say three) is reasonable.
Any thoughts on how to achieve it?
Thanks
--EDIT--
To answer some comments. The fade in effect has no influence on the problem (it just more easy to identify it) without the fade in, the image just appear, but the user would see the place holder of the photo (it takes few milliseconds, and it is depends on the current state of the OS).
To solve the problem there should be some caching mechanism to the list view that hold the last X elements that were seen by the user and scrolled off already, and when the user scroll back to those elements the list view should send the convertView of the cached view it saved.
I managed to do that artificially for one element, just to test the concept, and it worked fine. But I have reason to assume that some kind of caching mechanism of elements in a list view it is something that probably implemented already in the ListView itself, or someone already implemented something like that already.
You can think of it as follow. Now the ListView holds the number of elements seen on the screen and the last one that was seen and when there is a need to draw the next element the list view sends the last seen element as the convert view. The solution means that the list view would hold the seen elements + the X elements of the caching and the last element that was scroll off and is not in the caching anymore. When a new element should be drawn the list view will give it the element that is shouldn't be cached anymore, unless the user scroll back to an element that exist in the caching, then the element from the caching would be sent as the convert view
--Edit 2--
public View getView(int position, View convertView, ViewGroup parent)
{
//Get the item
final ItemForListActivity item = itemsListHashtable.get(position);
ViewGroup rowLayout;
//In case that the view is new create it
if (item.getItemType() == Interface.LIST_ITEM_TYPE_CONTACT)
{
ListItemViewHolder viewHolder;
if (convertView == null)
{
rowLayout = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.list_view_item, parent, false);
viewHolder = new ListItemViewHolder();
viewHolder.image1 = (ImageView) rowLayout.findViewById(R.id.image1);
viewHolder.image2 = (ImageView) rowLayout.findViewById(R.id.image2);
//We save the item id that is associate with the holder, for now nothing is associate with it.
viewHolder.itemId = -1;
rowLayout.setTag(viewHolder);
}
//Otherwise just use the converted view
else
{
rowLayout = (ViewGroup) convertView;
viewHolder = (ListItemViewHolder) rowLayout.getTag();
}
//Only in case that the item id is not the one that is already drawn, we need to draw the item
if (viewHolder.itemId != item.getItemId())
{
viewHolder.itemId = item.getItemId();
//Draw image1
drawImage1(viewHolder, item);
//Draw image2
drawImage2(viewHolder, item);
}
}
return rowLayout;
}

GridView children sometimes not drawing

I have a GridView with a custom view which basically look like buttons. The gridview starts off with no children, and everytime the user presses a button another custom view will be added.
There is some strange behaviour with this. I am drawing some things like text, lines etc on the custom view in onDraw and sometimes they are drawn at all. They are completely blank. The behaviour seems quite random in terms of which views show or don't show the drawn graphics.
I have a feeling it is to do with me setting the layoutparameters. I store the child views in an array once they are created, and in getView() I return the view relevant for the position parameter. So I only ever create buttons for each position once.
So I have two questions.
What am I doing to cause this?
Should I even be using a gridview for what I am doing?
The code for get view is:
public View getView(int position, View convertView, ViewGroup parent)
{
GridButton button;
GridView gridView = (GridView)parent;
if(childBuittons.size() <= position) //if we need to create a new button
{
button = createButton(position);
int nWidth = getButtonSize(gridView);
GridView.LayoutParams params = new GridView.LayoutParams(nWidth, nWidth);
button.setLayoutParams(params);
}
else //we already have a button that we created
{
button = buttons.get(position);
}
return button;
}
Some more information:
- The gridbutton class is just a class that extends View and overrides onDraw to draw some graphics such as text and lines
- What im trying to achieve is a grid of squares that the user can add or remove (although they wont do this often) and then press the squares to perform certain functions
- It is possible that there will be more squares than can fit in the screen
What am I doing to cause this?
Without providing more details related to the GridButton class nobody can really help you. It would be good to know how you handle the measuring of the view and what(and how) exactly do you draw in that view.
I have a feeling it is to do with me setting the layoutparameters.
I doubt this. To be sure you could also draw a simple color in that custom view and see if it appears on the grid. If it does the LayoutParams aren't the reason for what's happening. Also check what values do you get for nWidth.
I store the child views in an array once they are created, and in
getView() I return the view relevant for the position parameter. So I
only ever create buttons for each position once.
This is not ok. The main reason for using a GridView is that you could use its adapter to avoid having to create all grid views(occupying memory(if not crashing) and slowing your app) up front. You should look in implementing the proper recycling mechanism specific to a GridView/ListView. Your getView() method should be like this:
View getView(int position, View convertView, ViewGroup parent) {
GridButton button;
GridView gridView = (GridView)parent;
if(convertView == null) {
button = createButton(position);
int nWidth = getButtonSize(gridView);
GridView.LayoutParams params = new GridView.LayoutParams(nWidth, nWidth);
button.setLayoutParams(params);
} else {
button = (GridButton) convertView;
}
return button;
}
If you need access to those buttons then access them through the adapter, changing some data according to the button's position and calling notifyDataSetChanged().
Should I even be using a gridview for what I am doing?
You didn't say what you want do do. If you think that the number of cells is high enough and it's expected to not see all of them then use a GridView. If you think all the cells will be visible then you could make that grid using the standard layouts without a GridView.

Do I need to config ConvertView properties if it is not null?

In a custom adapter, how to know weither I need to reconfig the convertView or not?
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = (ImageView) mInflater.inflate(R.layout.avatar, parent, false);
// Should this 2 lines of code be here ?
User user = mUserList.getUserAt(position);
user.setAvatar(imageView);
} else {
imageView = (ImageView) convertView;
}
// or here ?
User user = mUserList.getUserAt(position);
user.setAvatar(imageView);
return imageView;
}
I would think that if it is recycled, I would not need to reset the user's avatar but I often see the configuration happening outside of the if block. Why is that?
I always configure the view outside of if block. This convertView that you get in the getView method might (and most probably will) be set up for another user by some previous call to getView because of ListView's policy to reuse item views when they go offscreen. If you do not set up a proper avatar you will have wrong one for this item. Of course you won't need to reset properties that are independent of the concrete position like background.
For example CursorAdapter separates getView into two parts: newView, that performs inflate and (mostly) position-independent setup and bindView that assigns actual position-dependent data.
It may also happen that you will get exactly the same view that you used for this position earlier. Of course you can avoid resetting a view in this case, but you need to check if data in this view are valid. Setting and then checking View's tag comes to my mind as a most obvious solution.
It is not truly recycled, it just means that you need to fill a "recycled" view with new data according to the its new position. If you don't do it this row will be filled with old data that shouln't be visible on the screen anymore since you scrolled away its position.
So in short you have to reconfigure view with fresh data each time getView() called (outside of if block in your code).
Your issue is only with the understanding of listview.
So here I'll make you clear how it works??
Let's say listview has to contain 20 items but your current screen can accommodate(show on screen) only 8 items(list items, in your case imageview).
When the listview tried to get items for 1----8th it will return you convertView as null because no recycling of objects happened yet.
but, when you try to scroll, in our case(scroll up!).
the 1st element of the list will be recycled when go out of screen, and will be supplied as convertView for 9th item.
In this way listview has to manage only 8th(in our case) to show any number of items.
The opposite will happen if we will scroll down wards.
So, on the basis of convert view (null or not) you have to design your logic either to create and fill or to fill.
Hope this will help you.

Inactive links in ListView ArrayAdaptor

I have a list of around 2500 items in a ListView object. I would like for some of those items to be visible, but greyed out. An activity launches for the ones that aren't greyed out (in my case, this is an AlertDialog). I'd like (for example) the first 500 items to be active and to have the usual behaviour, whilst the other items are greyed out and clicking on them does nothing.
What I'm trying to create is a trial version of a ListView app, so that the user can only access some of the items in the list for demonstration purposes.
What would be the simplest way of going about this? I can only think of having two lists that are appended to each other (the first being active, the second not). Ideally I would actually have interdigitated lists, such that (for example) every third item is active and the others are not, but I'll settle for two separate groups that run on the same list if that would be too complicated.
Simply extend whatever adapter you are using and add this simple check in getView():
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if(position < 500) {
view.setClickable(false);
view.setEnabled(true);
}
else {
view.setClickable(true);
view.setEnabled(false);
}
return view;
}
I did your simple check (if the row is in the first 500) then changed a couple characteristics.
setEnabled() turns the text grey when it is false.
setClickable() when true this prevents the ListView from receiving the TouchEvent.
Hope that helps!

what's exactly happening in getView

I see this a lot in Adapter extensions override of getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.whatever, null);
}
I get semantically what's happening - "if convertView is null then inflate it", but I don't really understand why - what circumstance would convertView be null, and in what circumstance would it already be a View?
Also (and I know "1 question per post"), but as it relates to the above - what's exactly happens during the inflate method? I know what it does generally ("inflates" a view resource to be parsed and populated), but don't fully grok it...
TYIA
AFAIK, this is how getView works.
Suppose you have a list with 50 items and you can only see 5 items at a time. The getView will be called 5 times initially and the convertView will be null for each row and a new row should be inflated.
As you scroll through the list the getView will be called again as the next rows get visible on the screen. Now since 5 rows are already rendered for the list, these rows will be recycled by updating existing rows with new values to show new rows. At this case the convertView will not be null.
Say your list adapter has 1000 objects. Each object is represented by a view. On the phone screen there's place only for maybe some 10 such views. You scroll the list wanting to see more items. Some items go out of sight because you're scrolling. It makes no sense for the Framework to create more views as they will be exactly the same as those which have just gone out of sight. Framework thus can reuse some views it created before and so it offers to you such a possibility by offering you a non-null convertView.
During the inflate method a View object gets created out of some XML resource. The XML resource has a description of a View sufficient for that View to be created and so the inflate method does that creation. So no view - you create a new one by inflating, there is a view already - you can reuse it (you don't have to but you should)
for full information i recommend you to watch this video
it will tell you alot of information include what is happening in getView()

Categories

Resources