How to animate zooming in Android - android

I'm developing an Android application, I used this library
barteksc:android-pdf-viewer:2.3.0
I have to implement a simple animation, this is the code that I have implemented:
private void selectArea(){
/* Stop movement, jump to page 2, disable all action on PDFView*/
pdfView.stopFling();
pdfView.jumpTo(2);
pdfView.setEnabled(false);
pdfView.zoomWithAnimation(0, 1500, (float) 1.95);
}
private void resetAnimation() {
pdfView.resetZoomWithAnimation();
}
I need to select a certain area, reset the zoom and select again the area, so the problem is :
When I call selectarea () function shows the area that I want to show, when it resetarea call () the zoom is restored, if i call again the selectarea function the pdfView.resetZoomWithAnimation() call is ignored, and I don't understand why.
Pseudocode of my app.
selectArea();
//do stuff
resetAnimation()
selectArea();

**** SOLUTION ****
After hours of attempts I found a solution of my problem:
private void selectAreaToSign(){
/* Stop movement, jump to page 2, disable all action on PDFView*/
pdfView.stopFling();
pdfView.clearFocus();
pdfView.jumpTo(2);
pdfView.setEnabled(false);
pdfView.clearAnimation();
pdfView.clearFocus();
if(isFirstTime){
pdfView.zoomWithAnimation(0, 1700, (float) 2.00);
}else{
resetAnimation();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
sv.setVisibility(View.VISIBLE);
pdfView.zoomWithAnimation(4500, 1700, (float) 2.00);
}
}, 1000);
}
}
private void resetAnimation() {
pdfView.resetZoomWithAnimation();
sv.setVisibility(View.INVISIBLE);
pdfView.zoomTo(1);
}

Related

How to obtain the expected order of actions in my game loop?

