Playing videos with TextureViews in RecyclerView, like Vine - android-mediaplayer

I have a RecyclerView and each ViewHolder of the RecyclerView has a MediaPlayer object. I am using a TextureView to hold the MediaPlayer objects, the issue is that when i am scrolling down it is very laggy. Why is this the case? I believe I am properly deallocating the MediaPlayer objects when the surface is destroyed.
For a reference I am using the following TextureView implementation taken from this repo: TextureVideoView
TextureVideoView.java
public class TextureVideoView extends TextureView implements TextureView.SurfaceTextureListener {
// Indicate if logging is on
public static final boolean LOG_ON = true;
// Log tag
private static final String TAG = TextureVideoView.class.getName();
private MediaPlayer mMediaPlayer;
private float mVideoHeight;
private float mVideoWidth;
private boolean mIsDataSourceSet;
private boolean mIsViewAvailable;
private boolean mIsVideoPrepared;
private boolean mIsPlayCalled;
private ScaleType mScaleType;
private State mState;
public enum ScaleType {
CENTER_CROP, TOP, BOTTOM
}
public enum State {
UNINITIALIZED, PLAY, STOP, PAUSE, END
}
public TextureVideoView(Context context) {
super(context);
initView();
}
public TextureVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public TextureVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
if (!isInEditMode()) {
initPlayer();
setScaleType(ScaleType.CENTER_CROP);
setSurfaceTextureListener(this);
}
}
public void setScaleType(ScaleType scaleType) {
mScaleType = scaleType;
}
private void updateTextureViewSize() {
float viewWidth = getWidth();
float viewHeight = getHeight();
float scaleX = 1.0f;
float scaleY = 1.0f;
if (mVideoWidth > viewWidth && mVideoHeight > viewHeight) {
scaleX = mVideoWidth / viewWidth;
scaleY = mVideoHeight / viewHeight;
} else if (mVideoWidth < viewWidth && mVideoHeight < viewHeight) {
scaleY = viewWidth / mVideoWidth;
scaleX = viewHeight / mVideoHeight;
} else if (viewWidth > mVideoWidth) {
scaleY = (viewWidth / mVideoWidth) / (viewHeight / mVideoHeight);
} else if (viewHeight > mVideoHeight) {
scaleX = (viewHeight / mVideoHeight) / (viewWidth / mVideoWidth);
}
// Calculate pivot points, in our case crop from center
int pivotPointX;
int pivotPointY;
switch (mScaleType) {
case TOP:
pivotPointX = 0;
pivotPointY = 0;
break;
case BOTTOM:
pivotPointX = (int) (viewWidth);
pivotPointY = (int) (viewHeight);
break;
case CENTER_CROP:
pivotPointX = (int) (viewWidth / 2);
pivotPointY = (int) (viewHeight / 2);
break;
default:
pivotPointX = (int) (viewWidth / 2);
pivotPointY = (int) (viewHeight / 2);
break;
}
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY, pivotPointX, pivotPointY);
setTransform(matrix);
}
private void initPlayer() {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
} else {
mMediaPlayer.reset();
}
mIsVideoPrepared = false;
mIsPlayCalled = false;
mState = State.UNINITIALIZED;
}
/**
* #see android.media.MediaPlayer#setDataSource(String)
*/
public void setDataSource(final String path) {
initPlayer();
((Activity) getContext()).runOnUiThread(new Runnable() {
#Override
public void run() {
try {
mMediaPlayer.setDataSource(path);
} catch (IOException e) {
e.printStackTrace();
}
}
});
mIsDataSourceSet = true;
prepare();
}
/**
* #see android.media.MediaPlayer#setDataSource(android.content.Context, android.net.Uri)
*/
public void setDataSource(Context context, Uri uri) {
initPlayer();
try {
mMediaPlayer.setDataSource(context, uri);
mIsDataSourceSet = true;
//prepare();
mMediaPlayer.setWakeMode(context.getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
/**
* #see android.media.MediaPlayer#setDataSource(java.io.FileDescriptor)
*/
public void setDataSource(AssetFileDescriptor afd) {
initPlayer();
try {
long startOffset = afd.getStartOffset();
long length = afd.getLength();
mMediaPlayer.setDataSource(afd.getFileDescriptor(), startOffset, length);
mIsDataSourceSet = true;
prepare();
} catch (IOException e) {
Log.d(TAG, e.getMessage());
}
}
private void prepare() {
try {
// Adjust the size of the MediaPlayer based on the Screen Resolution
mMediaPlayer.setOnVideoSizeChangedListener(
new MediaPlayer.OnVideoSizeChangedListener() {
#Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
mVideoWidth = width * metrics.density;
mVideoHeight = height * metrics.density;
updateTextureViewSize();
}
}
);
// Add a completion listener for when video ends and pass it to the listener
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mState = State.END;
if (mListener != null) {
mListener.onVideoEnd();
}
}
});
// don't forget to call MediaPlayer.prepareAsync() method when you use constructor for
// creating MediaPlayer
mMediaPlayer.prepareAsync();
// Play video when the media source is ready for playback.
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mIsVideoPrepared = true;
if (mIsPlayCalled && mIsViewAvailable) {
log("Player is prepared and play() was called.");
play();
}
if (mListener != null) {
mListener.onVideoPrepared();
}
}
});
} catch (IllegalArgumentException e) {
Log.d(TAG, "IllegalArgumentException");
Log.d(TAG, e.getMessage());
} catch (SecurityException e) {
Log.d(TAG, "SecurityException");
Log.d(TAG, e.getMessage());
} catch (IllegalStateException e) {
Log.d(TAG, "IllegalStateException");
Log.d(TAG, e.toString());
}
}
/**
* Play or resume video. Video will be played as soon as view is available and media player is
* prepared.
* <p/>
* If video is stopped or ended and play() method was called, video will start over.
*/
public void play() {
if (!mIsDataSourceSet) {
log("play() was called but data source was not set.");
return;
}
mIsPlayCalled = true;
if (!mIsVideoPrepared) {
log("play() was called but video is not prepared yet, waiting.");
return;
}
if (!mIsViewAvailable) {
log("play() was called but view is not available yet, waiting.");
return;
}
if (mState == State.PLAY) {
log("play() was called but video is already playing.");
return;
}
if (mState == State.PAUSE) {
log("play() was called but video is paused, resuming.");
mState = State.PLAY;
mMediaPlayer.start();
return;
}
if (mState == State.END || mState == State.STOP) {
log("play() was called but video already ended, starting over.");
mState = State.PLAY;
mMediaPlayer.seekTo(0);
mMediaPlayer.start();
return;
}
mState = State.PLAY;
mMediaPlayer.start();
}
/**
* Pause video. If video is already paused, stopped or ended nothing will happen.
*/
public void pause() {
if (mState == State.PAUSE) {
log("pause() was called but video already paused.");
return;
}
if (mState == State.STOP) {
log("pause() was called but video already stopped.");
return;
}
if (mState == State.END) {
log("pause() was called but video already ended.");
return;
}
mState = State.PAUSE;
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
}
}
/**
* Stop video (pause and seek to beginning). If video is already stopped or ended nothing will
* happen.
*/
public void stop() {
if (mState == State.UNINITIALIZED) {
log("stop() was called but video already released.");
}
if (mState == State.STOP) {
log("stop() was called but video already stopped.");
return;
}
if (mState == State.END) {
log("stop() was called but video already ended.");
return;
}
mState = State.STOP;
if (mMediaPlayer.isPlaying()) {
Log.d(TAG, "MediaPlayer is playing");
mMediaPlayer.pause();
mMediaPlayer.seekTo(0);
mMediaPlayer.stop();
}
}
/**
* Reset the video (only if the video has been stopped). This will reset the mediaplayer, else
* nothing happens
*/
public void reset() {
if (mState == State.PLAY) {
log("reset() was called but video is currently playing.");
return;
}
if (mState == State.PAUSE) {
log("reset() was called but video is currently paused.");
return;
}
if (mState == State.STOP) {
mMediaPlayer.reset();
}
}
public void release() {
if (mState == State.PLAY) {
log("release() was called but video is currently playing.");
return;
}
if (mState == State.PAUSE) {
log("release() was called but video is currently paused.");
return;
}
if (mState == State.STOP) {
log("release() was called but video is currently stopped.");
return;
}
mMediaPlayer.release();
mMediaPlayer = null;
mState = State.UNINITIALIZED;
}
/**
* #see android.media.MediaPlayer#setLooping(boolean)
*/
public void setLooping(boolean looping) {
Log.d(TAG, "Looping");
mMediaPlayer.setLooping(looping);
}
/**
* #see android.media.MediaPlayer#seekTo(int)
*/
public void seekTo(int milliseconds) {
mMediaPlayer.seekTo(milliseconds);
}
/**
* #see android.media.MediaPlayer#getDuration()
*/
public int getDuration() {
return mMediaPlayer.getDuration();
}
static void log(String message) {
if (LOG_ON) {
Log.d(TAG, message);
}
}
private MediaPlayerListener mListener;
/**
* Listener trigger 'onVideoPrepared' and `onVideoEnd` events
*/
public void setListener(MediaPlayerListener listener) {
mListener = listener;
}
public interface MediaPlayerListener {
void onVideoPrepared();
void onVideoEnd();
}
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Surface surface = new Surface(surfaceTexture);
mMediaPlayer.setSurface(surface);
mIsViewAvailable = true;
if (mIsDataSourceSet && mIsPlayCalled && mIsVideoPrepared) {
play();
}
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// Deallocate MediaPlayer
if (mMediaPlayer != null) {
mMediaPlayer.pause();
mMediaPlayer.stop();
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
}
return false;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
When I get an asset downloaded I serve it inside my adapter like so:
final TextureVideoView videoView = holder.videoview;
videoView.setDataSource(path);
videoView.setLooping(true);
videoView.setScaleType(TextureVideoView.ScaleType.CENTER_CROP);
videoView.play();
The issue is that when I am scrolling it is really laggy, how can I fix this? How can I make a RecyclerView like Vine where the TextureView pauses unless the view is mostly visible.
Looking for a very thorough answer to this question and good optimized implementation.

