So I have a custom view where I am trying to perform a drawing animation using the Path methods of moveto() and lineto(). I have an array of Points that I iterate through and call invalidate() at every iteration. In onDraw the path is drawn with drawPath(). Of course onDraw is not called until the loop is done. I believe I need to perform the iterations in a background thread. But at the same time I have read that it is bad practice perform UI changes in background thread. How can I can I redraw in every iteration?
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPaint(mBackgroundPaint);
canvas.drawPath(mPath, mStrokePaint);
}
public void drawPoints(){
mPath = new Path();
for (Stroke stroke : mStrokes){
mDuration = stroke.getStrokeDuration();
mInitialX = stroke.getInitialX();
mInitialY = stroke.getInitialY();
mPath.moveTo(mInitialX, mInitialY);
invalidate();
mPoints = new ArrayList<Point>();
mPoints = stroke.getStrokePoints();
int s = mPoints.size();
long delayTime = mDuration / (long) s;
for(Point point : mPoints){
mX = point.getX();
mY = point.getY();
handler.postDelayed(new Runnable() {
#Override
public void run() {
mPath.lineTo(mX, mY);
// want to call invalidate to redraw canvas
invalidate();
}
}, delayTime);
}
}
}
I had the same prob, put this into your onDraw:
((Main) context).runOnUiThread(new Runnable() {
#Override
public void run() {
CustomView.this.invalidate();
}
});
Related
Currently I have 2 screens, a main menu and a battle screen. When i move directly from the main menu screen to the battle screen without resizing, everything works normally, but when i resize my main menu and then move to my battlescreen, the particles (and only the particles) get rendered incorrectly.
I have tried :
-updating the viewport in the show method of the battlescreen
-setting the projectionmatrix of the spritebatch to the camera's combined of either the mapcamera or the unitcamera.
-applying every viewport (there are 3) before rendering.
THE MAIN MENU SCEEN
https://snag.gy/cJCnRt.jpg
private Stage _stage;
public MainMenuScreen(Object... params){
_stage = new Stage();
backgroundbatch = new SpriteBatch();
}
#Override
public void render(float delta) {
backgroundbatch.begin();
backgroundbatch.draw(_currentFrame, 0, 0,_stage.getViewport().getWorldWidth(),_stage.getViewport().getWorldHeight()); // Draw current frame at (0, 0)
backgroundbatch.end();
_stage.act(delta);
_stage.draw();
}
#Override
public void resize(int width, int height) {
_stage.getViewport().setScreenSize(width, height);
//_stage.getViewport().update(width, height);
}
THE BATTLESCREEN CLASS
private void initializeHUD() {
_hudCamera = new OrthographicCamera();
_hudCamera.setToOrtho(false, VIEWPORT.physicalWidth, VIEWPORT.physicalHeight);
}
#Override
public void show() {
_camera = new OrthographicCamera();
_camera.setToOrtho(false, map.getMapWidth(), map.getMapHeight());
_mapRenderer = new OrthogonalTiledMapRenderer(_mapMgr.getCurrentTiledMap(), Map.UNIT_SCALE);
_mapRenderer.setView(_camera);
spritebatch = new SpriteBatch();
map.makeSpawnParticles();
}
#Override
public void render(float delta) {
_camera.update();
_hudCamera.update();
//draw particles
ParticleMaker.drawAllActiveParticles(spritebatch, delta);
}
#Override
public void resize(int width, int height) {
Player.getInstance().getEntityStage().getViewport().update(width, height, true);
_playerBattleHUD.resize(width, height);
map.getTiledMapStage().getViewport().update(width, height, true);
}
PARTICLE MANAGER CLASS
public static void drawAllActiveParticles(SpriteBatch spriteBatch, float delta) {
for (ArrayList<Particle> particleTypeList : allParticles.values()) {
spriteBatch.begin();
for(Particle particle : particleTypeList) {
if(particle.isActive()) {
particle.update(delta);
particle.draw(spriteBatch,delta);
}
if (particle.isComplete()) {
particle.delete();
//particles.remove(particle);
}
}
spriteBatch.end();
}
}
PARTICLE CLASS
public void draw(SpriteBatch spriteBatch, float delta) {
Gdx.app.debug("Particle : ", "spritebatch position = " + spriteBatch.getProjectionMatrix().getScaleX() + " , " + spriteBatch.getProjectionMatrix().getScaleY() + ")");
particleEffect.draw(spriteBatch, delta);
}
expected results :
https://snag.gy/q2txr5.jpg (particles are the yellow dots)
results (after resizing the main screen):
https://snag.gy/g81ZJ7.jpg
noteworthy : after resizing, the spritebatch projectionmatrix shows a different value in comparison to not having resized the main menu.
For anyone else with similar issues, I fixed my problem by using my tiledmaprenderer's batch to render my particles.
I am using the following class to render an atlas on screen:
public class AnimationDemo implements ApplicationListener {
private SpriteBatch batch;
private TextureAtlas textureAtlas;
private Animation animation;
private float elapsedTime = 0;
#Override
public void create() {
batch = new SpriteBatch();
textureAtlas = new TextureAtlas(Gdx.files.internal("data/packOne.atlas"));
animation = new Animation(1 / 1f, textureAtlas.getRegions());
}
#Override
public void dispose() {
batch.dispose();
textureAtlas.dispose();
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
//sprite.draw(batch);
elapsedTime += Gdx.graphics.getDeltaTime();
batch.draw(animation.getKeyFrame(elapsedTime, true), 0, 0);
batch.end();
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
}
I am a beginner with libGDX, however with the above program my images are not rendered in order as random images appear. I was earlier using the following with the same . atlas file and it was working properly:
public class MyGdxGame implements ApplicationListener {
private SpriteBatch batch;
private TextureAtlas textureAtlas;
private Sprite sprite;
private int currentFrame = 1;
private String currentAtlasKey = new String("0001");
#Override
public void create() {
batch = new SpriteBatch();
textureAtlas = new TextureAtlas(Gdx.files.internal("data/packOne.atlas"));
TextureAtlas.AtlasRegion region = textureAtlas.findRegion("0001");
sprite = new Sprite(region);
sprite.setPosition(Gdx.graphics.getWidth() / 2 - sprite.getWidth() / 2, Gdx.graphics.getHeight() / 2 - sprite.getHeight() / 2);
sprite.scale(4.5f);
Timer.schedule(new Timer.Task() {
#Override
public void run() {
currentFrame++;
if (currentFrame > 393)
currentFrame = 1;
// ATTENTION! String.format() doesnt work under GWT for god knows why...
currentAtlasKey = String.format("%04d", currentFrame);
sprite.setRegion(textureAtlas.findRegion(currentAtlasKey));
}
}
, 0, 1 / 30.0f);
}
#Override
public void dispose() {
batch.dispose();
textureAtlas.dispose();
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
sprite.draw(batch);
batch.end();
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
}
Any hints about what might be wrong here?
I am also trying to adapt my program with Screen Viewport any headings as in how to implement this would also be welcome.
Edit: The .atlas file is located here
Your atlas file isn't ordered. If you call the code below, it will be ordered.
regions.sort(new Comparator<AtlasRegion>() {
#Override
public int compare(AtlasRegion o1, AtlasRegion o2) {
return Integer.parseInt(o1.name) > Integer.parseInt(o2.name) ? 1 : -1;
}
});
But I'm still checking why your atlas regions isn't ordered.
you should create array with frames ordered alphabetically instead of using textureAtlas.getRegions() which just gives you an array without caring of order.
The example for atlas with regions named like: region1, region2 and so on would be:
AtlasRegion[] frames = new AtlasRegion[framesCount];
for(int i = 0; i < framesCount; i++)
{
frames[i] = atlas.findRegion("region" + i);
}
so you can adjust it to your regions names.
If you want to get all frames from textureAtlas you can also do it like this:
Array<String> names = new Array<String>();
for(AtlasRegion region : textureAtlas.getRegions())
{
names.add( region.name );
}
names.sort();
Array<AtlasRegion> frames = new Array<AtlasRegion>();
for(String s : names)
{
frames.add( textureAtlas.findRegion(s) );
}
and then after get frames array just create animation object:
animation = new Animation(1/1f, frames.items); //or just frames depending on which type frames is
TexturePacker will index the images for you as long as you follow the naming scheme set forth here https://github.com/libgdx/libgdx/wiki/Texture-packer#image-indexes.
so your frames would be named something like
anim1_001.png
anim1_002.png
...
anim1_100.png
and a separate animation would simply be
anim2_001.png
....
anim2_100.png
EDIT:
additionally you can get the regions only related to certain animations. So instead of
animation = new Animation(1 / 1f, textureAtlas.getRegions());
you could use (yes it's findRegions() not findRegion()):
animation1 = new Animation(1 / 1f, textureAtlas.findRegions("anim1"));
animation2 = new Animation(1 / 1f, textureAtlas.findRegions("anim2"));
EDIT2:
If you're are using a stage it is quite easy to implement a screen viewport. I do it like this, (stage is a field and this step is in the show/create method):
stage = new Stage(new ScreenViewport());
Then in the resize method:
stage.getViewport().update(width, height, true);
Without a stage it's only slightly more complex
camera = new WhateverCamera();
viewport = new ScreenViewport(camera);
Then in the resize method:
viewport.update(width, height, true);
Use whatever camera you want, WhateverCamera is a placeholder and can be OrthographicCamera or PerspectiveCamera.
The last argument true centers the camera, if you don't want to do this set it to false or leave it out, it assumes false.
I have this code where there is a Backgroung under an Hand, both a Bitmap images. Now, i have to control with an onClick method if the click touches the hand or not. For this, i wrote here and some guys tell me to use a method called "bitmap.getPixel(int x, int y)" that gives the color of pixel touched. For control if the click doesn't touch the hand, i write "bitmap.getPixel(x, y) != Color.TRASPARENT", but doesn't work perfectly. Under this i write the code of SurfaceView.
P.S. The bitmap "Hand" is a picture of a central Hand with parts trasparent around.
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback{
private long missileStartTime;
private ThreadLv1 thread;
private OggettiLv1 hand;
private int conto=0;
private Sfondo sfondo;
MediaPlayer ouch;
public GamePanel(Context context){
super(context);
getHolder().addCallback(this);
thread = new ThreadLv1(getHolder(), this);
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){}
#Override
public void surfaceDestroyed(SurfaceHolder holder){
boolean retry = true;
while(retry)
{
try{thread.setRunning(false);
thread.join();
}catch(InterruptedException e){e.printStackTrace();}
retry = false;
}
}
#Override
public void surfaceCreated(SurfaceHolder holder){
hand = new OggettiLv1(BitmapFactory.decodeResource(getResources(), R.drawable.mano5));
sfondo = new Sfondo(BitmapFactory.decodeResource(getResources(), R.drawable.sfondo));
ouch= MediaPlayer.create(getContext(), R.raw.ouch);
knifesong.start();
thread.setRunning(true);
thread.start();
}
public void update() {
}
#Override
public void draw(Canvas canvas)
{
if(canvas!=null) {
final int savedState = canvas.save();
background.draw(canvas);
hand.draw(canvas);
drawText(canvas);
canvas.restoreToCount(savedState);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
float x = event.getX();
float y = event.getY();
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
int c = hand.sprite.getPixel((int) event.getX(), (int) event.getY());
if(c!=Color.TRANSPARENT){
conto++;
}
return true;
}
return false;
}
}
Someone know the problem and the solution in this code? Thanks in advance.
I think this might be the problem:
int c = hand.sprite.getPixel((int) event.getX(), (int) event.getY());
I think you are getting the value of a pixel at the x,y location on the sprite, you should get the value of the pixel on canvas.
EDIT:
Since your canvas is static, I suggest doing the following:
add a Bitmap to your class
private Bitmap canvasState;
In the draw function, initialize canvasState before anything else
canvasState = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.RGB_565));
canvas.setBitmap(canvasState);
after that, every change that you make to the canvas will be made to the canvasState, and you can use that in your event handler like so:
int c = canvasState.getPixel((int) event.getX(), (int) event.getY());
I'm not sure if the code I wrote will compile (I wrote them from the top of my head) but I think you can work out compile errors yourself.
I seem to have a problem with references to java objects, probably as a result of years of programming in C. The following code is supposed to enable me to move objects of "WordPart" around a canvas. When I choose the first object, it moves as expected. When I release the first object, and choose the second, the location of of both the first and the second objects becomes identical. (I have removed code needed for compilation, but irrelevant to the question.) As far as I can tell, the activeImage reference seems to point to both objects in the wordPartList simultaneously. Please help me find my misunderstanding.
...
public class ONG2Activity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new SurfaceView2(this));
}
public class SurfaceView2 extends SurfaceView implements SurfaceHolder.Callback {
ArrayList<WordParts> wordPartList;
WordParts activeImage;
Point activePosition;
String TAG = "SurfaceView2";
public SurfaceView2(Context context) {
super(context);
wordPartList = NewPartList(context);
this.getHolder().addCallback(this);
setFocusable(true);
activeImage = null;
activePosition = new Point();
}
/**
*
* #return list of word parts
*/
private ArrayList<WordParts> NewPartList(Context context) {
ArrayList<WordParts> newParts = new ArrayList<WordParts>();
newParts.add(new WordParts(context, new Point(400, 100), "foo"));
newParts.add(new WordParts(context, new Point(200, 100), "bar");
return newParts;
}
#Override
protected void onDraw(Canvas canvas) {
Log.d(TAG, "inside onDraw");
canvas.drawColor(Color.BLACK);
if (activeImage != null) {
activeImage.NewPosition(activePosition);
}
draw(canvas, BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher), new Point(100, 300));
draw(canvas, BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher), new Point(200, 300));
draw(canvas, BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher), new Point(300, 300));
for (WordParts wp : wordPartList) {
Point center = wp.getCenter();
Log.d(TAG, String.format("Word part %s at x=%d, y=%d", wp.mWordPartString, center.x, center.y));
draw(canvas, wp.getBitMap(), center);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, String.format("inside on touch, event is %s", event.toString()));
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// if within wordPart, set as active
for (WordParts wp : wordPartList) {
if (wp.isInside(event)) {
activeImage = wp;
Log.d(TAG, String.format("Touch down inside word part %s", wp.mWordPartString));
break; // end iteration
}
}
break;
case MotionEvent.ACTION_UP:
for (WordParts wp : wordPartList) {
if (activeImage == wp){
wp.endTouch();
Log.d(TAG, String.format("Released word part %s", wp.mWordPartString));
}
}
activeImage = null;
break;
default:
break;
}
activePosition.x = (int) event.getX();
activePosition.y = (int) event.getY();
if (activeImage != null)
this.postInvalidate(); // force call to onDraw
return true;
}
/**
* Draw the bitmap centered at specified point
* #param canvas - target of drawing
* #param bm - bitmap to draw
* #param centerPt
*/
public void draw(Canvas canvas, Bitmap bm, Point centerPt) {
canvas.drawBitmap( bm,
centerPt.x - (bm.getWidth() / 2),
centerPt.y - (bm.getHeight() / 2), null);
Log.d(TAG, String.format("canvas %s, bm %s, point %s", canvas.toString(), bm.toString(), centerPt.toString()));
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height)
{
}
public void surfaceCreated(SurfaceHolder holder) {
Log.d("surface", "Surface created");
setWillNotDraw(false);
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("surface", "Surface destroyed");
}
}
}
I suspect this is the problem:
if (activeImage != null) {
activeImage.NewPosition(activePosition);
}
Unfortunately we don't have the code for unconventionally-named NewPosition, but presumably it sets the value of a field in a WordParts. If you set the value of two fields within two separate WordParts objects to refer to the same Point, then later when you change the values within that Point like this:
activePosition.x = (int) event.getX();
activePosition.y = (int) event.getY();
then yes, those changes will be visible via both WordParts objects, as they both refer to the same object.
If you're new to Java, I would strongly advise you to learn the basics of the language through simple console apps. Learn about objects, references and primitives, inheritance, the core collections and IO etc, then start working in the trickier environment of UIs and mobile.
I would like to develop a simple Live Wallpaper with a sequence of images. The only animation required is for each image to fade in and fade out.
All the tutorials I have found online for LWP demonstrate how to use the Draw canvas for fancy animations and drawing. This is not necessary for my app, I only want to iterate through a collection of images.
Being a novice programmer, I need some help learning how to loop through an array of images and how to display them as a wallpaper.
Can anyone share some code or point me towards a good tutorial for this?
UPDATE
The LWP loads on my device but the wallpaper does not change. It is stuck on image3, ironman
Here is the code I have so far. I assume I am doing something wrong in draw()
public class Wallpaper extends WallpaperService {
public void onCreate() {
super.onCreate();
}
public void onDestroy() {
super.onDestroy();
}
public Engine onCreateEngine() {
return new CercleEngine();
}
class CercleEngine extends Engine {
private final Handler handler = new Handler();
private final Runnable drawRunner = new Runnable() {
#Override
public void run() {
draw();
}
};
private boolean visible = true;
public Bitmap image1, image2, image3;
CercleEngine() {
image1 = BitmapFactory.decodeResource(getResources(),
R.drawable.batman);
image2 = BitmapFactory.decodeResource(getResources(),
R.drawable.captainamerica);
image3 = BitmapFactory.decodeResource(getResources(),
R.drawable.ironman);
}
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
}
#Override
public void onVisibilityChanged(boolean visible) {
this.visible = visible;
if (visible) {
handler.post(drawRunner);
} else {
handler.removeCallbacks(drawRunner);
}
}
#Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
this.visible = false;
handler.removeCallbacks(drawRunner);
}
public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
float yStep, int xPixels, int yPixels) {
draw();
}
void draw() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
c.drawBitmap(image1, 0, 0, null);
c.drawBitmap(image2, 0, 0, null);
c.drawBitmap(image3, 0, 0, null);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
handler.removeCallbacks(drawRunner);
if (visible)
{
handler.postDelayed(drawRunner, 1000); // delay 1 sec
}
}
}
There is no easy way around the looping through an array of images. It would have to be done manually.
One approach that you could adopt is to keep your images in the /res/drawable
and then use an int[] array to store the resid's of the images and then loop through it.
A well explained tutorial on Live Wallpapers may be found here:
http://www.vogella.com/articles/AndroidLiveWallpaper/article.html
Good Luck