Change ViewPager animation duration when sliding programmatically - android

I'm changing slide with the following code:
viewPager.setCurrentItem(index++, true);
But it changes too fast. Is there a way to set manually the animation speed?

I've wanted to do myself and have achieved a solution (using reflection, however). I haven't tested it yet but it should work or need minimal modification. Tested on Galaxy Nexus JB 4.2.1. You need to use a ViewPagerCustomDuration in your XML instead of ViewPager, and then you can do this:
ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow
ViewPagerCustomDuration.java:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
import java.lang.reflect.Field;
public class ViewPagerCustomDuration extends ViewPager {
public ViewPagerCustomDuration(Context context) {
super(context);
postInitViewPager();
}
public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
postInitViewPager();
}
private ScrollerCustomDuration mScroller = null;
/**
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void postInitViewPager() {
try {
Field scroller = ViewPager.class.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
}
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}
}
ScrollerCustomDuration.java:
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;
public class ScrollerCustomDuration extends Scroller {
private double mScrollFactor = 1;
public ScrollerCustomDuration(Context context) {
super(context);
}
public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}
#SuppressLint("NewApi")
public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
}
}

I have found better solution, based on #df778899's answer and the
Android ValueAnimator API. It works fine without reflection and is very flexible.
Also there is no need for making custom ViewPager and putting it into android.support.v4.view package.
Here is an example:
private void animatePagerTransition(final boolean forward) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private int oldDragPosition = 0;
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
if (viewPager.beginFakeDrag()) {
animator.start();
}
}
UPDATE:
Just checked if this solution can be used to swipe several pages at once (for example if first page should be showed after the last one). This is slightly modified code to handle specified page count:
private int oldDragPosition = 0;
private void animatePagerTransition(final boolean forward, int pageCount) {
// if previous animation have not finished we can get exception
if (pagerAnimation != null) {
pagerAnimation.cancel();
}
pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
if (viewPager.beginFakeDrag()) { // checking that started drag correctly
pagerAnimation.start();
}
}
private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
viewPager.endFakeDrag();
oldDragPosition = 0;
viewPager.beginFakeDrag();
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
animator.setRepeatCount(pageCount);
return animator;
}

public class PresentationViewPager extends ViewPager {
public static final int DEFAULT_SCROLL_DURATION = 250;
public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;
public PresentationViewPager (Context context) {
super(context);
}
public PresentationViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setDurationScroll(int millis) {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new OwnScroller(getContext(), millis));
} catch (Exception e) {
e.printStackTrace();
}
}
public class OwnScroller extends Scroller {
private int durationScrollMillis = 1;
public OwnScroller(Context context, int durationScroll) {
super(context, new DecelerateInterpolator());
this.durationScrollMillis = durationScroll;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis);
}
}
}

Better solution is to simply access the private fields by creating the class in the support package. EDIT This is bound to the MAX_SETTLE_DURATION of 600ms, set by the ViewPagerclass.
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class SlowViewPager extends ViewPager {
// The speed of the scroll used by setCurrentItem()
private static final int VELOCITY = 200;
public SlowViewPager(Context context) {
super(context);
}
public SlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
}
}
You can, of course, then add a custom attribute so this can be set via XML.

Here is my code used in Librera Reader
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initMyScroller();
}
private void initMyScroller() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext())); // my liner scroller
Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
mFlingDistance.setAccessible(true);
mFlingDistance.set(this, Dips.DP_10);//10 dip
Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
mMinimumVelocity.setAccessible(true);
mMinimumVelocity.set(this, 0); //0 velocity
} catch (Exception e) {
LOG.e(e);
}
}
public class MyScroller extends Scroller {
public MyScroller(Context context) {
super(context, new LinearInterpolator()); // my LinearInterpolator
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, 175);//175 duration
}
}
}

I used Cicero Moura's version to make a Kotlin class that still works perfectly as of Android 10.
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller
import androidx.viewpager.widget.ViewPager
class CustomViewPager(context: Context, attrs: AttributeSet) :
ViewPager(context, attrs) {
private companion object {
const val DEFAULT_SPEED = 1000
}
init {
setScrollerSpeed(DEFAULT_SPEED)
}
var scrollDuration = DEFAULT_SPEED
set(millis) {
setScrollerSpeed(millis)
}
private fun setScrollerSpeed(millis: Int) {
try {
ViewPager::class.java.getDeclaredField("mScroller")
.apply {
isAccessible = true
set(this#CustomViewPager, OwnScroller(millis))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis)
}
}
}
Initializing from the activity class:
viewPager.apply {
scrollDuration = 2000
adapter = pagerAdapter
}

After wasting my whole day I found a solution set offscreenPageLimit to total no. of the page.
In order to keep a constant length ViewPager scrolls smooth, setOffScreenLimit(page.length) will keep all the views in memory. However, this poses a problem for any animations that involves calling View.requestLayout function (e.g. any animation that involves making changes to the margin or bounds). It makes them really slow (as per Romain Guy) because the all of the views that's in memory will be invalidated as well. So I tried a few different ways to make things smooth but overriding requestLayout and other invalidate methods will cause many other problems.
A good compromise is to dynamically modify the off screen limit so that most of the scrolls between pages will be very smooth while making sure that all of the in page animations smooth by removing the views when the user. This works really well when you only have 1 or 2 views that will have to make other views off memory.
***Use this when no any solution works because by setting offeset limit u will load all the fragments at the same time

Related

Android FlipView make ViewPager swipe first item without animation

I have a problem about the topic and now my situation is i have a viewpager inside scrollview and these inside emilsjolander FlipView, the structure look like:
<FlipView>
<ScrollView>
<ViewPager>
</ViewPager>
</ScrollView>
</FlipView>
And Because some issue of viewpager cannot detect touch so i just implement the custom Viewpager.
after i implemented the custom Viewpager, the touch issue is fixed but the viewpager has a problem about the animation.
when i swipe the viewpager, sometimes it work perfect but sometimes the viewpager just change the item without animation and it seem the first item change to Sec Item must cause this case. So i just put some log into startScroll method of ScrollerCustomDuration class and i found that when the viewpager change without animation, the startScroll method of ScrollerCustomDuration is not call.
So sorry for my poor english and thank you for all help, please let me know if u need me to provide more detail for this problem.
CustomViewPager Class:
public class CustomViewPager extends ViewPager {
private ScrollerCustomDuration mScroller = null;
public CustomViewPager(Context context) {
super(context);
postInitViewPager();
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
postInitViewPager();
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.e("onInterceptTouchEvent",event.toString());
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
this.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.onInterceptTouchEvent(event);
}
private void postInitViewPager() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = viewpager.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
Log.e("MyPager", e.getMessage());
}
}
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}}
ScrollerCustomDuration Class :
public class ScrollerCustomDuration extends Scroller {
private double mScrollFactor = 1;
public ScrollerCustomDuration(Context context) {
super(context);
}
public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}
#SuppressLint("NewApi")
public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
Log.e("##### FIX flash issue",""+startX);
Log.e("##### FIX flash issue",""+startY);
Log.e("##### FIX flash issue",""+dx);
Log.e("##### FIX flash issue",""+dy);
Log.e("##### FIX flash issue",""+duration);
super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
}
}
i just fixed the problem.
it seem the problem is not about ontouch.
i just refresh the flipview when flipview flip
the below code fixed my problem
fv.setOnFlipListener(new FlipView.OnFlipListener() {
#Override
public void onFlippedToPage(FlipView v, int position, long id) {
((BaseAdapter)fv.getAdapter()).notifyDataSetChanged();
}
});

Changing the speed of transition of ViewPager and setCurrentItem

I have a ViewPager that I want to rotate automatically every 5 seconds, whilst also allowing the user to swipe too. However, when I set the automatic change, the transition animation between pages happens really quickly, and I want this to be slower.
I've seen the answers for this question here:
Slowing speed of Viewpager controller in android
...but they all use reflection. Does anyone know of any way of slowing down the automatic speed of a ViewPager without using reflection?
I thought of using a PageTransformer, but not sure if that would work, and also it would probably affect the speed if the user swiped as well as the automatic one? Unless I can somehow detect the difference, and then do one or another PageTransformation?
Use this Custom View
public class ViewPagerCustomDuration extends ViewPager {
private FixedSpeedScroller mScroller = null;
public ViewPagerCustomDuration(Context context) {
super(context);
init();
}
public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/*
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void init() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
mScroller = new FixedSpeedScroller(getContext(),
new DecelerateInterpolator());
scroller.set(this, mScroller);
} catch (Exception ignored) {
}
}
/*
* Set the factor by which the duration will change
*/
public void setScrollDuration(int duration) {
mScroller.setScrollDuration(duration);
}
private class FixedSpeedScroller extends Scroller {
private int mDuration = 500;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
public void setScrollDuration(int duration) {
mDuration = duration;
}
}
}
You create custom view pager
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import java.lang.reflect.Field;
public class CustomViewPager extends ViewPager {
private FixedSpeedScroller mScroller = null;
private boolean enabled;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.enabled = true;
init();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (this.enabled) {
return super.onTouchEvent(event);
}
init();
return false;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (this.enabled) {
return super.onInterceptTouchEvent(event);
}
return false;
}
public void setPagingEnabled(boolean enabled) {
this.enabled = enabled;
}
/*
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void init() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
mScroller = new FixedSpeedScroller(getContext(),
new DecelerateInterpolator());
scroller.set(this, mScroller);
} catch (Exception ignored) {
}
}
/*
* Set the factor by which the duration will change
*/
public void setScrollDuration(int duration) {
mScroller.setScrollDuration(duration);
}
private class FixedSpeedScroller extends Scroller {
private int mDuration = 500;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
public void setScrollDuration(int duration) {
mDuration = duration;
}
}
}
you can set duration of view pager according to you
use schedule timer option for auto rotate the pages.
int i = 0;
static Timer timer = new Timer();
//Scroller scroll = new Scroller(this);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_circles);
mAdapter = new TestFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (CirclePageIndicator) findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
mPager.setCurrentItem(i);
SwitchPage(3);
}
public void SwitchPage(int seconds)
{
if(timer != null)
{
timer.cancel();
}
timer = new Timer(); // At this line a new Thread will be created
timer.schedule(new SwitchPageTask(),
2000, seconds * 2000);
// delay in milliseconds
}
class SwitchPageTask extends TimerTask
{
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
if(i < mAdapter.getCount())
{
i++;
mPager.setCurrentItem(i, true);
}
else
{
i=0;
mPager.setCurrentItem(i, true);
}
}
});
}
}
just cancel the timer like.. timer.cancel().. or
cancel timer from another activity or fragment activity..
textView1_page3.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (SampleCirclesDefault.timer != null) {
SampleCirclesDefault.timer.cancel();
Toast.makeText(getActivity(), "timer
cancled", 1).show();
}
}
});