Related

Can't read the value that comes with the GATT Service characteristic; Not sure if I setup the GATT server correctly

The BLE is advertising just fine. I can establish a connection with it but I can't read the characteristic value I set here on the initialization of GATT server.
This is the GATT Server setup
#ReactMethod
public void startServer(Callback srvCallBack) {
mBluetoothGattServer = mBluetoothManager.openGattServer(getReactApplicationContext(), mGattServerCallback);
if (mBluetoothGattServer == null) {
srvCallBack.invoke(false);
return;
}
BluetoothGattService gattService = new BluetoothGattService(SERVICE_UUID,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
BluetoothGattCharacteristic mCharacteristics = new BluetoothGattCharacteristic(SERVICE_UUID, 2, 1);
String currentDeviceName = BluetoothAdapter.getDefaultAdapter().getName();
boolean isValAdded = mCharacteristics.setValue(currentDeviceName);
if (isValAdded == false) {
Toast.makeText(getReactApplicationContext(), "Can't add value to characteristics.", Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(getReactApplicationContext(), "Value added to characteristics.", Toast.LENGTH_SHORT).show();
}
boolean isCharAdded = gattService.addCharacteristic(mCharacteristics);
if (isCharAdded == false) {
Toast.makeText(getReactApplicationContext(), "Can't add characteristics.", Toast.LENGTH_SHORT).show();
}
boolean isServAdded = mBluetoothGattServer.addService(gattService);
if (isServAdded == true) {
srvCallBack.invoke(true, gattService.getUuid().toString());
return;
}
}
Im not sure if the problem is with the GATT server callback, or is it even needed? Here is my callback
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
#Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
return;
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
return;
}
}
#Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
BluetoothGattCharacteristic characteristic) {
if (SERVICE_UUID.equals(characteristic.getUuid())) {
Toast.makeText(getReactApplicationContext(), "Sending device name.", Toast.LENGTH_SHORT).show();
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0,
characteristic.getValue());
} else {
Toast.makeText(getReactApplicationContext(), "uuid didn't matched.", Toast.LENGTH_SHORT).show();
}
}
};

