ListView inside a ScrollView [duplicate] - android

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Listview inside ScrollView is not scrolling on Android
I have a ListView inside a ScrollView and the problem is that the ScrollView is scrolling but ListView doesn't scroll. I think this is happening because of that ScrollView. Does anyone have a solution for this issue?

ListViews have built-in scrolling capabilities. Encapsulate it in any other layout like a LinearLayout or RelativeLayout.

I required having a listview inside a scrollview in order to prevent the entire screen from being extremely long. This allows you have to set the number of items displayed on the list, while the rest will be scrolled.
I use the following class in my app and so far it seems to work terrific.
public class NestedListView extends ListView implements OnScrollListener,
OnTouchListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 4;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null,
this);
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
listViewTouchAction = event.getAction();
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}

Use the following method and enjoy!
private void setListViewScrollable(final ListView list) {
list.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
listViewTouchAction = event.getAction();
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, 1);
}
return false;
}
});
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, -1);
}
}
});
}
listViewTouchAction is a global integer value.
If you can replace the line
list.scrollBy(0, 1);
with something else please share it with us.

You shouldn't nest a ListView inside a ScrollView.
If you're trying to scroll some other views along with the ListView, you might check out this answer.

Related

Android ListView handle scroll event to expand it fullscreen

I have a layout made of a MapView and a Listview 50-50 (image link below)
http://imgur.com/uRIS9AN
I want to handle the scroll event of the ListView, to expand it to fullscreen before it scrolls through the elements of the list.
Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.google.android.gms.maps.MapView
android:id="#+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fortress_listview">
</ListView>
</LinearLayout>
</LinearLayout>
take a look on my code which i am using here, Use this as in xml class :
<NestedListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fortress_listview">
Add this NestedListView Class in your app package :
public class NestedListView extends ListView implements View.OnTouchListener, AbsListView.OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}

how to set Listview height dynamically having multiline textview inside item in android

I am using code given at many places-
public void setListHeight(ListView list, CustomListPairingAdapter adapter, int screenRatio)
{
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int _hieght = size.y;
if (adapter != null) {
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, list);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = list.getLayoutParams();
totalHeight=totalHeight + (list.getDividerHeight() * (adapter.getCount()));
int desiredHeight=_hieght*screenRatio/100;
if(desiredHeight<=totalHeight)
params.height = desiredHeight;
else
params.height = totalHeight;
list.setLayoutParams(params);
list.requestLayout();
}
}
But it does not work when list item has multiline textview. How to resolve this problem. any ideas ?
Thanks already
int myHeight=20;
LayoutParams par=new RelativeLayout.LayoutParams
(LayoutParams.MATCH_PARENT, myHeight);
//Replace RelativeLayout with the parent layout of list view
ListView t=(ListView)findViewById(R.id.mylist);
t.setLayoutParams(par);
You can try with this custom listview to avoid listhieght having multiline textview problem
<namespace.epicurio.NestedListView
android:id="#+id/listComments"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:divider="#000000"
android:dividerHeight="1.0sp">
</namespace.epicurio.NestedListView>
public class NestedListView extends ListView implements OnTouchListener, OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}

Android add a ListView inside of ScrollView

