how can I access a single imageview which is located inside a row of a listview?
I have a listview that contains many rows custom xml. each row contains a imageview and a TextView. Once inserted these lines in listview how can I change the bitmap of an imageview to a specific row?
Assuming you know which position you want to update and you're on the UI thread, you can use
public View getViewForPosition(int position){
int relativePos = position - listview.getFirstVisiblePosition();
if( relativePos < 0 || relativePos > listview.getChildCount()){
return null;
}
return listview.getChildAt(relativePos);
}
A return of null from this function means that position is offscreen. A non null will return the view from getView, and you can then do a findViewById on it to find the child you want.
If calling from a non-UI thread, you can be off by 1 due to calculating as the view mapping is changing. There is a hack to fix this I came up with once, but I'd suggest just not doing it.
Edit: here's the thread safe hack.
public View getViewForPosition(int position){
int relativePos = position - listview.getFirstVisiblePosition();
//Hack for allowing us to get a view for a position that is currently being created
if( (relativePos == listview.getChildCount() || relativePos == -1) && currentProcessingView != null){
return currentProcessingView;
}
if( relativePos < 0 || relativePos >= listview.getChildCount()){
return null;
}
return listview.getChildAt(relativePos);
}
currentProcessingView should be set in getView to the current position's view at the very beginning, and to null at the end of getView. If the position is not currently on screen it will return null, you need to be able to handle that.
You could uniquely tag each of the listview items and then use findViewWithTag on it.
Related
I'm trying to implement selection of the first recycler view item + scrolling to position in a very simillar way of Sunshine ForecastFragment
https://github.com/udacity/Advanced_Android_Development/blob/master/app/src/main/java/com/example/android/sunshine/app/ForecastFragment.java
But I keep facing 2 problems :
scrolling is not always correct and
viewHolder is sometime null (and I need access to it in order to display the first row)
I first thaught this is due to the fact that when scrolling is incorrect the view is not on screen and the holder is not binded. So I tried several things.
First I tried that
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
Log.e("FF", Thread.currentThread().getStackTrace()[2] + "swapCursor ");
mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (mRecyclerView.getChildCount() > 0) {
// Since we know we're going to get items, we keep the listener around until
// we see Children.
mRecyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
int position = mAdapter.getSelectedItemPosition(getContext());
if (position == RecyclerView.NO_POSITION) {
position = mPosition == null ? getFirstContactPosition(mAdapter) : mPosition;
}
mRecyclerView.smoothScrollToPosition(position);
//this method findViewHolderForAdapterPosition will always return null if we
// call it after a swapCursor
//(because it always return null after a notifyDataSetChanged) that's why we
// call findViewHolderForAdapterPosition in the onPreDraw method
RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(position);
Log.e("FF", Thread.currentThread().getStackTrace()[2] + "vh " + vh);
if (null != vh) {
if (getResources().getInteger(R.integer.orientation) == MainActivity.W700dp_LAND)
mAdapter.selectView(vh);
}
mPosition = position;
return true;
}
return false;
}
});
}
Then I tried replacing
mRecyclerView.smoothScrollToPosition(position);
with every posible solutions I found
mRecyclerView.getLayoutManager().scrollToPosition(position);
mLayoutManager.scrollToPosition(position);
mLayoutManager.scrollToPositionWithOffset(position, 20);
mRecyclerView.scrollToPosition(position);
then I tried executing the scroll in postDelayed thread as follow
final int finalPosition = position;
mRecyclerView.postDelayed(new Runnable() {
#Override
public void run() {
mRecyclerView.smoothScrollToPosition(finalPosition);
}
}, 1000);
all of that with no success.
Now I have no clue in what directions to look for. And I'm still not completely sure of the fact that the viewHolder is null is due to the incorrect scroll.
Here are the correct behavior I have when I select an onScreen item
on first orientation change
- scrolling is correct
- view holder is not null
on second and following orientation change
- same as before, it work well
And here is the behaviour I have when I select an offScreen item
on first orientation change
- scrolling is correct
- view holder IS null
on second and following orientation change
- scrolling is incorrect (seems to scroll to the last onScreen item)
- view holder is NOT null
on third orientation change
- like first
on fourth orientation change
- like second
etc
Note by onScreen I mean the items that are first shown to the user without need to scroll.
Any ideas ?
Thanks in advance.
I want to find out the position or ids related to a ListView's items: only those ones which are completely visible on the screen.
Using listview.getFirstVisibleposition and listview.getLastVisibleposition takes partial list items into account.
I followed a little bit similar approach as suggested by Rich, to suit my requirement which was to fetch completely visible items on screen when List View is scrolled every time.
This is what i did
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//Loop to get tids of all completely visible List View's item scrolled on screen
for (int listItemIndex = 0; listItemIndex <= getListView().getLastVisiblePosition() - getListView().getFirstVisiblePosition(); listItemIndex++) {
View listItem = getListView().getChildAt(listItemIndex);
TextView tvNewPostLabel = (TextView) listItem.findViewById(R.id.tvNewPostLabel);
if (tvNewPostLabel != null && tvNewPostLabel.getVisibility() == View.VISIBLE) {
int listTid = (int) tvNewPostLabel.getTag();
if (listItem.getBottom() < getListView().getHeight()) {//If List View's item is not partially visible
listItemTids.add(listTid);
}
}
}
}
I have not tried this, but here are the pieces of the framework that I believe will get you to what you're looking for (at least this is what I'd try first)
As you've stated, you should get the last visible position from the list view using ListView.getLastVisiblePosition()
You can then access the View representing this position using ListView.getChildAt(position)
You now have a reference to the view, which you can call a combination of View.getLocationOnScreen(location) and View.getHeight()
Also call View.getLocationOnScreen(location) and View.getHeight() on the ListView. y + height of the View should be less than or equal to y + height of the ListView if it is fully visible.
I try to animate a single item in my ListView, I've figured out that this is kinda hard. But, after some googling I came up with some codesnippets that I combined, but when I try to call my ListView's getChildAt in order to return the View, this returns null.
public void animateSingleItemInListView() {
final Animation animBounce = AnimationUtils.loadAnimation(context, R.anim.bounce);
int totalItemsInListView = lastCases.getCount();
int wantedPosition = 1; // Whatever position you're looking for
int firstPosition = lastCases.getFirstVisiblePosition() - lastCases.getHeaderViewsCount(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
// Say, first visible position is 8, you want position 10, wantedChild will now be 2
// So that means your view is child #2 in the ViewGroup:
int childs = lastCases.getChildCount();
if (wantedChild < 0) {
Log.w("UPDATEUI", "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
// Could also check if wantedPosition is between listView.getFirstVisiblePosition() and listView.getLastVisiblePosition() instead.
View wantedView = lastCases.getChildAt(firstPosition);
wantedView.setAnimation(animBounce);
}
From the code:
The totalItemsInListView returns 10, which is correct number of rows in my ListView. The integer firstPosition is of course 0, but the getChildAt() method only returns null. How come? How can I get the first View from my ListView?
try to use
lastCases.getItemAtPosition(firstPosition)
I have a ListView, which contains more elements then I can display at one time.Now I want to get Index off all Elements, which are full visible ( -> excluding those that are only partially visible).
At this moment I use getFirstVisiblePosition() & getLastVisiblePosition() into an for-loop to iterate them, but these method is not accurate as I want to.
Is there any better solution?
A ListView keeps its rows organized in a top-down list, which you can access with getChildAt(). So what you want is quite simple. Let's get the first and last Views, then check if they are completely visible or not:
// getTop() and getBottom() are relative to the ListView,
// so if getTop() is negative, it is not fully visible
int first = 0;
if(listView.getChildAt(first).getTop() < 0)
first++;
int last = listView.getChildCount() - 1;
if(listView.getChildAt(last).getBottom() > listView.getHeight())
last--;
// Now loop through your rows
for( ; first <= last; first++) {
// Do something
View row = listView.getChildAt(first);
}
Addition
Now I want to get Index off all Elements, which are full visible
I'm not certain what that sentence means. If the code above isn't the index you wanted you can use:
int first = listView.getFirstVisiblePosition();
if(listView.getChildAt(0).getTop() < 0)
first++;
To have an index that is relative to your adapter (i.e. adapter.getItem(first).)
The way I would do this is I would extend whatever view your are passing in getView of the ListView adapter and override the methods onAttachedToWindow and onDetachedToWindow to keep track of the indexes that are visible.
Try onScrollListner and you can able to use getFirstVisiblePosition and getLastVisiblePosition.
This this link, it contain similar type of problem. I suppose you got your answer there..,.
The above code is somewhat correct. If you need to find the completely visible location of view use below code
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
View v = null;
if (scrollState == 0) {
int first =0;
if (view.getChildAt(first).getTop() < 0)
first++;
int last = list.getChildCount() - 1;
if (list.getChildAt(last).getBottom() > list
.getHeight())
last--;
// Now loop through your rows
for ( ; first <= last; first++) {
// Do something
View row = view.getChildAt(first);
// postion for your row............
int i=list.getPositionForView(row);
}
}
// set the margin.
}
I have a listView with a custom adapter. When something happens (a click in a child) I do some calculation things and modify the child View. IF some condition has been fulfilled then other child unrelated to the clicked child should be modified.
This sometimes works, but sometimes fails and the DDMS says that the view is null...
Let me show you the code:
if(invalidaEste != -1)
{
try
{
View v = lv_data.getChildAt(invalidaEste);
if( v== null)
{
Log.e("MY_LOG", "SIZE " + lv_data.getCount());
Log.e("MY_LOG", "IS_NULL " + String.valueOf(invalidaEste));
}
if(invalidaEste >= lv_data.getFirstVisiblePosition() &&
invalidaEste <= lv_data.getLastVisiblePosition())
{
RelacionFacturaPago rpf = (RelacionFacturaPago)lv_data.getAdapter().getItem(invalidaEste);
TextView tv = (TextView)v.findViewById(R.id.tv_pendiente);
tv.setText(Formato.double2Screen(rpf.getPorPagar()));
}
}
catch (Exception e)
{
Log.e("MY_LOG", "FAIL");
Log.e("MY_LOG", String.valueOf(invalidaEste));
}
}
invalidaEste is the view that I want to modify.
When v is null I log the index to check if it is OK. Always is smaller or equal than the listView.getCount()
Why is this happening?
More data: The code is inside of the onAnimationStart(Animation animation) of an AnimationListener listener.
Because of view recycling, listView.getChildAt() will only return a view for the positions it is displaying, and maybe one more. Maybe if you share more of your code we can help you figure out how to best tackle the problem.
Dmon and Azertiti are both correct... once your list is scrolled you find yourself in trouble. If the view isn't visible, then it doesn't exist (i.e. has been recycled by Android). You'll re-build the view once it's scrolled in.
Doing something like this should work:
View view;
int nFirstPos = lv_data.getFirstVisiblePosition();
int nWantedPos = invalidaEste - nFirstPos;
if ((nWantedPos >= 0) && (nWantedPos <= lv_data.getChildCount())
{
view = lv_data.getChildAt(nWantedPos);
if (view == null)
return;
// else we have the view we want
}
If that child is not visible on screen it means there is no View for it. I believe this is your not working case.
A good practice is to change the data behind your list adapter and call notifyDataSetChanged() on the adapter. This will inform the list the adapter has changed and paint again the views.
If you really want to manually update the view I guess the only solution is to retain the new values somewhere and wait until the View becomes visible. At that point you have a valid reference and can do the updates.
I test it with one line of code when I need to go within a valid position of a Listview. here is your variables used as an example. where lv_data is the ListView and tv is your TextView.
if ( lv_data.getChildAt(lv_data.getPositionForView(tv)) != null) {
int position = lv_data.getPositionForView(tv);
}