How can I decide if the game I coded using JavaFX uses adequate system resources?

I have coded a simple card game (using IntelliJ 3.3) that updates gui each second and contains many nodes in game which are used to display game events and animation. People who have tried the game said that their system slows down and memory and cpu usage goes up (I was shocked when I saw from one of my friend's screen that it used around 1.4 GB ram) is this supposed to happen?
I have checked some questions related to javaFX memory usage problems but could not relate to my matter. In one article, I read that using many nodes in a JavaFX game might result in high memory usage.
in a short runtime:
minimum memory usage is around 100-250 MB
maximum memory usage is around 800-1200 MB
average memory usage is around 500-700 MB
IMPORTANT:
THERE ARE 100 BUTTONS IN A 10X10 GRIDPANE WHICH DO NOT HAVE FX IDS
in gameButtonClicked matches are found buttons.indexOf(event.getSource())
UML DIAGRAM
Main.java
IMPORTS:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
public class Main extends Application {
static boolean isMuted;
static Stage window;
static MediaPlayer mediaPlayerBGM;
static MediaPlayer mediaPlayerSFX;
private static HashMap<String, Media> sounds;
private static ArrayList<Long> usage;
private final String[] SOUND_LIST = {"bgm_credits.mp3", "bgm_game.mp3", "bgm_game_1.mp3", "bgm_game_2.mp3", "bgm_game_3.mp3",
"bgm_how_to.mp3", "bgm_menu.mp3", "bgm_victory.mp3", "sfx_button_clicked.wav",
"sfx_card_unfold.wav", "sfx_toggle.wav"
};
public static void main(String[] args) {
launch(args);
AtomicLong sum = new AtomicLong();
usage.forEach(sum::addAndGet);
long average = sum.get() / usage.size();
System.out.printf("minimum usage: %d, maximum usage: %d, average usage: %d",
Collections.min(usage), Collections.max(usage), average);
}
static void playBGM(String key) {
mediaPlayerBGM.stop();
mediaPlayerBGM.setStartTime(Duration.ZERO);
if (key.equals("bgm_game")) {
String[] suffix = {"", "_1", "_2", "_3"};
Random random = new Random();
mediaPlayerBGM = new MediaPlayer(sounds.get(key + suffix[random.nextInt(4)]));
} else {
mediaPlayerBGM = new MediaPlayer(sounds.get(key));
}
mediaPlayerBGM.setCycleCount(MediaPlayer.INDEFINITE);
if (isMuted) {
mediaPlayerBGM.setVolume(0.0);
}
mediaPlayerBGM.play();
}
static void playSFX(String key) {
if (mediaPlayerSFX != null) {
mediaPlayerSFX.stop();
}
mediaPlayerSFX = new MediaPlayer(sounds.get(key));
if (isMuted) {
mediaPlayerSFX.setVolume(0.0);
}
mediaPlayerSFX.play();
}
#Override
public void start(Stage primaryStage) throws Exception {
sounds = new HashMap<>();
usage = new ArrayList<>();
isMuted = false;
for (String soundName :
SOUND_LIST) {
URL resource = getClass().getResource("/" + soundName);
System.out.println(soundName);
System.out.println(resource.toString());
sounds.put(soundName.substring(0, soundName.lastIndexOf('.')), new Media(resource.toString()));
}
mediaPlayerBGM = new MediaPlayer(sounds.get("bgm_menu"));
mediaPlayerBGM.setCycleCount(MediaPlayer.INDEFINITE);
mediaPlayerBGM.play();
window = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("menu.fxml"));
// long running operation runs on different thread
Thread thread = new Thread(() -> {
Runnable updater = () -> {
if (!Game.isGameIsOver() && Game.getScore() != 0 && window.getTitle().equals("The Main Pick") &&
Game.firstClickHappened()) {
Game.scoreCalculator();
}
};
while (true) {
try {
usage.add(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
System.out.printf("Used memory: %d\n", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("Interrupted");
}
// UI update is run on the Application thread
Platform.runLater(updater);
}
});
// don't let thread prevent JVM shutdown
thread.setDaemon(true);
thread.start();
window.setTitle("Main Menu");
window.setScene(new Scene(root, 600, 600));
window.setResizable(false);
window.show();
}
}
Controller.java
IMPORTS:
package com.sample;
import javafx.animation.FadeTransition;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
importjavafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.layout.GridPane;
import javafx.util.Duration;
import java.io.IOException;
public class Controller
{
#FXML
public RadioButton musicOnOff;
#FXML
public Button newGame;
#FXML
public Button howTo;
#FXML
public Button credits;
#FXML
public Button exit;
#FXML
public Button menu;
#FXML
public GridPane pane;
#FXML
public Label score;
#FXML
public Label time;
#FXML
public Label tries;
private boolean animating;
public void initialize() {
if (score != null && time != null && tries != null) {
score.textProperty().bind(Game.scoreProperty);
time.textProperty().bind(Game.timeProperty);
tries.textProperty().bind(Game.triesProperty);
animating=false;
}
if (musicOnOff!=null){
if(Main.isMuted){
musicOnOff.setSelected(true);
}
}
}
public void newGameButtonClicked() {
try {
Main.playSFX("sfx_button_clicked");
new Game();
Main.window.hide();
Main.window.setScene(getScene("game"));
Main.window.setTitle("The Main Pick");
Main.window.setMaximized(true);
Main.playBGM("bgm_game");
Main.window.show();
} catch (IOException e) {
System.out.println("could not change the scene to: game");
}
}
public void menuButtonClicked() {
try {
Main.playSFX("sfx_button_clicked");
Main.playBGM("bgm_menu");
if (Main.window.getTitle().equals("The Main Pick")) {
Main.window.hide();
Game.setGameOver();
Main.window.setScene(getScene("menu"));
Main.window.setMaximized(false);
Main.window.show();
} else {
Main.window.setScene(getScene("menu"));
}
Main.window.setTitle("Main Menu");
} catch (IOException e) {
System.out.println("could not change the scene to: game");
}
}
public void gameButtonClicked(ActionEvent event) {
ObservableList<Node> buttons = pane.getChildren();
int index = buttons.indexOf(event.getSource());
int column = index % 10;
int row = (index - index % 10) / 10;
if (!((Button) event.getSource()).getStyleClass().toString().equals("button button-treasure") &&
!((Button) event.getSource()).getStyleClass().toString().equals("button button-uncovered"))
{
FadeTransition transition = new FadeTransition();
transition.setNode((Button) event.getSource());
transition.setDuration(new Duration(500));
transition.setFromValue(1.0);
transition.setToValue(0.0);
transition.setCycleCount(1);
transition.setOnFinished(actionEvent -> {
if (((Button) event.getSource()).getStyleClass().toString().equals("button button-treasure")) {
loadVictoryScene();
}
if (!((Button) event.getSource()).getStyleClass().toString().equals("button button-treasure") &&
!((Button) event.getSource()).getStyleClass().toString().equals("button button-uncovered"))
{
transition.setFromValue(0.0);
transition.setToValue(1.0);
System.out.println(((Button) event.getSource()).getStyleClass().toString());
((Button) event.getSource()).getStyleClass().remove("button-covered");
((Button) event.getSource()).getStyleClass().add(Game.click(row, column));
transition.play();
transition.setOnFinished(ActionEvent->animating=false);
}
});
System.out.println(animating);
if(!animating){
animating=true;
transition.play();
Main.playSFX("sfx_card_unfold");}
}
System.out.printf("button index:%d,row:%d,column:%d\n", index, row, column);
}
public void howToPlayButtonClicked() {
try {
Main.playSFX("sfx_button_clicked");
Main.playBGM("bgm_how_to");
Main.window.setScene(getScene("howTo"));
Main.window.setTitle("How to Play");
} catch (IOException e) {
System.out.println("could not change the scene to: how to play");
}
}
public void creditsButtonClicked() {
try {
Main.playSFX("sfx_button_clicked");
Main.playBGM("bgm_credits");
Main.window.setScene(getScene("credits"));
Main.window.setTitle("Credits");
} catch (IOException e) {
System.out.println("could not change the scene to: credits");
}
}
public void exitButtonClicked() {
Main.playSFX("sfx_button_clicked");
Main.window.close();
}
public void musicOnOffRadioButtonChecked() {
Main.playSFX("sfx_toggle");
if (musicOnOff.isSelected()) {
Main.isMuted=true;
Main.mediaPlayerBGM.setVolume(0.0);
Main.mediaPlayerSFX.setVolume(0.0);
System.out.println("now selected");
} else {
Main.isMuted=false;
Main.mediaPlayerBGM.setVolume(1.0);
Main.mediaPlayerSFX.setVolume(1.0);
System.out.println("unselected");
}
}
private void loadVictoryScene() {
try {
Main.window.hide();
Main.playBGM("bgm_victory");
Main.window.setScene(getScene("victory"));
Main.window.setTitle("Victory");
Main.window.setMaximized(false);
Main.window.show();
} catch (IOException e) {
System.out.println("could not change the scene to: victory");
}
}
private Scene getScene(String name) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource(name + ".fxml"));
if (name.equals("game")) {
return new Scene(root, 800, 800);
}
return new Scene(root, 600, 600);
}
}
Game.java
IMPORTS:
package com.sample;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import java.util.Random;
public class Game {
static StringProperty scoreProperty;
static StringProperty timeProperty;
static StringProperty triesProperty;
private static int time;
private static int score;
private static int tries;
private static int[][] tiles;
private static boolean gameIsOver;
private static boolean firstClick;
public Game() {
System.out.println("Game created...");
tries = 0;
score = 100000;
time = 0;
gameIsOver = false;
firstClick = false;
scoreProperty = new SimpleStringProperty("" + score);
timeProperty = new SimpleStringProperty("0");
triesProperty = new SimpleStringProperty("0");
tiles = new int[10][10];
Random random = new Random();
int treasureColumn = random.nextInt(10);
int treasureRow = random.nextInt(10);
tiles[treasureRow][treasureColumn] = 1;
}
static boolean firstClickHappened() {
return firstClick;
}
static void setGameOver() {
gameIsOver = true;
}
static boolean isGameIsOver() {
return gameIsOver;
}
static int getScore() {
return score;
}
static void scoreCalculator() {
time++;
if (time < 10) {
score = score - 100;
} else if (time < 20) {
score = score - 200;
} else if (time < 30) {
score = score - 300;
} else if (time < 50) {
score = score - 500;
} else {
score = score - 1000;
}
if (score < 0) {
score = 0;
}
scoreProperty.setValue("" + score);
timeProperty.setValue("" + time);
triesProperty.setValue("" + tries);
System.out.printf("Score:%s,Time:%s,Tries%s\n", scoreProperty.getValue(), timeProperty.getValue(), triesProperty.getValue());
}
static String click(int row, int column) {
if (!firstClickHappened()) firstClick = true;
System.out.println(row + "," + column);
int clickValue = tiles[row][column];
System.out.println(clickValue);
if (clickValue == 0) {
tries++;
score -= 1000;
} else {
setGameOver();
}
return (clickValue == 1) ? "button-treasure" : "button-uncovered";
}
}

