Getting the ripple/selector effect without a clickable layout - android

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;
}
});

Related

Android touch event stucked in ListView

I have a container View that should process touches. It has Listview with overrided onTouch(), which returns false (it will return true, when container is expanded by swipe). When Listview touch occurs it passes to container View and everything is ok. But I need to add click handling for items of Listview.
When I set onClickListener for item, I see in logs it is no MotionEvent passed to container, it is stucked in ListView onTouch() (which return false and does nothing). I'm trying to set onTouchListener for item like this:
viewHolder.itemLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
return false;
case MotionEvent.ACTION_UP:
Log.d(TAG, "something like click detected");
return true;
}
return false;
}
});
But result is the same. Can someone explain why my container does not receive touch event even ListView returns false for its overrided onTouch()?
If you wish to handle the clicks of ListView items : use OnItemClickListener
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//parent.getItemAtPosition(position) returns the value of item clicked.. use it to do whatever you wish to do
}
});
I had the same problem about 2 weeks ago. Returning false from listview's onTouch method did not pass event to parent container. I ended up doing this:
1. You need custom layout for your container View. Then in that view, override onInterceptTouchEvent, for example like this:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
previousX_actionDOWN = ev.getRawX();
isOnClick = true;
timePressed = System.currentTimeMillis();
return false;
case MotionEvent.ACTION_MOVE:
if (isOnClick && (Math.abs(previousX_actionDOWN - ev.getRawX()) > SCROLL_THRESHOLD)) {
return true;
} else {
return false;
}
case MotionEvent.ACTION_UP:
if (!wasDragged) {
return false;
}
else {
return true;
}
}
return false;
}
I also have complicated onTouch method in that view but it's not worth pasting. Point is that your parent view should only intercept touch event if some threshold was passed while ACTION_MOVE - well at least that's what I wanted to achieve. Then in your listview you just implement standard onItemClickListener instead of onTouch.

Perform something onTouch if parent ScrollView doesn't handle it

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.

What motion event would I use for this situation?

I'm using an on touch listener to display and hide some volume controls, on ACTION_DOWN the controls are displayed and on ACTION_UP they are hidden. I want to be able to touch the controls without lifting my finger, I tried using the ACTION_MOVE motion and was unable to get it to work as the event is never triggered. I thought about drag event but I am unsure if it would be appropriate for this.
public boolean onTouch(View v, MotionEvent e)
{
if(v == audioControls)
{
if(e.getAction() == MotionEvent.ACTION_DOWN)
showVolumeControls();
else if(e.getAction() == MotionEvent.ACTION_UP)
hideVolumeControls();
}
else if(e.getAction() == MotionEvent.ACTION_MOVE)
{
if(v == mute)
//Do stuff with this volume control
}
return true;
}
#Demand answer, read my comment - here is the code:
public boolean onTouch(View v, MotionEvent e)
{
if(v == mute && e.getAction() == MotionEvent.ACTION_MOVE)
{
Toast.makeText(getApplicationContext(), "Muted.", Toast.LENGTH_SHORT).show();
hideVolumeControls();
return true;
}
else
return false;
}
So, you need to uderstand how android touch events work. If you touch down on View1, set onTouchListener for that view and return true for that event - other view will never get motion events from same chain.
For you it's mean that if you touch down on "audioControls" then no other views can catch motion events until you release your finger.
You can return false in your onTouch method. In this case parentView for audioControls will also catch all motionEvents. But views, which is not parent for audioControls in the view hierarchy will not catch motionEvent.
You need to catch all motion events in the container for your views and dispatch them for your self. This is the only way to catch motionEvents from one chain in defferent views.
UPDATE:
I will try to explain a little bit more.
Imagine you have layout like this:
<LinearLayout id="#+id/container">
<View id="#+id/view1"/>
<View id="#+id/view2"/>
</LinearLayout>
And you want to touch down on view1 and move your finger to view2. Android touch event flow can't do this. Only one view can catch whole event chain.
So you need to add onTouchListener to your container and do something like this.
public boolean onTouch(MotionEvent event) {
float x = event.getX();
float y = event.getY();
for (int i = 0; i<container.getChildCount(); i++) {
View child = container.getChildAt(i);
if (x>child.getLeft() && x < child.getRight() && y < child.getBottom() && y > child.getTop()) {
/*do whatever you want with this view. Child will be view1 or view2, depends on coords;*/
break;
}
}
}
Please note, I wrote this code right there and could make some mistake. I've tried to show the idea.

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;
}
});

