I have a ListView with about 100 entries. When the user does the "fling" from bottom to top it starts scrolling and keeps on scrolling even when the finger does not touch the display any more.
Is there a way to stop the scrolling animation at this point?
and we lookup the android source code (AbsListView), give it a ACTION_CANCEL touchEvent, can stop the fling. it is easy.
listView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0));
I didn't try the solution of Pompe de velo but since smoothScrollToPosition() is not available for API level less than 8 this didnt work for me.
I agree, changing default behaviour is not a good Idea, but sometimes you need to. So here is my (dirty) solution which uses reflection. This is by far not the recommended way since it's a hack but it works for me. There might be a better solution but I didn't found it.
class StopListFling {
private static Field mFlingEndField = null;
private static Method mFlingEndMethod = null;
static {
try {
mFlingEndField = AbsListView.class.getDeclaredField("mFlingRunnable");
mFlingEndField.setAccessible(true);
mFlingEndMethod = mFlingEndField.getType().getDeclaredMethod("endFling");
mFlingEndMethod.setAccessible(true);
} catch (Exception e) {
mFlingEndMethod = null;
}
}
public static void stop(ListView list) {
if (mFlingEndMethod != null) {
try {
mFlingEndMethod.invoke(mFlingEndField.get(list));
} catch (Exception e) {
}
}
}
}
Well there surely is a way to do it. But the point is more whether or not it is advisable to do it, in my opinion.
The list is a standard Android control that behaves constistently across all applications. So I would be surprised if I found a list that did not behave the same in your application. You can stop the fling by putting your finger back on the screen at any time.
That said, if you want to do extra work, you could subclass the list view and override its on touch method. Best way to know what to do is to get the source code of ListView (ListView in Android 1.6).
You can prevent flinging for ListViews in API 8 by overriding onTouchEvent and calling smoothScrollBy.
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
this.smoothScrollBy(0, 0);
break;
}
return super.onTouchEvent(ev);
}
This takes over from the fling scrolling and scrolls 0px instead.
My opinion is that you shouldn't modify this behaviour, since the fling behaviour is what the user expects.
However, to your question. I haven't tried this but in theory it should work.
Implement an OnScrollListener to your ListView and use the onScrollStateChanged() method to check if the current state is SCROLL_STATE_FLING. After you've determined that the scrolling perfoms by a fling you can get your ListView's first visible position by using the getFirstVisiblePosition() method and from there you can use smoothScrollToPosition() where you put in your getFirstVisiblePosition() value as an argument.
if you what disable default animation from list view just need set id for root (main) layout in xml and call void onClickListener in class for root layout
Related
I want to play animation when user clicks or swipes. But can we handle both behaviours in MotionLayout? They work separately perfectly, but if I add OnClick and OnSwipe in the same scene, only OnClick works. Is there any workarounds?
Yes, you can work around this by removing the onClick behavior from your MotionScene and implement the click yourself by extending the MotionLayout and overriding dispatchTouchEvent.
In this function, you decide if the touch event is a click inside the 'target area' where you want the onClick behavior to occur.
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if (touchEventInsideTargetView(clickableArea, ev)) {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
startX = ev.x
startY = ev.y
}
MotionEvent.ACTION_UP -> {
val endX = ev.x
val endY = ev.y
if (isAClick(startX!!, endX, startY!!, endY)) {
if (doClickTransition()) {
return true
}
}
}
}
}
return super.dispatchTouchEvent(ev)
}
A post on this and a full code example can be found here:
https://medium.com/vrt-digital-studio/picture-in-picture-video-overlay-with-motionlayout-a9404663b9e7
Sure, just handle the onTouch event in your parent class.
Then decide what you are doing with it and what to send it to.
onTouch(touchEvent){
motionLayout.doClickIfYouWant(touchEvent)
motionLayout.doSwipeIfYouWant(touchEvent)
}
Pseudo code, but you can send it to both if you catch it first and decide who gets it. Also don't forget to return handled boolean flag from onTouch callback to ensure it doesn't get handled twice. Lastly, if other items are touchable on the screen you may have to check the x,y of your touch to determine if it should go to the motionLayout or just return "not handled" and let the native behavior pass the touch on through.
Hope that helps. Also if the motionLayout class doesn't expose the methods you need to touch to send the correct touch event to more then one place, you can still get it using reflection. Of course reflection can be a little slower, so use with caution, but I've had to do it before for a layout class that had children images that I needed to move around, and controlled the touch at a parent level to decide if the image gets it or not, but it was not publicly available, so I looked at the code and touched it via reflection with no issues. Treat that as a last resort though, and maybe everything you need is exposed.
Also be aware some engineer despise reflection lol, so code with caution.
I am facing a strange behaviour with a RecyclerView as a second child of CoordinatorLayout, just after an AppBarLayout (as described in a lot of examples).
My problem is when I scroll the recycler view and I want to click on a particular item. Sometimes I need to click 2 times to select that item, it seems to be linked to the fling behaviour. For example, if I scrolled to the bottom of the recycler view, then if I fling my finger from the bottom of the screen to the top (in order to see more data, but in my case I can't see more data since I am already to the bottom) and then quickly click on an item, it seems to stop the fling, and the second click actually select the item...
This behaviour is clearly not happening when using a simple recycler view without CoordinatorLayout.
My recyclerview is just holding a simple list of String, and using the following layout behaviour : #string/appbar_scrolling_view_behavior
Do you have any idea why ?
[EDIT]
I just tried with the Android Studio sample Scrolling Activity, and it looks like it is a bug from Google support repository.
In fact, when using support version 26.1.O (same with 26.0.0 and 26.0.2), the bug I am talking about is present, but if you try with the version 26.0.0-alpha1 or 26.0.0-beta1, it is actually working...
There is two open bugs at Google about this :
https://issuetracker.google.com/u/1/issues/66996774
https://issuetracker.google.com/u/1/issues/68077101
Please star these bugs if you are facing the same problem
Google just posted a workaround for this bug, it will be publicly released later.
https://gist.github.com/chrisbanes/8391b5adb9ee42180893300850ed02f2
If Using RecyclerView in NestedScrollView add this line to RecyclerView :
android:nestedScrollingEnabled="false"
I hope it help you.
I also found this problem ... after wasting so many hours searching and trying different things, I came out with a trick, its not pretty but it could work for someone else too.
Basically the idea is simulate a click on the nestedScrollView.
In my case after I detect the 'AppBarLayout' its fully expanded, I send a tap to the nested.
protected void onCreate(final Bundle savedInstanceState) {
getAppBarLayout().addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
if (verticalOffset == 0) {
// State.EXPANDED
simulatedClick(nestedScroll)
} else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
// State.COLLAPSED
} else {
// State.IDLE
}
}
});
}
private void simulatedClick(#NonNull final View view) {
// Obtain MotionEvent object
final long downTime = SystemClock.uptimeMillis();
final long eventTime = SystemClock.uptimeMillis() + 100;
final MotionEvent motionEvent = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
// Dispatch touch event to view
view.dispatchTouchEvent(motionEvent);
}
NOTE: I don't really recommend the use of hacks like this, it's unprofessional and unmaintainable, but the more you know...
I am working on an Android app that runs on only one devicerunning KitKat.
The smooth scrolling feature for a RecylerView I used that was working on other physical tablets and genymotion has unfortunately stopped working on the one device it needs to work on.
Instead of scrolling to a certain position it passes over the target position and scrolls all the way to the bottom and looks really bad.
I am able to track down the error to the abstract SmoothScroller in the RecyclerView class.
if (getChildPosition(mTargetView) == mTargetPosition) {
onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
mRecyclingAction.runIfNecessary(recyclerView);
stop();
} else {
Log.e(TAG, "Passed over target position while smooth scrolling.");
mTargetView = null;
}
I was using a SnappingLinearLayoutManager that I found online, but swapped it out with the normal LinearLayoutManager from Android, and still am having the same problem.
The list is 7 items long (user can see 4 at a time) and I scroll to the 5th item (position 4) item.
When I scroll to the 3rd I don't receive this error.
Also after I scroll the list up and down once, the error stops happening.
EDIT:
I am able to use layoutManager.scrollToPositionWithOffset(); But I am trying to do this with the smooth scroll animation.
Here is some of my code and details:
private void setupMainRecyclerViewWithAdapter() {
mainLayoutManager = new SnappingLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mainListRecyclerView.setLayoutManager(mainLayoutManager);
settingsMainListAdapter = new SettingsListAdapter(SettingsActivity.this,
settingsPresenter.getSettingsItems(),
settingsPresenter);
mainListRecyclerView.setAdapter(settingsMainListAdapter);
mainListRecyclerView.addItemDecoration(new BottomOffsetDecoration(EXTRA_VERTICAL_SCROLLING_SPACE));
}
#Override
public void scrollMainList(boolean listAtTop) {
if(listAtTop) {
mainListRecyclerView.smoothScrollToPosition(4);
moveMainMoreButtonAboveList();
} else {
mainListRecyclerView.smoothScrollToPosition(0);
moveMainMoreButtonBelowList();
}
}
If you call recyclerView.smoothScrollToPosition(pos) will be called immediately on the UI thread and if recyclerView's Adapter is too much busy to generating view items then the calling of smoothScrollToPosition will be missed then because recyclerView has no data to smooth scroll. So it's better to do that in a background thread by recyclerView.post(). By calling this it goes into the Main thread queue and gets executed after the other pending tasks are finished.
Therefore you should do something like this which worked for my case:
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(pos);
}
});
Well, I realize it's too late, however I tried some different solutions and found one...
in custom LinearSmoothScroller I override updateActionForInterimTarget
#Override
protected void updateActionForInterimTarget(Action action) {
action.jumpTo(position);
}
It's appears not very smooth, but not instant in contrast with scrollToPositionWithOffset.
Just add one line for smooth scroll
recyclerView.setNestedScrollingEnabled(false);
it will work fine
Take a look at hasPendingAdapterUpdates(). You can use this along with a delay() for coroutines or Thread.sleep() to enable the backing data to be available before doing the scroll.
So I was trying to disable the screen for an app I am making for a brief period using this
#Override
public boolean onTouchEvent(MotionEvent pMotioneEvent) {
if(pMotioneEvent.getY() < TestSprite.getY()){
return false;
}else{
return true;
}
}
but this seems to have no effect. I read around and it seems like in general its a bad idea to disable the touch screen, but I'm still curious to know if there is a way.
Thanks
You could try
requestDisallowInterceptTouchEvent
If you want to check for touchevents in a certain area of your screen, you might want to put this in a View and set a touchEvent Listener to it.
I'm currently fighting against the OnLongClickListener on Android Api Lvl 8.
Take this code:
this.webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
System.out.println("long click");
return true;
}
});
It works perfectly. I can press anywhere on the WebView and the event triggers every time.
Now take a look at this one:
this.webView.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final EditText editText = getUrlTextField();
switch (editText.getVisibility()) {
case View.VISIBLE:
editText.setVisibility(View.GONE);
return true;
case View.GONE:
editText.setVisibility(View.VISIBLE);
return true;
default:
return false;
}
}
});
Assuming the URL EditText components is currently visible, it gets gone from the display and should be shown again when another long click event is triggered.
But if you run this, the event just works once (!) when one performs a long click on any position on the WebView. To make things complicated, the long click works again when it is performed on a link on the website...
Can anyone explain if it is a bug in the sdk and/or if there is a mistake in my thinking how the OnLongClickListener is working?!? :/
EDIT:
I've run now several different scenarios on a Nexus One and come to following conclussion: Changing the layout on runtime more or less kills the OnLongClickListener... I haven't found a way to get it work reliably at all...
I would really appreciate if anyone could give me a hint... I'm at my wits end :(
Personnally, I ended up by re-setting the listener after each relayout.
I've run into this issue as well. It seems that if the view layout changes in a way that child view bounds need to be modified (i.e. TextView is wrap_content width and you set its text to something longer/shorter than it was before), views in the hierarchy will have their onStartTemporaryDetach method called (most likely due to a layout pass, although I haven't dug deep enough to find out for sure). If you look at the source for View that onStartTemporaryDetach ultimately unsets the pressed state of the view.
Changing the views in your layout that will be updated periodically to have bounds that will not change regardless of the value you set, will fix the issue. Although, that is still not awesome.