Android: Only make certain part of custom view be clickable - android

I have a custom view, assume it looks like this:
I would like for my custom view to respond to the onClicks, however the catch is that I would like it to respond to the clicks ONLY on the red portion/circle. Not the whole view.
Is it possible to make to make the text above and the grey portion not clickable?
Thank you.

In a custom view, you handle clicks by overriding the onTouchEvent method of android's View class. First check that the location the user has clicked is within the circle. Then normally you would give some feedback on the MotionEvent.ACTION_DOWN event to let user know they have clicked, such as highlight the circle. Then on MotionEvent.ACTION_UP you can call your onClick method.
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean isTouchInCircle = checkTouchInCircle(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isTouchInCircle) {
circleColor = highlightColor;
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if (isTouchInCircle) {
circleColor = highlightColor;
} else {
circleColor = normalColor
}
invalidate();
break;
case MotionEvent.ACTION_UP:
if (isTouchInCircle) {
onClickCircle();
}
break;
}
return true;
}
// Circle click zone approximated as a square
private boolean checkTouchInCircle(float touchX, float touchY) {
if (touchX < circleCenterX + circleRadius
&& touchX > circleCenterX - circleRadius
&& touchY < circleCenterY + circleRadius
&& touchY > circleCenterY - circleRadius) {
return true;
} else {
return false;
}
}

Unfortunately the answer Carson posted was not exactly what I was looking for as my example was only a simple one, with the reality sometimes it being a lot more difficult and checking the touch locations would be convoluted (imagine multiple views/shapes within the custom view being the click locations).
What I did was in the custom view do a find view by id on the elements of the custom view. Then I did setOnClickListener(this) on each element that I would like to be clicked able rather than on the whole view itself, so mCircle.setOnClickListener(this); and mInput.setOnClickListener(this);, then did implements View.OnClickListener for the custom view to handle the actions.

Related

Android Drag and Drop, Click, and Scroll in a Listview

