I know the methods getFirstVisiblePosition() and getLastVisiblePosition() but these are only for the visible grid, if you check for the visibility for the element at position in the getView(int position, View convertView, ViewGroup parent) you will see that it lies outside the range of visible elements(which makes perfect sense). What can I do to check if an element at a position is visible or has been requested by the gridView/listView. Something which returns true in the getView(...) method itself.
Why I am trying to do it:
I have a bunch of images that I am showing in a gridView. The images have to be fetched via the network(I am caching them once they have been downloaded), so I have created an asyncTask to download the image and then show it in the corresponding grid. Since there might be a lot of images and the user might quickly scroll down, I want to insert some check in the onPreExecute() method which can tell me if it visible, otherwise I cancel the task for now.
if (!requested()) {
this.cancel(true);
}
I am looking for appropriate code for requested().
Really sorry for such a long question, finding it really difficult to explain. Any solutions or modifications to the question are most welcome.
I'm not sure if I understand your question. Do you want to know if, given a certain position, that view has ever been shown?
If that's what you want, you should have a boolean array in your list/grid adapter. In the getView function you set that position to true. Then you only have to implement a function to check if a certain position is true.
public class MyAdapter extends BaseAdapter {
private boolean[] hasBeenSeen;
public View getView (int position, View convertView, ViewGroup parent) {
hasBeenSeen[position] = true;
...
}
public boolean checkPosition(int position){
return hasBeenSeen[position];
}
}
The boolean array has to be initialized when you set the list/grid content.
You can try using View.getLocationOnScreen(); If the x and y positions are within your screen bounds then it should be on your screen. I'm not sure what this will return if it isn't on your screen, though.
Related
I have a custom list view which is being popluated via an array adaptor.
Each item/row contains three buttons and some related textViews.
All elements in a row describe the details for a device on the cloud. So data is fetched from the cloud and then the list is populated. No. of rows is equal of the number of devices.
Everything was fine till I added the feature for a periodic update for the items.
The problem is that after each periodic update it over writes the data for a device in the wrong row.
I tried two ways to refresh each row.
I kept a map for (DeviceID and view) and then based on the deviceId
i would get the view and update it. Now,this didn't work as the views are reused and so as i scroll
down, basically the same view is reused as shows the new data. And
so the map entry of the previous device is over written with the new
one.
I tried to directly call getView() and pass the position but that
also didn't work.
I understand that the views are reused so there is no way to know exactly which view is associated with a deviceID.
But could some please help me figure out how to update the correct view with the correct data?
Thanks.
If you are using Holder pattern, then there is a way to do this.
Step 1: Add one attribute i.e. position to Holder.
private class ViewHolder {
....
....
int position;
}
Step 2: Initialise the holder position into getView()
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
...
holder.position = position;
}
....
}
Step 3: Check holder position and view's position values. If both are same then do your task.
if (mHolder.position == mPosition) {
// This is you required row. Do your task.
}
Read Async loading for more details.
I have a gallery where I'm displaying a number of ID cards as images.
When the user clicks on an image, I want to change the image to another one which shows the 'back' of the card.
I have bound an arraylist of objects to the gallery, each object contains the front and back images.
I have two image views, one for the front and one for the back image. The gallery will initially show all front images ( the 'back' image view visibility is set to GONE). My idea was to change the visibility of the relevant image view when the user clicks.
That's about as far as I've got, buy I'm not sure how to implement the click functionality to change the views.
Any assistance would be greatly appreciated.
A Gallery is a specialization of AdapterView, which also contains ListView, Spinner, GridView... All of these have in common to display n times the same 'kind' of view with different data.
These particular views use an Adapter to handle the view creation and population process. The getView method is called on the adapter, providing this class with the data for creating the view for a particular row (cell, gallery item ...).
The getView method is called when the AdapterView deems it necessary, and the developer has no control on which view gets created or when it does.
Therefore, it is practically impossible to modify a child of the AdapterView directly. It would result it the modification being arbitrarily overwritten, or even transferred to another row of the view.
Instead, to act on the child views, it is necessary to act on the data that are used to populate the view. (Note that this enforce the MVC model, as the view remains a reflection of the model, rather than being manipulated by the controller.)
In this particular example, the view is to display a different image based on the checked status of the data. To achieve that, add a checked member to the model :
public class MyModel {
// ... Other members
public boolean checked = false;
}
Use that value in getView :
#Override
public View getView(int position, View convertView, View parent) {
MyModel current = getItem(position);
// ... Inflate and populate
if (current.checked) {
// Specific checked behavior
} else {
// Not checked behavior
}
}
The checked value is to be modified in the controller, probably a OnItemClickListener:
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Here, there can be several ways to access the clicked item.
// I assume myAdapter is the adapter used for the gallery
MyModel current = myAdapter.get(position);
current.checked = !current.checked;
myAdapter.notifyDataSetChanged();
}
I am new to Android development and reading through some example code. I have copied one method from the sample code in an Adapter class (derived from ArrayAdapter), the derived class has a checkbox in addition to the text view:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItem = super.getView(position, convertView, parent);
CheckedTextView checkMark = null;
ViewHolder holder = (ViewHolder) listItem.getTag();
if (holder != null) {
checkMark = holder.checkMark;
} else {
checkMark = (CheckedTextView) listItem.findViewById(android.R.id.text1);
holder = new ViewHolder(checkMark);
listItem.setTag(holder);
}
checkMark.setChecked(isInCollection(position));
return listItem;
}
private class ViewHolder {
protected final CheckedTextView checkMark;
public ViewHolder(CheckedTextView checkMark) {
this.checkMark = checkMark;
}
}
The sample code is to optimize the getView by caching the View within a ViewHolder object.
Where I am confused is I thought the convertView, if not null, would be re-purposed and then the View data is populated into it and returned.
If this is the case, then how could the setTag / getTag methods called in the code be relied upon? It would seem that the same object would have to be retrieved in order for it to work?
perhaps view returned from getTag on a subsequent call is for a different list item, and returns the wrong view
Adapters use a RecycleBin. This class allows the ListView to only create as many row layouts as will fit on the screen, plus one or two for scrolling and pre-loading. So if you have a ListView with 1000 rows and a screen that only displays 7 rows, odds are the ListViiew will only have 8 unique Views.
Now to your question using my example above: only eight row layouts and 8 subsequent ViewHolders are ever created. When the users scrolls no new row layouts are ever created; only the content of the row layout changes. So getTag() will always have a valid ViewHolder that references the appropriate View(s).
(Does that help?)
You're on the right track, here's some information that may help make more sense of how ListViews work:
A simple implementation of the getView() method has two goals. The first is inflating the View to be shown on the list. The second is populating the View with the data that needs to be shown.
As you stated, ListViews re-purpose the Views that compose the list. This is sometimes referred to as view recycling. The reason for this is scalability. Consider a ListView that contains the data of 1000 items. Views can take up a lot of space, and it would not be feasible to inflate 1000 Views and keep them all in memory as this could lead to performance hits or the dreaded OutOfMemoryException. In order to keep ListViews lightweight, Android uses the getView() method to marry Views with the underlying data. When the user scrolls up and down the list, any Views that move off the screen are placed in a pool of views to be reused. The convertView parameter of getView() comes from this list. Initially, this pool is empty, so null Views are passed to getView(). Thus, the first part of getView should be checking to see if convertView has been previously inflated. Additionally, you'll want to configure the attributes of convertView that will be common to all list items. That code will look something like this:
if(convertView == null)
{
convertView = new TextView(context);
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
The second part of an implementation of getView() looks at your underlying data source for the list and configures this specific instance of the View. For example, in our test list, we may have an Array of Strings to set the text of the view, and want to set the tag as the current position in the Data of this View. We know which item in the list we're working with based on the position parmeter. This configuration comes next.
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
This allows us to minimize the amount of time we spend inflating/creating new views, a costly operation, while still being able to quickly configuring each view for display. Putting it all together, your method will look like this:
#Override
public View getView(int position, View convertView, ViewGroup)
{
if(convertView == null)
{
convertView = new TextView(context);
//For more complex views, you may want to inflate this view from a layout file using a LayoutInflator, but I'm going to keep this example simple.
//And now, configure your View, for example...
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
//Configure the View for the item at 'position'
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
//Finally, we'll return the view to be added to the list.
return convertView;
}
As you can see, a ViewHolder isn't needed because the OS handles it for you! The Views themselves should be considered temporary objects and any information they need to hold onto should be managed with your underlying data.
One further caveat, the OS does nothing to the Views that get placed in the pool, they're as-is, including any data they've been populated with or changes made to them. A well-implemented getView() method will ensure that the underlying data keeps track of any changes in the state of views. For example, if you change text color of your TextView to red onClick, when that view is recycled the text color will remain red. Text color, in this case, should be linked to some underlying data and set outside of the if(convertView == null) conditional each time getView() is called. (Basically, static setup common for all convertViews happens inside the conditional, dynamic setup based on the current list item and user input happens after) Hope this helps!
Edited - Made the example simpler and cleaned up the code, thanks Sam!
How can I get the position of current image displaying in Gallery view while it is swiping?
So I don't think that is possible because of the way adapter views work. Basically most if not all calls to the adapter view's adapter have to be on the UI thread to work properly. And the animation is definitely on the UI thread. So since they both can't be happening at the same time on the same thread, you can't actually (accurately) read the position until the swiping animation is over.
If you ask the adapter getSelectedItemPosition() on any other thread it might give you the current position but might also be stale for some time since I am guessing the UI thread is going to update the value without checking for an exclusive lock.
We can get the position when we are swiping the image in gallery view,you overwrite Gallery View's setOnItemSelectedListener.
We have two methods from that listener like
avatar_gallery.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent,
View view,
int position,
long id){
Log.v("Selected", ""+position);
}
#Override
public void onNothingSelected(AdapterView<?> parent){
}
}
This question already has answers here:
Android ListView Refresh Single Row
(7 answers)
Closed 8 years ago.
I'm wondering if it is possible to rerender just one element in a listview? I assume by calling notifyDatasetChanged() is gonna rerender the whole list?
Thanks,
you can't render (refresh) a single row, but instead you can get the requested view and make chages on it directly by calling yourListView.getChildAt(int VisiblePosition); where the visiblePostion is the position in the ListView minus yourListView.getFirstVisiblePosition()
Like this :
View v = listViewItems.getChildAt(position -
listViewItems.getFirstVisiblePosition());
v.setBackgroundColor(Color.GREEN);
I hope this helps...
You can, but it's a bit convoluted. You would have to get the index of the first visible item in the list and then use that do decide how how far down in the list of visual items the item is that needs updated, then grab its view and update it there.
It's much easier to just call notifyDatasetChanged().
Also you can use this:
myListView.invalidateViews();
dataAdapter.remove(dataAdapter.getItem(clickedpos));
dataAdapter.insert(t.getText().toString(), clickedpos);
This is how I did it:
Your items (rows) must have unique ids so you can update them later. Set the tag of every view when the list is getting the view from adapter. (You can also use key tag if the default tag is used somewhere else)
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
For the update check every element of list, if a view with given id is there it's visible and update must be performed on it.
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
It's actually easier than other methods and better when you dealing with ids not positions! Also you must consider scenario when your view get invisible and visible again.
You need to keep track of your adapter (or custom adapter if you are set on fancy features). When you change the data for an item, simply change the fields you are interested in , in your adapter.
Then call notifyDatasetChanged , and the changes will be reflected in your listview.
Note that this approach works exactly the same for Gallery Views as well.