Smooth scroll does not work in ViewPager (support library)

I am writing application that uses ViewPager to host Fragments.
When I change fragment programmatically the smooth scroll function does not work. I use ViewPager.setCurrentItem(int item, boolean smoothScroll)` method.
Maybe anyone know a workaround this bug? Maybe with animations?
EDIT:
I am using support package. And the issue is that whether I use ViewPager.setCurrentItem(2, true) or ViewPager.setCurrentItem(2, false) the result is the same. The view switches really fast (not smoothly).
I've fixed this by creating a MyViewPager that overrides the ViewPager.mScroller using reflection.
public class MyViewPager extends ViewPager
{
public MyViewPager( Context context, AttributeSet attrs)
{
super( context, attrs );
setMyScroller();
}
private void setMyScroller()
{
try
{
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext()));
} catch (Exception e)
{
e.printStackTrace();
}
}
public class MyScroller extends Scroller
{
public MyScroller(Context context)
{
super(context, new DecelerateInterpolator());
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration)
{
super.startScroll(startX, startY, dx, dy, 1000 /*1 secs*/);
}
}
}
What worked for me was to call mPager.setCurrentItem after the mPagerAdapter.notifyDataSetChanged(), and not as a posted runnable.
Here is an example (in MonoDroid):
public void UpdateDetialsView(string type)
{
string typeName4 = Java.Lang.Class.FromType(typeof(ParcelRecordComplaintDetailsView)).Name;
if (_fragments.Count > 3)
{
_fragments.RemoveAt(_fragments.Count - 1);
////////////////////////////////////////////////////////////
// Called here it wasn't working for me (Posted or not)
//_viewPager.Post(new Action(() => { _viewPager.SetCurrentItem(3, true); }));
}
_fragments.Add(Fragment.Instantiate(Activity, typeName4));
_pagerAdapter.NotifyDataSetChanged();
////////////////////////////////////////////////////////////
// Called here it works as expected
_viewPager.SetCurrentItem(3, true);
}
where
protected MyAdapter _pagerAdapter;
protected ViewPager _viewPager;
private List<Android.App.Fragment> _fragments;
and
public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
{
private List<Fragment> _fragments;
public override Java.Lang.Object InstantiateItem(View p0, int p1)
{
return base.InstantiateItem(p0, p1);
}
public MyAdapter(Android.App.FragmentManager fm) : base(fm)
{
}
public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
{
_fragments = fragments;
}
public override int Count
{
//get { return FRAG_PAGES; }
get { return _fragments.Count; }
}
public override Android.App.Fragment GetItem(int p0)
{
return _fragments[p0];
}
public override float GetPageWidth(int p0)
{
//return base.GetPageWidth(p0);
return (float) (0.5f);
}
public override int GetItemPosition(Java.Lang.Object p0)
{
//return base.GetItemPosition(p0);
return PagerAdapter.PositionNone;
}
}

Automatic horizontal scroll in TextView

I have custom gallery.
Gallery represents items that are frame layout.
There are one imageView and textView above it.
If text in textView is too long, i need it to be scrolled automatically.
It's one line of text, and it's needed to be scrolled horizontally.
I've found this snippet of code:
TextView
android:text="Single-line text view that scrolls automatically"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit ="marquee_forever"
android:focusable="true"
android:focusableInTouchMode="true"
android:scrollHorizontally="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
It works in my test app with only one text view in it.
But it doesn't work in my gallery. Noting happens, text just stay still.
Any help?
Try this custom TextView class:
public class AutoScrollingTextView extends TextView {
public AutoScrollingTextView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public AutoScrollingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoScrollingTextView(Context context) {
super(context);
}
#Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}
#Override
public void onWindowFocusChanged(boolean focused) {
if (focused) {
super.onWindowFocusChanged(focused);
}
}
#Override
public boolean isFocused() {
return true;
}
}
and set the following XML attributes:
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
This works beautifully in my dictionary apps where multiple entries may need to auto-scroll simultaneously to display complete content.
The marquee effect on a TextView is only designed to work when the view is focused or selected. The XML code you have tries to make the TextView focused all the time. Unfortunately, since only one view can be focused at any time, and since you have multiple views in the gallery, this approach will not work for you.
The easiest way to accomplish this otherwise is to make the TextViews always be selected. Multiple TextViews can hold the selected state at one time. Selection is meant to be used for an active element of an AdapterView, but still works outside of one. Firstly, remove the attributes modifying the focus from the XML and then just call TextView.setSelected(true) sometime after the view is initialised, e.g. in Activity.onCreate(Bundle) (there is no XML attribute for this). If you are supplying the views from an adapter, then you can call TextView.setSelected(true) during the getView() method after you inflate the view.
Here is an example project showing marquee working for multiple TextViews, and the behaviour inside a Gallery.
Try using ViewPager instead of gallery. This is available in android support packages. http://android-developers.blogspot.in/2011/08/horizontal-view-swiping-with-viewpager.html
I've tried everything, and finally came up with this. This works for me...hope that this will help you someday. Cheers.
package com.gui.custom_views;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.media_player.AndroidMediaPlayerActivity;
/**
* Custom Automatic Scrollable Text View
*
* #author Veljko Ilkic
*
*/
public class AutomaticScrollTextView extends LinearLayout {
// Context of application
Context context;
// TextView
private TextView mTextField1;
// Horizontal scroll
private ScrollView mScrollView1;
// Animation on start
private Animation mMoveTextOnStart = null;
// Out animation
private Animation mMoveText1TextOut = null;
// Duration of animation on start
private int durationStart;
// Duration of animation
private int duration;
// Pain for drawing text
private Paint mPaint;
// Text current width
private float mText1TextWidth;
/**
* Control the speed. The lower this value, the faster it will scroll.
*/
public static final int MS_PER_PX = 80;
/**
* Control the pause between the animations. Also, after starting this
* activity.
*/
public static final int PAUSE_BETWEEN_ANIMATIONS = 0;
private boolean mCancelled = false;
// Layout width
private int mWidth;
// Animation thread
private Runnable mAnimation1StartRunnable;
public AutomaticScrollTextView(Context context) {
super(context);
init(context);
this.context = context;
}
public AutomaticScrollTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
this.context = context;
}
private void init(Context context) {
initView(context);
// init helper
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(1);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mWidth = getMeasuredWidth();
// Calculate
prepare();
// Setup
setupText1Marquee();
}
#Override
public void setOnClickListener(OnClickListener l) {
super.setOnClickListener(l);
mTextField1.setOnClickListener(l);
}
// Method to finally start the marquee.
public void startMarquee() {
prepare();
prepareTextFields();
startTextField1Animation();
mCancelled = false;
}
private void startTextField1Animation() {
mAnimation1StartRunnable = new Runnable() {
public void run() {
mTextField1.setVisibility(View.VISIBLE);
mTextField1.startAnimation(mMoveTextOnStart);
}
};
postDelayed(mAnimation1StartRunnable, PAUSE_BETWEEN_ANIMATIONS);
}
public void reset() {
mCancelled = true;
if (mAnimation1StartRunnable != null) {
removeCallbacks(mAnimation1StartRunnable);
}
mTextField1.clearAnimation();
prepareTextFields();
mMoveTextOnStart.reset();
mMoveText1TextOut.reset();
mScrollView1.removeView(mTextField1);
mScrollView1.addView(mTextField1);
mTextField1.setEllipsize(TextUtils.TruncateAt.END);
invalidate();
}
public void prepareTextFields() {
mTextField1.setEllipsize(TextUtils.TruncateAt.END);
mTextField1.setVisibility(View.INVISIBLE);
expandTextView(mTextField1);
}
private void setupText1Marquee() {
// Calculate duration of animations
durationStart = (int) ((mWidth + mText1TextWidth) * MS_PER_PX);
duration = (int) (2 * mWidth * MS_PER_PX);
// On start animation
mMoveTextOnStart = new TranslateAnimation(0, -mWidth - mText1TextWidth,
0, 0);
mMoveTextOnStart.setDuration(durationStart);
mMoveTextOnStart.setInterpolator(new LinearInterpolator());
mMoveTextOnStart.setFillAfter(true);
// Main scrolling animation
mMoveText1TextOut = new TranslateAnimation(mWidth, -mWidth
- mText1TextWidth, 0, 0);
mMoveText1TextOut.setDuration(duration);
mMoveText1TextOut.setInterpolator(new LinearInterpolator());
mMoveText1TextOut.setFillAfter(true);
mMoveText1TextOut.setRepeatCount(Animation.INFINITE);
// Animation listeners
mMoveTextOnStart
.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
invalidate();
mTextField1.invalidate();
}
public void onAnimationEnd(Animation animation) {
if (mCancelled) {
return;
}
mTextField1.startAnimation(mMoveText1TextOut);
}
public void onAnimationRepeat(Animation animation) {
invalidate();
mTextField1.invalidate();
}
});
mMoveText1TextOut
.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
invalidate();
mTextField1.invalidate();
}
public void onAnimationEnd(Animation animation) {
if (mCancelled) {
return;
}
}
public void onAnimationRepeat(Animation animation) {
invalidate();
mTextField1.invalidate();
}
});
}
private void prepare() {
// Measure
mPaint.setTextSize(mTextField1.getTextSize());
mPaint.setTypeface(mTextField1.getTypeface());
mText1TextWidth = mPaint.measureText(mTextField1.getText().toString());
setupText1Marquee();
}
private void initView(Context context) {
setOrientation(LinearLayout.VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, Gravity.LEFT));
setPadding(0, 0, 0, 0);
// Scroll View 1
LayoutParams sv1lp = new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT);
sv1lp.gravity = Gravity.CENTER_HORIZONTAL;
mScrollView1 = new ScrollView(context);
// Scroll View 1 - Text Field
mTextField1 = new TextView(context);
mTextField1.setSingleLine(true);
mTextField1.setEllipsize(TextUtils.TruncateAt.END);
mTextField1.setTypeface(null, Typeface.BOLD);
mScrollView1.addView(mTextField1, new ScrollView.LayoutParams(
mTextField1.getWidth(), LayoutParams.WRAP_CONTENT));
addView(mScrollView1, sv1lp);
}
public void setText1(String text) {
String temp = "";
if (text.length() < 10) {
temp = " " + text + " ";
} else {
temp = text;
}
mTextField1.setText(temp);
}
public void setTextSize1(int textSize) {
mTextField1.setTextSize(textSize);
}
public void setTextColor1(int textColor) {
mTextField1.setTextColor(textColor);
}
private void expandTextView(TextView textView) {
ViewGroup.LayoutParams lp = textView.getLayoutParams();
lp.width = AndroidMediaPlayerActivity.getScreenWidth();
textView.setLayoutParams(lp);
}
}
I came across this problem once and finally fixed the problem by calling .setFocus() on the textView.
Hi You have Tag in the xml file itself. And also use the Scrollview Property of FOCUS_DOWN in the java file ... Hope It helps to u ...
This code is working properly for me.
scrollview=(ScrollView)findViewById(R.id.scrollview1);
tb2.setTextSize(30);
tb2.setMovementMethod(new ScrollingMovementMethod());
scrollview.post(new Runnable() {
public void run() {
scrollview.fullScroll(View.FOCUS_DOWN);
}
});
public class ScrollingTextView extends TextView {
public ScrollingTextView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public ScrollingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrollingTextView(Context context) {
super(context);
}
#Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}
#Override
public void onWindowFocusChanged(boolean focused) {
if (focused) {
super.onWindowFocusChanged(focused);
}
}
#Override
public boolean isFocused() {
return true;
}
}
<com.test.autoscroll.ScrollingTextView
android:id="#+id/actionbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:textSize="16dip"
android:textStyle="bold"
android:lines="1"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:text="autoscrollable textview without focus to textview...working...."
android:marqueeRepeatLimit="marquee_forever"
/>
Add this code to your own
findViewById(R.id.yourtextviewid).setSelected(true);
maybe your problem is fixed.

Android: Detect when ScrollView stops scrolling

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 :)

Categories

Resources