Perform something onTouch if parent ScrollView doesn't handle it - android

OK, this might be trivial, but I can't figure out how to do it.
We have a container scrolling view, a subclass of ScrollView, and this view has a child view (among others) which should listen to touch events.
In particular, what I'd like to do is:
childView is clicked and MotionEvent.ACTION_DOWN comes;
check if that event is a scrolling event, i.e., check if that action would start a scroll on the scrollView;
if so, let the scrollView handle it and do nothing; if not, i.e. if the click upon childView did not start a scroll onto the parent view, do something.
Any ideas?
Pseudocoding, it should be something like:
ScrollView scrollView = ...;
View childView = scrollView.findViewById(...):
childView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
if (scrollView.onTouchEvent(event)) {
// event was handled by the view
return false;
} else {
// do something
return true;
}
}
return false;
}
});
As you can see I have not much insight on how motion events work.

Related

Differentiate touch events between customView and scroll view

I have one customView and customView added in scroll view. Now i want differentiate both touch events. My problem is when try to scroll, customView also getting touch event and when i try to change in customView, scroll view getting events.
How can we stop customView touch event when scrolling.
How can we stop scroll touch events when customView wants events.
Thanks in advance
You can set touch listener to child view and then in onTouch() event, you can block intercept touch event of parent.
i.e.
v.setOnTouchListener(new OnTouchListener() {
// Setting on Touch Listener for handling the touch inside ScrollView
#Override
public boolean onTouch(View v, MotionEvent event) {
// Disallow the touch request for parent scroll on touch of child view
v.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
About the second question, I don't know exactly what you're doing with customview but maybe you'd like to use click events instead because it's rather not user friendly to use different logic in ontouch and onclick as it will always fire up unexpectedly.
boolean isScrolling = false;
myScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { #Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY)
isScrolling = scrollX != oldScrollX;
//for vertical scrolling
});
//then onTouchListener
if(!isScrolling){//Do operations on non scrolled state}

Android: Intercept touch gesture handling from the parent to the child view

So I have the following view structure:
LinearLayout
HorizontalScrollView
Other Child Views
The parent LinearLayout is clickable has a custom selector (changes color when pressed). I want to be able to touch the HorizontalScrollView within the LinearLayout and still handle the touch in the LinearLayout as long as it is not a scroll motion. If I do a scroll motion then the HorizontalScrollView should intercept the gesture and cancel the touch for the LinearLayout. Basically, I want to be able to intercept the gesture from a child view as opposed from the parent which is the standard.
I have tried to handle the MotionEvent manually by creating extension classes that do the following:
LinearLayout
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
// Handle the motion event even if a child returned true for OnTouchEvent
base.OnTouchEvent(ev);
return base.OnInterceptTouchEvent(ev);
}
HorizontalScrollView
public override bool OnTouchEvent(MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
_intialXPos = e.GetX();
}
if (e.Action == MotionEventActions.Move)
{
float xDifference = Math.Abs(e.GetX() - _intialXPos);
if (xDifference > _touchSlop)
{
// Prevent the parent OnInterceptTouchEvent from being called, thus it will no longer be able to handle motion events for this gesture
Parent.RequestDisallowInterceptTouchEvent(true);
}
}
return base.OnTouchEvent(e);
}
This almost worked. When I touch the HorizontalScrollView, the LinearLayout shows the pressed state UI and activates when the click is completed. If I touch and scroll the HorizontalScrollView then scrolling works. When I let go of the scroll, the click handler for the LinearLayout does not fire because it was intercepted. But the problem is that before I start scrolling the LinearLayout changes to the pressed state and it does not reset even after the gesture is completed. In my additional attempt to try to manually cancel the gesture for the LinearLayout I kept running into other issues. Additionally, the LinearyLayout has other buttons inside it which when clicked should not allow the parent LinearLayout to display the pressed state. Any suggestions? Is there a set pattern for intercepting touch events from a child? I'm sure it is possible if both classes know about each other, but I am trying to avoid coupling them.
The following work for me for all cases:
InterceptableLinearLayout
public override bool DispatchTouchEvent(MotionEvent e)
{
bool dispatched = base.DispatchTouchEvent(e);
// Handle the motion event even if a child returns true in OnTouchEvent
// The MotionEvent may have been canceled by the child view
base.OnTouchEvent(e);
return dispatched;
}
public override bool OnTouchEvent(MotionEvent e)
{
// We are calling OnTouchEvent manually, if OnTouchEvent propagates back to this layout do nothing as it was already handled.
return true;
}
InterceptCapableChildView
public override bool OnTouchEvent(MotionEvent e)
{
bool handledTouch = base.OnTouchEvent(e);
if ([Meets Condition to Intercept Gesture])
{
// If we are inside an interceptable viewgroup, intercept the motionevent by sending the cancel action to the parent
e.Action = MotionEventActions.Cancel;
}
return handledTouch;
}

Getting the ripple/selector effect without a clickable layout

I have two view pager, one is nested in the other. To get the correct behaviour (swiping the inner view pager without changing the outer) I had to override the inner view pagers onTouchListener and put all my onTouch/onClick logic into it (got the idea from here).
Works all fine, but since I don't have a onClickListener anymore I lost my selector effect. When I put android:clickable="true" on the layout element I get my selector effect, but the view pagers behaviour is wrong again.
Is there any way to achieve the selector effect out of the onTouchListener?
innerPager.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
Log.d(DEBUG_LOG, "Single tap.");
return true;
} else if(event.getAction() == MotionEvent.ACTION_DOWN && v instanceof ViewGroup) {
((ViewGroup) v).requestDisallowInterceptTouchEvent(true);
}
return false;
}
});
This solved it for me. Just added the following code to my OnTouchListener and replaced the card view with my inner view pagers current item:
// Since the host view actually supports clicks, you can return false
// from your touch listener and continue to receive events.
myTextView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent e) {
// Convert to card view coordinates. Assumes the host view is
// a direct child and the card view is not scrollable.
float x = e.getX() + v.getLeft();
float y = e.getY() + v.getTop();
// Simulate motion on the card view.
myCardView.drawableHotspotChanged(x, y);
// Simulate pressed state on the card view.
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
myCardView.setPressed(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
myCardView.setPressed(false);
break;
}
// Pass all events through to the host view.
return false;
}
});

