I am stuck with status bar hiding. Let me explain I am Implementing activity and also place manifest file in android:windowSoftInputMode="adjustResize" for keyboard place below edittext, it is working fine but i want also hide navigation bar, so i am using "Theme.Holo.Light.NoActionBar.Fullscreen" theme. But when keyboard appearing app navigation bar also scrolling. How can i restrict scrolling navigation bar and hide notification bar when keyboard appearing.
I think this is help you => http://developer.android.com/reference/android/view/View.html#setSystemUiVisibility(int)
=>setSystemUiVisibility(newVis); //this method help you
=> this is class for information and better understand.
public static class Content extends ScrollView
implements View.OnSystemUiVisibilityChangeListener, View.OnClickListener {
TextView mText;
TextView mTitleView;
SeekBar mSeekView;
boolean mNavVisible;
int mBaseSystemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| SYSTEM_UI_FLAG_LAYOUT_STABLE;
int mLastSystemUiVis;
Runnable mNavHider = new Runnable() {
#Override public void run() {
setNavVisibility(false);
}
};
public Content(Context context, AttributeSet attrs) {
super(context, attrs);
mText = new TextView(context);
mText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
mText.setText(context.getString(R.string.alert_dialog_two_buttons2ultra_msg));
mText.setClickable(false);
mText.setOnClickListener(this);
mText.setTextIsSelectable(true);
addView(mText, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
setOnSystemUiVisibilityChangeListener(this);
}
public void init(TextView title, SeekBar seek) {
// This called by the containing activity to supply the surrounding
// state of the content browser that it will interact with.
mTitleView = title;
mSeekView = seek;
setNavVisibility(true);
}
#Override public void onSystemUiVisibilityChange(int visibility) {
// Detect when we go out of low-profile mode, to also go out
// of full screen. We only do this when the low profile mode
// is changing from its last state, and turning off.
int diff = mLastSystemUiVis ^ visibility;
mLastSystemUiVis = visibility;
if ((diff&SYSTEM_UI_FLAG_LOW_PROFILE) != 0
&& (visibility&SYSTEM_UI_FLAG_LOW_PROFILE) == 0) {
setNavVisibility(true);
}
}
#Override protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
// When we become visible, we show our navigation elements briefly
// before hiding them.
setNavVisibility(true);
getHandler().postDelayed(mNavHider, 2000);
}
#Override protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// When the user scrolls, we hide navigation elements.
setNavVisibility(false);
}
#Override public void onClick(View v) {
// When the user clicks, we toggle the visibility of navigation elements.
int curVis = getSystemUiVisibility();
setNavVisibility((curVis&SYSTEM_UI_FLAG_LOW_PROFILE) != 0);
}
void setBaseSystemUiVisibility(int visibility) {
mBaseSystemUiVisibility = visibility;
}
void setNavVisibility(boolean visible) {
int newVis = mBaseSystemUiVisibility;
if (!visible) {
newVis |= SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_FULLSCREEN;
}
final boolean changed = newVis == getSystemUiVisibility();
// Unschedule any pending event to hide navigation if we are
// changing the visibility, or making the UI visible.
if (changed || visible) {
Handler h = getHandler();
if (h != null) {
h.removeCallbacks(mNavHider);
}
}
// Set the new desired visibility.
setSystemUiVisibility(newVis);
mTitleView.setVisibility(visible ? VISIBLE : INVISIBLE);
mSeekView.setVisibility(visible ? VISIBLE : INVISIBLE);
}
}
I am using this library to create a floating action button in my android application. What I need is to hide the floating action button when I scroll down, and show it again when I scroll up. The problem is I have a FrameLayout and a ScrollView that doesn't contain a setOnScrollListener()!
I read this solution, but supposedly it is laggy.
Can anyone tell me how to reach get the functionality I'm looking for without losing performance?
Try this library.
It provides a floating action button that disappears when you scroll down :-)
One option is to create an ObservableScrollView that has a scroll listener. Google uses this approach in the IOSched14 app. One way of creating this might be:
public class ObservableScrollView extends ScrollView {
private boolean mScrollingEnabled = true;
private ArrayList<Callbacks> mCallbacks = new ArrayList<Callbacks>();
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
for (Callbacks c : mCallbacks) {
c.onScrollChanged(l - oldl, t - oldt);
}
}
#Override
public int computeVerticalScrollRange() {
return super.computeVerticalScrollRange();
}
public void addCallbacks(Callbacks listener) {
if (!mCallbacks.contains(listener)) {
mCallbacks.add(listener);
}
}
public static interface Callbacks {
public void onScrollChanged(int deltaX, int deltaY);
}
public void setScrollingEnabled(boolean scrollingEnabled) {
mScrollingEnabled = scrollingEnabled;
}
// #Override
// public boolean onTouchEvent(MotionEvent ev) {
// if (!mScrollingEnabled) return false;
// return super.onTouchEvent(ev);
// }
}
Simply add this instead of your ScrollView and then attach a listener using the addCallbacks method:
ObservableScrollView scrollView = new ObservableScrollView(context);
scrollView.addCallbacks(this);
After doing a lot of research on stackoverflow and looking for answers I found that I needed to create a subclass of WebView and then do an override on OnScrollChanged etc.. I have the following code...
SearchResultsWebView.setOnScrollChangedCallback(
new Sub_WebView_Results.OnScrollChangedCallback() {
#Override
public void onScroll(int l, int t) {
int tek = (int) Math.floor(SearchResultsWebView.getContentHeight() * SearchResultsWebView.getScale());
if (tek - SearchResultsWebView.getScrollY() == SearchResultsWebView.getHeight())
Toast.makeText(getActivity(), "End", Toast.LENGTH_SHORT).show();
}
});
HOWEVER the problem is that .getScale has been depreciated. I haven't found another way that works.
I tried using ..
SearchResultsWebView.setWebViewClient(new WebViewClient() {
#Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
currentScale = newScale;
}
});
And then just passing the [currentScale] but it seems this never gets called so I'm at a loss on how to do this.
Content height for web view is returned dp and thus we need to multiply with the devices density multiplier to get the actual height of the content
with the actual height i deduct the webview's height to calculate when the webview will be visible and compare with scroll y every time user picks up their finger.
This is what i did, working perfectly for me.
webView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (Math.floor((webView.getContentHeight() * getResources().getDisplayMetrics().density) - webView.getHeight()) == webView.getScrollY()) {
// Bottom Reached , it is necessary to calculate content height because
// it changes showAgreeButton();
return true;
}
return false;
}
});
}
In order to tell if the user has scrolled to the bottom of a web view, I extended the web view and had an interface callback when the user has got to the bottom of the view onScrollChanged. Here is the code:
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.webkit.WebView;
public class EULAWebView extends WebView {
//declare needed constants
private final String TAG = getClass().getSimpleName();
//declare needed variables
private EULAWebInterface eulaWebInteface;
private int paddingOffset = 200;
private boolean bottomReached;
public EULAWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EULAWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public EULAWebView(Context context) {
super(context);
}
public void setEULAScrollListener(Context context) {
try {
eulaWebInteface = (EULAWebInterface)context;
} catch (ClassCastException ex) {
Log.e(TAG, "UNABLE TO CAST CONTEXT TO EULAWebInterface");
ex.printStackTrace();
throw new ClassCastException();
}
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if(this.computeVerticalScrollRange() <= (this.computeVerticalScrollOffset() +
this.computeVerticalScrollExtent() + this.paddingOffset)) {
if(!bottomReached) {
bottomReached = true;
if(eulaWebInteface != null)
eulaWebInteface.atBottomOfScrollView(true);
}
} else {
if(bottomReached) {
bottomReached = false;
if(eulaWebInteface != null)
eulaWebInteface.atBottomOfScrollView(false);
}
}
super.onScrollChanged(l, t, oldl, oldt);
}
}
Here is the interface that is used to let the activity know that the bottom of the scroll view has changed:
public interface EULAWebInterface {
void atBottomOfScrollView(boolean atBottom);
}
And here is the interface implementation in the activity:
#Override
public void atBottomOfScrollView(boolean atBottom) {
findViewById(R.id.eula_action_layout).setVisibility(atBottom ? View.VISIBLE : View.GONE);
findViewById(R.id.eula_instruction_textview).setVisibility(atBottom ? View.GONE : View.VISIBLE);
}
Apparently I found the answer from: How can i get the current scale of a webView(android)
Instead of WebView.getScale()
You can use: getResources().getDisplayMetrics().density
Try this:
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
View view = (View) getChildAt(getChildCount()-1);
int diff = (view.getBottom()-(getHeight()+getScrollY()));// Calculate the difference in scrolling
if( diff == 0 ){ // The bottom has been reached if the difference is 0
Log.d(ScrollTest.LOG_TAG, "WebView: Bottom has been reached" );
// DO SOMETHING HERE WHEN THE WEBVIEW HAS REACHED THE BOTTOM!
}
super.onScrollChanged(x, y, oldx, oldy);
}
Btw, why use the scaling method when the above method may work better (it's easier to implement I think)
I have a listView. When I scroll and stops in a particular place.
How can I get the amount of pixels I scrolled(from top)?
I have tried using get listView.getScrollY(), but it returns 0.
I had the same problem.
I cannot use View.getScrollY() because it always returns 0 and I cannot use OnScrollListener.onScroll(...) because it works with positions not with pixels. I cannot subclass ListView and override onScrollChanged(...) because its parameter values are always 0. Meh.
All I want to know is the amount the children (i.e. content of listview) got scrolled up or down. So I came up with a solution. I track one of the children (or you can say one of the "rows") and follow its vertical position change.
Here is the code:
public class ObservableListView extends ListView {
public static interface ListViewObserver {
public void onScroll(float deltaY);
}
private ListViewObserver mObserver;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public ObservableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mTrackedChild == null) {
if (getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle();
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = getPositionForView(mTrackedChild);
}
} else {
boolean childIsSafeToTrack = mTrackedChild.getParent() == this && getPositionForView(mTrackedChild) == mTrackedChildPrevPosition;
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (mObserver != null) {
float deltaY = top - mTrackedChildPrevTop;
mObserver.onScroll(deltaY);
}
mTrackedChildPrevTop = top;
} else {
mTrackedChild = null;
}
}
}
private View getChildInTheMiddle() {
return getChildAt(getChildCount() / 2);
}
public void setObserver(ListViewObserver observer) {
mObserver = observer;
}
}
Couple of notes:
we override onScrollChanged(...) because it gets called when the listview is scrolled (just its parameters are useless)
then we choose a child (row) from the middle (doesn't have to be precisely the child in the middle)
every time scrolling happens we calculate vertical movement based on previous position (getTop()) of tracked child
we stop tracking a child when it is not safe to be tracked (e.g. in cases where it might got reused)
You cant get pixels from top of list (because then you need to layout all views from top of list - there can be a lot of items). But you can get pixels of first visible item: int pixels = listView.getChildAt(0).getTop(); it generally will be zero or negative number - shows difference between top of listView and top of first view in list
edit:
I've improved in this class to avoid some moments that the track was losing due to views being too big and not properly getting a getTop()
This new solution uses 4 tracking points:
first child, bottom
middle child, top
middle child, bottom
last child, top
that makes sure we always have a isSafeToTrack equals to true
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://stackoverflow.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener {
private final PixelScrollListener listener;
private TrackElement[] trackElements = {
new TrackElement(0), // top view, bottom Y
new TrackElement(1), // mid view, bottom Y
new TrackElement(2), // mid view, top Y
new TrackElement(3)};// bottom view, top Y
public PixelScrollDetector(PixelScrollListener listener) {
this.listener = listener;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
for (TrackElement t : trackElements)
t.syncState(view);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
boolean wasTracked = false;
for (TrackElement t : trackElements) {
if (!wasTracked) {
if (t.isSafeToTrack(view)) {
wasTracked = true;
if (listener != null)
listener.onScroll(view, t.getDeltaY());
t.syncState(view);
} else {
t.reset();
}
} else {
t.syncState(view);
}
}
}
public static interface PixelScrollListener {
public void onScroll(AbsListView view, float deltaY);
}
private static class TrackElement {
private final int position;
private TrackElement(int position) {
this.position = position;
}
void syncState(AbsListView view) {
if (view.getChildCount() > 0) {
trackedChild = getChild(view);
trackedChildPrevTop = getY();
trackedChildPrevPosition = view.getPositionForView(trackedChild);
}
}
void reset() {
trackedChild = null;
}
boolean isSafeToTrack(AbsListView view) {
return (trackedChild != null) &&
(trackedChild.getParent() == view) && (view.getPositionForView(trackedChild) == trackedChildPrevPosition);
}
int getDeltaY() {
return getY() - trackedChildPrevTop;
}
private View getChild(AbsListView view) {
switch (position) {
case 0:
return view.getChildAt(0);
case 1:
case 2:
return view.getChildAt(view.getChildCount() / 2);
case 3:
return view.getChildAt(view.getChildCount() - 1);
default:
return null;
}
}
private int getY() {
if (position <= 1) {
return trackedChild.getBottom();
} else {
return trackedChild.getTop();
}
}
View trackedChild;
int trackedChildPrevPosition;
int trackedChildPrevTop;
}
}
original answer:
First I want to thank #zsolt-safrany for his answer, that was great stuff, total kudos for him.
But then I want to present my improvement on his answer (still is pretty much his answer, just a few improvements)
Improvements:
It's a separate "gesture detector" type of class that can be added to any class that extends AbsListView by calling .setOnScrollListener(), so it's a more flexible approach.
It's using the change in scroll state to pre-allocate the tracked child, so it doesn't "waste" one onScroll pass to allocate its position.
It re-calculate the tracked child on every onScroll pass to avoiding missing random onScroll pass to recalculate child. (this could be make more efficient by caching some heights and only re-calculate after certain amount of scroll).
hope it helps
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://stackoverflow.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener {
private final PixelScrollListener listener;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public PixelScrollDetector(PixelScrollListener listener) {
this.listener = listener;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
if (mTrackedChild == null) {
syncState(view);
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mTrackedChild == null) {
// case we don't have any reference yet, try again here
syncState(view);
} else {
boolean childIsSafeToTrack = (mTrackedChild.getParent() == view) && (view.getPositionForView(mTrackedChild) == mTrackedChildPrevPosition);
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (listener != null) {
float deltaY = top - mTrackedChildPrevTop;
listener.onScroll(view, deltaY);
}
// re-syncing the state make the tracked child change as the list scrolls,
// and that gives a much higher true state for `childIsSafeToTrack`
syncState(view);
} else {
mTrackedChild = null;
}
}
}
private void syncState(AbsListView view) {
if (view.getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle(view);
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = view.getPositionForView(mTrackedChild);
}
}
private View getChildInTheMiddle(AbsListView view) {
return view.getChildAt(view.getChildCount() / 2);
}
public static interface PixelScrollListener {
public void onScroll(AbsListView view, float deltaY);
}
}
Try to implement OnScrollListener:
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
int last = view.getLastVisiblePosition();
break;
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
I'm using a ScrollView in Android and where the visible portion of the ScrollView is the same size as one of the cells inside the Scrollview. Every "cell" is the same height. So what I am trying to do is snap into position after the ScrollView has been scrolled.
Currently I am detecting when the user has touched the ScrollView and when they've started scrolling and working it out from there, but it is quite buggy. It also needs to work when the user just flicks it and it scrolls and then decelerates.
On iPhone there is a function that is something like didDecelerate and there I can do any code I want when the ScrollView has finished scrolling. Is there such a thing with Android? Or is there some code I could look at to figure out a better way of doing it?
I've looked over the Android docs and could not find anything like that.
I recently had to implement the function you described. What i did was to have a Runnable checking out if the ScrollView had stopped scrolling by comparing the value returned by getScrollY() when the onTouchEvent is first triggered with the value returned after a time defined by the variable newCheck.
See code below (working solution):
public class MyScrollView extends ScrollView{
private Runnable scrollerTask;
private int initialPosition;
private int newCheck = 100;
private static final String TAG = "MyScrollView";
public interface OnScrollStoppedListener{
void onScrollStopped();
}
private OnScrollStoppedListener onScrollStoppedListener;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
scrollerTask = new Runnable() {
public void run() {
int newPosition = getScrollY();
if(initialPosition - newPosition == 0){//has stopped
if(onScrollStoppedListener!=null){
onScrollStoppedListener.onScrollStopped();
}
}else{
initialPosition = getScrollY();
MyScrollView.this.postDelayed(scrollerTask, newCheck);
}
}
};
}
public void setOnScrollStoppedListener(MyScrollView.OnScrollStoppedListener listener){
onScrollStoppedListener = listener;
}
public void startScrollerTask(){
initialPosition = getScrollY();
MyScrollView.this.postDelayed(scrollerTask, newCheck);
}
}
Then i have:
scroll.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
scroll.startScrollerTask();
}
return false;
}
});
scroll.setOnScrollStoppedListener(new OnScrollStoppedListener() {
public void onScrollStopped() {
Log.i(TAG, "stopped");
}
});
Here is yet another fix to the, IMHO, missing OnEndScroll event bug in the ScrollView.
Its inspired by hambonious answer.
Simply drop this class into your project (change package to match your own) and use the below xml
package com.thecrag.components.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class ResponsiveScrollView extends ScrollView {
public interface OnEndScrollListener {
public void onEndScroll();
}
private boolean mIsFling;
private OnEndScrollListener mOnEndScrollListener;
public ResponsiveScrollView(Context context) {
this(context, null, 0);
}
public ResponsiveScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ResponsiveScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void fling(int velocityY) {
super.fling(velocityY);
mIsFling = true;
}
#Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (mIsFling) {
if (Math.abs(y - oldY) < 2 || y >= getMeasuredHeight() || y == 0) {
if (mOnEndScrollListener != null) {
mOnEndScrollListener.onEndScroll();
}
mIsFling = false;
}
}
}
public OnEndScrollListener getOnEndScrollListener() {
return mOnEndScrollListener;
}
public void setOnEndScrollListener(OnEndScrollListener mOnEndScrollListener) {
this.mOnEndScrollListener = mOnEndScrollListener;
}
}
again changing the package name to match your project
<com.thecrag.components.ui.ResponsiveScrollView
android:id="#+id/welcome_scroller"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/welcome_scroll_command_help_container"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="#+id/welcome_header_text_thecrag"
android:layout_margin="6dp">
....
</com.thecrag.components.ui.ResponsiveScrollView>
I subclassed (Horizontal)ScrollView and did something like this:
#Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if (Math.abs(x - oldX) > SlowDownThreshold) {
currentlyScrolling = true;
} else {
currentlyScrolling = false;
if (!currentlyTouching) {
//scrolling stopped...handle here
}
}
super.onScrollChanged(x, y, oldX, oldY);
}
I used a value of 1 for the SlowDownThreshold since it always seems to be the difference of the last onScrollChanged event.
In order to make this behave correctly when dragging slowly, I had to do this:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
currentlyTouching = true;
}
return super.onInterceptTouchEvent(event);
}
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
currentlyTouching = false;
if (!currentlyScrolling) {
//I handle the release from a drag here
return true;
}
}
return false;
}
My approach is determine scrolling state by a timestamp changed each time the onScrollChanged() is called.
It's very easy to determine when is start and end of scrolling.
You can also change threshold ( I use 100ms ) to fix sensitivity.
public class CustomScrollView extends ScrollView {
private long lastScrollUpdate = -1;
private class ScrollStateHandler implements Runnable {
#Override
public void run() {
long currentTime = System.currentTimeMillis();
if ((currentTime - lastScrollUpdate) > 100) {
lastScrollUpdate = -1;
onScrollEnd();
} else {
postDelayed(this, 100);
}
}
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (lastScrollUpdate == -1) {
onScrollStart();
postDelayed(new ScrollStateHandler(), 100);
}
lastScrollUpdate = System.currentTimeMillis();
}
private void onScrollStart() {
// do something
}
private void onScrollEnd() {
// do something
}
}
Here is yet another solution, quite simple and clean in my opinion, naturally inspired by answers above. Basically once user ended gesture check if getScrollY() is still changing, after a brief delay (here 50ms).
public class ScrollViewWithOnStopListener extends ScrollView {
OnScrollStopListener listener;
public interface OnScrollStopListener {
void onScrollStopped(int y);
}
public ScrollViewWithOnStopListener(Context context) {
super(context);
}
public ScrollViewWithOnStopListener(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
checkIfScrollStopped();
}
return super.onTouchEvent(ev);
}
int initialY = 0;
private void checkIfScrollStopped() {
initialY = getScrollY();
this.postDelayed(new Runnable() {
#Override
public void run() {
int updatedY = getScrollY();
if (updatedY == initialY) {
//we've stopped
if (listener != null) {
listener.onScrollStopped(getScrollY());
}
} else {
initialY = updatedY;
checkIfScrollStopped();
}
}
}, 50);
}
public void setOnScrollStoppedListener(OnScrollStopListener yListener) {
listener = yListener;
}
}
My approach for this question is to use a timer to check for the following 2 "events".
1) onScrollChanged() stopped being called
2) User's finger is lift from the scrollview
public class CustomScrollView extends HorizontalScrollView {
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Timer ntimer = new Timer();
MotionEvent event;
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
checkAgain();
super.onScrollChanged(l, t, oldl, oldt);
}
public void checkAgain(){
try{
ntimer.cancel();
ntimer.purge();
}
catch(Exception e){}
ntimer = new Timer();
ntimer.schedule(new TimerTask() {
#Override
public void run() {
if(event.getAction() == MotionEvent.ACTION_UP){
// ScrollView Stopped Scrolling and Finger is not on the ScrollView
}
else{
// ScrollView Stopped Scrolling But Finger is still on the ScrollView
checkAgain();
}
}
},100);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
this.event = event;
return super.onTouchEvent(event);
}
}
For a simple case like you described, you can probably get away with overriding fling method in your custom scroll view. Fling method gets called to perform "deceleration" every time user raises his finger from the screen.
So what you should do is something like this:
Subclass ScrollView.
public class MyScrollView extends ScrollView {
private Scroller scroller;
private Runnable scrollerTask;
//...
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(getContext()); //or OverScroller for 3.0+
scrollerTask = new Runnable() {
#Override
public void run() {
scroller.computeScrollOffset();
scrollTo(0, scroller.getCurrY());
if (!scroller.isFinished()) {
MyScrollView.this.post(this);
} else {
//deceleration ends here, do your code
}
}
};
//...
}
}
Subclass fling method and DO NOT call superclass implementation.
#Override
public void fling(int velocityY) {
scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, container.getHeight());
post(scrollerTask);
//add any extra functions you need from android source code:
//show scroll bars
//change focus
//etc.
}
Fling will not trigger if the user stops scrolling before raising up his finger (velocityY == 0). In case you want to intercept this sort of events aswell, override onTouchEvent.
#Override
public boolean onTouchEvent(MotionEvent ev) {
boolean eventConsumed = super.onTouchEvent(ev);
if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) {
if (scroller.isFinished()) {
//do your code
}
}
return eventConsumed;
}
NOTE Although this works, overriding fling method might be a bad idea. It is public, but its barely designed for subclassing. Right now it does 3 things - it initiates fling for private mScroller, handles possible focus changes and shows scroll bars. This might change in future android release. For instance, private mScroller instance changed its class from Scroller to OvershootScroller between 2.3 and 3.0. You have to keep in mind all this small differences. In any case, be ready for unforeseen consequences in the future.
My solution is a variation of Lin Yu Cheng's great solution and also detects when scrolling has started and stopped.
Step 1. Define a HorizontalScrollView and OnScrollChangedListener:
CustomHorizontalScrollView scrollView = (CustomHorizontalScrollView) findViewById(R.id.horizontalScrollView);
horizontalScrollListener = new CustomHorizontalScrollView.OnScrollChangedListener() {
#Override
public void onScrollStart() {
// Scrolling has started. Insert your code here...
}
#Override
public void onScrollEnd() {
// Scrolling has stopped. Insert your code here...
}
};
scrollView.setOnScrollChangedListener(horizontalScrollListener);
Step 2. Add the CustomHorizontalScrollView class:
public class CustomHorizontalScrollView extends HorizontalScrollView {
public interface OnScrollChangedListener {
// Developer must implement these methods.
void onScrollStart();
void onScrollEnd();
}
private long lastScrollUpdate = -1;
private int scrollTaskInterval = 100;
private Runnable mScrollingRunnable;
public OnScrollChangedListener mOnScrollListener;
public CustomHorizontalScrollView(Context context) {
this(context, null, 0);
init(context);
}
public CustomHorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
init(context);
}
public CustomHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
// Check for scrolling every scrollTaskInterval milliseconds
mScrollingRunnable = new Runnable() {
public void run() {
if ((System.currentTimeMillis() - lastScrollUpdate) > scrollTaskInterval) {
// Scrolling has stopped.
lastScrollUpdate = -1;
//CustomHorizontalScrollView.this.onScrollEnd();
mOnScrollListener.onScrollEnd();
} else {
// Still scrolling - Check again in scrollTaskInterval milliseconds...
postDelayed(this, scrollTaskInterval);
}
}
};
}
public void setOnScrollChangedListener(OnScrollChangedListener onScrollChangedListener) {
this.mOnScrollListener = onScrollChangedListener;
}
public void setScrollTaskInterval(int scrollTaskInterval) {
this.scrollTaskInterval = scrollTaskInterval;
}
//void onScrollStart() {
// System.out.println("Scroll started...");
//}
//void onScrollEnd() {
// System.out.println("Scroll ended...");
//}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollListener != null) {
if (lastScrollUpdate == -1) {
//CustomHorizontalScrollView.this.onScrollStart();
mOnScrollListener.onScrollStart();
postDelayed(mScrollingRunnable, scrollTaskInterval);
}
lastScrollUpdate = System.currentTimeMillis();
}
}
}
Try taking a look at this question here on StackOverflow - it's not exactly the same as your question, but it gives an idea on how you can manage the scroll event of a ScrollView.
Basicly you need to create your own CustomScrollView by extending ScrollView and override onScrollChanged(int x, int y, int oldx, int oldy). Then you need to reference this in your layout file instead of the standard ScrollView like com.mypackage.CustomScrollView.
There are some great answers here, but my code can detect when scrolling stops without having to extend ScrollView class.
every view instance can call getViewTreeObserver(). when Holding this instance of ViewTreeObserver you can add a OnScrollChangedListener using the function addOnScrollChangedListener().
declare the following:
private ScrollView scrollListener;
private volatile long milesec;
private Handler scrollStopDetector;
private Thread scrollcalled = new Thread() {
#Override
public void run() {
if (System.currentTimeMillis() - milesec > 200) {
//scroll stopped - put your code here
}
}
};
and in your onCreate (or another place) add:
scrollListener = (ScrollView) findViewById(R.id.scroll);
scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
milesec = System.currentTimeMillis();
scrollStopDetector.postDelayed(scrollcalled, 200);
}
});
you might want to take longer or slower time between this checks, but when scrolling this listner gets called really fast so it will work very fast.
Here's my solution which includes scroll tracking and scroll ending:
public class ObservableHorizontalScrollView extends HorizontalScrollView {
public interface OnScrollListener {
public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
public void onEndScroll(ObservableHorizontalScrollView scrollView);
}
private boolean mIsScrolling;
private boolean mIsTouching;
private Runnable mScrollingRunnable;
private OnScrollListener mOnScrollListener;
public ObservableHorizontalScrollView(Context context) {
this(context, null, 0);
}
public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ObservableHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_MOVE) {
mIsTouching = true;
mIsScrolling = true;
} else if (action == MotionEvent.ACTION_UP) {
if (mIsTouching && !mIsScrolling) {
if (mOnScrollListener != null) {
mOnScrollListener.onEndScroll(this);
}
}
mIsTouching = false;
}
return super.onTouchEvent(ev);
}
#Override
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (Math.abs(oldX - x) > 0) {
if (mScrollingRunnable != null) {
removeCallbacks(mScrollingRunnable);
}
mScrollingRunnable = new Runnable() {
public void run() {
if (mIsScrolling && !mIsTouching) {
if (mOnScrollListener != null) {
mOnScrollListener.onEndScroll(ObservableHorizontalScrollView.this);
}
}
mIsScrolling = false;
mScrollingRunnable = null;
}
};
postDelayed(mScrollingRunnable, 200);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScrollChanged(this, x, y, oldX, oldY);
}
}
public OnScrollListener getOnScrollListener() {
return mOnScrollListener;
}
public void setOnScrollListener(OnScrollListener mOnEndScrollListener) {
this.mOnScrollListener = mOnEndScrollListener;
}
}
I think this has come up in the past. AFAIK, you can't easily detect that. My suggestion is that you take a look at ScrollView.java (that's how we do things in Android land :)) and figure out how you can extend the class to provide the functionality you are looking for. This is what I would try first:
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if (mScroller.isFinished()) {
// do something, for example call a listener
}
}
this is an old thread but I'd like to add a shorter solution I came up with:
buttonsScrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
//YOUR CODE TO BE EXECUTED HERE
},1000)
}
Naturally there's a 1000 milliseconds delay. Adjust that if you need to.
I've made some improvements to ZeroG's answer. Mainly cancellation of excess task calls and implementing the whole thing as a private OnTouchListener, so all the scroll detection code would be in one place.
Paste the following code into your own ScrollView implementation:
private class ScrollFinishHandler implements OnTouchListener
{
private static final int SCROLL_TASK_INTERVAL = 100;
private Runnable mScrollerTask;
private int mInitialPosition = 0;
public ScrollFinishHandler()
{
mScrollerTask = new Runnable() {
public void run() {
int newPosition = getScrollY();
if(mInitialPosition - newPosition == 0)
{//has stopped
onScrollStopped(); // Implement this on your main ScrollView class
}else{
mInitialPosition = getScrollY();
ExpandingLinearLayout.this.postDelayed(mScrollerTask, SCROLL_TASK_INTERVAL);
}
}
};
}
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_UP)
{
startScrollerTask();
}
else
{
stopScrollerTask();
}
return false;
}
}
And then in your ScrollView implementation:
setOnTouchListener( new ScrollFinishHandler() );
this.getListView().setOnScrollListener(new OnScrollListener(){
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if( firstVisibleItem + visibleItemCount >= totalItemCount )
// Last item is shown...
}
Hope the snippet help :)