I am working on an application which looks somewhat like this:
The RecyclerView uses a custom LayoutManager with a custom implementation of "scrollVerticallyBy"
First of. The recyclerview scroll butter smooth and renders in way less than the required 16 ms. I think I can max i out around 5 ms on a Nexus 5.
However, I also want to be able to scroll the RecyclerView from touching the View above it (it's a RelativeLayout).
Here is the problem:
1) If I just forward the onTouchEvents the scrolling is butter smooth. However, RecyclerView.fling(...) is never invoked and scrolling only works as long as the finger is touching the screen. No flinging which feels very awkward.
#Override
public boolean onTouchEvent(MotionEvent event) {
return mRecyclerView.onTouchEvent(event);
}
2) If I override .fling(...) in the RecyclerView I can get "flinging" to work by using a Scroller. This works, but does cause some strange large lags.
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mScroller.computeScrollOffset()) {
Log.d("RecyclerView", "(ondraw) Y: " + mScroller.getCurrY());
scrollBy(0, mScroller.getCurrY()- mLastY);
mLastY = mScroller.getCurrY();
postInvalidateDelayed(16); // 60 Hz
}
}
#Override
public boolean fling(int velocityX, int velocityY) {
mLastY = 0;
mScroller.fling(0, 0, velocityX, velocityY, 0, 0, -10000 ,10000);
return true;
}
I am unsure what the correct way to do this is. I feels like that if I am not overriding .fling() and force my app to repeatedly call scrollBy() by using the Scroller the scrolling is working differently.
I suspect this may have something to do with "startSmoothScroll" and "smoothScrollToPosition" which I have not overwritten. I honestly don't know what smoothscrolling is but it sounds relevant to this problem :)
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 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);
}
});
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 have a wevbiew with a number of 'pages' (columns of text) in it, and want to be able to scroll horizontally between columns in response to Fling gestures. I can do this fine using scrollTo(), but that is rather sudden and I'd really like to be able to do a smooth transition from one 'page' to the next.
The problem as I see it: you can't attach a Scroller to a WebView, and shouldn't nest a WebView in a ScrollView.
Does anyone have any good ideas for implementing smooth or animated horizontal scrolling in a WebView?
Edited to add: I'm trying to implement an idea of knotmanish's where I use an unattached Scroller to compute the distance..
// vx and vy are from the onfling event of the gesture detector
scroller.fling(page*x, 0, vx, vy, page*(x + 1), page*(x + 1), 0, 0);
while(scroller.computeScrollOffset()){
webview.scrollTo(scroller.getCurrX(), 0);
webview.invalidate();
}
... but the while loop seems too slow to capture the scroller's movement, or something, because the behavior looks just like the scrollTo() method (the view jump to the end of the scroll).
You cannot attach a scroller to a webview however you can make use of the gesture to do the same.
The gesture detects the fling using this method
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
Give the velocityX, velocityY to the scoller method along with other parameters
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
scan the computeScrollOffset() value to find the getCurrX(),getCurrY() positions.
Extend the webview and override the methods onTouchEvent(MotionEvent ev), dispatchTouchEvent(MotionEvent ev) to return false so that by default the touch events are not consumed by the webview itself.
Now implement the gestureListener and override the onfling method mentioned above.
pass the velocity and other parameters to the scroller.
Start a loop to scan the computeScrollOffset() value to find the getCurrX() and getCurrY() and invalidate the view each time.
Pass this value to scroll the webview as needed.
Please let me know in case you need anything.