So I have a layout as such:
I am using a Listview right now and each row has an Imageview and 2 Textviews.
I want to be able to do 3 things on this page:
Clicking on a row (or just clicking on the Imageview in that row works for me too) brings me to another Fragment.
You can drag the image in each Listview row. It uses DragShadowBuilder and therefore can detect if you drop it into that darker gray zone in the bottom.
You can scroll up and down in the Listview to get to the other items that are overflowing right now.
As you can imagine, these three cases are difficult to capture because its hard to differentiate between the three (bc of overlaps in functionality).
I would prefer not to use onItemLongClickListener to do the drag and drop bc users usually don't think to hold their fingers down a long time to start drag and drop.
Any suggestions on how to implement this to capture all three use cases? Actually, it can be thought of as 2 use cases because if I dropped an image back into its original container, it could count as a click for me. The most complex part is to make that somehow work with scrolling up and down the Listview...
Thanks for your help in advance!
p.s. This entire view is rendered in a Fragment and clicking on a view or successfully dropping one into the gray area opens up a separate fragment.
/**
* Call this from a drag source view.
*/
#SuppressLint("NewApi")
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
return false;
}
final int action = ev.getAction();
final int screenX = clamp((int)ev.getRawX(), 0, mDisplayMetrics.widthPixels);
final int screenY = clamp((int)ev.getRawY(), 0, mDisplayMetrics.heightPixels);
switch (action) {
case MotionEvent.ACTION_DOWN:
// Remember where the motion event started
mMotionDownX = screenX;
mMotionDownY = screenY;
break;
case MotionEvent.ACTION_MOVE:
//Set background color of remove comment box layout
if(((int)ev.getY() <= 50))
ImageEditingActivityNew.rl_remove.setBackgroundColor(Color.RED);
else
ImageEditingActivityNew.rl_remove.setBackgroundColor(Color.TRANSPARENT);
// Update the drag view. Don't use the clamped pos here so the dragging looks
// like it goes off screen a little, intead of bumping up against the edge.
mDragView.move((int)ev.getRawX(), (int)ev.getRawY());
// Drop on someone?
final int[] coordinates = mCoordinatesTemp;
DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
if (dropTarget != null) {
if (mLastDropTarget == dropTarget) {
dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
}
mLastDropTarget = dropTarget;
/* The original Launcher activity supports a delete region and scrolling.
It is not needed in this example.
// Scroll, maybe, but not if we're in the delete region.
boolean inDeleteRegion = false;
if (mDeleteRegion != null) {
inDeleteRegion = mDeleteRegion.contains(screenX, screenY);
}
//Log.d(TAG, "inDeleteRegion=" + inDeleteRegion + " screenX=" + screenX
// + " mScrollZone=" + mScrollZone);
if (!inDeleteRegion && screenX < mScrollZone) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mScrollRunnable.setDirection(SCROLL_LEFT);
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
}
} else if (!inDeleteRegion && screenX > scrollView.getWidth() - mScrollZone) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
}
} else {
if (mScrollState == SCROLL_WAITING_IN_ZONE) {
mScrollState = SCROLL_OUTSIDE_ZONE;
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.removeCallbacks(mScrollRunnable);
}
}
*/
break;
case MotionEvent.ACTION_UP:
//When touch up then remove comment box
if(((int)ev.getY() <= 50)) {
ImageEditingActivity.rl_imageEdit_comment.removeAllViews();
ImageEditingActivity.rl_imageEdit_comment.invalidate();
//ImageEditingActivity.mDragLayer.removeView(ImageEditingActivity.rl_imageEdit_comment);
ImageEditingActivity.addComment = true;
ImageEditingActivity.changeIconBackground("Remove");
ImageEditingActivity.editParam.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
if (mDragging) {
drop(screenX, screenY);
}
endDrag();
break;
case MotionEvent.ACTION_CANCEL:
cancelDrag();
}
return true;
}
Instead of long press and hold you can use fling/swipe gesture. You can override onFling method of SimpleonGestureListner and detect fling based on the threshold.
Your requirements conflicts. I would do the next thing in your case :
Your row can be drag only with long pressed (then I would use the shadow drag)
also I would do that the view of the drop only appears when long press occurs.
override the onclick method of the ImageView that way you can click it and then call your fragment to expand
Now because you didn't override any onTouchListeners or onClick on the row you can scroll freely as long as you don't click a picture.

Drag and drop in ScrollView

I have some ImageViews inside a HorizontalScrollView.
I would like to be able to drag and drop the ImageViews somewhere else, but still maintain scrolling capability. Dragging should only be activated when the user starts a mostly vertical motion with their finger.
For now, I have drag and drop activate on long-press, but that is not a good solution.
To illustrate:
I had to do exactly this as well. After reading http://techin-android.blogspot.in/2011/11/swipe-event-in-android-scrollview.html I adapted the code as follows:
class MyOnTouchListener implements View.OnTouchListener {
static final int MIN_DISTANCE_Y = 40;
private float downY, upY;
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downY = event.getY();
return true;
}
case MotionEvent.ACTION_MOVE: {
upY = event.getY();
float deltaY = downY - upY;
// swipe vertical?
if (Math.abs(deltaY) > MIN_DISTANCE_Y) {
if (deltaY < 0) {
//Start your drag here if appropriate
return true;
}
if (deltaY > 0) {
//Or start your drag here if appropriate
return true;
}
}
}
}
return false;
}
}
And then set the listener on the ImageViews:
imageView.setOnTouchListener(new MyOnTouchListener());
In this version of the code I am only checking for movement in the vertical direction (I also changed the minimum movement to be 40 instead of 100 as in the original code). If a vertical movement is detected, the specific ImageView can begin to drag or do any other actions you want. If a vertical movement is not detected, the ImageView's MyTouchListener returns false which means the ImageView does not consume the touch event. This allows the parent ScrollView to eventually get the touch event and consume it (for scroll detection). The answers here are helpful for understanding touch events: MotionEvent handling in ScrollView in Android.

SemiClosedSlidingDrawer onClick not dispatched when semi-collapsed

