I have a scrollview with a lot of content. Now when user do a fling or scroll down, I want the scrollview to stop at a particular view location, where I am doing some animation, and then user can again fling or scroll down.
I have tried the disabling of scrollview as mentioned here but It only disables when the scrollview completely stops and cannot stop in the middle of a fling.
Is there any way I can stop a fling of the scrollview when a certain view location or a certain y value is reached?
I had the same problem my solution was.
listView.smoothScrollBy(0,0)
This will stop the scrolling.
To stop a fling at a particular point simply call
fling(0)
If you are only concerned about flings this is the most logical way to do so in my opinion, because velosityYis set to 0 and thereby the fling is stopped immediately.
Here is the javadoc of the fling method:
/**
* Fling the scroll view
*
* #param velocityY The initial velocity in the Y direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
One approach may be to use smoothScrollToPosition, which stops any existing scrolling motion. Note this method requires API level >= 8 (Android 2.2, Froyo).
Note that if the current position is far away from the desired position, then the smooth scrolling will take quite a long time and look a bit jerky (at least in my testing on Android 4.4 KitKat). I also found that a combination of calling setSelection and smoothScrollToPosition could sometimes causes the position to "miss" slightly, this seems to happen only when the current position was very close to the desired position.
In my case, I wanted my list to jump to the top (position=0) when the user pressed a button (this is slightly different from your use case, so you will need to adapt this to your needs).
I used the following method to
private void smartScrollToPosition(ListView listView, int desiredPosition) {
// If we are far away from the desired position, jump closer and then smooth scroll
// Note: we implement this ourselves because smoothScrollToPositionFromTop
// requires API 11, and it is slow and janky if the scroll distance is large,
// and smoothScrollToPosition takes too long if the scroll distance is large.
// Jumping close and scrolling the remaining distance gives a good compromise.
int currentPosition = listView.getFirstVisiblePosition();
int maxScrollDistance = 10;
if (currentPosition - desiredPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition + maxScrollDistance);
} else if (desiredPosition - currentPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition - maxScrollDistance);
}
listView.smoothScrollToPosition(desiredPosition); // requires API 8
}
In my action handler for the button I then called this as follows
case R.id.action_go_to_today:
ListView listView = (ListView) findViewById(R.id.lessonsListView);
smartScrollToPosition(listView, 0); // scroll to top
return true;
The above does not directly answer your question, but if you can detect when the current position is at or near your desired position, then maybe you could use smoothScrollToPosition to stop the scrolling.
You need to disable ScrollView handling of the fling operation. To do this simply override the fling method in ScrollView and comment super.fling(). Let me know if this works !
public class CustomScrollView extends ScrollView {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
return false;
}
#Override
public void fling (int velocityY)
{
/*Scroll view is no longer gonna handle scroll velocity.
* super.fling(velocityY);
*/
}
}
I'm trying to achieve a similar situation. I have a partial solution:
I've managed to stop the ScrollView at a particular y coordinate by overriding onScrollChanged and then calling smoothScrollTo. I allow the user to continue scrolling down past that point by keeping a boolean that indicates if this was the first fling/drag or not. And the same goes for scrolling from bottom to top - I stop the scroll at the same point again. And then allow the user to continue scrolling upward again.
The code looks something like this:
boolean beenThereFromTop = false;
boolean beenThereFromBottom = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView);
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
TextView whereToStop = (TextView) findViewById(R.id.whereToStop);
final int y = whereToStop.getBottom();
int scrollY = scrollView.getScrollY();
// manage scrolling from top to bottom
if (scrollY > y) {
if (!beenThereFromTop) {
beenThereFromTop = true;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, y);
}
});
}
}
if (scrollY < y && beenThereFromTop) {
beenThereFromTop = false;
}
// manage scrolling from bottom to top
if (scrollY < y) {
if (!beenThereFromBottom) {
beenThereFromBottom = true;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, y);
}
});
}
}
if (scrollY > y && beenThereFromBottom) {
beenThereFromBottom = false;
}
}
});
}
Well im actualy using this method:
list.post(new Runnable()
{
#Override
public void run()
{
list.smoothScrollToPositionFromTop(arg2, 150);
}
});
Related
I'm automatically scrolling the scrollView to the view's bottom.
scrollView.smoothScrollTo(0, scrollView.getBottom());
If the user touches the layout, I need the scrolling to stop & stay at the current position.
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//// TODO: 01/08/16 STOP SCROLLING
}
return false;
}
});
I've tried to smoothScrollBy(0,0); but it's not working.
Well I solved it by using an ObjectAnimator. It not only elegantly worked as a solution but also gave me control on the scrolling speed.
I replaced
scrollView.smoothScrollTo(0, scrollView.getBottom());
with
objectAnimator = ObjectAnimator
.ofInt(scrollView, "scrollY", scrollView.getBottom())
.setDuration(3000);
objectAnimator.start();
and then
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
objectAnimator.cancel();
}
return false;
}
});
One approach may be to use smoothScrollToPosition, which stops any existing scrolling motion. Note this method requires API level >= 8 (Android 2.2, Froyo).
Note that if the current position is far away from the desired position, then the smooth scrolling will take quite a long time and look a bit jerky (at least in my testing on Android 4.4 KitKat). I also found that a combination of calling setSelection and smoothScrollToPosition could sometimes causes the position to "miss" slightly, this seems to happen only when the current position was very close to the desired position.
In my case, I wanted my list to jump to the top (position=0) when the user pressed a button (this is slightly different from your use case, so you will need to adapt this to your needs).
I used the following method to
private void smartScrollToPosition(ListView listView, int desiredPosition) {
// If we are far away from the desired position, jump closer and then smooth scroll
// Note: we implement this ourselves because smoothScrollToPositionFromTop
// requires API 11, and it is slow and janky if the scroll distance is large,
// and smoothScrollToPosition takes too long if the scroll distance is large.
// Jumping close and scrolling the remaining distance gives a good compromise.
int currentPosition = listView.getFirstVisiblePosition();
int maxScrollDistance = 10;
if (currentPosition - desiredPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition + maxScrollDistance);
} else if (desiredPosition - currentPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition - maxScrollDistance);
}
listView.smoothScrollToPosition(desiredPosition); // requires API 8
}
In my action handler for the button I then called this as follows
case R.id.action_go_to_today:
ListView listView = (ListView) findViewById(R.id.lessonsListView);
smartScrollToPosition(listView, 0); // scroll to top
return true;
The above does not directly answer your question, but if you can detect when the current position is at or near your desired position, then maybe you could use smoothScrollToPosition to stop the scrolling.
I've a scrollview in my activity . When I scroll down , it doesn't scroll much and I want to increase the the amount of scroll .
How can I do so?
Flinging is the type of scrolling that occurs when a user drags and lifts her finger quickly.
#Override
public void fling(int velocityY) {
int topVelocityY = (int) ((Math.min(Math.abs(velocityY), MAX_SCROLL_SPEED) ) * Math.signum(velocityY));
super.fling(topVelocityY);
}
I copied it from here.
I have a problem while scrolling in Scrollview. I would like to cancel scrolling immediately when I've scrolled to a specific position (like end of scrollview). That means my finger is still held on screen, but Scrollview can't scroll anymore. How can I accomplish this?
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mScrollable) return false;
else return super.onInterceptTouchEvent(ev);
}
If you want to cancel/stop scroll when you have reached particular position, you can use smoothScrollToPosition(int pos), where pos is the adapter position.
Also to make it a smooth experience for user, you can tell the listview to post it when ui thread is free:
getListView().post(new Runnable() {
#Override
public void run() {
getListView().smoothScrollToPosition(pos);
}
});
Imagine a grid (implemented using a canvas) that is infinite to the right and bottom, but has a bound on left and top. I want to be able to scroll this grid using fling gestures. I implemented this using the GestureDetector and Scroller classes like below. While this is mostly acceptable and looks good, the distance and speed of the scrolling is rather short / slow when doing a strong fling.
This is good when doing a less powerful fling, for example like you would do when you only wanted to scroll your contacts list or browser a little. For a fling that would scroll your contacts list a lot however, this method only scrolls my grid a little more than the previous one does.
I'm not sure what to set the minX/maxX/minY/maxY parameters to in order to achieve those effects, or what else to change. The value I chose so far is a good compromise for my purposes: it never scrolls too much or too fast, but it does scroll too little and too slow for strong flings.
How can I achieve a similar fling behavior to the contacts list (I'm thinking about the ICS / JB one, but I think all of them behave roughly the same in this regard) and browsers in my infinite grid?
Relevant code listing:
private int mFlingPrevX, mFlingPrevY;
private void moveSurface() {
if (mFlinger.computeScrollOffset()) {
mScroll.x = -mFlinger.getCurrX() + mFlingPrevX;
mScroll.y = -mFlinger.getCurrY() + mFlingPrevY;
mFlingPrevX = mFlinger.getCurrX();
mFlingPrevY = mFlinger.getCurrY();
invalidate();
post(new Runnable() {
#Override
public void run() {
moveSurface();
}
});
}
}
#Override
public boolean onFling(final MotionEvent motionEvent1, final MotionEvent motionEvent2, final float velocityX, final float velocityY) {
ViewConfiguration config = ViewConfiguration.get(mContext);
if (Math.abs(velocityX) < config.getScaledMinimumFlingVelocity() || Math.abs(velocityY) < config.getScaledMinimumFlingVelocity()) {
return true;
}
mFlinger = new Scroller(mContext);
mFlingPrevX = getScrollX();
mFlingPrevY = getScrollY();
// 380 is a magic number that works OK, but it doesn't do what I'm asking here
int distance = (int)(GameGlobals.getScreenDensityFactor(mContext) * 380);
mFlinger.fling(mFlingPrevX, mFlingPrevY, -(int)velocityX, -(int)velocityY, -distance, distance, -distance, distance);
post(new Runnable() {
#Override
public void run() {
moveSurface();
}
});
return true;
}
Well, this was easier than expected. The first two parameters actually give information about where the fling started and where it ended, so we can find the distance variable based on that.
float dx = motionEvent1.getRawX() - motionEvent2.getRawX();
float dy = motionEvent1.getRawY() - motionEvent2.getRawY();
int distance = 3 * (int)Math.sqrt(dx * dx + dy * dy);
mFlinger.fling(mFlingPrevX, mFlingPrevY, -(int)velocityX, -(int)velocityY,
-distance, distance, -distance, distance);
I like this one best so far, but obviously it can be changed to anything you like.
I'm having a problem implementing a long press within my custom view, based on a HorizontalScrollView.
The HorizontalScrollView has a child LinearLayout, which in turn has a child View. The View draws bitmaps to the canvas via OnDraw().
I'd like to allow the HorizontalScrollView to scroll normally, either fast or slow. But, if the user holds their finger (even if scrolling) on one of the images, it would immediately cancel the scrolling and allow the user to perform a function with the selected image. (In this particular case, they'd be moving the image around the screen, but it could really be any number of functions.)
I've tried many combinations of handling the events (true, false, super) within each layer (HorizontalScrollView and View) but none seem to work 100%. Some combinations get there most of the way, some others part of the way, but they always seem to be missing one feature or another (scroll, hit test, etc.).
The closest I've gotten is to return false within the HorizontalScrollView's onInterceptTouchEvent() and true within the View's onTouch() event. This allows the scroll and also registers the hit test on the image. But, it immediately passes control back to the onTouch() event of the HorizontalScrollView. That makes it impossible to check if the image has been pressed for a number of seconds (long press).
If I return true within the View's onTouch() event, the hit test registers, and I'm able to check if the user has long pressed the image within ACTION_MOVE. But, then the HorizontalScrollView doesn't scroll.
Am I missing something completely obvious, or have I simply chosen two views that don't play well together? Any insight is appreciated.
right,
dont know if you have sorted this or not, I have mashed some bits together that I think do what you ask, if not then hey ho.
I have an activity that loads in the horizontal scroller, this might not be the best way but it works for me:
HolderActivity class (the one that loads in the HorizontalScrollView class) I have:
int selectedItem;
public boolean onLongClick(View v, int position) {
selectedItem = position;
openContextMenu(v);
return true;
}
public boolean onItemClick(int position) {)//do what you want here on click (press)
#Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
String[] menuItems = {"Menu item 1 text", "Cancel"};
for (int i = 0; i<menuItems.length; i++) {
menu.add(Menu.NONE, i, i, menuItemsRemove[i]);
}
menu.setHeaderTitle("My menu title");
}
in your HorizontalScrollView class's constructor pass I pass through a context in there like so:
public MyScroller(Context context) {
super(context);
this.context = context;
}
I have a method for creating the items from an ArrayList called setFeatureItems like so:
public void setFeatureItems(ArrayList<MyListEntity> items){}
Within this method I add a GestureDetector passing the context to it to each item like so:
mGestureDetector = new GestureDetector(context, new MyGestureDetector());
And the MyGestureDetector nested class which has the reference to the all important parentActivity is like this:
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public void onLongPress(MotionEvent arg0) {
parentActivity.onLongClick(MyScroller.this, mActiveFeature);
};
#Override
public boolean onSingleTapUp(MotionEvent arg0) {
parentActivity.onItemClick(mActiveFeature);
return true;
};
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature < (mItems.size() - 1))? mActiveFeature + 1:mItems.size() -1;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature > 0)? mActiveFeature - 1:0;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
} catch (Exception e) {
Log.e("Fling", "There was an error processing the Fling event:" + e.getMessage());
}
return false;
}
}
I have cut this from an existing proj so there might be remnants where I have not made it generic enough, I hope this makes sense or helps, let me know If i can add any more detail