I already spent hours searching and trying, but i cant get on the right path.
First my Code:
public class MyThreadLoc extends Thread{
Context context;
final ViewGroup view;
MyThreadLoc(Context context){
super();
this.context = context;
view = (ViewGroup) findViewById(R.id.mLayout);
}
int height;
#Override public void run() {
Looper.prepare();
int i = 0;
int y = 10;
while(running && i < 20){
i++;
if(y >= 200){
runOnUiThread(new Runnable() {
public void run() {
view.removeAllViews();
view.setBackgroundColor(Color.BLACK);
}
});
y=10;
}
try {
Thread.sleep( 500 );
Log.d("mystuff", "Sleep");
} catch ( InterruptedException e ) { e.printStackTrace(); }
final ViewGroup.LayoutParams lParam = new ViewGroup.LayoutParams(500,30); //MATCH_PARENT, 30);
while(y < 200 && running){
final float yb = y;
runOnUiThread(new Runnable() {
TextView tview;
public void run() {
Log.d("mystuff", "innerrun");
synchronized (view){
tview = new TextView(context);
tview.setText("Test");
tview.setY(yb);
view.addView(tview, lParam);
height = tview.getHeight();
view.updateViewLayout(tview, new RelativeLayout.LayoutParams(500,30));
Log.d("mystuff", Integer.toString(view.getChildCount()));
try {
Thread.sleep( 50 );
Log.d("mystuff", "Sleep");
} catch ( InterruptedException e ) { e.printStackTrace(); }
}
}
});
y = y + 100; //height +5;
}
The View doesn't appear first, but in the end 2 textviews with "Test" appear.
How can I update it, so that I see every single textview appearing?
Why didnt the somewhat-error that helped me to get this fixed, appear earlier so i didt need to ask this Question?
The error was: "Skipped 300 frames! The application may be doing too much work on its main thread"
Which brought me to: The application may be doing too much work on its main thread
So i removed most of the stuff from RunOnUiThread and now it works. Only 2 Textviews appeared because i had y < 200 set to low for more textviews to appear.
Related
I have done a game in Android Studio with the goal of avoiding falling objects.
Generally the app is running good, but for some reason when i get to the game over screen and press an edittext to add highscore the game experience a lot of stutter and lag (with the keyboard und keypresses).
I have already called finish() (which can be seen in "FishView") on my main activity so I don't understand how it can be so slow on the game over screen, as it shouldn't have to worry about anything but the game over screen once it's there and the game over screen is very simple.
I'm having a hard time locating where the problem comes from, hence why I'm asking for help here.
Here is some code that I hope is sufficent for locating the problem:
MainActivity (deals with animation, level increase, spawn objects and interaction between objects and rules)
public class MainActivity extends AppCompatActivity implements GarbageListener {
//global variable of FishView
private FishView gameView;
//handle animation task
private final Handler handler = new Handler();
//global variable of screen
private RelativeLayout screen;
//time before level update
private int levelChangeTime = 3; //initialize small garbage in X seconds
private int spawnBossGarbage = 25; //initialize big garbage in X seconds
private int spawnHeart = 40; //initialize heart in X seconds
//pause variables
private Button pauseButton;
private boolean pauseFlag = false;
//left and right button
private Button leftButton;
private Button rightButton;
//List of small garbage on screen
private final List<SmallGarbage> smallGarbages = new ArrayList<>();
//List of big garbage on screen
private List<BigGarbage> bigGarbages = new ArrayList<>();
//List of heart on screen
private List<LifePoint> lifePoints = new ArrayList<>();
//create timer for animation and level increase
private Timer mainTimer;
//create timer fro holding left or right
private Timer movingLeft;
private Timer movingRight;
private final boolean buttonIsPressed = false; //so players can't hold both buttons down
private final int holdMovementPeriod = 9;
//keep track of song
public static Intent themeSong;
//keep track of how far we are in the song, serviceStop() deletes everything in service ThemeSong so variable must be saved elsewhere
public static int lengthOfSong = 0;
public static boolean backButtonPressed = false; //check if backButton was pressed in service ThemeSong oonDestroy() since that's the last thing that is run
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
themeSong=new Intent(this, ThemeSong.class);
startService(themeSong); //OR stopService(svc);
leftButton = findViewById(R.id.leftArrow);
rightButton = findViewById(R.id.rightArrow);
screen = findViewById(R.id.gameScreen);
gameView = new FishView(this);
screen.addView(gameView);
pauseButton = findViewById(R.id.pauseButton);
mainTimer = new Timer();
createNewAnimationTask();
createNewLevelTask();
//create listeners fo holding left or right button
findViewById(R.id.leftArrow).setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
holdLeft();
rightButton.setEnabled(false);}
if (event.getAction() == MotionEvent.ACTION_UP) {
rightButton.setEnabled(true);
if (movingLeft!=null){
movingLeft.cancel();
}}
return false;}
});
findViewById(R.id.rightArrow).setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
holdRight();
leftButton.setEnabled(false);}
if (event.getAction() == MotionEvent.ACTION_UP) {
leftButton.setEnabled(true);
if (movingRight!=null){
movingRight.cancel();}}
return false;}
});
}
public void moveLeft(#SuppressWarnings("unused") View v){
if (buttonIsPressed){return;}
gameView.setLeftPressed(true);
gameView.leftFishAnimation();//before running the animations we first set which fish animations to run (left or right)
gameView.invalidate();
}
public void moveRight(#SuppressWarnings("unused") View view) {
if (buttonIsPressed){return;}
gameView.setRightPressed(true);
gameView.rightFishAnimation();
gameView.invalidate();
}
public void pauseGame(View v){
String resume = "Resume";
String pause = "Pause";
if (!pauseFlag){
stopService(themeSong); //turn of music
pauseFlag = true;
pauseButton.setText(resume);
pauseButton.setBackgroundResource(R.drawable.roundbuttonred);
//disable animation and level tasks
mainTimer.cancel();
//disable all falling garbage on screen
for (SmallGarbage smallGarbage : smallGarbages) {smallGarbage.disableGarbageTimer();}
for (BigGarbage bigGarbage : bigGarbages) {bigGarbage.disableGarbageTimer();}
for (LifePoint lifePoint : lifePoints) {lifePoint.disableGarbageTimer();}
//disable buttons
leftButton.setEnabled(false);
rightButton.setEnabled(false);
}
else{
startService(themeSong); //start music
pauseFlag=false;
pauseButton.setText(pause);
leftButton.setEnabled(true);
rightButton.setEnabled(true);
pauseButton.setBackgroundResource(R.drawable.roundbuttonblue);
//resume falling garbage
for (SmallGarbage smallGarbage : smallGarbages) {smallGarbage.startFallingGarbage();}
for (BigGarbage bigGarbage : bigGarbages) {bigGarbage.startFallingGarbage();}
for (LifePoint lifePoint : lifePoints) {lifePoint.startFallingGarbage();}
//resume animation and level increase
mainTimer = new Timer();
createNewAnimationTask();
createNewLevelTask();
}
}
private void createNewAnimationTask(){
TimerTask newAnimationTask = new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
//here we set the animation
int selectedFish = gameView.getSelectedFish();
selectedFish ++;
if (selectedFish==2){
selectedFish = 0;}
gameView.setSelectedFish(selectedFish);
//update screen
gameView.invalidate();
}
});
}
};
long animationPeriod = 600;
mainTimer.scheduleAtFixedRate(newAnimationTask, 0, animationPeriod);
}
private void createNewLevelTask(){
TimerTask levelCountDown = new TimerTask(){
#Override
public void run() {
levelChangeTime--;
spawnBossGarbage--;
spawnHeart--;
if (levelChangeTime==0 || spawnBossGarbage == 0 || spawnHeart == 0){
//move task that updates the UI onto the main thread
runOnUiThread(new Runnable() { //this tells the program to run this on the UI(aka main) thread, we could also call on new Thread if wanted to start new thread
#Override
public void run() {
if (levelChangeTime==0){generateNewGarbage("smallGarbage");}
if (spawnBossGarbage==0){generateNewGarbage("bigGarbage");}
if (spawnHeart==0){generateNewGarbage("lifePoint");}// when this is added we can't lose life?
}
});
}
}
};
mainTimer.scheduleAtFixedRate(levelCountDown,0,1000);
}
private void holdLeft(){
movingLeft = new Timer();
final View v = new View(this); //create view so moveLeft() can called
TimerTask holdLeftTask = new TimerTask(){
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
moveLeft(v);
}
});
}};
movingLeft.scheduleAtFixedRate(holdLeftTask,0,holdMovementPeriod);
}
private void holdRight(){
movingRight = new Timer();
final View v = new View(this);
TimerTask holdRightTask = new TimerTask(){
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
moveRight(v);
}
});
}};
movingRight.scheduleAtFixedRate(holdRightTask,0,holdMovementPeriod);
}
private void generateNewGarbage(String garbage){
switch (garbage){
case "bigGarbage":
spawnBossGarbage = 40; //time to next spawn
BigGarbage newBigGarbage = new BigGarbage(MainActivity.this);
newBigGarbage.setListener(MainActivity.this);
bigGarbages.add(newBigGarbage);
screen.addView(newBigGarbage);
break;
case "smallGarbage":
levelChangeTime = new Random().nextInt(20)+3; //set seconds between 3 and 20 at random
//this create SmallGarbage and initialize its task
SmallGarbage newGarbage = new SmallGarbage(MainActivity.this);
newGarbage.setListener(MainActivity.this); // set listener for garbage
smallGarbages.add(newGarbage);
screen.addView(newGarbage);
break;
case "lifePoint":
spawnHeart=30; //time to next spawn
//this create SmallGarbage and initialize its task
LifePoint newLifePoint = new LifePoint(MainActivity.this);
newLifePoint.setListener(MainActivity.this); // set listener for garbage
lifePoints.add(newLifePoint);
screen.addView(newLifePoint);
break;
}
}
//here starts the GarbageListener
#Override
public void handleAvoidedGarbage(String avoidedGarbage) {
gameView.avoidedGarbage(avoidedGarbage);
}
#Override
public boolean handleHitPlayer(int x, int y, String garbageType) {
return gameView.hitWasteChecker(x,y, garbageType);
}
#Override
public void handleLoseLife() {
gameView.loseLife();
}
//empty lives on screen, once they have landed or hit player
#Override
public void emptyLifePointList(){
lifePoints.clear();
lifePoints = new ArrayList<>();
}
//empty big garbage on screen, once they have landed or hit player
#Override
public void emptyBigGarbageList(){
bigGarbages.clear();
bigGarbages = new ArrayList<>();
}
//saving and setting length of played song
public static int getLengthOfSong() {
return lengthOfSong;
}
public static void setLengthOfSong(int lengthOfSong) {
MainActivity.lengthOfSong = lengthOfSong;
}
//onStop runs AFTER onBackPressed(), so lengthOfSong must be reset there
#Override
public void onBackPressed() {
super.onBackPressed();
backButtonPressed = true;
}
public static boolean isBackButtonPressed() {
return backButtonPressed;
}
public static void setBackButtonPressed(boolean backButtonPressed) {
MainActivity.backButtonPressed = backButtonPressed;
}
//this runs whenever the app is closed
#Override
protected void onStop(){
super.onStop();
//stop music
stopService(themeSong);
setLengthOfSong(0);
//pause game, this will also reset sound upon start
final View v = new View(this);
pauseFlag = false;
pauseGame(v);
}
}
FishView (deals with creating the player, rules and HANDELING the starting the game over screen)
public class FishView extends View {
private final Bitmap[] fish = new Bitmap[3];
private final Bitmap gameBackground;
private final Bitmap[] lifePoints = new Bitmap[2];
private int selectedFish;
private final Paint scorePaint = new Paint();
private int score, fishLives;
private static final int fishY = 1200;
private int fishX = 400;
private int speedX = 0;
private boolean leftPressed = false;
private boolean rightPressed = false;
public FishView(Context context) {
super(context);
//set background
gameBackground = BitmapFactory.decodeResource(getResources(),R.drawable.underwater);
//set default/start fish animations
leftFishAnimation();
//set selected fish animation to default start on 0
selectedFish = 0;
//set life points
lifePoints[1] = BitmapFactory.decodeResource(getResources(),R.drawable.lifepoint);
lifePoints[0] = BitmapFactory.decodeResource(getResources(),R.drawable.deadlife);
//set score
scorePaint.setColor(Color.WHITE);
scorePaint.setTextSize(80);
// scorePaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); //??
scorePaint.setAntiAlias(true); //(graphic improvement) this removes the staircase effect aka make smoother
scorePaint.setTypeface(Typeface.SERIF);
score = 0;
//set fish lives
fishLives = 3;
}
//in a View, the onDraw method is called whenever:
//the view is initially drawn or whenever invalidate() is called on the view
//in our case we call on the constructor which initially the View
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//should maybe be canvas.getWidth() here
int canvasWidth=getWidth();
//set game boundaries
int minFishX = 0; //should not be able to go of screen (to the left)
int maxFishX = canvasWidth-fish[0].getWidth(); //furthers you can go to the right (to the right)
//check boundaries
if (fishX < minFishX) {
fishX = minFishX;
}
if (fishX > maxFishX) {
fishX = maxFishX;
}
//set position dependent on speed
fishX += speedX;
//draw background
canvas.drawBitmap(gameBackground, 0, 0, null);
//this draws the bitmap we decoded from the image
if (leftPressed){
speedX -= 15;
}
else if (rightPressed){
speedX += 15;
}
if (speedX != 0){
while (speedX != 0){
if (leftPressed){
fishX -= 1;
speedX += 1;
canvas.drawBitmap(fish[selectedFish],fishX,fishY,null);
invalidate();
}
else if (rightPressed){
fishX += 1;
speedX -= 1;
canvas.drawBitmap(fish[selectedFish],fishX,fishY,null);
invalidate();
}
}}
else{ //if nothing happens when we stay here
canvas.drawBitmap(fish[selectedFish],fishX,fishY, null);
}
leftPressed=false;
rightPressed=false;
//draw score
canvas.drawText("Score: " + score, 20 , 90, scorePaint);
//draw life points and life point we have lost
for (int lives = 0; lives < 3 ; lives++) {
int lifeX = 650 + 140*lives;
int lifeY = 10;
if (lives < fishLives){
canvas.drawBitmap(lifePoints[1],lifeX,lifeY,null);
}
else{
canvas.drawBitmap(lifePoints[0],lifeX,lifeY,null);
}
}
}
public boolean hitWasteChecker(int x, int y, String garbageType){
switch (garbageType){
//define hit boxes
//first check is how far above, second how much underneath, third how much to the left, and fourth how much to the right
case "smallGarbage":
return fishY <= y + 80 && fishY + fish[selectedFish].getHeight() >= y + 75 && fishX <= x + 75 && x + 20 <= (fishX + fish[selectedFish].getWidth());
case "bigGarbage":
return fishY <= y + 170 && fishY + fish[selectedFish].getHeight() >= y + 75 && fishX <= x + 180 && x + 20 <= (fishX + fish[selectedFish].getWidth());
case "lifePoint":
if (fishY <= y + 25 && fishY + fish[selectedFish].getHeight() >= y + 60 && fishX <= x + 110 && x + 35 <= (fishX + fish[selectedFish].getWidth())){
if (fishLives<3){fishLives++;
return true;} //if not full life gain a life
if (fishLives==3){score+=40; //if already full life then gain 40 points
return true;}}
return false;
default:
return false;
}}
public void loseLife(){
fishLives--;
if (fishLives<=0){
//stop theme song from playing
getContext().stopService(MainActivity.themeSong);
//through these lines a new Activity can be created from a View
Intent gameOverIntent = new Intent(getContext(), GameOverActivity.class);
gameOverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); //not possible to go back from game over screen
gameOverIntent.putExtra("final score", score); // send data to game over activity
getContext().startActivity(gameOverIntent);
((MainActivity) getContext()).overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
((MainActivity) getContext()).finish(); //TIMERS is till running
}
}
public void leftFishAnimation(){
fish[0] = BitmapFactory.decodeResource(getResources(),R.drawable.leftfish1);
fish[1] = BitmapFactory.decodeResource(getResources(),R.drawable.leftfish2);
}
public void rightFishAnimation(){
fish[0] = BitmapFactory.decodeResource(getResources(),R.drawable.rightfish1);
fish[1] = BitmapFactory.decodeResource(getResources(),R.drawable.rightfish2);
}
public void setLeftPressed(boolean leftPressed) {
this.leftPressed = leftPressed;
}
public void setRightPressed(boolean rightPressed) {
this.rightPressed = rightPressed;
}
public int getSelectedFish() {
return selectedFish;
}
public void setSelectedFish(int selectedFish) {
this.selectedFish = selectedFish;
}
public void avoidedGarbage(String avoidedGarbage){
switch (avoidedGarbage){
case "smallGarbage":
score += 10;
break;
case "bigGarbage":
score += 25;
break;
}
}
}
GameOver: (display the game over screen)
public class GameOverActivity extends AppCompatActivity {
//create instance of database
private DatabaseHelper db;
private EditText usernameInput;
private int score;
private MediaPlayer gameOverSound;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_over);
score = Objects.requireNonNull(getIntent().getExtras()).getInt("final score");
usernameInput = findViewById(R.id.addUsername);
db = new DatabaseHelper(this);
//easier way of doing it
gameOverSound = MediaPlayer.create(this, R.raw.gameoversound);
gameOverSound.setVolume(0.2f,0.2f);
gameOverSound.start();
String yourFinalScore = "Your final score: " + score;
TextView finalScore = findViewById(R.id.finalScore);
finalScore.setText(yourFinalScore);
}
public void restartGame(View v){
Intent restartIntent = new Intent(this, MainActivity.class);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); //so we can't go back to game over
startActivity(restartIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); //transition between activities
finish(); //end this activity, MainActivity is already ended so can't only call on finish here to go back
}
public void backToStartMenu(View view) {
Intent startMenuIntent = new Intent(this, MenuActivity.class);
startActivity(startMenuIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish(); //end this activity
}
public void addHighscore(View view) {
String writtenUsername = usernameInput.getText().toString();
if (!writtenUsername.equals("") && score != 0){
//insert writtenUsername and score into database
boolean insertedData = db.insertData(writtenUsername, score);
if (insertedData){
Toast.makeText(this, "Highscore was added", Toast.LENGTH_SHORT).show();
Intent startMenuIntent = new Intent(this, MenuActivity.class);
startActivity(startMenuIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();}
else{
Toast.makeText(this, "Highscore couldn't be added", Toast.LENGTH_SHORT).show();
}
}
}
//this runs whenever the app is closed, mobile arrow is pressed or we switch activity
#Override
protected void onStop(){
super.onStop();
gameOverSound.stop();
gameOverSound.release(); //solve error: if run twice the app will close because we cant release it twice
}
//if pressed mobile back button go back to start menu
#Override
public void onBackPressed() {
super.onBackPressed();
View v = new View(this);
backToStartMenu(v);
}
}
What I find weird about this problem is that the MainActivity, with the player animation and all the falling view objects, runs fine. However, something as small as the game over screen is lagging. This leaves me to believe that I somehow don't quit my Activities as I should which leads to the main thread not being able to handel it. Anyways thank you for your time! :)
Update:
I checked it and it seems to be some real problem with the transition from MainActivity to GameOver. Using Android help -> find-action -> profiler,
I was able to see that the memory useage was about 110 MB while in MainActivity and as soon as I got to the game over screen it went all the way up to 400 MB. But still I'm not able to locate why it occurs.
I figured it out. The problem was the sharp background image that I was using in my GameOver screen, apparently it was too much for the game too handle. So if you have any similar problem try changing the images and backgrounds on the screen.
I want to redraw a customview multiple times within a for loop in a ClickListener. However, the view is only refreshed once on completion of the ClickListener code, not within the for loop.
Here is the relevant code snippet
vw = (TouchEventView)findViewById(R.id.TouchEventView);
clear = (Button)findViewById(R.id.button2);
clear.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
coords = TouchEventView.getFloats(TouchEventView.points);
if (coords.length < 4) {
Toast.makeText(MainActivity.this,"You must have at least one line",Toast.LENGTH_LONG).show();
}
else if ( coords.length < 6) {
SendData(length(coords[0],coords[1],coords[2],coords[3]));
SendData("s");
}
else {
for (int i = 0; i<coords.length-4; i=i+4) {
SendData(length(coords[i],coords[i+1],coords[i+2],coords[i+3]));
SendData(turn(coords[i],coords[i+1],coords[i+2],coords[i+3],coords[i+6],coords[i+7]));
}
int i = coords.length-4;
SendData(length(coords[i],coords[i+1],coords[i+2],coords[i+3]));
SendData("s");
// Now do the overlay
TouchEventView.retrace = true;
reords = new float[coords.length];
for (int j=0; j<coords.length-1; j=j+4) {
try {
Thread.sleep(1000);
}
catch (Exception e) {
}
// Log.e("pitrack","i : " + j);
TouchEventView.leg = j;
vw.postInvalidate();
}
}
}
});
and here is the onDraw method of the customview (TouchEventView)
#Override
protected void onDraw(Canvas canvas) {
if (retrace) {
canvas.drawLines(MainActivity.coords,paintRed);
canvas.drawLine(MainActivity.coords[leg],MainActivity.coords[leg+1],MainActivity.coords[leg+2],MainActivity.coords[leg+3],paintGreen);
}
else {
if (points.size() > 3) {
canvas.drawLines(getFloats(points),paintRed);
}
if (x > -1) {
canvas.drawLine(x, y, x1, y1, paintRed);
canvas.drawBitmap(finish,x1-96,y1-96,paintRed);
canvas.drawBitmap(start,startx-48,starty-48,paintRed);
}
}
}
Neither vw.invalidate() nor vw.postInvalidate() work other than at the end of the ClickListener process.
How can I force a redraw of the screen within the for loop?
The only solution I have found so far is to embed the customview.postInvalidate() as a runnable in a separate thread.
It is a shady workaround BUT you can force the view to invalidate, if you add another onclickListener off somekind. It worked for me when i came to showing a Progressbar while uploading something in the main thread (i had to do it that way). It is not pretty but it is working. Good Luck with that.
I have this code what works just fine: On this custom View, 3 Ball object is bouncing continuously:
public class AnimatedView extends android.support.v7.widget.AppCompatImageView {
private Context mContext;
private boolean running = true;
private Handler h;
private final int FRAME_RATE = 10;
ArrayList<Ball> balls = new ArrayList<>();
public AnimatedView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
h = new Handler();
balls.add(new Ball(mContext, AnimatedView.this.getWidth(), AnimatedView.this.getHeight(), false));
balls.add(new Ball(mContext, AnimatedView.this.getWidth(), AnimatedView.this.getHeight(), false));
balls.add(new Ball(mContext, AnimatedView.this.getWidth(), AnimatedView.this.getHeight(), false));
}
private Runnable r = new Runnable() {
#Override
public void run() {
if (running) {
invalidate();
}
}
};
protected void onDraw(Canvas c) {
if (running) {
for (Ball ball : balls) {
if (ball.getX() < 0 && ball.getY() < 0) {
ball.setX(this.getWidth() / 2);
ball.setY(this.getHeight() / 2);
} else {
ball.setX(ball.getX() + ball.getxVelocity());
ball.setY(ball.getY() + ball.getyVelocity());
if ((ball.getX() > this.getWidth() - ball.getBall().getWidth()) || (ball.getX() < 0)) {
ball.setxVelocity(ball.getxVelocity() * -1);
}
if ((ball.getY() > this.getHeight() - ball.getBall().getHeight()) || (ball.getY() < 0)) {
ball.setyVelocity(ball.getyVelocity() * -1);
}
}
c.drawBitmap(ball.getBall(), ball.getX(), ball.getY(), null);
}
h.postDelayed(r, FRAME_RATE);
}
}
}
My only problem is, that it needs a lot of memory and hardware resources.
On my brand new phone, it's running perfectly smoothly (using 130-140MB memory with 3 bouncing balls), but it's terrible on my older phones, where the app can only use 20-30MB.
What, and how should I refactor my code in order to be more efficient (I'm pretty sure, that this is the problem).
It seems to me, that these calculations are necessary for every ball in every new frame.
Even if I set FRAME_RATE to 33 (which means cca. 30FPS), there is no noticable difference in my older phones.
Thanks! :)
I have a method named loadBalls(). In this method, I called another method named removeOldBalls().
In removeOldBalls(), I have a runnable to detach children on the scene.
Below are 2 methods :
public static void loadBalls() {
removeOldBalls();
for (int i = 0; i < MAX_BALL; i++) {
int x = MathUtils.random(0, CAMERA_WIDTH - BALL_SIZE);
int y = BALL_SIZE;
final Ball ball = new Ball(x, y, BALL_SIZE, BALL_SIZE, GraphicsManager.trBalloons[i]);
scene.registerTouchArea(ball);
balls.add(ball);
if (!balls.get(i).hasParent()) {
scene.attachChild(balls.get(i));
}
Log.e("test", "load");
}
}
public static void removeOldBalls() {
((BaseLiveWallpaperService) LWP.context).runOnUpdateThread(new Runnable() {
public void run() {
Log.e("test", "remove");
scene.detachChildren();
}
});
if (balls != null) {
int length = balls.size();
for (int i = 0; i < length; i++) {
fsw.destroyBody(balls.get(i).body);
}
balls.clear();
Log.e("test", "clear");
}
}
What I need is all children are removed before adding new ones. But when running above source code, the children are added first, then removed.
Please tell me how to wait for removing finish before adding.
I think look for the class android.os.Handler. Then you can create two threads: one to remove all the children and another to add the children. Then add those threads to the Handler like this:
handler.post(new Runnable(){
#Override
public void run() {
// Thread to remove children
}
});
handler.post(new Runnable(){
#Override
public void run() {
// Thread to add children
}
});
Once you add them one after another the Android SDK will execute them in the order they were added. So that will take care of your ordering issue.
Move the code which adds the children to another method, for example addBalls:
private void addBalls() {
for (int i = 0; i < MAX_BALL; i++) {
int x = MathUtils.random(0, CAMERA_WIDTH - BALL_SIZE);
int y = BALL_SIZE;
final Ball ball = new Ball(x, y, BALL_SIZE, BALL_SIZE, GraphicsManager.trBalloons[i]);
scene.registerTouchArea(ball);
balls.add(ball);
if (!balls.get(i).hasParent()) {
scene.attachChild(balls.get(i));
}
Log.e("test", "load");
}
}
And call this method fron the run method of the Runnable after you call scene.detachChildren:
public void run() {
Log.e("test", "remove");
scene.detachChildren();
addBalls();
}
I wanted to display 9 images one after the other. I have included the 9 images as an array:
imageHolders = new ArrayList<ImageView>();
imageHolders.add((ImageView) view.findViewById(R.id.imgOne));
imageHolders.add((ImageView) view.findViewById(R.id.imgTwo));
imageHolders.add((ImageView) view.findViewById(R.id.imgThree));
imageHolders.add((ImageView) view.findViewById(R.id.imgFour));
imageHolders.add((ImageView) view.findViewById(R.id.imgFive));
imageHolders.add((ImageView) view.findViewById(R.id.imgSix));
imageHolders.add((ImageView) view.findViewById(R.id.imgSeven));
imageHolders.add((ImageView) view.findViewById(R.id.imgEight));
imageHolders.add((ImageView) view.findViewById(R.id.imgNine));
This is what I have tried:
public void handleMessage(Message msg) {
int currentImage = 0;
int nextImage = 0;
// Logic to change the images
for (final ImageView imageView : imageHolders) {
currentImage = Integer.parseInt(imageView.getTag().toString());
if (currentImage > 1) {
nextImage = currentImage - 1;
} else {
nextImage = 9;
}
imageView.setTag(""+nextImage);
new CountDownTimer(10000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
imageView.setVisibility(VISIBLE);
}
}.start();
}
super.handleMessage(msg);
}
}
There is a delay between the first and the second images. I am not able to introduce a delay between the rest. I have no clue about introducing the delay. Any suggestions would be appreciated.
you could just use AnimationDrawable which does this for you , and allows you to set a duration of time (in milliseconds) using android:duration attribute .
The AnimationDrawable is probably the best answer, but for another solution you can look at AsyncTask.
http://developer.android.com/reference/android/os/AsyncTask.html
Then, you can in the background just sleep for some set amount of time, then display the next image.
You may want an event though to handle when this task is done, and in the listener you call the next one.
If you want to make just a simple static animation (all images at the same position) you can use AnimationDrawable but you can't easly change it dynamically - like change speed or something.
I just wrote a simple class for this so I can set multiple animations for one ImageView and change speed.
public class ImageSequence extends ImageView {
ArrayList<Drawable> draws = new ArrayList<Drawable>();
ArrayList<Integer> bnds = new ArrayList<Integer>(); //bounds of sequences - index of first and last frame.
ArrayList<String> names = new ArrayList<String>(); //sequences names.
Timer timer = new Timer(true);
TimerTask task;
int p = -1; //current playback position.
int cS = -1; //current sequence.
public ImageSequence(Context context) {
super(context);
createTask();
}
public void addSequence(String name, int[] res){
names.add(name);
bnds.add(draws.size());
bnds.add(draws.size() + res.length - 1);
for (int i = 0; i < res.length; i++){
draws.add(getContext().getResources().getDrawable(res[i]));
}
}
public void play(String sequence, float speed){
cS = -1;
for(int i = 0; i < names.size(); i++){
if(names.get(i) == sequence){ cS = i; break;}
}
if(cS<0) return;
p = -1;
task.run();
task.cancel();
if(speed <= 0) return;
createTask();
timer.schedule(task, (long) (1000/speed), (long) (1000/speed));
}
public void stop(){
task.cancel();
}
public void cancelTimer(){
timer.cancel();
}
private void createTask() {
task = new TimerTask(){
public void run() {
p++;
mHandler.obtainMessage(1).sendToTarget();
}
};
}
public Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
int l = p;
if(l > bnds.get(cS*2+1) || l < bnds.get(cS*2)) l = bnds.get(cS*2);
p = l;
ImageSequence.this.setImageDrawable(draws.get(l));
}
};
}
From outside you can use it like this:
seq.addSequence("orange", new int[]{R.drawable.ic_oval_diode_orange, R.drawable.ic_oval_diode_off});
seq.addSequence("green", new int[]{R.drawable.ic_oval_diode_green, R.drawable.ic_oval_diode_off});
seq.addSequence("red", new int[]{R.drawable.ic_oval_diode_red, R.drawable.ic_oval_diode_off});
seq.play("red", 4);
I'm not really experienced in Android and Java but it works and it do the job for me. Hope it will help someone.