Implement Infinite scroll with ViewModel And Retrofit in recyclerview

Before adding viewmodel & livedata , i successfully implemented infinity scroll with retrofit. But after adding viewmodel & livedata with Retrofit, My can't update recyclerview with new data call or viewmodel observer not update the list.
I simply want to infinite scrolling as my code does before. I add a global variable to reuse next page token. Am i missing anything or any sample to implement infinite recyclerview with viewmodel & retrofit will be awesome.
public static String NEXT_PAGE_URL = null;
I coded like that.
My Activity -> PlaceListActivity
placeRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LogMe.d(tag, "onScrollStateChanged:: " + "called");
// check scrolling started or not
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true;
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LogMe.d(tag, "onScrolled:: " + "called");
super.onScrolled(recyclerView, dx, dy);
currentItem = layoutManager.getChildCount();
totalItems = layoutManager.getItemCount();
scrolledOutItems = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
LogMe.d(tag, "currentItem:: " + currentItem);
LogMe.d(tag, "totalItems:: " + totalItems);
LogMe.d(tag, "scrolledOutItems:: " + scrolledOutItems);
if (isScrolling && (currentItem + scrolledOutItems == totalItems)) {
LogMe.d(tag, "view:: " + "finished");
isScrolling = false;
if (ApplicationData.NEXT_PAGE_URL != null) {
LogMe.d(tag, "place adding:: " + " onScrolled called");
ll_loading_more.setVisibility(View.VISIBLE);
// todo: call web api here
callDataFromLocationAPi(type, ApplicationData.NEXT_PAGE_URL, currentLatLng);
} else {
LogMe.d(tag, "next_page_url:: " + " is null");
}
}
}
});
private void callDataFromLocationAPi(String type, String next_page_url, LatLng latLng) {
if (Connectivity.isConnected(activity)) {
showProgressDialog();
model.getNearestPlaces(type, next_page_url, latLng).
observe(activity, new Observer<List<PlaceDetails>>() {
#Override
public void onChanged(#Nullable List<PlaceDetails> placeDetails) {
ll_loading_more.setVisibility(View.GONE);
LogMe.i(tag, "callDataFromLocationAPi: onChanged called !");
hideProgressDialog();
if (placeDetails != null) {
placeDetailsList = placeDetails;
placeListAdapter.setPlaceList(placeDetails);
}
}
});
} else {
showAlertForInternet(activity);
}
}
In PlaceViewModel
public class PlaceViewModel extends AndroidViewModel {
//this is the data that we will fetch asynchronously
private MutableLiveData<List<PlaceDetails>> placeList;
private PlaceRepository placeRepository;
private String tag = getClass().getName();
public PlaceViewModel(Application application) {
super(application);
placeRepository = new PlaceRepository(application);
}
//we will call this method to get the data
public MutableLiveData<List<PlaceDetails>> getNearestPlaces(String type,
String next_page_token,
LatLng latLng) {
//if the list is null
if (placeList == null) {
placeList = new MutableLiveData<>();
//we will load it asynchronously from server in this method
//loadPlaces(type, next_page_token, latLng);
placeList = placeRepository.getNearestPlacesFromAPI(type, next_page_token, latLng);
}
//finally we will return the list
return placeList;
}
}
In my PlaceRepository.java looks
public class PlaceRepository {
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// Since we didn't alter the table, there's nothing else to do here.
}
};
private PlaceDatabase placeDatabase;
private CurrentLocation currentLocation = null;
private String tag = getClass().getName();
//this is the data that we will fetch asynchronously
private MutableLiveData<List<PlaceDetails>> placeList;
public PlaceRepository(Context context) {
placeDatabase = PlaceDatabase.getDatabase(context);
//addMigrations(MIGRATION_1_2)
placeList =
new MutableLiveData<>();
}
public MutableLiveData<List<PlaceDetails>> getNearestPlacesFromAPI(String type, final String next_page_token, LatLng latLng) {
List<PlaceDetails> placeDetailsList = new ArrayList<>();
try {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiService.getNearbyPlaces(type,
latLng.latitude + "," +
latLng.longitude, ApplicationData.PROXIMITY_RADIUS,
ApplicationData.PLACE_API_KEY, next_page_token);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(Call<Example> call, Response<Example> response) {
try {
Example example = response.body();
ApplicationData.NEXT_PAGE_URL = example.getNextPageToken();
// next_page_url = example.getNextPageToken();
LogMe.i(tag, "next_page_url:" + ApplicationData.NEXT_PAGE_URL);
if (example.getStatus().equals("OK")) {
LogMe.i("getNearbyPlaces::", " --- " + response.toString() +
response.message() + response.body().toString());
// This loop will go through all the results and add marker on each location.
for (int i = 0; i < example.getResults().size(); i++) {
Double lat = example.getResults().get(i).getGeometry().getLocation().getLat();
Double lng = example.getResults().get(i).getGeometry().getLocation().getLng();
String placeName = example.getResults().get(i).getName();
String vicinity = example.getResults().get(i).getVicinity();
String icon = example.getResults().get(i).getIcon();
String place_id = example.getResults().get(i).getPlaceId();
PlaceDetails placeDetails = new PlaceDetails();
if (example.getResults().get(i).getRating() != null) {
Double rating = example.getResults().get(i).getRating();
placeDetails.setRating(rating);
}
//List<Photo> photoReference = example.getResults().
// get(i).getPhotos();
placeDetails.setName(placeName);
placeDetails.setAddress(vicinity);
placeDetails.setLatitude(lat);
placeDetails.setLongitude(lng);
placeDetails.setIcon(icon);
placeDetails.setPlace_id(place_id);
//placeDetails.setPlace_type(place_type_title);
double value = ApplicationData.
DISTANCE_OF_TWO_LOCATION_IN_KM(latLng.latitude, latLng.longitude, lat, lng);
//new DecimalFormat("##.##").format(value);
placeDetails.setDistance(new DecimalFormat("##.##").format(value));
String ph = "";
if (example.getResults().
get(i).getPhotos() != null) {
try {
List<Photo> photos = example.getResults().
get(i).getPhotos();
//JSONArray array = new JSONArray(example.getResults().
//get(i).getPhotos());
//JSONObject jsonObj = new JSONObject(array.toString());
//ph = jsonObj.getString("photo_reference");
ph = photos.get(0).getPhotoReference();
//LogMe.i(tag, "\n" + ph);
} catch (Exception e) {
e.printStackTrace();
//placeDetails.setPicture_reference(ph);
//PLACE_DETAILS_LIST.add(placeDetails);
//LogMe.i(tag, "#### Exception Occureed ####");
ph = "";
//continue;
}
}
placeDetails.setPicture_reference(ph);
placeDetailsList.add(placeDetails);
placeList.postValue(placeDetailsList);
}
} else {
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<Example> call, Throwable t) {
Log.e("onFailure", t.toString());
}
});
} catch (RuntimeException e) {
//hideProgressDialog();
Log.d("onResponse", "RuntimeException is an error");
e.printStackTrace();
} catch (Exception e) {
Log.d("onResponse", "Exception is an error");
}
return placeList;
}
}
I precise code due to question simplicity.
Though you already use android-jetpack, take a look at Paging library. It's specially designed for building infinite lists using RecyclerView.
Based on your source code, I'd say that you need PageKeyedDataSource, here is some example which includes info about how to implement PageKeyedDataSource -
7 steps to implement Paging library in Android
If talking about cons of this approach:
You don't need anymore to observe list scrolling (library doing it for you), you just need to specify your page size in the next way:
PagedList.Config myPagingConfig = new PagedList.Config.Builder()
.setPageSize(50)
.build();
From documentation:
Page size: The number of items in each page.
Your code will be more clear, you'll get rid of your RecyclerView.OnScrollListener
ViewModel code will be shorter, it's will provide only PagedList:
#NonNull
LiveData<PagedList<ReviewSection>> getReviewsLiveData() {
return reviewsLiveData;
}

