How does scrolling work in the Google+ Android app? - android

I've noticed, while playing around with it, that if you scroll up and down in the stream from the Google+ Android app, the scroll bar changes size depending on the vertical size(s) of the currently visible post(s). For example, if you scroll into a long posting, the bar shrinks in size, and if you scroll into a short post, it lengthens. How is this implemented?
Now, I don't particularly need this feature, but it's just something that has piqued my curiosity.

It's a side effect of using a recycling ListView. As new posts are scrolled in to view, the rest of the items are virtualized - basically Android guesses as to how much space the rest of the list will take up, but it doesn't actually render them so it can't be sure. As you scroll on to a big post, it assumes the rest of the list has big posts in it and therefore the list is longer.
You can get the same functionality by using the convertView parameter of getView like so:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = null;
if(convertView == null)
{
view = // set your view here
}
else
{
view = convertView
}
// set all your properties on the view here.
return view;
}

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.

Cause onClick of a ListView row to offer editing options

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.

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!

How do you display 2 overlapping view in a gridview?

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.

Categories

Resources