Dragging custom view in scroll area - android

So I have a custom view widget I am allowing a user to drag around by their finger. They are dragging it in a custom scrolling view based on a FrameLayout (code here: http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/) that contains a RelativeLayout, so that they can place it where they want and have plenty of space that is not visible on screen to place the item as well.
What I would like to happen is if the user is moving the widget near the edge of the visible scroll area is to scroll the area in the appropriate direction until they either let go (ACTION_UP, in which case no more events would take place anyway) or have moved away from the edge. The code right now has two problems:
1) My phone (T-Mobile G2) continues to trigger ACTION_MOVE even when my finger is still where as my Nexus 7 tablet does not continue to trigger until it has moved. I need to reliably continue to trigger this event until ACTION_UP.
2) The area scrolls but only as I move my finger towards the edge of the screen. In otherwords, I'd like for a finger that is staying still to continue to scroll and move the object when it is near the edge, but this is not happening.
What am I missing? Here is the onTouchEvent that handles the moving of the widget
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE
&& this.getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
// mScrollParent gets set in a setter and points to the TwoDScrollView
// mOffsetX/Y is based on where the user touches versus the position of the custom view
// mWidth/mHeight gets set once in the GestureDetector based on the custom view's getWidth()/getHeight()
// mTitleBarHeight gets set once in the GestureDetctor onDown event
// lp is a ViewGroup.LayoutParams type
if (!mGestureDetector.onTouchEvent(event)) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
int finalX = (int) (event.getRawX() - mOffsetX);
int finalY = (int) (event.getRawY() - mOffsetY - (mTitleBarHeight > 0 ? mTitleBarHeight * 2
: 0));
if (finalX < 0 || finalY < 0)
return false;
int scrollLeft = (int) Math.ceil(mScrollParent.getScrollX() * 1.05);
int scrollTop = (int) Math.ceil(mScrollParent.getScrollY() * 1.05);
int scrollRight = (int) Math.floor((scrollLeft + mScrollParent.getRight()) * 0.95);
int scrollBottom = (int) Math.floor((scrollTop + mScrollParent.getBottom()) * 0.95);
int scrollX = 0;
int scrollY = 0;
if (scrollLeft > finalX)
scrollX = finalX - scrollLeft;
if (scrollRight < (finalX + mWidth))
scrollX = (finalX + mWidth)- scrollRight;
if (scrollTop > finalY)
scrollY = finalY - scrollTop;
if (scrollBottom < (finalY + mHeight))
scrollY = (finalY + mHeight) - scrollBottom;
if (scrollX != 0 || scrollY != 0)
mScrollParent.scrollBy(scrollX * 4, scrollY * 4);
lp.setMargins(finalX + scrollX, finalY + scrollY, 0, 0);
setLayoutParams(lp);
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
return true;
} else {
return super.onTouchEvent(event);
}
} else {
return true;
}
}

Related

android how to get last movement

For example, I move my finger down the screen and then back up. So, this should count as two drags, the last movement before I paused for a split second and after that when I moved back up. I basically count every time I make a new movement without lifting my finger off the screen. So, how do I get the last movement before I stop movement without lifting my finger?
I'm using motion event. Here is the code in action_move:
case MotionEvent.ACTION_MOVE:
posY = event.getY();
posX = event.getX();
diffPosY = posY - oldY;
diffPosX = posX - oldX;
if (checkMovement(posY, oldY)){
if (diffPosY > 0 || diffPosY < 0){
count +=1;
}
}
public boolean checkMovement(float posY, float oldY) {
int newY = Math.round(posY);
double distance = Math.abs(newY - oldY);
oldY = newY;
if (distance < 25)
return false;
return true;
}
Simple like this
private int mLastMovY = 0;
case MotionEvent.ACTION_MOVE:
posY = event.getY();
posX = event.getX();
diffPosY = posY - oldY;
diffPosX = posX - oldX;
if(diffPosY > 0){//up
if(mLastMovY != 0){//if have any drag down before, the value will != 0
count +=1;
//could save value of mLastMovY before reset it, this is last position when user drag down
mLastMovY = 0;//reset it to avoid 'count' be increased
}
}
else{//down
mLastMovY = posY;//drag down will assign value to mLastMovY
}