Move Events AFTER LongPress

How can I listen the move events after the LongPress is caled in my GestureDetector?
When the user LongClick he starts the selection mode, and can drag a square into the screen. But I noticed that the onScroll is not called after LongPress is consumed.
Tried to do fight this for a while, and for now the solution is:
Disable the longpress using setIsLongpressEnabled(isLongpressEnabled) on your gestureDetector
Here is my OnTouch method of my View:
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event)== true)
{
//Fling or other gesture detected (not logpress because it is disabled)
}
else
{
//Manually handle the event.
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
//Remember the time and press position
Log.e("test","Action down");
}
if (event.getAction() == MotionEvent.ACTION_MOVE)
{
//Check if user is actually longpressing, not slow-moving
// if current position differs much then press positon then discard whole thing
// If position change is minimal then after 0.5s that is a longpress. You can now process your other gestures
Log.e("test","Action move");
}
if (event.getAction() == MotionEvent.ACTION_UP)
{
//Get the time and position and check what that was :)
Log.e("test","Action down");
}
}
return true;
}
My device returned ACTION_MOVE whenever I hold finger on the screen. If your doesnt, just check the state of some pressed flag after 0.5s using a timer or thread.
Hope that helps!
I have done this task by using the following concepts:
Suppose I have the Image View and on long press on it, image inside this image View would be drag-able and placed inside the another view(e.g. Relative Layout)
Set MyClickListner on Image View's setOnLongClickListener() method.
private final class MyClickListener implements View.OnLongClickListener {
// called when the item is long-clicked
#Override
public boolean onLongClick(View view) {
// TODO Auto-generated method stub
// create it from the object's tag
ClipData.Item item = new ClipData.Item((CharSequence)view.getTag());
String[] mimeTypes = { ClipDescription.MIMETYPE_TEXT_PLAIN };
ClipData data = new ClipData(view.getTag().toString(), mimeTypes, item);
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag( data, //data to be dragged
shadowBuilder, //drag shadow
view, //local data about the drag and drop operation
0 //no needed flags
);
// view.setVisibility(View.INVISIBLE);
return true;
}
}
Then set MyDragListner on Relative Layout(e.g. bigImageRelativeLayoutVw.setOnDragListener(new MyDragListener());)
class MyDragListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
int X=(int)event.getX();
int Y=(int)event.getY();
int touchX = 0,touchY=0;
// Handles each of the expected events
switch (event.getAction()) {
//signal for the start of a drag and drop operation.
case DragEvent.ACTION_DRAG_STARTED:
// do nothing
break;
//the drag point has entered the bounding box of the View
case DragEvent.ACTION_DRAG_ENTERED:
break;
//the user has moved the drag shadow outside the bounding box of the View
case DragEvent.ACTION_DRAG_EXITED:
// v.setBackground(normalShape); //change the shape of the view back to normal
break;
//drag shadow has been released,the drag point is within the bounding box of the View
case DragEvent.ACTION_DROP:
// if the view is the bottomlinear, we accept the drag item
if(v == bigImageRelativeLayoutVw) {
View view = (View) event.getLocalState();
touchX=X-viewCoords[0]-20;
touchY=Y-viewCoords[1]-20;
View view1=new View(getActivity());
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(30,30);
layoutParams.leftMargin =touchX;
layoutParams.topMargin = touchY;
view1.setBackgroundResource(R.drawable.heavy_damage);
view1.setLayoutParams(layoutParams);
RelativeLayout containView = (RelativeLayout) v;
containView.addView(view1);
view.setVisibility(View.VISIBLE);
} else {
View view = (View) event.getLocalState();
view.setVisibility(View.VISIBLE);
break;
}
break;
//the drag and drop operation has concluded.
case DragEvent.ACTION_DRAG_ENDED:
// v.setBackground(normalShape); //go back to normal shape
default:
break;
}
return true;
}
}

Categories

Resources