Google Play Sign in Failure Api Exception 4

Im trying to integrate google sign in and took the code from another project that it works on, but for the new app it goes thru the log in but the dialog to allow doesnt show up and instead I get sign in failure with Api Exception 4.
Here is all the applicable code:
//Create the client used to sign in to Google services
mGoogleSignInClient = GoogleSignIn.getClient(this,
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode(getString(R.string.client_id))
.requestEmail()
.build());
private void signInSilently(){
Log.d(TAG, "signInSilently()");
mGoogleSignInClient.silentSignIn().addOnCompleteListener(this,
new OnCompleteListener<GoogleSignInAccount>() {
#Override
public void onComplete(#NonNull Task<GoogleSignInAccount> task) {
if (task.isSuccessful()) {
Log.d(TAG, "signInSilently(): success");
onConnected(task.getResult());
} else {
Log.d(TAG, "signInSilently(): failure", task.getException());
onDisconnected();
}
}
});
}
private void startSignInIntent() {
startActivityForResult(mGoogleSignInClient.getSignInIntent(), RC_SIGN_IN);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task =
GoogleSignIn.getSignedInAccountFromIntent(data);
try {
GoogleSignInAccount account = task.getResult(ApiException.class);
onConnected(account);
} catch (ApiException apiException) {
String message = apiException.getMessage();
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error);
}
onDisconnected();
new AlertDialog.Builder(this)
.setMessage(message)
.setNeutralButton(android.R.string.ok, null)
.show();
}
}
}
private void onDisconnected() {
Log.d(TAG, "onDisconnected()");
mPlayersClient = null;
// Show sign-in button on main menu
if(game.user != null) {
game.user.setDisplayName(null);
game.user.setPlayerID(null);
}
}
private void onConnected(final GoogleSignInAccount googleSignInAccount) {
Log.d(TAG, "onConnected(): connected to Google APIs");
GamesClient gamesClient = Games.getGamesClient(AndroidLauncher.this, googleSignInAccount);
View view = ((AndroidGraphics) Gdx.graphics).getView();
gamesClient.setViewForPopups(view);
gamesClient.setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
mPlayersClient = Games.getPlayersClient(this, googleSignInAccount);
// Set the greeting appropriately on main menu
mPlayersClient.getCurrentPlayer()
.addOnCompleteListener(new OnCompleteListener<Player>() {
#Override
public void onComplete(#NonNull Task<Player> task) {
String displayName;
if (task.isSuccessful()) {
user = new User();
user.setDisplayName(task.getResult().getDisplayName());
user.setPlayerID(task.getResult().getPlayerId());
game.user = user;
firebaseAuthWithPlayGames(googleSignInAccount);
} else {
Exception e = task.getException();
handleException(e, getString(R.string.players_exception));
displayName = "???";
}
}
});
}
private void handleException(Exception e, String details) {
int status = 0;
if (e instanceof ApiException) {
ApiException apiException = (ApiException) e;
status = apiException.getStatusCode();
}
String message = getString(R.string.status_exception_error, details, status, e);
new AlertDialog.Builder(AndroidLauncher.this)
.setMessage(message)
.setNeutralButton(android.R.string.ok, null)
.show();
}

