I need to check all element on a ListView to set a label only to a one of those.
I can't edit the database or the adapter, I just want to scroll the ListView to perform a check and set a string on a TextView.
#Override
protected void onResume(){
super.onResume();
...
cursor = getCursor();
startManagingCursor(cursor);
adapter = new SimpleCursorAdapter(this,R.layout.profile_item_layout,cursor,from,to);
lst_profiles.setAdapter(adapter);
SharedPreferences customSharedPreference = PreferenceManager.getDefaultSharedPreferences(ProfilerActivity.this.getApplicationContext());
long current = customSharedPreference.getLong(ProfileManager.CURRENT, -1);
toast(lst_profiles.getChildCount()); //display 0
if(current!=-1){
for(int i=0;i<lst_profiles.getChildCount();i++){
if(lst_profiles.getItemIdAtPosition(i)==current)
((TextView)lst_profiles.getChildAt(i).findViewById(R.id.activeLabel)).setText(getString(R.string.active));
else
((TextView)lst_profiles.getChildAt(i).findViewById(R.id.activeLabel)).setText("");
}
}
}
How can I do? I need to wait something?
P.S. Obviusly the ListView is not empty.
That seems to be a nasty hack. But okay...
The thing is, that your list won't have children as long as the list is not displayed to the user. But you have to understand that getChildCount will return the amount of visible list items (so maybe about 10 views) and the position of them will never relate to the actual item position in the adapter.
If you really need to communicate with the views on a such low level you could try to attach a scroll listener to your list:
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
for (int i = 0; i < visibleItemCount; i++) {
View child = getChildAt(i);
// i + firstVisibleItem == the actual item position in the adapter
}
}
Related
I have a ListView with a set of children vertically listed (View objects) to be viewed by the users. I have to track the the user views, say,
a. If a user views a set of items for around 1 second, I should track the impressions.
b. If the same user scrolls the items out of the viewport and return back, I should track again, if he viewed for 1 second.
I tried several options like getGlobalVisibleRect(), getLocalVisibleRect(), getLocationOnScreen() and they are confusing in the first place and didn't help me get the right coordinates and visibility of the child items of the listView.
I checked Track impression of items in an android ListView which is a bit similar to my requirement but I thought to check if there is a better solution. I am new to Android and apologies if I am not clear on some explanations
To get your desired result, I think we have two different solutions. First, create Handler for each of the item and call / remove in scroll view if it is visible. But this is very much stupid one as creating so many Handlers will make your app's life hell.
Second and best way is to use call / remove a single Handler for the entire visible items. If it persist for a time "A second" (1 second for you), use impression count in each of your item's model class and increase it with ++ operator.
You can add scroll listener in your listivew. The script will be like-
ListView listView = null;
int firstVisibleItemIndex = 0;
int visibleCount = 0;
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
for (int i = firstVisibleItemIndex; i < firstVisibleItemIndex + visibleCount; i++) {
try {
//Get impression count from model for the visible item index i
int count = modelList.get(i).getImpressionCount();
//Set impression count to the model for the visible item index i
modelList.get(i).setImpressionCount(++count);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
//Can call this method body in onCreate directly
private void addListScrollListener() {
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// You cat determine first and last visible items here
// final int lastVisibleItem = firstVisibleItem + visibleItemCount - 1;
handler.removeCallbacks(runnable);
firstVisibleItemIndex = firstVisibleItem;
visibleCount = visibleItemCount;
handler.postDelayed(runnable, 1000);
}
#Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
// TODO Auto-generated method stub
}
});
}
I assume that you will bind your ListView with the id in your onCreate method. Also you can call the listener thing in your onCreate after binding the view with the variable.
I hope this will work for your requirement.
Let me know your feedback.
I'm developing a chat application and I'm trying to load 10 messages at a time, and when the user scrolls up it loads 10 more. therefore I used stackFromBottom=true to load the messages from the bottom :
<ListView
android:id="#+id/messagesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transcriptMode="normal"
android:stackFromBottom="true">
And I have a scroll listener to know when the user reached the top (firstVisibleItem == 0 , i.e the first message) so I can load more messages:
messagesList.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem ==0 && !loading && !startApp) {
loading = true;
skip += 10;
fetchMessages = new FetchMessages(); //load more items
fetchMessages.execute(true);
}
}
});
The issue is when you scroll up and reach the top, 10 more messages load but the Listview automatically scrolls to the bottom. I want to stay in the exact same position when new items are added to the Listview, just like any regular chat application.
This is the add messages method:
#Override
protected void onPostExecute(String json) {
if(json!=null){
...
//After Parse JSON ...
chatMessageAdapter.list.add(0,chatMessage); //add the message to the top of the list
chatMessageAdapter.notifyDataSetChanged(); //listview scrolls to the bottom afterwards
}
loading = false;
startApp = false;
}
I tried many of the suggestions in other threads but none of them actually worked. I hope someone could help here.
Many thanks.
what did work for me was that:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// notify dataset changed or re-assign adapter here
// restore the position of listview
mList.setSelectionFromTop(index, top);
Do note you have to remove android:transcriptMode="normal" from your xml, otherwise it wouldn't work.
I am building an application like techcrunch.
I am fetching data from server in JSON format and displaying the data in list view like article title,author name and image.
I have applied pagination means when user scroll more articles load in a list view. My pagination works fine but there is an issue in the scroll function as the fresh or new data loads the scroll dose not aligns with the data.
To clarify more in simple words my scroll-er goes at the top of the page when i am actually scrolling down
this is my code :
listView.setOnScrollListener(new AbsListView.OnScrollListener()
{
#Override
public void onScrollStateChanged(AbsListView absListView, int i)
{
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount){
if (mPreLast != lastItem)
{
mPreLast = lastItem;
onStart();
}
}
}
});`
You can use:
listView.setSelectionFromTop(newPosition, 0);
With this method you can set the position to a ListView, using the first parameter as the new position on the list, and the second parameter can be used to set the distance from the top
I have a ListView with a custom child view as its row layout. My requirement is to invalidate the row's child view only when the ListView is scrolled. Right now I achieve this by calling ChildView.invalidate() from the child view's onDraw() method and things works very well as I expected, but this approach also invalidates the child view even when the ListView is not scrolled, so I observe that it consumes a lot of CPU when the app is running. I am looking for an inexpensive solution for this.
Is there any call back occured when the ListView is scrolled? I could not see anything.
Please answer, Thank you.
protected void onDraw (Canvas canvas) {
drawBackground(canvas);
drawContent(canvas);
this.invalidate();//Recursive calling...? But no error or warnings issued.
//super.onDraw(canvas);
}
#pskink Thanks It works! but did not solve the issue of "do it with an inexpensive approach". Please refer the comments in the code and answer what to do with arguments of onScroll() to only invalidate views which are visible on the screen. View.getChildAt(int).invalidate does not work
import android.app.ListActivity;
import android.widget.ListView;
import android.widget.AbsListView;
public class MyListActivity extends ListActivity implements ListView.OnScrollListener {
...
#Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.list_activity_layout);
getListView().setOnScrollListener(this);
}
....
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
Log.e("MyListActivity", "onScrollStateChanged()");
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
Log.e("MyListActivity", "onScroll()");
Log.e("view.getId():", "" + view.getId());
//view.invalidateViews();//Calling this method does the job, but it seems that It might re-measure
// and redraw each and every views of each and every rows in the ListView..!
// So, still it is an expensive approach. is it?, but better than doing
// recursive calling of invalidate(); am I right?
// I am not satisfied with this approach.
// At least it should be possible to invalidate the views which are visible on the screen, as we expect from -
// the arguments of this method.
// Let me attempt to do it, shown below..
if(visibleItemCount != 0){
int lastVisibleItemCount = firstVisibleItem + visibleItemCount;
for(int i=firstVisibleItem; i<lastVisibleItemCount; i++){
if(view.getChildAt(i) != null){
view.getChildAt(i).invalidate();// Not works.
}
Log.e("view.getItemIdAtPosition(i):", "" + view.getItemIdAtPosition(i));
}
}
}
}
#pskink Here is the exact solution I was looking for. It only invalidates a single child view, which is in visibility range. It works without any issue.
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//view.invalidateViews();//It does.., but still being expensive...!
//The following only invalidates a single child view of the Row view in the visibility range.
//Initializing i=0 may seem unnecessary, but it resolves many unexpected behavior of
//the ListView when it is initialized to i=firstVisibleItem.
if(visibleItemCount != 0){
int lastVisibleItemCount = firstVisibleItem + visibleItemCount;;
for(int i=0; i<lastVisibleItemCount; i++){
mRowView = mListView.getChildAt(i);//returns the row view.
if(mRowView != null){
mChildView = (CustomChildView) mRowView.findViewById(R.id.custom_child_view);//Single child of the Row View.
if(mChildView != null){
mChildView.invalidate();//only invalidates the required Child view of the row view.
}
}
Log.e("Row view pos: ", "" + i);
}
}
}
Scope
I need to scroll to certain position smoothly and then "jump" to another position with setSelection(anotherPosition). This is done to create an illusion of smooth scrolling of (e.g.) 100 items in ListView. smoothScrollToPosition(100) lasts too much, you know.
Problem
setSelection() doesn't wait till smoothScrollToPosition finishes its work, so setSelection() is being called immediately and user sees quick jumping only;
Code
private final int scrollableItems = 20;
int firstVisiblePosition = mListView.getFirstVisiblePosition();
if (firstVisiblePosition < scrollableItems) {
mListView.smoothScrollToPosition(0);
} else {
mListView.smoothScrollToPosition(firstVisiblePosition - scrollableItems);
mListView.setSelection(0);
}
mListView.clearFocus();
Idea
OK, we could change logic of smoothness illusion: first setSelection(), then scroll smoothly (we're scrolling to the very first item on top of the list):
int firstVisiblePosition = mListView.getFirstVisiblePosition();
if (firstVisiblePosition < scrollableItems) {
mListView.smoothScrollToPosition(0);
} else {
mListView.setSelection(scrollableItems);
mListView.smoothScrollToPosition(0);
}
mListView.clearFocus();
final ListView listView = ...;
View listItemView = ...;
listView.smoothScrollBy(listItemView.getHeight() * NUMBER_OF_VIEWS,
DURATION * 2);
listView.postDelayed(new Runnable() {
public void run() {
listView.smoothScrollBy(0, 0); // Stops the listview from overshooting.
listView.setSelection(0);
}
}, DURATION);
Of course, direction of the scroll etc. would need to be adjusted for your use case (go to the top of the list)
EDIT: Old solution could overshoot if the velocity of the scroll was too high, smoothScrollBy(0,0) will stop the smooth scrolling before setting the selection properly and immediately.
Another way is to add an OnScrollListener.
private final int scrollableItems = 20;
int firstVisiblePosition = mListView.getFirstVisiblePosition();
if (firstVisiblePosition < scrollableItems) {
mListView.smoothScrollToPosition(0);
} else {
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
if (i == SCROLL_STATE_IDLE) {
mListView.setSelection(0);
}
}
})
mListView.smoothScrollToPosition(firstVisiblePosition - scrollableItems);
}
mListView.clearFocus();