I don't fully understand what is going on behind the scene, and therefore, what I can do to correctly code this issue. I'm looking for an explanation that will lead me to figure it out myself. This is just a fun home based project(I'm not a student), where I'm coding a turn based app. However, the battle scenes are randomly calculated durations, rather than turn based, so my desire is as follows:
Present initial battle count on screen for 2 seconds
Calculate first skirmish
Present updated battle count on screen for 2 seconds
Calculate 2nd skirmish
...
...
Present Victory or Defeat on screen
The problem I'm having is that the app is performing as follows currently:
Present initial battle count on screen
Calculate all skirmishes
Page displays null for the number, since it's apparently already returned?
Code looks like this:
void fightBattle(){
setContentView(R.layout.brigands);
boolean winnerDetermined = false;
while(!winnerDetermined) {
boolean brigandsWon = brigandsWon(player, brigandCount);
if(brigandsWon) {
player.removeWarriors(2);
}
displayWarriors(player);
if(brigandsWon){
if(player.getWarriors() < 2){
winnerDetermined = true;
}
}
if(!brigandsWon) {
brigandCount = brigandCount / 2;
}
displayBrigands();
if(brigandCount == 0){
winnerDetermined = true;
}
}
}
private void displayWarriors(Player player){
final Player currentPlayer = player;
new CountDownTimer(2000, 2000) {
public void onTick(long millisUntilFinished) { }
public void onFinish() {
setContentView(R.layout.warriors);
TextView warrior_count_tv = findViewById(R.id.warrior_count_tv);
warrior_count_tv.setText(currentPlayer.getWarriors());
}
}.start();
}
private void displayBrigands(Player player){
new CountDownTimer(2000, 2000) {
public void onTick(long millisUntilFinished) { }
public void onFinish() {
setContentView(R.layout.brigands);
TextView brigand_count_tv = findViewById(R.id.brigand_count_tv);
brigand_count_tv.setText(Integer.toString(brigandCount));
}
}.start();
}
Ultimately, what I want to see is something like the below sudo-code:
displayPage1For2Seconds;
while(somethingIsTrue){
calculateNumber;
displayPage2For2Seconds;
displayPage3for2Seconds;
}
displayPage4For2Seconds;
Calculate all skirmishes
Your current code does this because the while loop doesn't actually stops to wait. The flow will be like this:
enter while loop -> call displayWarriors() -> create CountDownTimer() to do something after 2 seconds -> return to while loop -> call displayBrigands() -> create CountDownTimer() to do something after 2 seconds -> return to while loop -> do the same until you exit while
With this code you'll end up with a bunch of CountDownTimers that are created and executed at the same(almost) time so after two seconds they all try to set a view to some value(with an indefinite behavior like you mention it happens).
There are several ways to do what you want. You could use a Thread for example:
void fightBattle(){
setContentView(R.layout.brigands);
new Thread(new Runnable() {
public void run() {
// I assume R.layout.brigands is the initial screen that you want to show for 2 seconds?!? In this case wait 2 seconds
TimeUnit.Seconds.sleep(2);
boolean winnerDetermined = false;
while(!winnerDetermined) {
// ...
// from your code it seems you want to show this for 2 seconds?
displayWarriors(player);
TimeUnit.Seconds.sleep(2);
//...
displayBrigands();
// also show this for 2 seconds
TimeUnit.Seconds.sleep(2);
// ...
}
}
}).start();
}
Then your display methods will be something like this:
private void displayWarriors(Player player){
// you need to wrap this code in a runOnUiThread() method(from the activity)
// because we are on a background thread and we are changing views!
final Player currentPlayer = player;
setContentView(R.layout.warriors);
TextView warrior_count_tv = findViewById(R.id.warrior_count_tv);
warrior_count_tv.setText(currentPlayer.getWarriors());
}
Another approach would be to use a Handler and break your code in Runnables that you then schedule at appropriate times.

Simultaneous, linked threads in android

I'm working on a math game which presents the player with a simple math problem of the sort 4 + 3 = ? or 6 / 2 = ?. The player is offered 4 possible answers and clicks one. The screen has two buttons, one with an arrow, and one with a square.
The player selects the answer they think is correct. If the answer is correct they get a message. If the answer is incorrect, that choice is grayed out, and they can keep choosing until they get it right.
I want two modes of play: practice and timed.
In practice mode, the player clicks the arrow button and gets a new question. This works fine.
I also want a timed play mode. When the player presses the arrow button, a timer starts and the first problem comes up. If they get it right, they are immediately asked a new question. Again, if they are incorrect, they keep asking until they get it right. Meanwhile, there should be a timer counting down. Play stops when the timer ends or the player presses the square button.
I don't know how to do the timed play where there are two simultaneous activities: playing the game, and the timer.
I'm including pseudo-code instead of code -- the code is long. But if necessary, I can post the entire code.
Update:
This was my naive attempt. The problem is that I can't figure out how to wait in the while loop for until the player to ask the question before askQuestion() is called again. Perhaps it needs to be in a separate thread and have the while loop wait for that thread to return. Would the timer keep going? I want the timer to run simultaneously to a separate task in which a question is posed --> Player answers the question --> a new question is posed .....
public void playTimed()
{
// Toast.makeText(getApplicationContext(), "in playPractice", Toast.LENGTH_SHORT).show();
go_button.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
go_button.setEnabled(false);
new CountDownTimer(10000, 1000) {
public void onTick(long millisUntilFinished)
{
timer.setText("seconds remaining: " + millisUntilFinished / 1000);
}
public void onFinish()
{
timer.setText("done!");
go_button.setEnabled(true);
timer_done = true;
}
}.start();
while(!timer_done)
{
askQuestion();
}
}
});
}
onCreate()
{
-For all four answer buttons:
answer_button.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
correct = checkAnswer(answer_button_id);
}
});
-get intent extras to know which game
-swith-case to play selected game
}
checkAnswer()
{
if (correct)
{
// feedback
handleRightAnswer();
}
else
{
// feedback
handleWrongAnswer();
}
}
askQuestion()
{
-choose type of problem (+,-,*,/)
-generate arguments and answer
-create random answer list
where one choice is correct
-display problem
-display answer choices
}
playTimed()
{
-When player presses go_button,
timer starts and first question
appears. When correct answer is
selected a new question immediately
appears. Continues until timer runs
out or player presses stop_button.
Seconds left is displayed during play.
}
playPractice()
{
//When player presses go_button
// a new question appears.
go_button.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
askQuestion();
}
});
}
As Gabe Sechan stated, you can do this using a timer to update a counter, then when the counter hits 0, do something. You can also use the timer to update a TextView (to display the time remaining).
Here is a class that I have used in the past, originally found here:
public class UIUpdater{
private Handler mHandler = new Handler(Looper.getMainLooper());
private Runnable mStatusChecker;
private int UPDATE_INTERVAL = 1000; //updates every second
public UIUpdater(final Runnable uiUpdater) {
mStatusChecker = new Runnable() {
#Override
public void run() {
// Run the passed runnable
uiUpdater.run();
// Re-run it after the update interval
mHandler.postDelayed(this, UPDATE_INTERVAL);
}
};
}
public UIUpdater(Runnable uiUpdater, int interval){
this(uiUpdater);
UPDATE_INTERVAL = interval;
}
public synchronized void startUpdates(){
mStatusChecker.run();
}
public synchronized void stopUpdates(){
mHandler.removeCallbacks(mStatusChecker);
}
}
Then, in your method where you want to update your counter, you would put:
UIUpdater mUIUpdater = new UIUpdater(new Runnable() {
#Override
public void run() {
//method to update counter
}
});
//to start the method:
mUIUpdater.startUpdates();
//to stop the method, ie, when counter == 0
mUIUpdater.stopUpdates();

