I am trying to make all my drawn Sprites dragable for a little game. It should be able to touch anywhere and the sprites should move the same distance, the finger moves.
With the following method they will move on an ACTION_MOVE event, but only very slow, a shorter distance and sometimes they dont:
addToX/Y only adds the gap to the coordinates of the sprites
#Override
public boolean onTouchEvent(MotionEvent evt){
switch(evt.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if(getHistorySize() > 0){
for(int i = 1, n = evt.getHistorySize(); i < n; i++){
int calcX = (int) getHistoricalX(i) - (int) getHistoricalX(i-1);
int calcY = (int) getHistoricalY(i) - (int) getHistoricalY(i-1);
for(Sprite sprite : spriteList) {
sprite.addToX(calcX);
sprite.addToY(calcY);
}
}
}
return true;
}
Any ideas on this?
Assuming your Sprite class is an (potentially-indirect) extension of android.view.View, then you can use setOnDragListener() to define an onDrag() override for them. Then you can use startDrag(...) on them to begin the drag. This is typically triggered by a long-press gesture on the view to be dragged, but in your case you can trigger it from within onTouchEvent() in ACTION_MOVE once (or even ACTION_DOWN). See here for more details on these methods.
Also, with respect to the code you posted, one issue with it that probably explains why it doesn't always work is that you are only using the historical points (which may or may not have accumulated on any particular call to onTouchEvent()). Whether or not getHistorySize() is greater than 0, you should still also use evt.getX() and evt.getY() on each call to onTouchEvent(). But of course, if you use the drag listener approach I suggested instead, you won't need to worry about this.
Update per comment
If you want to move all of the sprites at once, you can put the sprites into a full-screen FrameLayout and attach a GestureDetector that uses a GestureDetector.SimpleOnGestureListener to capture onScroll() callbacks and then calls scrollTo() on the FrameLayout. When the parent FrameLayout scrolls, all of its children sprites will appear to move together.
Related
I'm developing a simple game which uses normal android views, not openGL or other apis, simply uses views and moves them on the scren. I have a game loop which calls to AsteroidManager.updateAsteroidsPositions() which iterates in all the screen asteroids calculating it's possitions.
After that, the thread, calls to a AsteroidManager.invalidateAsteroids() method using runOnUiThread() method, because in Android you need to manipulate views on the main thread. AsteroidManager.invalidateAsteroids() method simply iterates all the asteroids and set's x,y positions to the view and calls invalidate().
The problem is that I disscovered that it gives a much more smooth and faster behaviour if you put the logic of calculatePositions inside the onDraw method of the view. Doing that, the logic of calculating possitions is not being done in the game loop thread... its being done in the main UI thread!!
How is that possible? It is breaking all the game development logic... about doing the position calculations on Game Loop thread instead of other places like main thread or onDraws...
This the slower original code:
AsteroidManager class:
public void updateAsteroidsPositions(){
for (int i = 0; i<onScreenAsteroids.size(); i++){
onScreenAsteroids.get(i).updatePosition();
}
}
public void invalidateAsteroids() {
for (int i = 0; i<onScreenAsteroids.size(); i++){
onScreenAsteroids.get(i).invalidate();
}
}
Asteroid Class:
public void updatePosition(){
currentScale = (Float) scaleX.getAnimatedValue();
factor = currentScale/MAX_SCALE;
//adding a minimum of factor, because with too low factor the movement is not realistic
if (factor < 0.250f)
factor = 0.250f;
x = x-((float)GameState.getInstance().getJoyX()*factor);
y = y-((float)GameState.getInstance().getJoyY()*factor);
}
public void invalidate(){
view.setX(x);
view.setY(y);
view.invalidate();
}
this is the trick done in Asteroid class which does the behaviour of the game smooth and faster:
Asteroid Class:
public Asteroid(Bitmap bitmap, Context context) {
view = new ImageView(context){
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentScale = (Float) scaleX.getAnimatedValue();
factor = currentScale/MAX_SCALE;
//adding a minimum of factor, because with too low factor the movement is not realistic
if (factor < 0.250f)
factor = 0.250f;
x = x-((float)GameState.getInstance().getJoyX()*factor);
y = y-((float)GameState.getInstance().getJoyY()*factor);
view.setX(x);
view.setY(y);
}
};
view.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
view.setImageBitmap(bitmap);
}
public void updatePosition(){
}
public void invalidate(){
view.invalidate();
}
If you have too many items in onScreenAsteroids list it takes some time to execute updatePosition() for each of them. Try to use single method for them:
public void updateAndInvalidateAsteroidsPositions(){
for (int i = 0; i<onScreenAsteroids.size(); i++){
onScreenAsteroids.get(i).updatePosition();
onScreenAsteroids.get(i).invalidate();
}
}
Not all games need game loop. Thread switching has its own cost.
Game Loop separates game state from rendering. Ideally the game loop has the responsibility to processes all the onscreen objects in the game and objects have the responsibility to draw itself in its place. This way we have central place to react to events(mouse click, user touch etc) and update view positions and views have the responsibility to draw themselves at updated position.
For eg consider that we have 10 moving asteroids on screen and we are updating them in onDraw(), now two of them collide, but asteroid1 does not know position of asteroid2, so how will they detect collision? By game logic the game loop knows position of all 10 asteroids, it can detect collision. If don't care about messy code, then collision can be detected in onDraw also. But consider following...
If two are colliding , then we need to check if some other asteroid is near by collision region, if so then how much impact? Mess increases linearly...
After collision we decide to show collision graphic effects. Mess increases exponentially....
Asteroids collided, game state = 'EXPLOSIONS IN THE SKY', user gets a call, game goes to background, game state is to be saved, but our asteroids are master of their own destiny, now we need to provide every asteroid's state to our activity and save it in onPause(). Its all mess now...
User returns after few hours, we can't welcome them directly with 'EXPLOSIONS IN THE SKY', need to rewind to the state where asteroids are about to collide and then show BANG-BANG... Mess goes ALL HELL BREAK LOOSE.....
Views are slaves and they should not be empowered.
Where to display view, its dimens? -> comes from outside.
What to draw in view? -> comes from outside/ can have little say here.
How to animate view? -> comes from outside.
Coming to your particular case, you are using both versions differently, in onDraw() case you are directly invalidating asteroid's (first one is drawn instantly) whereas in game loop case you are first computing all asteroid's position and then invalidating, I don't know how many asteroids you have but if they are significant number, then this coupled with thread switching costs, may trick you to believe onDraw() is faster.
I have an Imageview with different number of touch points on them. It basically an app which is detecting the swipe between 2 touch points and not allowing the user to swipe any other point or in or out of other direction. It should constrict user to just swipe between two touch points.
Just take a look at following picture:
Now the user should start swiping from point 1 to point 2. if the swipe is not started from starting point 1, it should not color the path between point 1 and point 2.
But if the user successfully swipe between the point 1 and point 2 now swipe between point 2 to 3 should be enabled. Thus user should go through Point 1 to 2, Point 2 to 3 , Point 3 to 4 , point 4 to point 5 to complete round 1.
Please tell me how to achieve this functionality . I know about gestures, gesture overlay etc but none of them fits to my condition as they uses general touch events and gesture directions.
Please suggest me the way to achieve this and keep in mind I want to make this app to be able to run on all type of devices , so I can simply give the hard coded x,y values.
Edit : on Demand
I am posting the link of the app on play store who has same functionality , But I do not know How they are achieving this functionality .
https://play.google.com/store/apps/details?id=al.trigonom.writeletters
If each touch point can be created as individual views(e.g. ImageView), then you can create an inViewInBounds() function.
I used code from here to create code where I needed to detect finger press movement over multiple imageViews:
Rect outRect = new Rect();
int[] location = new int[2];
//Determine if a touch movement is currently over a given view.
private boolean inViewInBounds(View view, int x, int y){
view.getDrawingRect(outRect);
view.getLocationOnScreen(location);
outRect.offset(location[0], location[1]);
return outRect.contains(x, y);
}
To use this function, on a parent view to all those child touch points, set the Click and Touch Listeners:
//This might look redundant but is actually required: "The empty OnClickListener is required to keep OnTouchListener active until ACTION_UP"
parentView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {}
});
//All the work gets done in this function:
parentView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int)event.getRawX();
int y = (int)event.getRawY();
// ** myTouchPoint might be an array that you loop through here...
if ( inViewInBounds(myTouchPoint, x, y) ) doLogic(myTouchPoint);
return false;
}
});
The code above only shows detecting when one of your views are 'touched'.
if none are 'touched' but a view is 'active' (e.g. When a touch is detected, set a variable like: viewLastTouched = myTouchPoint) then you would call something like drawingLine(viewLastTouched, x, y) function - for whatever it needed to do to draw the line and/or detect boundaries etc.
They are not using android native java code to build this app.
The app is running with this code
import Runtime.MMFRuntime;
public class Main extends MMFRuntime {
}
This in turn is from https://github.com/ClickteamLLC/android/blob/master/docs/index.md
This is used to package apps / games written using - http://www.clickteam.com/clickteam-fusion-2-5
I am using multiple animations and playing animations one after other. Currently using onAnimationEnd() to play animations one after other. In case of onTouch, I need to stop the animation and need to set different bitmap to imageview in touch location. Currently using below code but facing problems in case of clearAnimation().
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_UP:
imageArray[g_animCount - 1].clearAnimation();
break;
default:
break;
}
return true; // indicate event was handled
}
#Override
public void onAnimationEnd(Animation animation) {
layout.removeView(imageArray[g_animCount - 1]);
if ( (g_animCount < count))
{
startNextAnimation();
}
else
{
g_animCount = 0;
isBegin = false;
}
}
Problems and queries:
After clear animation, I could see image again at the beginning location, how to keep it at touch location ? tried setFillAfter(true) but no use.
How to define onAnimationEnd() in case to play animations one after other ? do we need to remove imageview?
Without clearAnimation() I do not have any problems but it does not solve my problem. Kindly correct my code.
1) to keep the ImageView to the new location you have to specify the new coordinates.
You can do it in this way:
MarginLayoutParams l = new MarginLayoutParams(v.getLayoutParams());
l.leftMargin = left;
l.topMargin = top;
v.setLayoutParams(l);
v is your ImageView, left and top are the coordinates (x,y) to place the view on the screen. The parent should be a RelativeLayout.
2) it's better to start all the animation in sequence avoid calling new start in the callback methods (onAnimationEnd, etc). You don't need to remove the view if you have to use it later in the flow
My reply may be a bit out of date, but better later than never.
I also noticed this nasty bug: when you call cancelAnimation, the AnimationListener.onAnimationEnd is called, however the view stays and there is no "legal" ways to hide it (tried remove view from layout, set visibility to View.INVISIBLE, even negative marginTop and marginBottom - nothing works). The bug is present with Andoid 2.3 and Android 4.0. It doesn't turn up with KitKat, presumably has been fixed.
Here's the good news. I've found a work around with ImageView: in AnimationListener.onAnimationEnd I say animationView.setImageDrawable(null) and the view disappears. Hopefully this will help someone.
What should I use if I want to have buttons on an image? I mean, being able to click on certain points of an image and see some info.
What if I want to display text on that points? Imagine I am having a map of my neighbor for example, and I want some info to be displayed (for example my home). And if someone press on my home he could see further details, like my name, my phone and so on. The image(aka the map) will be stable, it wont change, like a background.
You'll have to implement the method onTouch from the interface OnTouchListener. For example:
OnTouchListener listener = new OnTouchListener() {
#Override
public false onTouch(View v, MotionEvent event) {
if(!event.getAction() == MotionEvent.ACTION_DOWN)
return false; //If the touch event was not putting the finger down on the screen, return false(Actions may be move, up, and so on)
final float x = event.getX();
final float y = event.getY();
if(x and y are in some range)
doFirstThing();
else if(x and y are in another range..)
doSecondThing();
//... and so on...
//In the end, you must return a boolean saying whether you "consumed" the event - if you handled the event or not.
}
}
And don't forget to set the listener as the OnTouchListener for your ImageView!
imageView.setOnTouchListener(listener);
I believe for what you are trying to achieve you don't need to use buttons. Instead, create a custom view, in your onDraw method draw the images and handle the onTouch events as Jong suggested. Once you detected a click on your home for example, you could create another view with info about it and display it to user.
For info on how to create custom view you may refer to this queestion for example. Google around for it
I have an overlay which I want to declutter based on the range from the map center.
I want to detect a change in the current map center so that If a person is constantly moving the map around, panning to another area, I can declutter and repopulate data in the new area.
Is there a listener for this or am I going to have to create a thread to periodically check the map center?
I forget the name of it ..... you'll have to read the API documentation ..... but there are properties/methods of the MapActivity that define the boundries (in terms of lat / long) of the currently displayed map that you can use to determine what the center value would be. If the GPS is reporting the user is currently at XXX value, but they have panned the map in a direction that puts the calculated center YYYY away from where the GPS is reporting, then you can determine "how far away from center" the Map is now positioned. Use the Great Circle method, by the way, when doing the math.
I think something like this might work
When you first load the map get a projection and store the center GeoPoint of the middle of the view the map is being displayed on by using the MapView's Projection class
mapPosition = myMapView.getProjection().fromPixels(x,y)
Will return a GeoPoint related to x,y in this case x y needs to be he center of the MapView
Extend the MapView class
public myMapView extends Mapview { }
So you can override onDispatchDraw
In that method you can check if the zoom level has changed, if it has not then it's probably because the map is being panned, could be other reasons I suppose dependent on what you are doing but it's reasonable to think most redraws are pans if you exclude zooms
Compare the current projection GeoPoint of the center of the screen with the one you stored at startup. If it meets your distance critera perform your logic and update the mapPosition geopoint.
If I understand your question correctly something like that should work. Of course you could over ride other members of the extended class as well if you dont want to check on every redraw, the motion events could be used as well but you said you were having problems with those.
UPDATE:
You might be able to do this directly on MapView vs in a custom container but what I have done in the past is override dispatchTouchEvent on an extended container class that the mapview lives inside, in this case a Framelayout so
public class myFrameLayout extends FrameLayout {
Then inside of that I use something like this to track the state of the touch motions, I will reduce it to the minimum that should work, the "modes" are enums I created, where you would do
logic is when the user lifts the finger at the bottom of the case statement
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_UP: // Second Finger
case MotionEvent.ACTION_DOWN: // First finger
mode = GESTURE_MODE.PRE_DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (GESTURE_MODE.PRE_DRAG == mode) // Pan
mode = GESTURE_MODE.DRAG;
else
{
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = GESTURE_MODE.MULTI_TOUCH;
break;
case MotionEvent.ACTION_UP: // All fingers removed
if (GESTURE_MODE.DRAG == mode)
{
// DO YOUR PANNING MOVED FAR LOGIC HERE
}
mode = GESTURE_MODE.NONE;
break;
}
return super.dispatchTouchEvent(event);
}