I know it is a very bad practice that "don't put a ListView into a ScrollView" since ListView has it's own scroll. However, I have couple of other items (buttons and textviews) additional to ListView. For those, I definitely need to use a ScrollView. I found some solution (http://nex-otaku-en.blogspot.com/2010/12/android-put-listview-in-scrollview.html) to prevent collapsing the Listview. But, as i said, I need a ScrollView that encapsulates all items on my XML form. I have added my xml code, Please guide how to achieve this.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/listView1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice">
</ListView>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="#+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button1" />
</LinearLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/longtext" />
</LinearLayout>
</ScrollView>
ListView by itself is scrollable. Do not put ListView inside a scroll view.
Move your ListView outside scorllview. You can also add a header and footer to the listview.
Check the video by google
https://www.youtube.com/watch?v=wDBM6wVEO70
I found this solution from another post & found it working.
While all other solutions work when the list-items are of same height, this solution still provides perfect result even if the list-items are of variable height.
Use this customized class in the xml & you are good to go.
public class NestedListView extends ListView implements OnTouchListener, OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}
You can use the Expandable Listview for using the listview inside the scrollview, it will expends the listview as long the content length, create a Custom class with name ExpandableHeightListView
package com.knight.utils;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ListView;
public class ExpandableHeightListView extends ListView
{
boolean expanded = false;
public ExpandableHeightListView(Context context)
{
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs,int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
After that Programmatically add this line into your code, it will expend the listview in side the scrollview,
Listview listview=(Listview)findviewbyid(R.id.listview1);
((ExpandableHeightListView)listview).setExpandable(true);

Detect Scroll Up & Scroll down in ListView

I have the following requirement:
At first, data for page no: 2 is fetched from the server & the items
are populated in a ListView.
Considering that both the prev page & next page are available in a scenario, the following code has been added:
if(prevPageNo > 0){
mListViewActual.setOnScrollListener(this);
}
if(nextPageNo > 0){
mListViewActual.setOnScrollListener(this);
}
What conditions should I put to detect scroll up & scroll down on the following methods:
void onScroll(AbsListView view, int firstVisibleItem, int
visibleItemCount, int totalItemCount)
void onScrollStateChanged(AbsListView view, int scrollState)
After the action: scroll up & scroll down is detected , accordingly a service will be called with either the prev page no or next page no , to fetch the items to be populated in the Listview.
Any inputs will be helpful.
Gone through the following links but its not returning the correct scroll up / scroll down action:
link 1
link 2
try using the setOnScrollListener and implement the onScrollStateChanged with scrollState
setOnScrollListener(new OnScrollListener(){
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
final ListView lw = getListView();
if(scrollState == 0)
Log.i("a", "scrolling stopped...");
if (view.getId() == lw.getId()) {
final int currentFirstVisibleItem = lw.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
mIsScrollingUp = false;
Log.i("a", "scrolling down...");
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
mIsScrollingUp = true;
Log.i("a", "scrolling up...");
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
});
Here is a working modified version from some of the above-indicated solutions.
Add another class ListView:
package com.example.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
public class ListView extends android.widget.ListView {
private OnScrollListener onScrollListener;
private OnDetectScrollListener onDetectScrollListener;
public ListView(Context context) {
super(context);
onCreate(context, null, null);
}
public ListView(Context context, AttributeSet attrs) {
super(context, attrs);
onCreate(context, attrs, null);
}
public ListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
onCreate(context, attrs, defStyle);
}
#SuppressWarnings("UnusedParameters")
private void onCreate(Context context, AttributeSet attrs, Integer defStyle) {
setListeners();
}
private void setListeners() {
super.setOnScrollListener(new OnScrollListener() {
private int oldTop;
private int oldFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (onScrollListener != null) {
onScrollListener.onScrollStateChanged(view, scrollState);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (onScrollListener != null) {
onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (onDetectScrollListener != null) {
onDetectedListScroll(view, firstVisibleItem);
}
}
private void onDetectedListScroll(AbsListView absListView, int firstVisibleItem) {
View view = absListView.getChildAt(0);
int top = (view == null) ? 0 : view.getTop();
if (firstVisibleItem == oldFirstVisibleItem) {
if (top > oldTop) {
onDetectScrollListener.onUpScrolling();
} else if (top < oldTop) {
onDetectScrollListener.onDownScrolling();
}
} else {
if (firstVisibleItem < oldFirstVisibleItem) {
onDetectScrollListener.onUpScrolling();
} else {
onDetectScrollListener.onDownScrolling();
}
}
oldTop = top;
oldFirstVisibleItem = firstVisibleItem;
}
});
}
#Override
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
public void setOnDetectScrollListener(OnDetectScrollListener onDetectScrollListener) {
this.onDetectScrollListener = onDetectScrollListener;
}
}
And an interface:
public interface OnDetectScrollListener {
void onUpScrolling();
void onDownScrolling();
}
And finally how to use:
com.example.view.ListView listView = (com.example.view.ListView) findViewById(R.id.list);
listView.setOnDetectScrollListener(new OnDetectScrollListener() {
#Override
public void onUpScrolling() {
/* do something */
}
#Override
public void onDownScrolling() {
/* do something */
}
});
In your XML layout:
<com.example.view.ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
This is my first topic, do not judge me harshly. =)
this is a simple implementation:
lv.setOnScrollListener(new OnScrollListener() {
private int mLastFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(mLastFirstVisibleItem<firstVisibleItem)
{
Log.i("SCROLLING DOWN","TRUE");
}
if(mLastFirstVisibleItem>firstVisibleItem)
{
Log.i("SCROLLING UP","TRUE");
}
mLastFirstVisibleItem=firstVisibleItem;
}
});
and if you need more precision, you can use this custom ListView class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
/**
* Created by root on 26/05/15.
*/
public class ScrollInterfacedListView extends ListView {
private OnScrollListener onScrollListener;
private OnDetectScrollListener onDetectScrollListener;
public ScrollInterfacedListView(Context context) {
super(context);
onCreate(context, null, null);
}
public ScrollInterfacedListView(Context context, AttributeSet attrs) {
super(context, attrs);
onCreate(context, attrs, null);
}
public ScrollInterfacedListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
onCreate(context, attrs, defStyle);
}
#SuppressWarnings("UnusedParameters")
private void onCreate(Context context, AttributeSet attrs, Integer defStyle) {
setListeners();
}
private void setListeners() {
super.setOnScrollListener(new OnScrollListener() {
private int oldTop;
private int oldFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (onScrollListener != null) {
onScrollListener.onScrollStateChanged(view, scrollState);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (onScrollListener != null) {
onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (onDetectScrollListener != null) {
onDetectedListScroll(view, firstVisibleItem);
}
}
private void onDetectedListScroll(AbsListView absListView, int firstVisibleItem) {
View view = absListView.getChildAt(0);
int top = (view == null) ? 0 : view.getTop();
if (firstVisibleItem == oldFirstVisibleItem) {
if (top > oldTop) {
onDetectScrollListener.onUpScrolling();
} else if (top < oldTop) {
onDetectScrollListener.onDownScrolling();
}
} else {
if (firstVisibleItem < oldFirstVisibleItem) {
onDetectScrollListener.onUpScrolling();
} else {
onDetectScrollListener.onDownScrolling();
}
}
oldTop = top;
oldFirstVisibleItem = firstVisibleItem;
}
});
}
#Override
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
public void setOnDetectScrollListener(OnDetectScrollListener onDetectScrollListener) {
this.onDetectScrollListener = onDetectScrollListener;
}
public interface OnDetectScrollListener {
void onUpScrolling();
void onDownScrolling();
}
}
an example for use:
(don't forget to add it as an Xml Tag in your layout.xml)
scrollInterfacedListView.setOnDetectScrollListener(new ScrollInterfacedListView.OnDetectScrollListener() {
#Override
public void onUpScrolling() {
//Do your thing
}
#Override
public void onDownScrolling() {
//Do your thing
}
});
With all the method posted, there are problems recognizing when the user is scrolling up from the first element or down from the last.
Here is another approach to detect scroll up/down:
listView.setOnTouchListener(new View.OnTouchListener() {
float height;
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
float height = event.getY();
if(action == MotionEvent.ACTION_DOWN){
this.height = height;
}else if(action == MotionEvent.ACTION_UP){
if(this.height < height){
Log.v(TAG, "Scrolled up");
}else if(this.height > height){
Log.v(TAG, "Scrolled down");
}
}
return false;
}
});
ListView listView = getListView();
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
view.setOnTouchListener(new OnTouchListener() {
private float mInitialX;
private float mInitialY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialX = event.getX();
mInitialY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
final float x = event.getX();
final float y = event.getY();
final float yDiff = y - mInitialY;
if (yDiff > 0.0) {
Log.d(tag, "SCROLL DOWN");
scrollDown = true;
break;
} else if (yDiff < 0.0) {
Log.d(tag, "SCROLL up");
scrollDown = true;
break;
}
break;
}
return false;
}
});
My solution works perfectly giving the exact value for each scroll direction.
distanceFromFirstCellToTop contains the exact distance from the first cell to the top of the parent View. I save this value in previousDistanceFromFirstCellToTop and as I scroll I compare it with the new value. If it's lower then I scrolled up, else, I scrolled down.
private int previousDistanceFromFirstCellToTop;
listview.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View firstCell = listview.getChildAt(0);
int distanceFromFirstCellToTop = listview.getFirstVisiblePosition() * firstCell.getHeight() - firstCell.getTop();
if(distanceFromFirstCellToTop < previousDistanceFromFirstCellToTop)
{
//Scroll Up
}
else if(distanceFromFirstCellToTop > previousDistanceFromFirstCellToTop)
{
//Scroll Down
}
previousDistanceFromFirstCellToTop = distanceFromFirstCellToTop;
}
});
For Xamarin developers, the solution is the following:
Note: don't forget to run on UI thread
listView.Scroll += (o, e) =>
{
View firstCell = listView.GetChildAt(0);
int distanceFromFirstCellToTop = listView.FirstVisiblePosition * firstCell.Height - firstCell.Top;
if (distanceFromFirstCellToTop < previousDistanceFromFirstCellToTop)
{
//Scroll Up
}
else if (distanceFromFirstCellToTop > previousDistanceFromFirstCellToTop)
{
//Scroll Down
}
previousDistanceFromFirstCellToTop = distanceFromFirstCellToTop;
};
Just set scroll listener to your listview.
If you have a header or footer you should check the visible count too. If it increases it means you are scrolling down. (Reverse it if there is a footer instead of header)
If you don't have any header or footer in your listview you can remove the lines which cheks the visible item count.
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mLastFirstVisibleItem > firstVisibleItem) {
Log.e(getClass().toString(), "scrolling up");
} else if (mLastFirstVisibleItem < firstVisibleItem) {
Log.e(getClass().toString(), "scrolling down");
} else if (mLastVisibleItemCount < visibleItemCount) {
Log.e(getClass().toString(), "scrolling down");
} else if (mLastVisibleItemCount > visibleItemCount) {
Log.e(getClass().toString(), "scrolling up");
}
mLastFirstVisibleItem = firstVisibleItem;
mLastVisibleItemCount = visibleItemCount;
}
public void onScrollStateChanged(AbsListView listView, int scrollState) {
}
});
and have this variables
int mLastFirstVisibleItem;
int mLastVisibleItemCount;
I've used this much simpler solution:
setOnScrollListener( new OnScrollListener()
{
private int mInitialScroll = 0;
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
{
int scrolledOffset = computeVerticalScrollOffset();
boolean scrollUp = scrolledOffset > mInitialScroll;
mInitialScroll = scrolledOffset;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
To also detect scrolling with larger elements, I prefere an onTouch Listener:
listview.setOnTouchListener(new View.OnTouchListener() {
int scrollEventListSize = 5;
float lastY;
// Used to correct for occasions when user scrolls down(/up) but the onTouchListener detects it incorrectly. We will store detected up-/down-scrolls with -1/1 in this list and evaluate later which occured more often
List<Integer> downScrolledEventsHappened;
#Override
public boolean onTouch(View v, MotionEvent event) {
float diff = 0;
if(event.getAction() == event.ACTION_DOWN){
lastY = event.getY();
downScrolledEventsHappened = new LinkedList<Integer>();
}
else if(event.getAction() == event.ACTION_MOVE){
diff = event.getY() - lastY;
lastY = event.getY();
if(diff>0)
downScrolledEventsHappened.add(1);
else
downScrolledEventsHappened.add(-1);
//List needs to be filled with some events, will happen very quickly
if(downScrolledEventsHappened.size() == scrollEventListSize+1){
downScrolledEventsHappened.remove(0);
int res=0;
for(int i=0; i<downScrolledEventsHappened.size(); i++){
res+=downScrolledEventsHappened.get(i);
}
if (res > 0)
Log.i("INFO", "Scrolled up");
else
Log.i("INFO", "Scrolled down");
}
}
return false; // don't interrupt the event-chain
}
});
Store the firstVisibleItem and on the next onScroll check if the new firstVisibleItem is smaller or greater than the previous one.
Example pseudocode (not tested):
int lastVisibleItem = 0;
boolean isScrollingDown = false;
void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > lastVisibleItem) {
isScrollingDown = true;
}
else {
isScrollingDown = false;
}
lastVisibleItem = firstVisibleItem;
}
For some reason the Android doc doesnt cover this, and the method used isnt even in the docs... took me a while to find it.
To detect if your scroll is at the top you would use this.
public boolean checkAtTop()
{
if(listView.getChildCount() == 0) return true;
return listView.getChildAt(0).getTop() == 0;
}
This will check if your scroller is at the top. Now, in order to do it for the bottom, you would have to pass it the number of children that you have, and check against that number. You might have to figure out how many are on the screen at one time, and subtract that from your number of children. I've never had to do that. Hope this helps
Those methods cannot be used to detect scrolling directions directly. There are many ways of getting the direction. A simple code(untested) for one such method is explained below :
public class ScrollTrackingListView extends ListView {
private boolean readyForMeasurement = false;
private Boolean isScrollable = null;
private float prevDistanceToEnd = -1.0;
private ScrollDirectionListener listener = null;
public ScrollTrackingListView(Context context) {
super(context);
init();
}
public ScrollTrackingListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScrollTrackingListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
ViewTreeObserver observer = getViewTreeObserver();
observer.addOnGlobalLayoutListener(globalLayoutListener);
setOnScrollListener(scrollListener);
}
private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener
= new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
readyForMeasurement = true;
calculateDistanceToEnd();
}
};
public void registerScrollDirectionListener(ScrollDirectionListener listener) {
scrollDirectionListener = listener;
}
public void unregisterScrollDirectionListener() {
scrollDirectionListener = null;
}
private OnScrollListener scrollListener
= new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
calculateDistanceToEnd();
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
// Do nothing
}
};
private void calculateDistanceToEnd() {
if (readyForMeasurement) {
// I'm using the height of the layout, horizontal scrollbar and
// content along with scroll down offset
// computeVerticalScrollExtent is used to compute the length of the thumb within the scrollbar's track.
// The length of the thumb is a function of the view height and the content length.
int verticalScrollExtent = computeVerticalScrollExtent();
int verticalScrollOffset = computeVerticalScrollOffset();
int verticalScrollRange = computeVerticalScrollRange();
int horizontalScrollBarHeight = getHorizontalScrollbarHeight();
/**
* 1. Let "R" represent the range of the vertical scrollbar. This corresponds to the length of the content
* in the view.
* 2. Let "E" represent the extent of the vertical scrollbar. The extent is a constant value and is
* (probably) equal to a value proportional to the height of the view.
* 3. Offset "o" represents the current position in the range that is visible to the user. It can take
* values from "0 to E".
*
* Now the DistanceToEnd is calculated using these three values as follows :
*
* DistanceToEnd = (R - o) / E
*
* DistanceToEnd will hold the value in NumberOfScreenToEnd units.
*
*/
float distanceToEnd =
((float)(verticalScrollRange - verticalScrollOffset))/((float)(verticalScrollExtent));
if(prevDistanceToEnd == -1) {
prevDistanceToEnd = distanceToEnd;
} else {
if(listener != null) {
if(distanceToEnd > prevDistanceToEnd) {
// User is scrolling up
listener.onScrollingUp();
} else {
// User is scrolling up
listener.onScrollingDown();
}
}
prevDistanceToEnd = distanceToEnd;
}
if(isScrollable == null) {
// Check if the view height is less than a screen (i.e., no scrolling is enabled)
if((horizontalScrollBarHeight + verticalScrollExtent) >= verticalScrollRange) {
isScrollable = Boolean.FALSE;
} else {
isScrollable = Boolean.TRUE;
}
}
}
}
public interface ScrollDirectionListener {
public void onScrollingUp();
public void onScrollingDown();
}
}
The idea is to calculate the distanceToEnd. If distanceToEnd increases, the user is scrolling up and if it decreases, the user is scrolling down. That will also give you the exact distance to the end of the list.
If you are just trying to know whether the user is scrolling up or down you can override the onInterceptTouchEvent to detect the scrolling direction like below :
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialX = event.getX();
mInitialY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
final float x = event.getX();
final float y = event.getY();
final float yDiff = y - mInitialY; // yDiff less than 0.0 implies scrolling down while yDiff greater than 0.0 implies scrolling up. If I try to add the less than or greater than symbols, the preview refuses to display it.
if(yDiff less than 0.0) listener.onScrollingDown();
else if(yDiff greater than 0.0) listener.onScrollingUp();
break;
}
return super.onInterceptTouchEvent(event);
}
Trick about detect scroll up or down in listview, you just call this function on onScroll function in OnScrollListener of ListView.
private int oldFirstVisibleItem = -1;
private protected int oldTop = -1;
// you can change this value (pixel)
private static final int MAX_SCROLL_DIFF = 5;
private void calculateListScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == oldFirstVisibleItem) {
int top = view.getChildAt(0).getTop();
// range between new top and old top must greater than MAX_SCROLL_DIFF
if (top > oldTop && Math.abs(top - oldTop) > MAX_SCROLL_DIFF) {
// scroll up
} else if (top < oldTop && Math.abs(top - oldTop) > MAX_SCROLL_DIFF) {
// scroll down
}
oldTop = top;
} else {
View child = view.getChildAt(0);
if (child != null) {
oldFirstVisibleItem = firstVisibleItem;
oldTop = child.getTop();
}
}
}
Simple way to detect scroll up/down on android listview
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount){
if(prevVisibleItem != firstVisibleItem){
if(prevVisibleItem < firstVisibleItem)
//ScrollDown
else
//ScrollUp
prevVisibleItem = firstVisibleItem;
}
dont forget
yourListView.setOnScrollListener(yourScrollListener);
Simple way to load more items on scroll up/down event in android GridView
grid.setOnScrollListener(new AbsListView.OnScrollListener() {
private int mLastFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
Log.d("state",String.valueOf(scrollState));
if(scrollState == 0)
Log.i("a", "scrolling stopped...");
if (view.getId() == grid.getId()) {
final int currentFirstVisibleItem = grid.getLastVisiblePosition();
mLastFirstVisibleItem = grid.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
mIsScrollingUp = false;
if(!next.contains("null")){
//Call api to get products from server
}
Log.i("a", "scrolling down...");
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
mIsScrollingUp = true;
Log.i("a", "scrolling up...");
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("on scroll","");
}
});
Here's what I would try first:
1) Create an interface (let's call it OnScrollTopOrBottomListener) with these methods:
void onScrollTop();
void onScrollBottom();
2) In your list's adapter, add a member instance, typed as the interface you created and supply a setter and getter.
3) In the getView() implementation of your adapter, check if the position parameter is either 0 or getCount() - 1. Also check that your OnScrollTopOrBottomListener instance is not null.
4) If the position is 0, call onScrollTopOrBottomListener.onScrollTop(). If position is getCount() - 1, call onScrollTopOrBottomListener.onScrollBottom().
5) In your OnScrollTopOrBottomListener implementation, call the appropriate methods to get the desired data.
Hope that helps in some way.
-Brandon
I have encountered problems using some example where the cell size of ListView is great. So I have found a solution to my problem which detects the slightest movement of your finger . I've simplified to the minimum possible and is as follows:
private int oldScrolly;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View view = absListView.getChildAt(0);
int scrolly = (view == null) ? 0 : -view.getTop() + absListView.getFirstVisiblePosition() * view.getHeight();
int margin = 10;
Log.e(TAG, "Scroll y: " + scrolly + " - Item: " + firstVisibleItem);
if (scrolly > oldScrolly + margin) {
Log.d(TAG, "SCROLL_UP");
oldScrolly = scrolly;
} else if (scrolly < oldScrolly - margin) {
Log.d(TAG, "SCROLL_DOWN");
oldScrolly = scrolly;
}
}
});
PD: I use the MARGIN to not detect the scroll until you meet that margin . This avoids problems when I show or hide views and avoid blinking of them.