Android Google Maps v2 Camera Animation

So im not sure if this is a bug or not yet... might be or I may have missed something.
Anyway so here is the link to Google Maps V2 Camera Controls. https://developers.google.com/maps/documentation/android/views#moving_the_camera
The issue :
Animate to a location already animated to does not call onFinish();
How to replicate:
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLocation.getLatLng(), zoomLevel), 200, new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
//DO some stuff here!
Log.d("animation", "onFinishCalled");
}
#Override
public void onCancel() {
Log.d("animation", "onCancel");
}
});
This issue may well come about when a user double taps something which called the same animation even if there is a long time between, onFinish will only be called for a successful animation. When the camera is already positioned the onFinish method will not be called!
I could go around doing checks before I do any camera animation but I don't like that as its wasteful.
Any help would be appreciated.
Thanks.
I have the same problem when i want to move camera to the same position, it seems like a bug.
Even if old and new position are not the same and the difference is so small , ex: old position.latitude = 94.54284009112, new position.latitude = 94.54284003451, it dosen't work.
my solution is to truncate values to get only old_position.latitude = new_position.latitude = 94.54, then i do a test.
There is another problem with moving camera and scroll the map in the same time, for that i disable scroll gesture before moving and enable it on the onFinish() and the onCancel().
public void animateCameraTo(final double lat, final double lng)
{
_googleMap = getMap();
CameraPosition camPosition = _googleMap.getCameraPosition();
if (!((Math.floor(camPosition.target.latitude * 100) / 100) == (Math.floor(lat * 100) / 100) && (Math.floor(camPosition.target.longitude * 100) / 100) == (Math.floor(lng * 100) / 100)))
{
_googleMap.getUiSettings().setScrollGesturesEnabled(false);
_googleMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(lat, lng)), new CancelableCallback()
{
#Override
public void onFinish()
{
_googleMap.getUiSettings().setScrollGesturesEnabled(true);
}
#Override
public void onCancel()
{
_googleMap.getUiSettings().setAllGesturesEnabled(true);
}
});
}
}
Hope this helps you ;)
Your should check the pixel distance, not the geo distance:
LatLngBounds myBounds = YOUR_BOUNDS;
LatLngBounds visibleBounds = map.getProjection().getVisibleRegion().latLngBounds;
Point myCenter = map.getProjection().toScreenLocation(myBounds.getCenter());
Point visibleCenter = map.getProjection().toScreenLocation(visibleBounds.getCenter());
int dist = (int) Math.sqrt(Math.pow(myCenter.x - visibleCenter.x, 2) + Math.pow(myCenter.y - visibleCenter.y, 2));
if (dist > YOUR_THRESHOLD) {
map.animateCamera(CameraUpdateFactory.newLatLngBounds(myBounds, YOUR_PADDING), new GoogleMap.CancelableCallback() {
#Override
public void onFinish() {
// do something
}
#Override
public void onCancel() {
// do something
}
});
} else {
// do something
}
The only solution I have found is doing the wasteful pixel value checks, setting up a bogus (but close) CameraUpdate object and making a nested animateCamera() call using the bogus CameraUpdate first and then calling another cameraUpdate() inside the first onFinish() with the correct CameraUpdate. I installed the Google MAP v2 update today but it didn't help. This is a problem on device rotation for me. I am displaying bounding rectangles and the centroid doesn't change on rotation so the animateCamera() doesn't work and when the device is rotated the selected rectangle may be partially off the screen.
Did you find another solution yet?
Thanks
Here's another workaround:
You do know the animation duration of your map animations. So you can post a delayed runnable with a delay of the animation duration + some offset and there you can check if the finished/canceld listener was called... if not you can fetch it there and call the apropriate listener

