How to prevent touches prematurely stopping smoothScroll in RecyclerView? - java

I am using an Adapter to fill a RecyclerView with numerous CardView layouts (horizontally). Each CardView has a Button at the bottom of the layout, which when pressed prompts the call:
recyclerView.smoothScrollBy(x, y);
This scrolls horizontally to the next card as expected, unless the user touches the screen as it is scrolling. I have attempted disabling the TouchEvent for the RecyclerView and the CardView to no avail.
I obviously cannot rely on a user to not touch the screen during this scrolling animation, so I was wondering if anyone had any ideas as to why the scrolling is halted prematurely and how I can stop it.
Thanks.

problem is onInterceptTouchEvent
if you add listener to recyclerView this way:
addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
return true
}
})
if it is return true you can't scroll recyclerView manually (programmatically you can)
if it is return true scrolling is stopped/interrupted
this option worked for me (add this to recyclerView with previous code snippet (to prevent manual scrolling) to prevent scrolling interrupt on touch):
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
if (layoutManager?.isSmoothScrolling == true || scrollState == SCROLL_STATE_SETTLING) {
return true
}
return super.onInterceptTouchEvent(e)
}
override fun onTouchEvent(e: MotionEvent): Boolean {
if (layoutManager?.isSmoothScrolling == true || scrollState == SCROLL_STATE_SETTLING) {
return true
}
return super.onTouchEvent(e)
}

Disable user interactions before the scroll and reenable them a little after. I would use window flags:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
recyclerView.smoothScrollBy(x, y);
new Handler().postDelayed(new Runnable() {
public void run(){
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
}
}, 500);

Related

How to know that last recyclerView item of first or current screen/page is loaded, any Callbacks but gets called once?

I am using a grid layout of recyclerView and have tried several options using either onLayoutCompled methods and also by onScollListner methods, tried this answer but it's stiil calling multiple times as each item keeps loading, nothing is working.
Just want to detect the last recyclerView item of the particular screen is completely loaded or visible to the user according to their screen size Or we can say that when the user's current screen is full of the recyclerView Items, any way to detect that? ANd should be called only once. Any help would be great.
Thank you for your valuable time.
You can use a Sharedpref value which can be set to true in your onScrollStateChanged method based on your condition.
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState != RecyclerView.SCROLL_STATE_IDLE) {
return;
}
boolean isLastPositionVisible = mFollowingLayoutManager.findLastVisibleItemPosition() == getList().size() - 1
if(isLastPositionVisible && getSharedPrefValue()) {
// Call your method
// Set sharedpref value to true.
}
}

Disable NestedScrollView when Boolean comes back false

I currently have a CoordinatorLayout with both a ConstraintLayout and a NestedScrollView in it. The NestedScrollView has a peek height of 50dp which is just a title. I want to be able to pull up the NestedScrollView when the device is connected, however when it is not connected I only want to be able to see the peek and not be able to drag up the rest of the view.
if(deviceConnected) {
mBottomSheetText.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
} else {
//Want to prevent it form being draggable
}
The DragCallback interface allows to choose whether the sibling scrolling view should be controlled by scrolls onto the AppBarLayout.
You can do that like below:
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return false;
}
});
By always returning false, your scrolling view will not be controlled by the appbarLayout any longer.
Note: before calling this you should check that ViewCompat.isLaidOut(appBarLayout), otherwise params.getBehavior() will return null.
Check this link.

Disable Touch Event Intercept

I have a ScrollView with a LinearLayout and then a bunch of TextViews inside that layout, the goal is to make a CustomListView that will open and close various TextViews along the LinearLayout However, I also want to make an autoscroll feature where when the user double taps the screen the screen will scroll by its self.
I already have the second part done, when ever the user touches the ScrollView I have extended ScrollView and implemented this:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("ScrollView Intercept", "ACTION_DOWN");
return true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
Log.d("ScrollView Intercept", "ACTION_UP");
return true;
} else if (ev.getAction() == MotionEvent.ACTION_CANCEL) {
Log.d("ScrollView Intercept", "ACTION_CANCEL");
}
return super.onInterceptTouchEvent(ev);
}
Now the ScrollView has all the touch events.
However, I still want the events for the CustomListView, I have tried to implement a equestDisallowInterceptTouchEvent but it doesnt seem to be working:
#Override
public boolean onTouchEvent(MotionEvent event) {
getParent().getParent().requestDisallowInterceptTouchEvent(true);
Log.d("touch listner","Touch");
return true;
}
PS. I have tried to se ListView in the past, but it was a but confusing so I just extended the TextView class and wanted to use on click and manually make the children Visible and Gone