ListView onTouchListener is omitting fling

I've wrote this onTouchListener to hide/show actionBar on listview scroll, similar to current functionality in Instagram app. Everything works fine except listView will not fling. I know I've blocked that action somehow, but I'm still a novice and cant figure it out. Can anyone help me understand what's going on here?
list.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int newTextPosition = (int) (text.getY() * -1);
int move;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = event.getY();
maxPath = text.getHeight();
break;
case MotionEvent.ACTION_UP:
v.performClick();
break;
case MotionEvent.ACTION_MOVE:
newY = event.getY();
move = (int) (newY - startY);
// Going UP
if(newY < startY){
if((text.getY() * -1) == text.getHeight()){
startY = event.getY();
return false;
}
int maxPath = (int) (text.getHeight() + text.getY());
if((move * -1) > maxPath){
move = maxPath * -1;
}
text.setY(text.getY() + move);
list.setY(list.getY() + move);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) list.getLayoutParams();
params.height = list.getHeight() + (move * -1);
list.setLayoutParams(params);
break;
}
// Going DOWN
if(newY > startY){
if((text.getY() * -1) == 0){
startY = event.getY();
return false;
}
if(text.getY() >= 0){
move = 0;
}
if(move > newTextPosition)
move = newTextPosition;
text.setY(text.getY() + move);
list.setY(list.getY() + move);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) list.getLayoutParams();
params.height = list.getHeight() + (move * -1);
list.setLayoutParams(params);
break;
}
}
return true;
}
});
EDIT: From some reason returning false at the end did the job. Any explanation is welcome
Captain here with an explanation!
When you return true from overriding onTouchEvent() on Views or from the OnTouchListeners onTouch() method, you tell android that this touch event has been 'consumed'. This way the touch event won't be passed back to the underlying views and will stop there. This is good when you need to override what happens on a touch event, but if you need the default action to happen you must return false, this way you tell android that the event wasn't 'consumed' and it must notify every other listener that a touch event has happened.
So by returning true, you had overridden the default behavior, which was the swipe and so it wasn't called on any touch event, because you 'consumed' all touch events.

Multiple touch events