Panning and Zooming on MapView

I am trying to create an app that uses the panning and zooming features on the map. I have spent a lot of time trying to implement this. I first tried to create an on touch method inside the MapActivity, but I soon realized it would be a little more than that. I found some code that I tried to implement that created a subclass of a mapview and ran into some issues with it. I need some help with panning around the map. Everywhere I try to clear and display the overlay causes problems. Either the overlays get removed and displayed at the wrong place, or a loop is created causing flashing. Please let me know what I need to do to get this working. I realized that I may need to override the onScroll method if there is one instead of the onTouch method. Right now it is pretty buggy I need some help, so that I can make it bug free. Let me know if my thinking is correct. I need to get this up on the market right away. Here is my code. Thanks in advance.
//MapActivity class
//Package name omitted
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;
public class MapActivityNearby extends MapActivity {
private static final String TAG = "MAPACTIVITY";
double lat;
double lng;
EnhancedMapView mv;
ArrayList<MapItem> allCats;
private static final String PREF_NAME = "cookie";
private float latMax;
private float latMin;
private float longMax;
private float longMin;
GeoPoint p;
private List<Overlay> mapOverlays;
private MyItemizedOverlay itemizedOverlay;
boolean waitTime = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_nearby);
LinearLayout ll = (LinearLayout) findViewById(R.id.maplayout);
mv = new EnhancedMapView(this, "0ieXSx8GEy9Hm7bCZMLckus7pmPKg0w8kelRO_g");
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mv.setLayoutParams(lp);
mv.setClickable(true);
mv.setBuiltInZoomControls(true);
mv.setOnZoomChangeListener(new EnhancedMapView.OnZoomChangeListener() {
#Override
public void onZoomChange(MapView view, int newZoom, int oldZoom) {
Log.v("test", "zoom center changed");
if (isNetworkAvailable(MapActivityNearby.this))
{
LogIn log = new LogIn();
log.execute(mv);
}
else
{
AlertDialog.Builder builder = new AlertDialog.Builder(MapActivityNearby.this);
builder.setMessage("No network connection. Please try again when your within coverage area.")
.setTitle("Network Connection")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//close dialog
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
});
mv.setOnPanChangeListener(new EnhancedMapView.OnPanChangeListener() {
public void onPanChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter) {
Log.v("test", "pan center changed");
if (isNetworkAvailable(MapActivityNearby.this))
{
LogIn log = new LogIn();
log.execute(mv);
}
else
{
AlertDialog.Builder builder = new AlertDialog.Builder(MapActivityNearby.this);
builder.setMessage("No network connection. Please try again when your within coverage area.")
.setTitle("Network Connection")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//close dialog
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
});
ll.addView(mv);
SharedPreferences cookies = getSharedPreferences(PREF_NAME,
Context.MODE_PRIVATE);
lat = Double.parseDouble(cookies.getString("lat", "0"));
lng = Double.parseDouble(cookies.getString("lng", "0"));
GeoPoint p = new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6));
MapController mapControl = mv.getController();
mapControl.setCenter(p);
mapControl.setZoom(11);
}
public void onResume()
{
super.onResume();
if (isNetworkAvailable(MapActivityNearby.this))
{
LogIn log = new LogIn();
log.execute(mv);
}
else
{
AlertDialog.Builder builder = new AlertDialog.Builder(MapActivityNearby.this);
builder.setMessage("No network connection. Please try again when your within coverage area.")
.setTitle("Network Connection")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//close dialog
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
public void plotPoints(ArrayList<MapItem> i) {
mapOverlays = mv.getOverlays();
// first overlay
Drawable drawable;
drawable = getResources().getDrawable(R.drawable.marker);
itemizedOverlay = new MyItemizedOverlay(drawable, mv);
mapOverlays.add(itemizedOverlay);
for (MapItem x : i) {
GeoPoint point = new GeoPoint((int) (x.getLat() * 1E6),
(int) (x.getLng() * 1E6));
OverlayItem overlayItem = new OverlayItem(point,
x.getTitle(), x.getSubtitle());
itemizedOverlay.addOverlay(overlayItem);
}
}
private class LogIn extends AsyncTask<EnhancedMapView, Void, Boolean> {
String result = "";
InputStream is;
#Override
protected void onPreExecute() {
}
#Override
protected Boolean doInBackground(EnhancedMapView...params) {
if (!(mv.getOverlays().isEmpty()))
{
mv.getOverlays().clear();
}
int maxCount = 100;
EnhancedMapView mapView = params[0];
for (int i = 0; i < maxCount; i++)
{
p = mapView.getMapCenter();
float latCenter = (float) (p.getLatitudeE6()) / 1000000;
float longCenter = (float) (p.getLongitudeE6()) / 1000000;
float latSpan = (float) (mapView.getLatitudeSpan()) / 1000000;
float longSpan = (float) (mapView.getLongitudeSpan()) / 1000000;
latMax = latCenter + (latSpan/2);
latMin = latCenter - (latSpan/2);
longMax = longCenter + (longSpan/2);
longMin = longCenter - (longSpan/2);
if (latMin == latMax)
{
try
{
Thread.sleep(80);
}
catch(InterruptedException e)
{
}
}
else
{
p = mapView.getMapCenter();
latCenter = (float) (p.getLatitudeE6()) / 1000000;
longCenter = (float) (p.getLongitudeE6()) / 1000000;
latSpan = (float) (mapView.getLatitudeSpan()) / 1000000;
longSpan = (float) (mapView.getLongitudeSpan()) / 1000000;
latMax = latCenter + (latSpan/2);
latMin = latCenter - (latSpan/2);
longMax = longCenter + (longSpan/2);
longMin = longCenter - (longSpan/2);
break;
}
}
log(latMin);
log(latMax);
try {
final String catURL = "url goes here";
log(catURL.toString());
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(catURL);
ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
waitTime = true;
nameValuePairs.add(new BasicNameValuePair("PHPSESSID", getCookie()));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
is = entity.getContent();
result = convertStreamToString();
log(result);
allCats = new ArrayList<MapItem>();
JSONObject object = new JSONObject(result);
JSONArray temp = object.getJSONArray("l");
log("Starting download");
log(temp.length());
long start = System.currentTimeMillis();
for (int k = 0; k < temp.length(); k++) {
JSONObject j = temp.getJSONObject(k);
MapItem c = new MapItem();
c.setObject_id(j.getInt("object_id"));
c.setTitle(j.getString("title"));
c.setColor(j.getString("color"));
c.setLat(j.getDouble("lat"));
c.setLng(j.getDouble("lng"));
c.setSubtitle(j.getString("subtitle"));
allCats.add(c);
log(allCats.toString());
}
long end = System.currentTimeMillis();
log("Download Took: " + (end - start) / 1000 + " seconds.");
// log(allCats.toString());
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
#Override
protected void onPostExecute(Boolean result) {
// pBar.setVisibility(View.GONE);
plotPoints(allCats);
}
private String convertStreamToString() {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(is, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
is.close();
result = sb.toString();
result.trim();
} catch (Exception e) {
Log.e("log_tag", "Error converting result " + e.toString());
}
return result;
}
}
public String getCookie() {
String cookie = "";
SharedPreferences cookies = getSharedPreferences(PREF_NAME,
Context.MODE_PRIVATE);
if (cookies.contains("cookie")) {
cookie = cookies.getString("cookie", "null");
}
return cookie;
}
private void log(Object obj) {
Log.d(TAG, TAG + " :: " + obj.toString());
}
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo[] info = connectivity.getAllNetworkInfo();
if (info != null) {
for (int i = 0; i < info.length; i++) {
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
#Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}
}
// CustomMapView class
// package name omitted
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
public class EnhancedMapView extends MapView {
public interface OnZoomChangeListener {
public void onZoomChange(MapView view, int newZoom, int oldZoom);
}
public interface OnPanChangeListener {
public void onPanChange(MapView view, GeoPoint newCenter, GeoPoint oldCenter);
}
private EnhancedMapView _this;
// Set this variable to your preferred timeout
private long events_timeout = 500L;
private boolean is_touched = false;
private GeoPoint last_center_pos;
private int last_zoom;
private Timer zoom_event_delay_timer = new Timer();
private Timer pan_event_delay_timer = new Timer();
private EnhancedMapView.OnZoomChangeListener zoom_change_listener;
private EnhancedMapView.OnPanChangeListener pan_change_listener;
public EnhancedMapView(Context context, String apiKey) {
super(context, apiKey);
_this = this;
last_center_pos = this.getMapCenter();
last_zoom = this.getZoomLevel();
}
public EnhancedMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EnhancedMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setOnZoomChangeListener(EnhancedMapView.OnZoomChangeListener l) {
zoom_change_listener = l;
}
public void setOnPanChangeListener(EnhancedMapView.OnPanChangeListener l) {
pan_change_listener = l;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == 1) {
is_touched = false;
} else {
is_touched = true;
}
return super.onTouchEvent(ev);
}
#Override
public void computeScroll() {
super.computeScroll();
if (getZoomLevel() != last_zoom) {
// if computeScroll called before timer counts down we should drop it and start it over again
zoom_event_delay_timer.cancel();
zoom_event_delay_timer = new Timer();
zoom_event_delay_timer.schedule(new TimerTask() {
#Override
public void run() {
zoom_change_listener.onZoomChange(_this, getZoomLevel(), last_zoom);
last_zoom = getZoomLevel();
}
}, events_timeout);
}
// Send event only when map's center has changed and user stopped touching the screen
if (!last_center_pos.equals(getMapCenter()) || !is_touched) {
pan_event_delay_timer.cancel();
pan_event_delay_timer = new Timer();
pan_event_delay_timer.schedule(new TimerTask() {
#Override
public void run() {
pan_change_listener.onPanChange(_this, getMapCenter(), last_center_pos);
last_center_pos = getMapCenter();
}
}, events_timeout);
}
}
}
Main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:apiKey="0TqoE_fzA3Pv-m8188NwttzIKcYBzhNJsYRJkKQ"
android:id="#+id/mapview"
android:clickable="true"
android:enabled="true"
/>
</LinearLayout>
<RelativeLayout
android:layout_width="35dip"
android:layout_marginTop="300dip"
android:layout_marginLeft="270dip"
android:layout_height="70dip"
android:background="#AA000000">
<TextView android:layout_height="20dip"
android:gravity="center" android:textStyle="bold"
android:textColor="#000000" android:textSize="20dip"
android:id="#+id/zoomButton1"
android:layout_width="100dip"
android:text="+" />
<TextView android:layout_height="20dip"
android:layout_below="#id/zoomButton1"
android:textStyle="bold"
android:textColor="#000000"
android:gravity="center"
android:textSize="20dip"
android:id="#+id/zoomButton2"
android:layout_width="80dip"
android:text="-" />
</RelativeLayout>
</RelativeLayout>
MyMapActivity
public class MyMapActivity extends MapActivity {
/** Called when the activity is first created. */
MapView mapView;
ZoomControls zoomControls;
MapController mapController;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mapView = (MapView)findViewById(R.id.mapview);
System.out.println("oncreate");
mapView.setStreetView(true);
mapView.invalidate();
mapController = mapView.getController();
// zoom contrlos
int y=10;
int x=10;
/*MapView.LayoutParams lp;
lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT,
MapView.LayoutParams.WRAP_CONTENT,
x, y,
MapView.LayoutParams.TOP_LEFT);
View zoomControls = mapView.getZoomControls();
mapView.addView(zoomControls, lp);
mapView.displayZoomControls(true);*/
/*zoomControls = (ZoomControls) findViewById(R.id.zoomControls1);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mapController.zoomIn();
}
});
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mapController.zoomOut();
}
});*/
TextView zButton =(TextView)findViewById(R.id.zoomButton1);
TextView zButton1 =(TextView)findViewById(R.id.zoomButton2);
zButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
mapController.zoomIn();
Toast.makeText(myMapActivity.this, "Zoomin", Toast.LENGTH_SHORT).show();
}
});
zButton1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
mapController.zoomOut();
Toast.makeText(myMapActivity.this, "ZoomOUT", Toast.LENGTH_SHORT).show();
}
});
Double lat = 38.899049*1E6;
Double lng = -77.017593*1E6;
GeoPoint point = new GeoPoint(lat.intValue(),lng.intValue());
System.out.println("Points"+point);
mapController.setCenter(point);
mapController.setZoom(15);
mapController.animateTo(point);
// http://maps.google.com/maps?ie=UTF8&hl=en&layer=t&ll=38.899049,-77.017593&spn=0.268261,0.6427&z=11
}
#Override
protected boolean isRouteDisplayed() {
return false;
}
}