i'm using a SemiClosedSlidingDrawer (http://pastebin.com/FtVyrcEb) and i've added on content part some buttons on the top of slider which are always visibles.
The problems is that they are clickable (or click event is dispatched) only when slider is fully opened... When slider is "semi-opened" click event not seems dispached to button... I have inspected with debugger into onInterceptTouchEvent() and in both cases (opened/semi-collapsed) the following code
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mLocked) {
return false;
}
final int action = event.getAction();
float x = event.getX();
float y = event.getY();
final Rect frame = mFrame;
final View handle = mHandle;
handle.getHitRect(frame);
//FOLLOWING THE CRITICAL CODE
if (!mTracking && !frame.contains((int) x, (int) y)) {
return false;
}
return false but only when slider is opened event was dispached...
It checks if a (x,y) relative to the click are contained in a rectangle created starting from the HandleButton view of sliding drawer...
final Rect frame = mFrame;
final View handle = mHandle;
handle.getHitRect(frame);
and this is obviously false because i'm clicking on a button contained inside the content part of slidingdrawer and that's ok...
As i said above the problem is that in semi-collapsed state, buttons contained in content part are not receiving the event...
Have you any idea how can i solve this issue?
Can be some state of slidingdrawer that avoid to click childs when collapsed?
Thanks in advance...
Right, I think I've figured out a way to do this.
First you need to modify onInterceptTouchEvent() to return true whenever the user presses the visible content during the semi-opened state. So, for instance, if your SemiClosedSlidingDrawer view is located at the very bottom of the screen, you can use a simple detection algorithm, like this:
public boolean onInterceptTouchEvent(MotionEvent event) {
...
handle.getHitRect(frame);
// NEW: Check if the user pressed on the "semi-open" content (below the handle):
if(!mTracking && (y >= frame.bottom) && action == MotionEvent.ACTION_DOWN) {
return true;
}
if (!mTracking && !frame.contains((int) x, (int) y)) {
...
}
Now the touch events during the user's interaction with the semi-opened content will be dispatched to onTouchEvent(). Now we just need to intercept these events and "manually" redirect them to the right view (note that we also need to offset the coordinates for the child view):
public boolean onTouchEvent(MotionEvent event) {
...
if (mTracking) {
...
}
else
{
// NEW: Dispatch events to the "semi-open" view:
final Rect frame = mFrame;
final View handle = mHandle;
handle.getHitRect(frame);
float x = event.getX();
float y = event.getY() - frame.bottom;
MotionEvent newEvent = MotionEvent.obtain(event);
newEvent.setLocation(x, y);
return mContent.dispatchTouchEvent(newEvent);
}
return mTracking || mAnimating || super.onTouchEvent(event);
}
It's a bit of a messy implementation, but I think the basic concept is right. Let me know how it works for you!

Capturing touch events in an android list item and prevent scrolling

I'm trying a wild idea here by putting a custom control within the items of a certain list view. The control is only "activated" if the user touches down on a certain trigger point and then they can "drag around."
My question is, what can I do in onTouchEvent(...) to prevent the listview from receiving the event and scrolling. Right now I can touch and get ahold of the control, but if I move my finger too much up or down the listview takes over and starts scrolling, then my view doesn't even receive a ACTION_UP event.
Here is my current onTouchEvent code:
if (e.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("SwipeView", "onTouchEvent - ACTION_DOWN" + e.getX() + " " + e.getY());
int midX = (int)(this.getWidth() / 2);
int midY = (int)(this.getHeight() / 2);
if (Math.abs(e.getX() - midX) < 100 &&
Math.abs(e.getY() - midY) < 100) {
Log.d("SwipeView", "HEY");
setDragActive(true);
}
this.invalidate();
return true;
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
_current[0] = e.getX();
_current[1] = e.getY();
this.invalidate();
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
_current[0] = 0;
_current[1] = 0;
setDragActive(false);
this.invalidate();
return true;
}
I'm sure it has something to do with the event hierarchy in some fashion.
This might not be exactly what you're looking for, but it's possible to implement capture capabilities in your activity. add
private View capturedView;
public void setCapturedView(View view) { this.capturedView = view); }
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
return (this.capturedView != null) ?
this.capturedView.dispatchTouchEvent(event) :
super.dispatchTouchEvent(event);
}
to your activity, then simply pass your view on ACTION_DOWNand null on ACTION_UP. it's not exactly pretty, but it works. i'm sure there's a proper way to do this though.
finally learned the correct way to do this: requestDisallowInterceptTouchEvent

Android View stops receiving touch events when parent scrolls

I have a custom Android view which overrides onTouchEvent(MotionEvent) to handle horizontal scrolling of content within the view. However, when the ScrollView in which this is contained scrolls vertically, the custom view stops receiving touch events. Ideally what I want is for the custom view to continue receiving events so it can handle its own horizontal scrolling, while the containing view hierarchy deals with vertical scrolling.
Is there any way to continue receiving those motion events on scroll? If not, is there any other way to get the touch events I need?
Use requestDisallowInterceptTouchEvent(true) in the childview to prevent from vertical scrolling if you want to continue doing horizontal scrolling and latter reset it when done.
private float downXpos = 0;
private float downYpos = 0;
private boolean touchcaptured = false;
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
downXpos = event.getX();
downYpos = event.getY();
touchcaptured = false;
break;
case MotionEvent.ACTION_UP:
requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_MOVE:
float xdisplacement = Math.abs(event.getX() - downXpos);
float ydisplacement = Math.abs(event.getY() - downYpos);
if( !touchcaptured && xdisplacement > ydisplacement && xdisplacement > 10) {
requestDisallowInterceptTouchEvent(true);
touchcaptured = true;
}
break;
}
super.onTouchEvent(event);
return true;
}
I'm answering my own question in case anyone else is as bad at Googling for the answer as I apparently was. :P
A workaround for this problem is to extend ScrollView and override the onInterceptTouchEvent method so that it only intercepts touch events where the Y movement is significant (greater than the X movement, according to one suggestion).

Categories

Resources