Passing touch event from one component to ListView

I have a simple Button, that has a touch listener. Once it's triggered on ACTION_DOWN action, a ListView appears on top of that button (so that the ListView is under user's finger).
What I want is to "pass" that touch event from Button to that ListView, so that when moved up/down, the list view would also scroll up/down.
Simply put:
User touches a Button
A list view appears on covering that button
WITHOUT RELEASING A FINGER, user starts moving finger to the top/bottom screen edge,
The list view scrolls.
UPDATE
I tried making a custom ListView component with onInterceptTouchEvent overridden, but I do not clearly get what should go into that method?
You should create a custom class extending ListView, then override the method onInterceptTouchEvent(MotionEvent e). That method gets called whenever a view inside the list is touched.
You don't need any custom component.
Set your button onTouchListener as:
button.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
switch (motionEvent.getAction())
{
case MotionEvent.ACTION_DOWN:
// popup the listView
break;
case MotionEvent.ACTION_MOVE:
// lstPopup is your ListView
lstPopup.dispatchTouchEvent(motionEvent);
break;
case MotionEvent.ACTION_UP:
// do other stuff
}
return false;
}
});
And set your listView onTouchListener as:
lstPopup.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_MOVE)
{
lstPopup.setSelectionFromTop(0, (int) motionEvent.getY());
}
return false;
}
});

OnItemClickListener and Custom OnTouchListener

I have a list of views on a list view that I would like to click on and have the OnItemClickListener get triggered. Though at the same time I want to be able to swipe each view and have a custom action occur. This means that I had to create our own OnTouchEvent for each view when it is made in the ArrayAdapter.
Is there a way to have both of those working together, so that I can have a custom action such as swiping an item and clicking on the item occur easily
This is very similar to how android Recent Activities are handled - you know, they show a list of all recently opened apps, they can be swiped to remove, or clicked to open. Check out their code, I think you'll get a pretty good idea: https://github.com/android/platform_frameworks_base/tree/master/packages/SystemUI/src/com/android/systemui/recent
You class can implement both View.OnTouchListener, AdapterView.OnItemClickListener
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getAction() == MotionEvent.ACTION_UP){
Log.d(TAG, "ontouch: UP");
**// Here you can figure if it was simple item click event.
// We return false only when user touched once on the view.
// this will be handled by onItemClick listener.**
if(lastAction == -1){
lastAction = MotionEvent.ACTION_UP;
view.clearFocus();
return true;
}
return false;
}
else if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
Log.d(TAG, "ontouch: DOWN");
return false;
}
else {
// This is all action events.
lastAction = -1;
return true;
}
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// We come here after onTouch event figured out that its a simple touch event and needs to be handled here.
}

Categories

Resources