I'm working on a little Android Game with libGDX.
The structure is as follows:
Main > Screen:Splashscreen > Screen:MainMenu > Screen: Settings or GameScreen
I have a Preference for the state of music(if running "music" is true).
If the music is true the music starts in the MainMenu.
Now the user should be abled to turn the music on and off in the Settings Screen.
To manage the music I made a class called MusicPlayer.
The Code:
MainMenu:
public class StartScreen implements Screen {
MusicPlayer musicPl;
Preferences scorepref;
boolean music;
(...)
#Override
public void show() {
(...)
scorepref = Gdx.app.getPreferences("Highscore");
musicPl = new MusicPlayer();
music = scorepref.getBoolean("music");
if(music){
musicPl.play();
}
}
#Override
public void dispose() {
musicPl.dispose();
}
}
Settings:
public class SettingScreen implements Screen {
Preferences scorepref;
//Setting Values
boolean tut = false;
boolean music = false;
boolean sounds = false;
int theme = 0;
//
MusicPlayer musicPl;
int touchX = 0;
int touchY = 0;
boolean touchD = false;
boolean touchU = false;
#Override
public void show() {
(handling Input and setting touchX,touchY,touchD and touchU)
(...)
musicPl = new MusicPlayer();
scorepref = Gdx.app.getPreferences("Highscore");
}
#Override
public void render(float delta) {
tut = scorepref.getBoolean("tut");
music = scorepref.getBoolean("music");
sounds = scorepref.getBoolean("sounds");
(...)
//if Touched
if(touchD == true && touchU == true){
touchD = false;
touchU = false;
//wo?
Vector3 posn = new Vector3(Gdx.input.getX(),Gdx.input.getY(), 0);
camera.unproject(posn);
if(){
(...)
} else if (posn.x >= -192 && posn.x <= 192 && posn.y <= 53 && posn.y >= -75) {
if(music){
music = false;
scorepref.putBoolean("music", music);
scorepref.flush();
musicPl.stop(); //Error line 206 is here
} else {
music = true;
scorepref.putBoolean("music", music);
scorepref.flush();
musicPl.play();
}
} (...)
} else if (posn.x >= -344 && posn.x <= -248 && posn.y <= 543 && posn.y >= 463) {
((Game) Gdx.app.getApplicationListener()).setScreen(new StartScreen());
}
//var speichern
scorepref.putBoolean("tut", tut);
scorepref.putBoolean("music", music);
scorepref.putBoolean("sounds", sounds);
scorepref.flush();
}
#Override
public void dispose() {
musicPl.dispose();
}
}
MusicPlayer:
public class MusicPlayer{
Music menuMusic;
boolean isrunning;
Preferences scorepref;
public void play (){
scorepref = Gdx.app.getPreferences("Highscore");
if(scorepref.getBoolean("running") == true){
} else {
menuMusic = Gdx.audio.newMusic(Gdx.files.internal("Theme.mp3"));
menuMusic.setLooping(true);
scorepref.putBoolean("running", true);
scorepref.flush();
menuMusic.play();
}
}
public void stop(){
scorepref = Gdx.app.getPreferences("Highscore");
scorepref.putBoolean("running", false);
scorepref.flush();
menuMusic.stop(); //Error line 33 is here
}
public void dispose(){
menuMusic.dispose();
}
}
My Problem:
When I am in the Settings Screen the tunring on and off is working fine.
When I turn the music on and go back to the MainScreen the music is still playing. So far so good.
But when I return to the SettingsScreen with running music and I want to turn it off the App crashes.
I think the crash is caused by the stop method, because the MusicPlayer doesn't know what to stop. But how can I tell him or how can i solve this problem with a different technique?
Thanks for helping.
P.S.
Here is the error I get, when I run the App on the desktop:
Exception in thread "LWJGL Application" java.lang.NullPointerException
at de.hatgames.canone.MusicPlayer.stop(MusicPlayer.java:33)
at de.hatgames.canone.SettingScreen.render(SettingScreen.java:206)
at com.badlogic.gdx.Game.render(Game.java:46)
at de.hatgames.canone.CanoneMain.render(CanoneMain.java:26)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:215)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)
CanoneMain.java:26 is just this:
super.render();
when you return to SettingScreen you create a new MusicPlayer instance each time.
musicPl = new MusicPlayer();
The Music member is only instanciated when play() is called:
menuMusic = Gdx.audio.newMusic(Gdx.files.internal("Theme.mp3"));
That is why menuMusic is null when you call stop() before calling play()
There are several solutions. You can make the MusicPlayer static and global accesable.
Or you can make sure MusicPlayer is only instanitated once like this:
if(musicPl == null) {
musicPl = new MusicPlayer();
}
But this will only work when you make sure that SettingsScreen is only instantiated once.
Good topics to read are probably singleton and factory pattern since this case will often occur when programming games.
Additionally using a Singleton for the music player as part of it being a global object will be helpful.
Related
I am creating a Jewel like game for android, however my problem is that the game is very laggy, I tried creating an AsyncTask to reduce the pressure on the UIthread, it helped but it's still too laggy to play it, any tips on how I should change my code? I also tried creating Two different arrayList of ImageView to reduce time in the for loops : liste_rubies and liste_rubies_falling. Basically they have to stop falling once they touch another ruby and stack unto one another, it works but it's way too laggy past a certain number of rubies on the board. Thanks for your answers
public boolean TestColisions(ImageView Ruby){
boolean test = false;
Rect Rect_tempo = new Rect();
Ruby.getHitRect(Rect_tempo);
for(int y=0;y<liste_Rubies.size();y++){
if(liste_Rubies.get(y).getY() != Ruby.getY()){
Rect Rect_tempo2 = new Rect();
liste_Rubies.get(y).getHitRect(Rect_tempo2);
Rect_tempo2.set( Rect_tempo2.left, (int) (Rect_tempo2.top), Rect_tempo2.right, Rect_tempo2.bottom);
if (Rect.intersects(Rect_tempo2, Rect_tempo)) {
test = true;
liste_Rubies.remove(y);
if(Ruby.getY()<H10)
MessageVictoire();
}
}
}
return test;
}
//-----------------------------------------------------------------------------------------------------------------MoveDown V2 --
private class MoveDownV2 extends AsyncTask<String, Void, String> {
ArrayList<ImageView>liste_Rubies, liste_Rubies_falling;
public MoveDownV2(ArrayList<ImageView>liste_Rubies, ArrayList<ImageView>liste_Rubies_falling) {
this.liste_Rubies = liste_Rubies;
this.liste_Rubies_falling = liste_Rubies_falling;
String launch = doInBackground();
}
#Override
protected String doInBackground(String... params) {
MoveDown=new Timer();
MoveDown.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
for(int x = 0; x < liste_Rubies_falling.size(); x++){
float tempo = liste_Rubies_falling.get(x).getY();
if(liste_Rubies_falling.get(x).getY() < H10*7 && !TestColisions(liste_Rubies_falling.get(x))){
liste_Rubies_falling.get(x).setY(tempo+50);}
else{liste_Rubies.add(liste_Rubies_falling.get(x));
liste_Rubies_falling.remove(liste_Rubies_falling.get(x));}
}
LeCounter++;
if(LeCounter%17==0){
RubyPopUp();
}
}
}, 82, 82);
return null;
}
}
public void RubyPopUp() {
SpartanRuby.this.runOnUiThread(new Runnable() {
#Override
public void run() {
ImageView Ruby = new ImageView(context);
Ruby.setX((float) (L10*5));
Ruby.setY(0);
Ruby.setScaleType(ImageView.ScaleType.FIT_XY);
Ruby.setImageResource(R.drawable.rubies_rouge);
RubyTest = Ruby;
liste_Rubies_falling.add(Ruby);
liste_Rubies_total.add(Ruby);
((ViewGroup) layout_spartanruby).addView(Ruby);
SetSize(Ruby);
}
});
}
I am developing an application that finds files in different folder locations and displays all the files in a table view. If a user clicks on a row then that file gets displayed on the right side of the program. Currently, it is kind of working for txt, mp3 files and mp4 files.
DESCRIPTION:
I have created two services, FileSearchService and FilePreviewService. The FileSearchService searches for files and add those in a static observableList. The FilePreviewService gets the selected row, checks if the file is of type txt or mp3 or other and uses a FileProcessor abstract class to process the selected item. If the item selected is of type txt, the child class of FileProcessor, TextProcessor comes into play and returns an AnchorPane with TextArea as a child attached to that AnchorPane. The textarea sets the text obtained from the selected item(row). Finally the anchorPane is returned to the main controller. The Main Controller then displays the item.
Problem:
If i click on a row that holds a mp4 (or mp3) file then the mp4 (or mp3) file plays fine and gets displayed on my anchorPane. While the mp4 (or mp3) is playing, if i click on a txt file then the data of that file gets displayed on anchorPane but the audio of mp4(or mp3) is still playing.
Below are two images which describes my problem.
I clicked on a video file, video displays at the right side of my application.
Now i click on a txt file, then the anchorPane shows textData but the video(rather audio) is still playing.
Now, if i click a mp3 file then both mp3 and mp4 audios superimpose.
What I want
I want only one item to get executed. If a mp3 file is being played and if i click on a mp4 video then the mp3 should stop and video should play.My application can handle multiple consecutive mp3 or mp4 or txt clicks. Clicking a mp4 file followed by a txt file click does not get handled.
FilePreviewService.Java
public class FilePreviewService extends Service<Void> {
FileModel model;
private FileProcesser fileProcesser;
String fileExtension = "";
public FileProcesser getFileProcesser() {
return fileProcesser;
}
public FilePreviewService(FileModel model) {
this.model = model;
this.fileExtension = reverseFileName(getFileExtension(model));
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
System.out.println("FIlePreviewService, createTask() Thread = " + Thread.currentThread().getName());
fileProcesser = FileUtility.getFileProcesserFromUtil(fileExtension);
getFileProcesser().processFile(model.getFileLocation());
return null;
}
};
}
}
AudioProcesser.Java
public final class AudioProcesser extends FileProcesser{
static AudioProcesser audioProcesser;
Media media;
MediaPlayer mediaPlayer;
public static AudioProcesser getAudioProcesser() {
if (audioProcesser == null)
audioProcesser = new AudioProcesser();
return audioProcesser;
}
#Override
public void processFile(String fileLocation) throws Exception {
switch (getAudioMediaStatus()) {
case NOT_PLAYED:
playMedia(fileLocation);
break;
case PLAYING:
/* TIP:
If mediaPlayer.stop is placed after the line
* media = new Media(new File(fileLocation).toURI().toString());
mediaPlayer = new MediaPlayer(media);
then multiple music play together when multiple different row gets selected,
one after another
*/
mediaPlayer.stop();
playMedia(fileLocation);
break;
default:
System.out.println("Audio in default case");
}
}
private void playMedia(String fileLocation) {
media = new Media(new File(fileLocation).toURI().toString());
mediaPlayer = new MediaPlayer(media);
mediaPlayer.play();
setAudioMediaStatus(PLAYING);
}
}
VideoProcesser.Java
public class VideoProcesser extends FileProcesser {
static VideoProcesser videoProcesser = null;
MediaPlayer mediaPlayer;
Media media;
MediaView mediaView;
#Override
public void processFile(String fileLocation) throws Exception {
switch (getVideoMediaStatus()) {
case NOT_PLAYED:
playVideo(fileLocation);
break;
case PLAYING:
mediaPlayer.stop();
playVideo(fileLocation);
break;
default:
System.out.println("Audio in default case");
}
// pane.getChildren().add();
}
#Override
public AnchorPane getPane(){
return pane;
}
private void playVideo(String fileLocation) {
System.out.println("VideoProcesser Thread = " + Thread.currentThread().getName());
media = new Media(new File(fileLocation).toURI().toString());
mediaPlayer = new MediaPlayer(media);
// mediaPlayer.setAutoPlay(true);
if(mediaView == null) {
mediaView = new MediaView(mediaPlayer);
}
mediaView.setMediaPlayer(mediaPlayer);
mediaView.setPreserveRatio(true);
mediaPlayer.play();
mediaPlayer.setOnError(() -> System.out.println("Current error: "+mediaPlayer.getError()));
setVideoMediaStatus(PLAYING);
pane.getChildren().add(mediaView);
}
public static FileProcesser getVideoProcesser() {
if(videoProcesser == null)
videoProcesser = new VideoProcesser();
return videoProcesser;
}
}
TextProcesser.Java
public class TextProcesser extends FileProcesser {
static TextProcesser textProcesser = null;
public static FileProcesser getTextProcesser() {
if(textProcesser == null)
textProcesser = new TextProcesser();
return textProcesser;
}
#Override
public void processFile(String fileLocation) throws IOException {
InputStream inputStream = new FileInputStream(fileLocation);
Scanner sc = null;
//text file: 25.1_-_Marvel_Graph.txt, size 1.5MB
System.out.println("Data reading started = " + new Date());
if (inputStream != null) {
StringBuilder txtData = new StringBuilder("");
try {
sc = new Scanner(inputStream, "UTF-8");
while (sc.hasNextLine()) {
txtData.append(sc.nextLine());
}
// note that Scanner suppresses exceptions
if (sc.ioException() != null) {
throw sc.ioException();
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (sc != null) {
sc.close();
}
}
dataToDisplay = txtData.toString();
}
System.out.println("Data reading finished = " + new Date());
}
#Override
public AnchorPane getPane(){
TextArea txtArea = new TextArea();
txtArea.setEditable(false);
txtArea.setText((String) dataToDisplay);
txtArea.setPrefHeight(778);
txtArea.setWrapText(true);
pane.getChildren().add(txtArea);
return pane;
}
}
MainController.Java
public void initialize(URL location, ResourceBundle resources) {
init();
initFilePreviewExecutors();
for (int i = 0; i < locationsToSearch.length; i++) {
fileModel = new FileModel(locationsToSearch[i]);
FileSearchService fileSearchService = new FileSearchService(fileModel);
fileSearchService.setExecutor(fileSearchExecutor);
fileSearchService.setOnSucceeded(e -> {
fileSearchService.setFileSearchCompleted(true);
searchFinished = true;
});
fileSearchService.start();
CacheFileService cfs = new CacheFileService(locationsToSearch[i]);
}
try {
stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
nameCol.setCellValueFactory(new PropertyValueFactory<>("fileName"));
sizeCol.setCellValueFactory(new PropertyValueFactory<>("fileSize"));
locationCol.setCellValueFactory(new PropertyValueFactory<>("fileLocation"));
recordsTableView.setItems(fileModel.getData());
recordsTableView.setContextMenu(new ContextMenu(showRecordInfo));
recordsTableView.setRowFactory(tv -> {
TableRow<FileModel> row = new TableRow<>();
row.setOnMouseClicked(event -> {
if(!row.isEmpty() && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
FileModel fileModel = row.getItem();
filePreviewService = new FilePreviewService(fileModel);
filePreviewService.setExecutor(filePreviewExecutor);
filePreviewService.setOnSucceeded(event1 -> {
recordPreviewPane = filePreviewService.getFileProcesser().getPane();
if(recordPreviewPane == null) {
System.out.println("RECORDPREVIEWPANE IS NULL");
}
previewPane.setContent(recordPreviewPane);
});
filePreviewService.restart();
} else if(!row.isEmpty() && event.getButton() == MouseButton.SECONDARY) {
FileModel fileModel = row.getItem();
showRecordInfo.setOnAction( e -> {
Scene scene = defaultViewFactory.getRecordInfoScene(fileModel);
Stage stage = new Stage();
stage.setScene(scene);
stage.show();
});
}
});
return row;});
}
I hope i made my question clear. In case if you want to see other java file then below is a link to the project.
Github :
Edit: The way i am solving my problem is very inefficient. I added some more code in FilePreviewService. Ofcourse the below code needs to satisfy some more condition. It's just a very inefficient approach.
getFileProcesser().processFile(model.getFileLocation());
if(i > 0) {
oldFileProcesser = fileProcesserStack.pop();
}
if(fileProcesser != null) {
fileProcesserStack.push(fileProcesser);
}
//audio and video consecutive play fixing
if(i > 0 && (oldFileProcesser instanceof AudioProcesser || oldFileProcesser instanceof VideoProcesser)
&& !(fileProcesser instanceof AudioProcesser || fileProcesser instanceof VideoProcesser)) {
if(oldFileProcesser instanceof AudioProcesser) {
AudioMediaStatus.setAudioMediaStatus(AudioMediaStatus.JUST_STOP); }
if(oldFileProcesser instanceof VideoProcesser) {
VideoMediaStatus.setVideoMediaStatus(VideoMediaStatus.JUST_STOP);}
oldFileProcesser.processFile("");
}
i++;
If I understood correctly, you are trying to stop a .mp3 when a .mp4 is played, or vice-versa.
First the why of your problem. You are calling AudioProcesser when you start an .mp3, cool, great. So you start a new MediaPlayer through that class.
BUT, you do the same thing using the VideoProcesser class. So now you have two MediaPlayers running at the same time and thats why the audio overlays.
The solution, have an instance variable and expose some new methods for other classes to call, namely a stopMediaPlayer() method.
Example below, with as little changes to your code as possible:
public final class AudioProcesser extends FileProcesser{
//Always have one instance of the variable.
static AudioProcesser audioProcesser = new AudioProcesser();
private Media media;
private MediaPlayer mediaPlayer;
public static AudioProcesser getAudioProcesser() {
return audioProcesser;
}
//Added an exposure to the underlying audioMediaStatus
public void setAudioMediaStatus(AudioMediaStatus status){
AudioMediaStatus.setAudioMediaStatus(status);
}
//Another exposure to the underlying audioMediaStatus
public AudioMediaStatus getAudioMediaStatus(){
return AudioMediaStatus.getAudioMediaStatus();
}
//Used only for this class
private void setMediaPlayer(String fileLocation){
Media media = new Media(new File(fileLocation).toURI().toString());
mediaPlayer = new MediaPlayer(media);
}
//Exposed a stop method.
public void stopMedia(){
if(mediaPlayer != null) {
//Change this accordingly.
setAudioMediaStatus(AudioMediaStatus.NOT_PLAYED);
mediaPlayer.stop();
}
}
#Override
public void processFile(String fileLocation) throws Exception {
switch (getAudioMediaStatus()) {
case NOT_PLAYED:
playMedia(fileLocation);
break;
case PLAYING:
/* TIP:
If mediaPlayer.stop is placed after the line
* media = new Media(new File(fileLocation).toURI().toString());
mediaPlayer = new MediaPlayer(media);
then multiple music play together when multiple different row gets selected,
one after another
*/
mediaPlayer.stop();
playMedia(fileLocation);
break;
default:
System.out.println("Audio in default case");
}
}
private void playMedia(String fileLocation) {
VideoProcesser.getVideoProcesser().stopMedia();
//Moved the previous statements to its own method.
setMediaPlayer(fileLocation);
mediaPlayer.play();
setAudioMediaStatus(AudioMediaStatus.PLAYING);
}
}
As you can see I've added a few items, namely some methods and I got rid of your static imports. The important things to note are the stopMedia() method and the first line in the playMedia() method.
The stopMedia method does exactly what it's name says. In the playMedia method, you can see that I've added VideoProcesser.getVideoProcesser().stopMedia() as the first line.
The Audio/VideoProcesser classes are almost identical so the added methods and minor tweaks will transfer over, I have tested this and it does work.
Since you also want to stop audio/video when you select a txt file, you will need to add the same call to video and audios stopMedia method in that class as well.
I have a problem with my code. The point is that I have 16 images and when the load initially my Scrolling acitivity no lag issues, but after several setimageas becomes very slow. I hope you can help me. ps: the 16 images are loaded on a fragment the problem of lag occurs when I move from one fragment to 'another scroll through.
I suggest you to use Picaso library to load images as its very fast and efficient
its as simple as
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
for more information I suggest you to go to Picaso documentation
You can use Android Universal Image Loader library to get rid off this problem. Also try to use List View or Grid View.
This is the code, then if the load images via xml src nothing lag, just use setImageDrawable lag comes out..
for(int h=0;h<16;h++){
final int finalH = h;
button[h].setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
for (int i = 0; i < 8; i++) {
if (tresy[i] == ((int) button[finalH].getTag())) {
return true;
}
}
if ((test)&&(tempo)) {
Log.d("id", String.valueOf(button[finalH].getTag()));
button[finalH].setImageDrawable(carte[((int) button[finalH].getTag())]);
//set image resource tag!
logTaG[0] = (int) button[finalH].getTag();
logBtn = finalH;
test = false;
} else if(tempo) {
test = true;
button[finalH].setImageDrawable(carte[((int) button[finalH].getTag())]);
//set image resource tag!
logTaG[1] = (int) button[finalH].getTag();
if ((logTaG[0] == logTaG[1] && (logBtn != finalH))) {
tresy[j] = logTaG[0];
Intent intent = new Intent(v.getContext(), Browser_descrizione.class);
intent.putExtra("Tag", tresy[j]);
startActivity(intent);
j++;
Log.d("bravo", "hai vinto");
if (j == 7) {
Log.d("Bravo", "hai completato il gioco");
//Far comparire warning con scritto ricomincia o esci
attempt++;
SM.sendData(attempt);
}
} else {
Log.d("Riprova", "sarai piĆ¹ fortunato");
final Handler handler = new Handler();
tempo = false;
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Do something after 5s = 5000ms
button[finalH].setImageResource(R.drawable.back_card);
button[logBtn].setImageResource(R.drawable.back_card);
tempo = true;
}
}, 500);
attempt++;
SM.sendData(attempt);
}
}
return true;
}
return false;
}
});
}
I am creating this app.
code of my onsingletapup.cs file
class SingleTapUp : Android.Views.GestureDetector.SimpleOnGestureListener
{
public override bool OnSingleTapUp(MotionEvent e) {
// Toast.MakeText(this,, ToastLength.Long).Show();
return true;
}
}
here is my mainactivity.cs
public class MainActivity : ActionBarActivity, View.IOnTouchListener
{
GestureDetector gestureDetector;
float _viewX;
float _viewY;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
PopulateListView(someList,anynumbertoshow)
}
private void QueueListView(Queue<FeedItem> feedItemsList, int count)
{
RelativeLayout rl = this.FindViewById<RelativeLayout>(Resource.Id.newsContainer);
if(rl.Visibility == ViewStates.Gone)
{
this.FindViewById<LinearLayout>(Resource.Id.newsList).Visibility = ViewStates.Gone;
rl.Visibility = ViewStates.Visible;
}
Paint layerPaint = new Paint();
layerPaint.AntiAlias = true;
layerPaint.FilterBitmap = true;
layerPaint.Dither = true;
// RelativeLayout parentLayout = (RelativeLayout)LayoutInflater.from(context).inflate(R.layout.myLayout, null);
rl.SetLayerType(LayerType.Hardware, layerPaint);
rl.SetClipChildren(false);
Random rnd = new Random();
//this.progressDialog.Dismiss();
for (int i = 0; i < count; i++)
{
FeedItem rss = theNewsQueue.Dequeue();
var viewObj = this.LayoutInflater.Inflate(Resource.Layout.NewTile, rl, false);
TextView tv = viewObj.FindViewById<TextView>(Resource.Id.textView2);
TextView link = viewObj.FindViewById<TextView>(Resource.Id.link);
link.Text = rss.Link;
tv.Text = rss.Title;
viewObj.Rotation = angle;
angle = rnd.Next(-3, 3);
viewObj.SetLayerType(LayerType.Hardware, layerPaint);
rl.AddView(viewObj);
gestureDetector = new GestureDetector(this, new SingleTapUp());
viewObj.SetOnTouchListener(this); //Here I am adding my listener to all my control
rl.SetLayerType(LayerType.Hardware, layerPaint);
theNewsQueue.Enqueue(rss);
rss = null;
}
}
public bool OnTouch(View v, MotionEvent e)
{
if (gestureDetector.OnTouchEvent(e))
{
//will detect a click and open in browser
return true;
}
else
{
int initialTouchX = 0, initialTouchY = 0;
int newx = 0;
var x = v.Left;
switch (e.Action)
{
case MotionEventActions.Down:
{
_viewX = e.GetX();
_viewY = e.GetY();
initialTouchX = (int)e.RawX;
initialTouchY = (int)e.RawY;
break;
}
case MotionEventActions.Up:
{
int lastX = (int)e.GetX();
int lastY = (int)e.GetY();
if ((x - newx) > 40)
{
//right Swipe
sendViewToBack(v);
}
else if ((newx - x > 40))
{
//left Swipe
sendViewToBack(v);
}
break;
}
case MotionEventActions.Move:
{
// click = false;
var left = (int)(e.RawX - _viewX);
newx = left;
var right = (int)(left + v.Width);
var top = (int)(e.RawY - _viewY);
var bottom = (int)(top + v.Height);
v.Layout(left, top, right, bottom);
break;
}
}
}
// _gestureDetector.OnTouchEvent(e);
return true;
}
public void sendViewToBack(View child)
{
var parent = (ViewGroup)child.Parent;
if (null != parent)
{
parent.RemoveView(child);
if(viewType==0)
parent.AddView(QueueListView (theNewsQueue), 0);
else
parent.AddView(QueueListView (theNewsQueue), parent.ChildCount-1);
}
}
}
Now my question is on some devices my current code is giving some abnormal behavior. Like even if I perform OnSingleTapUp() which is supposed to perform click operation but it is performing a move operation. My question is what is wrong with my code so that it is not working correctly. Any help will be greatly appreciated. Thank you.
The onTouch and onClick doesn't work together. In all the cases the onTouch is going to get the priority, in fact onClick in also sort of fine implementation of onTouch. If you want to have onClick sort of functionality, let go the original onClick and try to handle that in onTouch. You can take help of the GestureDetector.SimpleOnGestureListener in Xamarin. For an example to override the double tap you can do it like this
class MyDoubleTapListener : GestureDetector.SimpleOnGestureListener
{
public override bool OnDoubleTap(MotionEvent e)
{
//Your code here
return false;
}
}
and then in your activity
public class Test : Activity, View.IOnTouchListener
{
private GestureDetector _gestureDetector = null;
protected override void OnCreate (Bundle bundle)
{
_gestureDetector = new GestureDetector(new MyDoubleTapListener (this));
_editText.SetOnTouchListener(this);
}
public bool OnTouch(View v, MotionEvent e)
{
return _gestureDetector.OnTouchEvent(e);
}
}
GestureDetector also provides you other methods that you can overide to suit your need. Follow this, https://developer.xamarin.com/api/type/Android.Views.GestureDetector/
I couldn't get exactly what your are trying to do inside onTouch interface. Anyway there's some points you must know:
1) When you handle a touch event, onTouch method returns a boolean that indicates if event was consumed (true) or not (false). If you consume touch event related to click, click listener won't be triggered. So, make sure you are only consuming what is desired.
2) When you set listeners as onClick or onTouch to some view, it becomes clickable and touchable, respectively, if it wasn't. If you are setting this attributes to false in some part of your code make sure it's enabled again when you want to handle such events.
I'm an Android newbie developer and having an issue with a tiny Android app I'm developing which I suspect is related to timing of dynamic view creation. It's a little scorekeeper app and it has dynamically generated player buttons and text fields, defined in a Player class. Here's an excerpt of how I'm doing this:
public Player(String name, int score, int number, boolean enabled, RelativeLayout mainScreen, List<Button> presetButtonList, Editor editor, Context context) {
super();
this.name = name;
this.score = score;
this.number = number;
this.enabled = enabled;
this.editor = editor;
// Get the lowest child on the mainScreen
Integer count = mainScreen.getChildCount(), lowestChildId = null;
Float lowestChildBottom = null;
for (int i = 0; i < count; i++) {
View child = mainScreen.getChildAt(i);
if ((lowestChildId == null || child.getY() > lowestChildBottom) &&
!presetButtonList.contains(child)) {
lowestChildId = child.getId();
lowestChildBottom = child.getY();
}
}
playerNameText = (EditText) setTextViewDefaults(new EditText(context), name);
playerNameText.setSingleLine();
// playerNameText.setMaxWidth(mainScreen.getWidth());
// playerNameText.setEllipsize(TextUtils.TruncateAt.END); //TODO: Prevent names which are too long for screen
playerNameText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable changedText) {
setName(changedText.toString());
updateScreen();
}
public void beforeTextChanged(CharSequence changedText, int start, int count, int after) {}
public void onTextChanged(CharSequence changedText, int start, int before, int count) {}
});
RLParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (lowestChildId != null) {
RLParams.addRule(RelativeLayout.BELOW, lowestChildId);
} else {
RLParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
RLParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
RLParams.setMargins(0, 10, 0, 10);
mainScreen.addView(playerNameText, RLParams);
the class further defines buttons and etc in a similar fashion, aligning tops with the name text view. I call this when a user clicks a button to add a player, and it works fine, displaying each player below the first on down the screen. The problem comes in when I'm loading a bunch of saved players at the start. Here's where i load the players:
public void loadData() {
RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
playerCount = savedData.getInt("playerCount", playerCount);
Player player;
String name;
playerList.clear();
for (int i=1; i<=playerCount; i++) {
name = savedData.getString("name" + i, null);
if (name != null) {
Log.v("name", name);
player = new Player(name, savedData.getInt("score" + i, 0), i, savedData.getBoolean("enabled" + i, false), mainScreen, presetButtonList, editor, this);
playerList.add(player);
player.updateScreen();
}
}
updateScreen();
}
Finally, I call the loadData() method when the app starts, here:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
savedData = this.getPreferences(Context.MODE_PRIVATE);
editor = savedData.edit();
presetButtonList.add((Button)findViewById(R.id.newGame));
presetButtonList.add((Button)findViewById(R.id.addPlayer));
presetButtonList.add((Button)findViewById(R.id.removePlayer));
loadData();
}
The result? When there are more than two players to load, all the players get loaded to the same spot, on top of Player 2.
I suspect somehow that the players are all being generated at the same time and thus all believing that the lowest player view is Player 1, and not checking each other. I've tried triggering the load later than onCreate() and it still happens. I also tried adding a 3 second sleep within the for loop of loadData() after each player loads to see if that helped, but no luck.
Am I making bad assumptions? What am I doing wrong and how might I fix it?
I suspect what is happening here is that you are attempting to position views that don't have IDs (it looks like you don't set them anywhere in your code). Dynamically created views don't automatically have IDs, so a call to getId() will return NO_ID (See documentation). If you want to use an ID you will have to set it yourself using View.setId().
It was a timing issue after all. I needed to wait for the main screen to load before I could add views to it. Unfortunately, it would never load until all activity on the UI thread was finished.
Thus, I needed threading. I ended up putting loadData() in a thread, like this:
new Thread(new loadDataAsync(this)).start();
class loadDataAsync implements Runnable {
MainActivity mainActivity;
public loadDataAsync(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Override
public void run() {
try {
Thread.sleep(700);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
loadData(mainActivity);
}
}
Then I created the players back on the UI thread after the initial sleep to give time for the UI to load.
runOnUiThread(new Runnable() {
Player player;
RelativeLayout mainScreen = (RelativeLayout)findViewById(R.id.relativeLayout);
#Override
public void run() {
player = new Player(savedData.getString("name" + playerNum, null), savedData.getInt("score" + playerNum, 0), playerNum, savedData.getBoolean("enabled" + playerNum, false), mainScreen, presetButtonList, editor, mainActivity);
playerList.add(player);
player.updateScreen();
mainScreen.invalidate();
}
});
Not the most elegant solution, but it works for now.