I made a game that you should bump the mole. I am using postDelayed to make the mole appear and disappear every few seconds.
The problem is that when I touch the the mole, it turns to the squeezed mole picture and the mole disappears (as it should), but when the mole comes back to the screen, it appears with the wrong picture (the squeezed one).
STAGE View Class
private Runnable everyThreeSeconds = new Runnable() {
public void run() {
moleView.getMole().moveMole();
isPop = !isPop;
postDelayed(everyThreeSeconds, (3000)) ;
}
};
public AllViews(Context context) {
super(context);
test = new Paint();
first = new First_Stage();
isPop=false;
post(everyThreeSeconds);
}
public void setViews(StageView mainView, MoleView moleView,
PointsView pointsView, TimerView timerView,ClubView clubView)
{
this.mainView = mainView;
this.moleView = moleView;
this.pointsView = pointsView;
this.timerView = timerView;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mainView.onDraw(canvas);
pointsView.onDraw(canvas);
timerView.onDraw(canvas);
clubPic=BitmapFactory.decodeResource(getResources(), R.drawable.clubdown);
canvas.drawBitmap(clubPic, this.x-39,this.y-20, null);
if (isPop){
moleView.onDraw(canvas);
}
invalidate();
}
Mole View Class
public MoleView(Context context, Mole mole) {
super(context);
this.mole=mole;
}
#Override
protected void onDraw(Canvas canvas) {
if (!bool){
molePic=BitmapFactory.decodeResource(getResources(), R.drawable.nest_full_mole);
canvas.drawBitmap(molePic, mole.getX(), mole.getY(), null);
}else {
molePic=BitmapFactory.decodeResource(getResources(), R.drawable.pow);
canvas.drawBitmap(molePic, mole.getX(), mole.getY(), null);
}
}
just added to the if condition an else that return the true back to false and it work
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mainView.onDraw(canvas);
pointsView.onDraw(canvas);
timerView.onDraw(canvas);
clubPic=BitmapFactory.decodeResource(getResources(), R.drawable.clubdown);
canvas.drawBitmap(clubPic, this.x-39,this.y-20, null);
if (isPop){
moleView.onDraw(canvas);
}else{
moleView.setBool(false);
}
invalidate();
Related
I have two custom views (canvas views) and both of them have an observer which triggers an event on the screen and draw something on canvas. I am reusing the container and rendering the canvas in the same parent layout by removing other views. Right now, no matter what my both view observer values.
I have tried an dirty hack where I am passing a boolan to both views as true being one and other being false on switch which kinda work but I don't want that. Because it is just a hack. Can anyone tell me an android way to do it?
public class FakeViewOne extends View {
private EventViewModel eventViewModel;
private float l = 0.0f;
public FakeViewOne(Context context, View ParentView) {
super(context);
eventViewModel = new ViewModelProvider((ViewModelStoreOwner) getContext()).get(EventViewModel.class);
eventViewModel.getTrigger().observe((LifecycleOwner) getContext(), new Observer<Float>() {
#Override
public void onChanged(Float val) {
l = val;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*
* if l > 50 change background of a linearlayout
* draw a line on a canvas
* */
invalidate();
}
}
public class FakeViewTwo extends View {
private EventViewModel eventViewModel;
private float l = 0.0f;
public FakeViewTwo(Context context, View ParentView) {
super(context);
eventViewModel = new ViewModelProvider((ViewModelStoreOwner) getContext()).get(EventViewModel.class);
eventViewModel.getTrigger().observe((LifecycleOwner) getContext(), new Observer<Float>() {
#Override
public void onChanged(Float val) {
l = val;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*
* if l > 50 change background of a tile
*
* */
invalidate();
}
}
CODE Inside a Fragment where I am switch between those two FakeViews. How can I make those not observe values when they are not-rendered/inactive/removed from the view. I am using mycanvas.removeAllViews();.
final RelativeLayout mycanvas = view.findViewById(R.id.myCanvas);
gestureViewModel.getGestureType().observe(getActivity(), new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
assert s != null;
mycanvas.removeAllViews();
switch (s){
case "shake":
ShakeControl(view,myView,mycanvas);
break;
case "move":
MoveControl(view,myView,mycanvas);
break;
}
}
});
I have solved the problem with removing the observers on window detach in each FakeView.
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d("Detached","Head");
if (eventViewModel != null && eventViewModel.getTrigger().hasObservers()) {
eventViewModel.getTrigger().removeObservers((LifecycleOwner) ctx);
}
}
To preface, I'm probably going to work with Views instead of SurfaceView given this weird interaction, but my curiosity is getting the better of me and I want to know what's going on.
So I have a matrix class variable. I run matrix.setTranslate() once, and every other frame I say matrix.setTranslate(). After this I call canvas.drawBitmap(, matrix, null). After 100 frames, I stop drawing the bitmap.
Here's the code:
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// matrix = new Matrix(); //Doesn't matter if I add this.
// matrix.reset(); //This doesn't matter either.
if (!once) {
matrix.setTranslate(100, 100);
} else {
matrix.setTranslate(800,800);
}
once = true;
if (++timer < 100) {
canvas.drawBitmap(ball, matrix, null);
}
}
What I expect to happen: Three possibilities.
Only the bottom right bitmap is visible since the entire screen was invalidated
Both bitmaps are visible because SurfaceView was smart with the dirty rectangle
Nothing is shown because nothing was drawn.
What actually happens: The top left bitmap blinks, bottom right bitmap displays solidly.
I'm pretty sure I have all of my bases covered:
All class variables are properly set in the constructor.
SurfaceView.onDraw() is called every 33 millis in its own thread.
Thread calls lockCanvas, then onDraw, then unlockCanvasAndPost
So, what's going on here? And bonus question, do these ghost images consume any extra resources and how can I clear them?
Rest of the code, mostly boilerplate:
public class FullscreenActivity extends Activity {
GameSurface ball;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ball = new GameSurface(this);
setContentView(ball);
}
}
class GameSurface extends SurfaceView implements SurfaceHolder.Callback {
GameThread thread;
boolean once = false;
Bitmap ball;
Matrix matrix;
int timer = 0;
public GameSurface(Context context) {
super(context);
ball = BitmapFactory.decodeResource(getResources(),R.drawable.football);
matrix = new Matrix();
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// matrix = new Matrix(); //Doesn't matter if I add this.
// matrix.reset(); //This doesn't matter either.
if (!once) {
matrix.setTranslate(100, 100);
} else {
matrix.setTranslate(800,800);
}
//matrix.postRotate(timer); //For even more weirdness...
once = true;
if (++timer < 100) {
canvas.drawBitmap(ball, matrix, null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
class GameThread extends Thread {
private SurfaceHolder surfaceHolder;
private GameSurface gameView;
private boolean run = false;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameView) {
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
public void setRunning(boolean run) {
this.run = run;
}
#Override
public void run() {
Canvas c;
while (run) {
c = null;
try {
Thread.sleep(33);
}
catch(InterruptedException e) {}
try {
c = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
gameView.onDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
I click dice button in arenalayout.xml (this xml is displayed by 'ArenaLayout' class). But canvas doesn't draw char 2.I want to... if I click dice button then I draw char 2.
Note : After I click dice button, value of 'haveFirstDiced' variable in Arena class change to TRUE. There's condition inside of onDraw in 'Arena' class. . . if 'haveFirstDiced' variable have TRUE value then drawing char 2.
public class ArenaLayout extends Activity {
private Arena arena;
ImageButton diceButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.arena_layout);
diceButton = (ImageButton) findViewById(R.id.dice);
dice();
}
private void dice() {
diceButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
arena.dicing();
}
});
}
}
This is 'Arena' class :
public class Arena extends View{
private Paint p;
private Handler threadHandler = new Handler();
private Bitmap char1;
private float x = 20, y = 300;
Canvas c;
boolean haveFirstDiced = false;
public Arena(Context context, AttributeSet aSet) {
super(context, aSet);
p = new Paint();
}
public void dicing() {
new Thread(new XXX()).start();
}
#Override
synchronized public void onDraw(Canvas canvas) {
char2 = BitmapFactory.decodeResource(getResources(), R.drawable.char_2);
if(haveFirstDiced == true) {
canvas.drawBitmap(char2,x,y,null);
}
}
class XXX implements Runnable {
#Override
public void run() {
threadHandler.post(new Runnable() {
#Override
public void run() {
haveFirstDiced = true;
}
});
}
}
}
This is the most convoluted solution I've ever seen. There's no need for a thread, a runnable, or for onDraw to be synchronized. Here's what it should be:
private void dice() {
diceButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
arena.dicing();
}
});
}
public class Arena extends View{
private Paint p;
private Handler threadHandler = new Handler();
private Bitmap char2;
private float x = 20, y = 300;
Canvas c;
boolean haveFirstDiced = false;
public Arena(Context context, AttributeSet aSet) {
super(context, aSet);
p = new Paint();
char2 = BitmapFactory.decodeResource(getResources(), R.drawable.char_2);
}
public void dicing() {
haveFirstDiced = true;
invalidate();
}
#Override
public void onDraw(Canvas canvas) {
if(haveFirstDiced == true) {
canvas.drawBitmap(char2,x,y,null);
}
}
}
I have custom SurfaceView, setting a background in onDraw method, then drawing canvas, but see only background. How can I draw with canvas on a background?
Tried to add a background with canvas, it works, but at top left 2-3dp was deleted, and redrawing every time background will eat a lot of memory.
So can it be done with 1st method or 2nd without empty spaces on top left?
Edit...
GameView.java
public class GameView extends SurfaceView {
private Bitmap background;
private SurfaceHolder holder;
private GameManager gameLoopThread;
public GameView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
setWillNotDraw(false);
gameLoopThread = new GameManager(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
background = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
}
public void onDraw(Canvas canvas) {
canvas.drawBitmap(background, 0, 0, null);
}
public void drawCanvas(Canvas canvas) {
if (x < getWidth() - 10) {
x += 10;
} else {
gameLoopThread.setRunning(false);
}
canvas.drawLine(0, 200, x, 200, paint);
}
}
GameManager.java
public class GameManager extends Thread {
private GameView view;
private boolean running = false;
public GameManager(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while (running) {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.drawCanvas(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
}
MainActivity.java
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout ll = (LinearLayout) findViewById(R.id.main);
ll.setLayoutParams(new LinearLayout.LayoutParams(640, 400));
}
}
Main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_horizontal|top">
<LinearLayout
android:id="#+id/main"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<com.android.GameView
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
The result is http://oi57.tinypic.com/15znrpy.jpg
If I comment GameView onDraw method and add canvas.drawBitmap(background, 0, 0, null); to drawCanvas the result will be
http://oi60.tinypic.com/t0g802.jpg
There's not really a specific way to set a SurfaceView's background.
If you set a background in onDraw then draw to the canvas somewhere outside of onDraw, it's going to call onDraw and draw the background over whatever was just drawn.
Redrawing the background every time will not eat a significant amount of memory, assuming you're not trying to do it on the UI thread.
Also, I'm not quite sure what you mean by "at top left 2-3dp was deleted" and "empty spaces on top left". Can you post an image of this, along with the code you've tried?
Well, I'll try to explain exactly what I pretend:
I want to draw a bitmap in a canvas in random positions according to the coordinates of the screen on touch, and I want a message(text) to be displayed everytime the user touches the screen.
So, if a draw the text in the same thread than I do for the bitmap, it will appear and dissapear right after, and I want it to stay in the screen for a few seconds and the dissapear. My first idea was to use Thread.sleep(), but for that I have to create a thread only for the text, or I will mess with the Bitmap too.
I've been trying to use multithreading in the same canvas, but I don't know how. Can someone please explain to me...
That's some of the code i've got so far:
private void init() {
// CREATE SURFACEHOLDER AND ADD THIS CLASS AS HIS CALLBACK
enemyHolder = getHolder();
enemyHolder.addCallback(this);
scoreHolder = getHolder();
scoreHolder.addCallback(this);
hasSurface = false;
}
public void resume() {
if (surfaceViewThread == null) {
surfaceViewThread = new SurfaceViewThread(); // CREATE A NEW
// THREAD
if (hasSurface)
surfaceViewThread.start(); // START OUR THREAD
}
if (secondThread == null) {
secondThread = new SecondThread();
if (hasSurface)
secondThread.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
hasSurface = true;
if (surfaceViewThread != null)
surfaceViewThread.start();
if (scoreShow == 1) {
if (secondThread != null)
secondThread.start();
}
}
// THREAD
private final class SurfaceViewThread extends Thread {
private boolean done;
SurfaceViewThread() {
super();
done = false;
}
#Override
public void run() {
// TODO Auto-generated method stub
super.run();
SurfaceHolder surfaceHolder = enemyHolder;
while (!done) {
Canvas canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(enemy1, enemy1X, enemy1Y, null); // DRAW
// FIRST
// ENEMY
// SECOND THREAD
private final class SecondThread extends Thread {
private boolean done;
SecondThread() {
super();
done = false;
}
#Override
public void run() {
// TODO Auto-generated method stub
super.run();
SurfaceHolder surfaceHolder = scoreHolder;
while (!done) {
Canvas canvas = surfaceHolder.lockCanvas();
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawText("xD", 50, 50, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
scoreShow = 0;
}
}
Do you need to use a SurfaceView? It seems like a hard work way of doing things.
I've just done something similar by creating a custom view class and overriding the onDraw method. Then use canvas.save() and canvas.restore(). Here's the relevant bits of my onDraw.
#Override
public void onDraw(Canvas canvas) {
canvas.save();
// scale the canvas
canvas.scale(scaleFactor, scaleFactor); //, mid.x, mid.y);
// and translate...
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
super.onDraw(canvas);
// draw the lights
for(Light light:lights){
if (light.isOn){
canvas.drawCircle(light.getX(),light.getY(), light.getDiameter() / scaleFactor,light.paint);
}
}
canvas.restore();
}
The lights are turned on and off by a separate thread back in the activity which has inflated the view. They stay on screen as long as they are on.
Cheers
Here's some incomplete pseudo code to do what you've described. There are plenty of good examples around for each of these elements to help you fill in the blanks.
public class MyCustomView extends View {
int touchPointX;
int touchPointY;
private String mText;
public MyCustomView(Context context) {
// do initialisation stuff
}
public setText(String text){
mText = text;
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
canvas.save();
super.onDraw(canvas);
canvas.drawBitmap(touchPointX, touchPointY, bitmap)
if (!mText.equals(""){
canvas.drawText(touchPointX, touchPointY + 10, mText)
}
canvas.restore();
}
}
public class MyActivity extends Activity implements OnTouchListener{
private Handler mHandler = new Handler();
private MyCustomView mCustomView;
#Override
protected void onCreate(Bundle savedInstanceState) {
mCustomView = (MyCustomView) findViewById(r.id.myCustomView);
}
#Override
public boolean onTouch(View v, MotionEvent rawEvent) {
// get x and y of touch
mCustomview.touchPointX = x;
mCustomview.touchPointY = y;
mCustomView.setText("Screen touched");
// clear the text after 5 seconds
mHandler.postDelayed(clearText, 5000);
// redraw the view
mCustomView.invalidate();
}
private void clearText(){mCustomView.setText("");}
}