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.
Related
I know it's a little bit generic. So I have a recyclerView, and inside it there are multiple items which can expand and collapse (each of them has a webview and expand and collapse to decide if the full content needs to be shown). I need to set images for a BiImageView in the viewAdapter like this
ItemViewHolder viewHolder = (ItemViewHolder) recyclerView
.findViewHolderForLayoutPosition(position);
viewHolder.rightIndicator.setImageResources()
However it only works when every item is collapsed, when the first item expand, it will take up the screen and other items will be hidden or say below the bottom of the screen. Then only the first few items can set the image resource. For other hidden items, the viewHolder is null. It looks like recyclerView removed them from layout, I'm wondering if there is any way to make sure I can get the viewHolder? Like some callback? I tried onCreate and onBind, but when I scroll down or collapse the first item, these two methods didn't get called.
I think, I can help you with some pseudo code, but I dont remember how to use it with view model. First of all, you have to change all your views in onBindViewHolder method in depend on your model, then in your model class you must have field, for example
boolean expanded = false; // initial state
And in your onBindViewHolder:
if (modelsList.get(position).isExpanded()){
//show expanded view
} else {
//show collapsed view
}
After it, you can set onClickListener to some view in your collapsed view, to expand:
view.setOnClickListener(new OnClickListener(){
public void onClick(View v){
modelsList.get(position).setIsExpanded(!modelsList.get(position).getIsExpanded());
MyAdapter.this.notifyItemChanged(position);
}
When you will click on this view, your model will change and RecyclerView will recreate view binded to this model with new data.
Hope it will help you
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 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.
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 woud like to create a listview that alternates background images. For example, the first item would have background image a and the second item would have background image b and the third backgroud a. In basic terms I would like help on creating a listview that for every odd item (egg first, third, fith) has a certian background image different to those listview items which are even (egg second, fourth, and sixth listview item). Here's an exampe.
http://www.gadgetreview.com/wp-content/uploads/2011/10/SIRI-Reminders.jpg
In this example the speech bubbles are background image and each different background image is a different listview item.
In your list adapter in the getView method divide the position attribute that gets sent into the method by 2. If the remaining number is 0 than you are in the even row of your listview. Depending on that you can change the layout of your list view item.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(position % 2 = 0)
//set layout for even row
}else{
//set layout for odd row
}
Last time that I tried I didn't find an xml parameter to do that, but you can try to use the same workaround used in this question:
Stack Overflow: How do I alternate colors in between Listviews?
you have to make your own custom adapter and then in the following method:
public View getView(int position, View convertView, ViewGroup parent) {
}
you can use position to change the background if position is odd
There is SetEmptyView method in list view use it
listView.setEmptyView( findViewById( R.id.empty_list_view ) );