I am currently working with recyclerView and cards.
I have a cards layout. Now i want to add scrollview inside the cards is that possible. If yes then how ?
The brown layout is the recycler view layout while the blue layout is the cards layout. Inside each card i need a scrollview is that possible ?
I am not sure if this will work but you can try this
Disable the touch of recycler view when scrollview is being touched.
Similarly disable scrollview touch on recyclerview touch.
This can be acheived like this
//For scrollView
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// Disallow the touch request for parent scroll on touch of child view
view.getParent().requestDisallowInterceptTouchEvent(false);
return true;
}
});
see if this helps you.
I finally found an another way how the ScrollView can be scrolled inside RecyclerView viewHolder. As Ankit Khare's answer, in order to this works, we should disable the touch of RecyclerView when ScrollView is being touched, but his solution is too simple and it didn't quite work well after I tried that. Yeah I know both question and answers were posted from 6 years ago, maybe at that time it worked, but now it's not work anymore after I tried that.
Here is my analysis. Instead of disable the touch of RecyclerView, we should disable its scroll mechanism instead. In order to RecyclerView's scroll can be disabled, we should use function canScrollVertically() from LayoutManager which you use for the RecyclerView. Then, in RecyclerView's adapter, I implemented onTouchListener() for the ScrollView to trigger the touch on it and disable or enable the RecyclerView. So, here is my sample code (I'm using Kotlin):
class MyActivity : AppCompatActivity(), MyAdapter.AdapterListener {
private var isVerticalScrollEnabled = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/* Set RecyclerView adapter here */
recyclerView.apply {
layoutManager = object : LinearLayoutManager(this#MyActivity) {
override fun canScrollVertically(): Boolean {
return isVerticalScrollEnabled
}
}
adapter = MyAdapter(callback = this#MyActivity)
}
}
/**
* Callback from MyAdapter.AdapterListener.
*/
override fun onScrollViewTouched(isTouched: Boolean) {
isVerticalScrollEnabled = !isTouched
}
}
class MyAdapter internal constructor(private val callback: AdapterListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
/* set touch listener for scrollview */
scrollView.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
return when (event?.action) {
MotionEvent.ACTION_DOWN -> {
callback.onScrollViewTouched(isTouched = true)
true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
callback.onScrollViewTouched(isTouched = false)
true
}
else -> {
/* return false so that scrollview can scroll the content */
false
}
}
}
})
}
internal interface AdapterListener {
fun onScrollViewTouched(isTouched: Boolean)
}
}
Hope this will help on someone who look up another answer.
Try this instead.
public class NoInterceptScrollView extends ScrollView {
public NoInterceptScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
}
Related
I can't figure out how to use a gesture detector in a custom view.
I want to useonLongPress, but I don't know where to put it or how to use it.
class CustomView(context: Context, attr: AttributeSet) : View(context, attr) {
val listener = object :
GestureDetector.SimpleOnGestureListener() {
}
}
val detector = GestureDetector(context, listener)
}
Maybe someone can help me.
Thank you.
Have you read this? https://developer.android.com/training/gestures/detector#detect-all-supported-gestures
It's worth reading the whole thing, but basically you override the functions for the gestures you want to handle in your SimpleOnGestureListener
val listener = object : GestureDetector.SimpleOnGestureListener() {
override fun onLongPress (MotionEvent e) {
// do whatever
}
}
Then you create a GestureDetector using that listener:
val detector = GestureDetectorCompat(context, listener)
then you override your view's onTouchEvent method and let your detector handle the events:
override fun onTouchEvent(event: MotionEvent): Boolean {
detector.onTouchEvent(event)
return super.onTouchEvent(event)
}
I am using a RecyclerView in a Fragment to display a list of elements, each containing a checkbox, as shown below.
Upon clicking the START button, I want to disable all of the elements in the RecyclerView.
Here is my code to do so:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
startButton = view.findViewById<Button>(R.id.start_button)
startButton.setOnClickListener {
// yes, recyclerView is defined
setViewGroupEnabled(recyclerView, false)
}
}
fun setViewGroupEnabled(view: ViewGroup, enabled: Boolean) {
Log.d(TAG, "Numkids: ${view.childCount}")
view.isEnabled = enabled
for (v in view.children) {
if (v is ViewGroup) setViewGroupEnabled(v, enabled)
else v.isEnabled = enabled
}
}
This code disables most of the elements in recyclerView, but for some reason it skips some, often multiple at a time. It also appears to skip subviews in a pattern that varies based on how far down the list I have scrolled.
Why is it behaving so strangely?
A RecyclerView has an Adapter. Its job is to handle the layout of each item of the RecyclerView. This includes disabling an item.
Add a class parameter to your adapter:
private var disabled = false
Add a method to your Adapter:
fun setDisabled(disabled: Boolean) {
this.disabled = disabled
notifyDatasetChanged()
}
In your onBindViewHolder method, check for the disabled parameter and disable the view as you want to:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (this.disabled) {
//disable you view, by disabling whichever subview you want
} else {
// The normal non disabled flow (what you have now)
}
}
Now call setDisabled(true) on a button click:
startButton.setOnClickListener {
// yes, recyclerView is defined
adapter.setDisabled(true)
}
And call setDisabled(false) to enable the items back.
This is probably a noob question but I'm still new to Kotlin. I'm creating a single gesture detector for a few views and would like to pass the view to the listener, or even better a custom parameter.
My code:
val gestureDetector = GestureDetector(this#EditProfileActivity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
Log.d("myApp", "single tap confirmed press")
}
})
profilePic1.setOnTouchListener { view, event -> gestureDetector.onTouchEvent(event) }
profilePic2.setOnTouchListener { view, event -> gestureDetector.onTouchEvent(event) }
I'm attaching the same listener to different views and want a way to differenatiate between the views in the override functions. How can I get the view in those functions, and in addition is it possible to pass a custom parameter such as Int
Also, I tried to create my custom gestureDetector, but I get an error that "none of the following functions can be called with the arguments supplied", since I add the custom position argument, although I'm making a custom detector so I don't understand why it's not working:
class detector (context: Context?, listener: GestureDetector, handler: Handler, unused: Boolean, val position: Int )
: GestureDetector(this#EditProfileActivity, GestureDetector.SimpleOnGestureListener(), null, false, position ) {
So I did find a solution, maybe it can benefit others. I'm not sure if it's ideal to create more than one gesture detectors, although they all direct to the same class:
val gesture1 = GestureDetector(this#EditProfileActivity, GestureListener(1))
profilePic1.setOnTouchListener { view, event -> gesture1.onTouchEvent(event) }
val gesture2 = GestureDetector(this#EditProfileActivity, GestureListener(2))
profilePic2.setOnTouchListener { view, event -> gesture2.onTouchEvent(event) }
class GestureListener (val position: Int) : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent?): Boolean {
Log.d("HAVERI", position.toString())
return super.onDoubleTap(e)
}
}
I have i custom ViewPager that detects taps, long press and long press up events with a GestureDetector. It also allows to wipe ViewPager.
Gesture listener is simple:
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?) = true
override fun onLongPress(e: MotionEvent?) {
mTouchListener.invoke(TAPEVENT.LONGTAP)
mWasLongTap = true
}
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
mTouchListener.invoke(TAPEVENT.TAP)
return true
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
if (mWasLongTap) {
mWasLongTap = false
mTouchListener.invoke(TAPEVENT.LONGTAPUP)
}
return true
}
}
//override view group methods
override fun onTouchEvent(ev: MotionEvent?): Boolean {
super.onTouchEvent(ev)
return mDetector.onTouchEvent(ev)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
mDetector.onTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}
And i have fragments inside a ViewPager. In my fragments i have, lets say, a Button view.
When my button is clicked onSingleTapConfirmed event is fired too.
Cannot figure out what to do to force ViewPager do not process event if there was a click on a child view of a fragment.
Fragment view looks something like this:
<CoordinatorLayout>
<View>
</View>
<Button>
</Button>
</CoordinatorLayout>
I ended up with such a solution.
GestureListener is the same, but onTouchEvent is overridden the other way.
override fun onTouchEvent(ev: MotionEvent): Boolean {
if (ev.action == MotionEvent.ACTION_UP && mWasLongTap) {
mWasLongTap = false
mTouchListener.invoke(TAPEVENT.LONGTAPUP)
}
return mDetector.onTouchEvent(ev) || super.onTouchEvent(ev)
}
How can I get an "top" View of my application (which contains Activity and all DialogFragments)? I need to intercept all touch events to handle motion of some View between DialogFragment and my Activity.
I've tried to catch them (event) through the decor view of Activity's Window with no luck:
getWindow().getDecorView().setOnTouchListener(...);
You can override the Activity.dispatchTouchEvent to intercept all touch events in your activity, even if you have some Views like ScrollView, Button, etc that will consume the touch event.
Combining withViewGroup.requestDisallowInterceptTouchEvent, you can disable the touch event of the ViewGroup. For example, if you want to disable all the touch event in some ViewGroup, try this:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
requestDisallowInterceptTouchEvent(
(ViewGroup) findViewById(R.id.topLevelRelativeLayout),
true
);
return super.dispatchTouchEvent(event);
}
private void requestDisallowInterceptTouchEvent(ViewGroup v, boolean disallowIntercept) {
v.requestDisallowInterceptTouchEvent(disallowIntercept);
int childCount = v.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = v.getChildAt(i);
if (child instanceof ViewGroup) {
requestDisallowInterceptTouchEvent((ViewGroup) child, disallowIntercept);
}
}
}
If you wonder how to intercept all touch events in DialogFragments, here you go:
abstract class BaseDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return object : Dialog(requireContext()){
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
// do your thing here
return super.dispatchTouchEvent(ev)
}
}
}
}