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.
Related
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;
}
I have a ListView which I want to be able to toggle between two states. In the first state, all items in the list are a square shape besides some text. In the second state, all items in the list are the same except that the square shape is now a circle shape. What I want is to be able to transition the square in all visible items to a circle and vice-versa (using a ViewSwitcher or something like that). Anyone know whether this is possible and how I could go about implementing it? (My ListView's data comes from an ArrayAdapter.)
Turned out to be simpler than first thought. In the getView(int position, View convertView, ViewGroup parent) method of my adapter, I inflate a new View if convertView is null, and following that I find my ViewSwitcher from within my View, check the ViewSwitcher.getDisplayedChild() method, and call ViewSwitcher.showNext() or ViewSwitcher.showNext() as appropriate.
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.
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.
I'm working on displaying images to the screen in a Gridview and currently am using an ImageAdapter to load the downloadeded images into the Gridview. If tapped, I'm drawing another image on top of the selected image to signify selection, the problem is I have to add them as children via the RelaytiveLayout, so when I scroll the images stay where they were created and don't follow along with the scroll. So I then tried creating a custom view with 2 imageviews overlapping, setting one hidden and the other was filled by the Adapter. When selected, the plan was to unhide the overlapping image. When loading from the adapter, my custom view(which actually had to extend ViewGroup in order to contain the two imageviews, as Views can't add children) would load a frame that was selectable, but completely transparent with neither image displaying. I know it was selectable, because a blue box would appear in the dimensions I set when I touched the screen. To display the custom view, I was loading my it in the publc getView() function. Am I going about this the wrong way? I've attempted quite a bit to see if anyone else had done something like this, but all I could find was tutorials for drawing overlapping images on a RelativeLayout or loading images into a GridView, but not both. My apologies, I'm new to Android.
If I am not wrong, what you need is selected state for a view in gridView when you tap. I don't think drawing another image on top of existing is necessary. You can change the existing view background itself using viewHolder pattern as you are using custom adapter. Have a look at following link. You may get some idea how to do.
Dynamically hiding Views in Custom Listview in Android
ListViews do not retain a visual indication of focus (or selection) while in touch mode. You will only see this when you use the hardware keyboard or controls to navigate your UI.
See the Google Touch Mode Android Blog article for more details.
So, if you are only using touch mode, you will never see focus or selection on ListViews.
What you are doing will work fine with few modifications.
Place the ImageView(child) inside a container(parent like RelativeLayout,etc).
Set some padding to the parent so that it's background is visible.
When clicked change the parents background of the clicked item(in the onClick() method of the OnClickListener of the GridView).
call notifyDataSetChanged() on the adapter (to redraw the currently visible items)
here is a sample code for this:
grid.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick (AdapterView<?> parent,
View v, int position, long Id)
{
highlighted = position; //highlighted is a global variable
//container is the root view of the list row layout
LinearLayout container = (LinearLayout)v.findViewById(R.id.container);
container.setBackgroundResource(R.drawable.highlighted_backg);
mGridAdapter.notifyDataSetChanged();
}
});
Code for getView() method:
public View getView (int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
if(convertView == null) {
convertView = inflater.inflate(R.layout.row_item, null);
holder = new ViewHolder();
holder.itemName1 = (TextView)convertView.findViewById(R.id.text1);
...
holder.container = (LineaLayout)convertView.findViewById(R.id.container);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if(MainActivity.highlighted == position) {
holder.container.setBackgroundResource(R.drawable.highlighted_backg);
}else {
holder.foodItemCol1.setBackgroundResource(R.drawable.normal_back);
}
return convertView;
}
ImageView iv = (ImageView)v.findViewById(R.id.grid_item_image);
iv.setBackgroundResource(imageIDs[4]);
iv.setImageResource(imageIDs[2]);
You can set color filter for the selected imageview, for example imageView.setColorFilter(Color.parse("#77000000"));(add it in your onClick method of the ImageView's onClickListener), this will add a semitransparent gray layer over the selected ImageView to identified it was selected.