How to detect if a listview is scrolling up or down in android?

Is there a way to identify if listview is being scroll up or down?
OnScrollListener doens't help me in this case.
Thanks in advance!
this is a simple implementation:
lv.setOnScrollListener(new OnScrollListener() {
private int mLastFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState){}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(mLastFirstVisibleItem < firstVisibleItem){
// Scrolling down
}
if(mLastFirstVisibleItem > firstVisibleItem){
// scrolling up
}
mLastFirstVisibleItem = firstVisibleItem;
}
});
There is a method in ScrollViews that reports the shifting of scrolls. It is called onScrollChanged(). However, it is declared protected, so you must create a wrapper class to gain access. For the usage of the method, please check android docs.
First, create an interface to expose the protected method
public interface OnScrollListener {
void onScrollChanged(int x, int y, int oldx, int oldy);
}
Then, create your wrapper and extend ScrollView
public class ReportingScrollView extends ScrollView {
private OnScrollListener onScrollListener = null;
public ReportingScrollView(Context context) {
super(context);
}
public ReportingScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ReportingScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (onScrollListener != null) {
onScrollListener.onScrollChanged(x, y, oldx, oldy);
}
}
}
Finally, include it in your XML layout like a custom view, and attach listeners like usual.
<your.package.ReportingScrollView />
scrollingandtrolling.setOnScrollListener(new OnScrollListener() {...});
for whom ever still looking for an answer for kotlin, this works for it
MainListView.setOnScrollListener(object :AbsListView.OnScrollListener {
// var VisibleItem: Int = 0
override fun onScroll(p0: AbsListView?, FirstVisibleItem: Int, i2: Int, i3: Int) {
/*
if(VisibleItem < FirstVisibleItem)
{
Toast.makeText(applicationContext,"Scrolling Down",Toast.LENGTH_SHORT).show()
fab.hide()
}
if(VisibleItem >FirstVisibleItem)
{
fab.show()
Toast.makeText(applicationContext,"Scrolling Up",Toast.LENGTH_SHORT).show()
}
VisibleItem=FirstVisibleItem;
*/
}
override fun onScrollStateChanged(p0: AbsListView?, p1: Int) {
if (p1 == 1) {
fab.hide()
} else {
fab.show()
}
}
})
}
Uncomment to use an alternative method.
An easy and working solution.
private int lastPosition = -1;
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if(lastPosition == firstVisibleItem)
{
return;
}
if(firstVisibleItem > lastPosition)
{
Logger.print("Going Down");
}
else
{
Logger.print("Going Up");
}
lastPosition = firstVisibleItem;
}
The solution given by Some Noob Student is good: you have to create a wrapper to use the protected onScrollChanged(int x, int y, int oldx, int oldy) method, which is triggered more often then the standard AbsListView.OnScrollListener.onScrollStateChanged(AbsListView view, int scrollState).
Unfortunately the onScrollChanged() always returns 0 as parameters, no matter how I scroll, and seems to detect only when the scroll ends, reaching the top or the bottom of the list.
So I used the following code to detect the right direction of the scrolling: it uses the getTop() of the first visible item but uses also its position to understand when the first item is no more visible; during this item transition, the scroll direction is given by the item position itself.
#Override
public void onScrollChanged(int x, int y, int oldx, int oldy) {
if(listView.getChildAt(0) != null) {
int scrollingDir = NO_SCROLLING;
int firstChildTop = listView.getChildAt(0).getTop();
if (listView.getFirstVisiblePosition() == mLastFirstVisiblePosition) {
if (mLastFirstChildTop < firstChildTop) {
//Scrolling DOWN
scrollingDir = SCROLLING_DOWN;
} else if (mLastFirstChildTop > firstChildTop) {
//Scrolling UP
scrollingDir = SCROLLING_UP;
}
} else if(listView.getFirstVisiblePosition() > mLastFirstVisiblePosition) {
//Scrolling UP
scrollingDir = SCROLLING_UP;
} else {
//Scrolling DOWN
scrollingDir = SCROLLING_DOWN;
}
mLastFirstVisiblePosition = listView.getFirstVisiblePosition();
mLastFirstChildTop = firstChildTop;
switch(scrollingDir) {
case SCROLLING_DOWN:
//Your DOWN scrolling code here
break;
case SCROLLING_UP:
//Your UP scrolling code here
break;
default:
case NO_SCROLLING:
break;
}
}
}
list.setOnScrollListener(new OnScrollListener() {
int last_item;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(last_item<firstVisibleItem+visibleItemCount-1){
System.out.println("List is scrolling upwards");
}
else if(last_item>firstVisibleItem+visibleItemCount-1){
System.out.println("List is scrolling downwards");
}
last_item = firstVisibleItem+visibleItemCount-1;
}
});
Here based on the last visible item i decide whether the list is going up or down

Categories

Resources