Zooming and animating to point mapview android

So in my current application, I want to zoom to a lat/long point, but also use the animateTo (or equivalent) to make sure the screen is properly centered. Currently I'm just doing something like this:
_mapController.animateTo(new GeoPoint(googleLat, googleLng));
// Smooth zoom handler
int zoomLevel = _mapView.getZoomLevel();
int targetZoomLevel = AppConstants.MAP_ZOOM_LEVEL_SWITCH;
long delay = 0;
while (zoomLevel++ < targetZoomLevel) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
_mapController.zoomIn();
}
}, delay);
delay += 350; // Change this to whatever is good on the device
}
This kind of works, but what happens is that my thread starts, BEFORE the 'animate to' finishes, so it zooms in and then 'jumps' to display the correct center geoPoint. Is there a way to smoothly 'drill down' to a certain zoom level, as well as move to a geoPoint at the same time? Similar to how the official Googl eMaps application zooms to an address after you've searched for it is what I'm looking to do.
I would do something like:
_mapController.animateTo(new GeoPoint(googleLat,googleLong), new Runnable() {
public void run()
{
_mapController.zoomIn ();
}
});
This will achieve a pan-then-zoom effect. You can try your same logic inside the runnable to perform multi-step zoomIn's.

Android: How to capture the end of panning on MapView

Could you please tell me how to capture the end of panning of MapView? At the beginning, I thought I could use the MotionEvent.ACTION_UP. However, when my touch is moved fast, the panning animation is still in progress while the ACTION_UP event is already fired before.
I had to come up with a solution to this problem also. It appears there is not a method in the android SDK to handle this event. What I did was create a timer that runs every 200ms and checks to see if the centerpoint of the mapview has changed. If it has and the a flag is set. The timer method knows when the panning has stopped when the centerpoint is no longer changing(oldMapCenter==newMapCenter). It may not be the best solution, but it is working for me. Insert the code below in your MapActivity.
NOTE: This will not update the view WHILE the user is panning, only AFTER.
GeoPoint newMapCenter,oldMapCenter;
int newZoomLevel=0,oldZoomLevel=0;
Boolean isMoving=false;
#Override
public void onResume() {
super.onResume();
autoUpdate = new Timer();
autoUpdate.schedule(new TimerTask() {
#Override
public void run() {
((Activity) getActivity()).runOnUiThread(new Runnable() {
public void run() {
if(oldMapCenter==null){
oldMapCenter=mapView.getMapCenter();
}else{
newMapCenter=mapView.getMapCenter();
if(!geoPointsAreSame(newMapCenter,oldMapCenter)&& (!isMoving)){
isMoving=true;
}else{
if(geoPointsAreSame(newMapCenter,oldMapCenter)&&(isMoving)){
//Insert update overlay code here
isMoving=false;
}
}
oldMapCenter=newMapCenter;
}
newZoomLevel=mapView.getZoomLevel();
if(oldZoomLevel==0){
oldZoomLevel=mapView.getZoomLevel();
}else{
if(oldZoomLevel!=newZoomLevel){
//insert update overlay code here
}
}
oldZoomLevel=mapView.getZoomLevel();
}
}
private boolean geoPointsAreSame(GeoPoint newMapCenter,
GeoPoint oldMapCenter) {
if(newMapCenter.getLatitudeE6()==oldMapCenter.getLatitudeE6()){
if(newMapCenter.getLongitudeE6()==oldMapCenter.getLongitudeE6()){
return true;
}
}
return false;
}
});
}
}, 3000, 200); // updates each 200 millisecs`-

Categories

Resources