I want to have four different areas on the screen and be able to determine if each of those areas are touched. Every area should have a corresponding boolean value that is true if touched. When one area is true that part of the canvas will become a different color.
It is very important that each area works independently, so if area 1 and two are true and the user lets go of area 1 it will instantly become false without affecting area 2.
Thanks!
Edit:
I've tried so many things but I've just started over again. This is from a class that has a SurfaceView with a canvas. I can't figure out what goes where.
public boolean onTouch(View v, MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x = ev.getX();
y = ev.getY();
if (canvasHeight != 0 && canvasWidth != 0) {
if (x < canvasWidth/2 && y < canvasWidth/2){
x1 = x;
y1 = y;
}
if (x < canvasWidth && y > canvasHeight){
x2 = x;
y2 = y;
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
Here's how I'd approach your problem:
Create rects that define the four regions of the screen that can be
pressed.
Using multi-touch, check if the coordinated are in any of the defined
rects. If this is the case, then set the boolean value of that region
of the screen to true, so a color will be rendered to that region. If
the coordinate is not in the rect, then set the boolean to false.
I hope this gets you started!
Update:
I suggest you start off simple and don't use multitouch. In you touch method you can get the x and y value of the touch. Once the screen is touched you could call a method like this with x and y being your parameters eg. checkRegion(x,y).
The method could return an int of the region (since in this case you can only touch one at a time):
public int checkRegion(int x, int y) {
int clickedRegion;
// Some code that will return the region number: 1 = top left, 2 = top right, 3 = bottom left, 4 = bottom right
return clickedRegion;
}
First off, you need to check all four areas;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x = ev.getX();
y = ev.getY();
if (canvasHeight != 0 && canvasWidth != 0) {
//you need four sections, not the two?
if (x < canvasWidth/2 && y < canvasWidth/2){
//set your respective canvas color to what you want it for this quadrant
}
if (x < canvasWidth/2 && y > canvasWidth/2){
//set your respective canvas color to what you want it for this quadrant
}
if (x < canvasWidth && y > canvasHeight){
//set your respective canvas color to what you want it for this quadrant
}
if (x < canvasWidth && y < canvasHeight){
//set your respective canvas color to what you want it for this quadrant
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
Now, assuming you can get that to work, all you need to do is do it again in the action_pointer_down and it will register as a "secondary" click.
If the action is Action_up, just figure out where the user liftes his finger and do the same thing. One issue you should think about is this; what if a user clicks in Quadrant 1, but lifts it up in quadrant 3? That is a slightly more complicated condition, but I think you can skip it for now.

Android 2.2 Click and Drag Image Centered Under Touch

I am trying to drag and drop a crosshair onto a MapView. I have an ImageView button on the screen. When I touch it, the button disappears and a new ImageView appears. The new ImageView is supposed to sit centered under my finger until I release it somewhere, but for some reason the offset is incorrect. The crosshair appears down-right of where I am touching.
The image does move proportionately so I believe the problem is with the offset_x and offset_y which I define in the ACTION_DOWN section. Then, in ACTION_UP I need to createMarker(x,y) on the correct coordinates under my finger, but that is offset incorrectly as well.
I have tried various ways to make it centered, and some are better than others. I have so far been unable to make it work without using magic numbers.
As it is, I am using the click location and subtracting half the picture size. This makes sense to me. It's closer to correct if I subtract the whole picture size. I have tried various examples from the web, all of them suffer from inaccuracies with the View location.
Can you give me some advice?
crosshair = (ImageView)findViewById(R.id.crosshair);
frameLayout = (FrameLayout)findViewById(R.id.mapframe);
params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
crosshairImage = BitmapFactory.decodeResource(getResources(), R.drawable.crosshair);
crosshair.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
dragStatus = START_DRAGGING;
// hide the button and grab a mobile crosshair
v.setVisibility(View.INVISIBLE);
image = new ImageView(getBaseContext());
image.setImageBitmap(crosshairImage);
// set the image offsets to center the crosshair under my touch
offset_x = (int)event.getRawX() - (int)(crosshairImage.getWidth()/2) ;
offset_y = (int)event.getRawY() - (int)(crosshairImage.getHeight()/2) ;
// set the image location on the screen using padding
image.setPadding(offset_x, offset_y, 0, 0);
// add it to the screen so it shows up
frameLayout.addView(image, params);
frameLayout.invalidate();
if(LOG_V) Log.v(TAG, "Pressed Crosshair");
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if(dragStatus == START_DRAGGING) {
dragStatus = STOP_DRAGGING;
// place a marker on this spot
makeMarker((int)event.getX() + offset_x, (int)event.getY() + offset_y);
// make the button visible again
v.setVisibility(View.VISIBLE);
// remove the mobile crosshair
frameLayout.removeView(image);
if(LOG_V) Log.v(TAG, "Dropped Crosshair");
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (dragStatus == START_DRAGGING) {
// compute how far it has moved from 'start' offset
int x = (int)event.getX() + offset_x;
int y = (int)event.getY() + offset_y;
// check that it's in bounds
int w = getWindowManager().getDefaultDisplay().getWidth();
int h = getWindowManager().getDefaultDisplay().getHeight();
if(x > w)
x = w;
if(y > h)
y = h;
// set the padding to change the image loc
image.setPadding(x, y, 0 , 0);
// draw it
image.invalidate();
frameLayout.requestLayout();
if(LOG_V) Log.v(TAG, "(" + offset_x + ", " + offset_y + ")");
return true;
}
}
return false;
}
});
You have written this code within touch_Down.
offset_x = (int)event.getRawX() - (int)(crosshairImage.getWidth()/2) ;
offset_y = (int)event.getRawY() - (int)(crosshairImage.getHeight()/2) ;
// set the image location on the screen using padding
image.setPadding(offset_x, offset_y, 0, 0);
Try writing it outside the if conditions, just to make sure, it is applied to all the touch events.
You can use new api available in drag and drop
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}

OnTouch bitmap overlayed

I'm developing a game where different bitmap run on the screen (a SurfaceView). Since they have different speed, it happen that sometimes two characters are superimposed, one behind the other. I've used the OnTouch function to catch the moment when the player touch the character. However, when two characters are superimposed, I always "touch" the one that is behind, I never touch the other one in front of him. Why? How I can change this behaviour? Since I'm touching the one in front, it's is really annoying that I always touch the other behind.
I hope the problem is clear.
Thanks!
Edit: This is my OnTouchEvent function:
#Override
public boolean onTouchEvent(MotionEvent event){
synchronized (getHolder()) {
boolean chibi_toccato = false;
if (!blocco_tocco){
// Here I have the structure (a Vector) containing the chibis to be shown
for (int i = (mChibiVector.size() - 1); i >= 0; i--) {
Chibi chibi = mChibiVector.elementAt(i);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (!chibi_toccato){
// touched is a flag that tell if a chibi were touched before
if (chibi.touched) {
// if I touch on the boat bitmap (somewhere on the screen) the chibi will be safe
int navexend = posiz_nave_x + thread.mNave.getWidth();
int naveyend = posiz_nave_y + thread.mNave.getHeight();
if ((int) event.getX() >= posiz_nave_x &&
(int) event.getX() <= navexend &&
(int) event.getY() >= posiz_nave_y &&
(int) event.getY() <= naveyend){
chibi.chibisalvo = true;
thread.calcola_vel_x(chibi);
thread.calcola_vel_y(chibi);
} else {
chibi.touched = false;
int temp_chibi_y = chibi.getCoordinate().getY();
chibi.getCoordinate().setY(
temp_chibi_y +
chibi.getBitmapDimensions()[1]);
}
chibi_toccato = true;
} else {
// Here I check if a chibi is touched:
chibi = thread.checkTouched(chibi, (int) event.getX(),
(int) event.getY());
if (chibi.touched){
// If the chibi is touched, I save the X-Y info:
chibi.getCoordinate().setTouchedX((int) event.getX());
chibi.getCoordinate().setTouchedY((int) event.getY());
int temp_chibi_y = chibi.getCoordinate().getY();
chibi.getCoordinate().setY(
temp_chibi_y -
chibi.getBitmapDimensions()[1]);
chibi_toccato = true;
}
}
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (chibi.touched){
chibi_toccato = false;
int navexend = posiz_nave_x + thread.mNave.getWidth();
int naveyend = posiz_nave_y + thread.mNave.getHeight();
if ((int) event.getX() >= posiz_nave_x &&
(int) event.getX() <= navexend &&
(int) event.getY() >= posiz_nave_y &&
(int) event.getY() <= naveyend){
chibi.chibisalvo = true;
thread.calcola_vel_x(chibi);
thread.calcola_vel_y(chibi);
}
}
}
}
}
}
return true;
}
Well, after a while, finally I got the issue. The problem were in the for cycle:
for (int i = (mChibiVector.size() - 1); i >= 0; i--)
Since I started to pick the frames from the end of the vector, the first touch were always on the image behind the others. I'm still wondering why, but finally I changed the for cycle starting from the first vector item to the end and it worked.
Hope it could be useful for others.

Categories

Resources