Prevent Click-through of Android ImageVIew?

I have an ImageView overlay inside of a RelativeLayout and want to prevent any clicks from going through the ImageView to the Buttons etc that are behind it (thus in effect disabling the entire RelativeLayout).
Is the a simpler way of doing this then iterating the RelativeLayout views and setting them to disabled as I currently am doing using this code:
RelativeLayout rlTest = (RelativeLayout ) findViewById(R.id.rlTest);
for (int i = 0; i < rlTest.getChildCount(); i++) {
View view = rlTest.getChildAt(i);
view.setEnabled(true);
}
you can set the image to be
android:clickable="true"
Simply call rlTest.setClickable(false). This will prevent the click to be propagate to the children
There is a much cleaner way
You can use:
android:onClick="preventClicks"
in XML and in your Activity
public void preventClicks(View view) {}
This works with fragments.
Example inside this Activity has multiple fragments overlapping one another, just by adding the XML attribute in the background of your fragment it will still call the Activity.preventClicks and will prevent touches on fragments behind it
The following solution works in the general case:
_container.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// NOTE: This prevents the touches from propagating through the view and incorrectly invoking the button behind it
return true;
}
});
It basically blocks any touches from propagating through the view by marking the touch event as handled.
This works on both UI controls and layout containers (ie: LinearLayout, FrameLayout etc.).
The solution to set "clickable" as false did not work for me for layout containers either in code or in the view XML.
I assume that you are using onClickListeners.
How about using onTouchListener instead of onClickListeners. By doing this you will have a control over how deep down in your hierarchy the touch even can be visible. For example, if you have toch listeners on a relative-layout(RL) and a image-view(IV)(contained in RL), and you assign touchListeners to both. Now if you return true from IV's touch event, the lower down member RL will not receive the touch event. However if you return false from from IV's touch event, the lower down member RL will receive the touch event.
Hope this helps!
Just add these two listeners:
// Set an OnTouchListener to always return true for onTouch events so that a touch
// sequence cannot pass through the item to the item below.
view.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.onTouchEvent(event);
return true;
}
});
// Set an OnHoverListener to always return true for onHover events so that focus cannot
// pass through the item to the item below.
view.setOnHoverListener(new OnHoverListener() {
#Override
public boolean onHover(View v, MotionEvent event) {
v.onHoverEvent(event);
return true;
}
});
You could use databindings and consume the clicks like this:
android:onClick="#{() -> true}"
In C#, I use an empty delegate:
objectName.Click += delegate {};
I haven't encountered any problems with it but it does prevent clicks from filtering through to underlying controls.
you can also se the root click listener to null:
// Do not process clicks on other areas of this fragment
binding.root.setOnClickListener(null)
This works 100%.
It doesnt affect other listeners that are already set on the fragment's views.

screenshots don't change when layout changes

I'm using ViewPager as my main navigation in app. I'm using ActionBar, as well. What I want to achieve is that when user clicks search button in ActionBar I want to have blurred background. So my approach is to take a screenshot, blur it, set as ImageView and display as an overlay over the whole view. And it works. But problem is that actually when I first open search it displays proper screenshot. Then I turn overlay off, change page in ViewPager, click search again and ... I can see the previous screenshot- not new one with new page on it. Here are my snippets of code:
show and hide overlay on search click (I'm passing R.id.container view which is parent id of my content view, menuOverlay is my ImageView referenced from layout)
Here's my method that takes screenshot and makes it blurry
// Ad. 1
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
#Override
public boolean onMenuItemActionExpand(MenuItem menuItem) {
if(menuItem.getItemId() == R.id.action_search) {
menuOverlay.setImageBitmap(Utils.takeSnapshot(findViewById(R.id.container)));
menuOverlay.setVisibility(View.VISIBLE);
}
return true;
}
#Override
public boolean onMenuItemActionCollapse(MenuItem menuItem) {
if(menuItem.getItemId() == R.id.action_search) {
menuOverlay.setVisibility(View.GONE);
}
return true;
}
});
// Ad. 2
public static Bitmap takeSnapshot(View v) {
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
Bitmap bm = v.getDrawingCache();
Bitmap scaled = Bitmap.createScaledBitmap(bm, bm.getWidth()/5, bm.getHeight()/5, false);
return Utils.fastblur(scaled, 5);
}
I;m using fast blur algorithm found somewhere here on StackOverflow which is quite fast.
Where's the problem? Why it happens?
ok, I found the answer. if you want to always have "fresh" screenshot you need to destroy cache after all operations:
v.setDrawingCacheEnabled(false);

Categories