I need to create a live wallpaper where it just pulls an image from the drawable directory.
Is there an example somewhere that I can refer to?
It would be nice if the example also shows how to draw something simple on top of the image as well. If not, its ok =)
the one on the Android.com site just has drawing a cube =(
thanks for any comment
It is very easy))). Use something like this.
In your Engine constructor use something like this
Bitmap _background = BitmapFactory.decodeResource(getResources(), R.drawable.test);
and in your code use this
private final int WEATHER_ANIMATION_INTERVAL = 1000;
private final Handler _handler = new Handler();
private final Runnable weatherAnimation = new Runnable()
{
#Override
public void run()
{
drawNextFrame();
}
};
private void drawNextFrame()
{
final SurfaceHolder holder = getSurfaceHolder();
try {
_canvas = holder.lockCanvas();
if (_canvas != null)
{
drawAnimation(_canvas);
}
}
finally
{
if (_canvas != null)
holder.unlockCanvasAndPost(_canvas);
}
// schedule the next frame
_handler.removeCallbacks(weatherAnimation);
if (_visible)
{
_handler.postDelayed(weatherAnimation, WEATHER_ANIMATION_INTERVAL);
}
return;
}
private void drawAnimation(Canvas c)
{
c.drawBitmap(_background, _xOffset, _yOffset, _paint);
_weather.draw(c, _xOffset, _yOffset, _paint);
}
I hope this help you
Related
I am using TarsosDSP to calculate pitch frequencies in real time. It uses an AudioDispatcher which implements Runnable and post the results via handlePitch method to make use of in the main thread.
I am using SurfaceView to draw this value as it updates. SurfaceView also requires another thread to draw on canvas. So I have 2 runnable objects. I couldnt manage how to update surface view via one thread while getting the pitch values from another thread (audiodispatcher).
I just want to use cent value which I get in the handlePitch() method to update my drawing over surfaceview. But my app freezes. Any idea?
In MainAcitivity.java (onCreate(...))
myView = (MySurfaceView) findViewById(R.id.myview);
int sr = 44100;//The sample rate
int bs = 2048;
AudioDispatcher d = AudioDispatcherFactory.fromDefaultMicrophone(sr,bs,0);
PitchDetectionHandler printPitch = new PitchDetectionHandler() {
#Override
public void handlePitch(final PitchDetectionResult pitchDetectionResult, AudioEvent audioEvent) {
final float p = pitchDetectionResult.getPitch();
runOnUiThread(new Runnable() {
#Override
public void run() {
if (p != -1){
float cent = (float) (1200*Math.log(p/8.176)/Math.log(2)) % 12;
System.out.println(cent);
myView.setCent(cent);
}
}
});
}
};
PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.YIN; //use YIN
AudioProcessor pitchEstimator = new PitchProcessor(algo, sr,bs,printPitch);
d.addAudioProcessor(pitchEstimator);
d.run();//starts the dispatching process
AudioProcessor p = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(p);
new Thread(d,"Audio Dispatcher").start();
In SurfaceView.java (below code is triggered from the constructor)
myThread = new MyThread(this);
surfaceHolder = getHolder();
bmpIcon = BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher);
iconWidth = bmpIcon.getWidth();
iconHeight = bmpIcon.getHeight();
density = getResources().getDisplayMetrics().scaledDensity;
setLabelTextSize(Math.round(DEFAULT_LABEL_TEXT_SIZE_DP * density));
surfaceHolder.addCallback(new SurfaceHolder.Callback(){
#Override
public void surfaceCreated(SurfaceHolder holder) {
myThread.setRunning(true);
myThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
myThread.setRunning(false);
while (retry) {
try {
myThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}});
protected void drawSomething(Canvas canvas) {
updateCanvas(canvas, this.cent); //draws some lines depending on the cent value
}
public void setCent(double cent) {
if (this.cent > maxCent)
this.cent = maxCent;
this.cent = cent;
}
UPDATE:
MyThread.java
public class MyThread extends Thread {
MySurfaceView myView;
private boolean running = false;
public MyThread(MySurfaceView view) {
myView = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while(running){
Canvas canvas = myView.getHolder().lockCanvas();
if(canvas != null){
synchronized (myView.getHolder()) {
myView.drawSomething(canvas);
}
myView.getHolder().unlockCanvasAndPost(canvas);
}
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If I understand your problem correctly, you have an independent source of events working on its own thread (PitchDetectionHandler) and a SurfaceView that you want to re-paint on its own thread when the event from the source comes. If this is the case, then I think the whole idea with sleep(1000) is wrong. You should track actual events and react to them rather than sleep waiting for them. And it seems that on Android the easiest solution is to use HandlerThread/Looper/Handler infrastructure like so:
Beware of the bugs in the following code; not only haven't I tried it but I even haven't compiled it.
import android.graphics.Canvas;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.view.SurfaceHolder;
public class SurfacePitchDrawingHelper implements Handler.Callback, SurfaceHolder.Callback2 {
private static final int MSG_DRAW = 100;
private static final int MSG_FORCE_REDRAW = 101;
private final Object _lock = new Object();
private SurfaceHolder _surfaceHolder;
private HandlerThread _drawingThread;
private Handler _handler;
private float _lastDrawnCent;
private volatile float _lastCent;
private final boolean _processOnlyLast = true;
#Override
public void surfaceCreated(SurfaceHolder holder) {
synchronized (_lock) {
_surfaceHolder = holder;
_drawingThread = new HandlerThread("SurfaceDrawingThread") {
#Override
protected void onLooperPrepared() {
super.onLooperPrepared();
}
};
_drawingThread.start();
_handler = new Handler(_drawingThread.getLooper(), this); // <-- this is where bug was
_lastDrawnCent = Float.NaN;
//postForceRedraw(); // if needed
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (_lock) {
// clean queue and kill looper
_handler.removeCallbacksAndMessages(null);
_drawingThread.getLooper().quit();
while (true) {
try {
_drawingThread.join();
break;
} catch (InterruptedException e) {
}
}
_handler = null;
_drawingThread = null;
_surfaceHolder = null;
}
}
#Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
postForceRedraw();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
synchronized (_lock) {
_surfaceHolder = holder;
}
postForceRedraw();
}
private void postForceRedraw() {
_handler.sendEmptyMessage(MSG_FORCE_REDRAW);
}
public void postRedraw(float cent) {
if (_processOnlyLast) {
_lastCent = cent;
_handler.sendEmptyMessage(MSG_DRAW);
} else {
Message message = _handler.obtainMessage(MSG_DRAW);
message.obj = Float.valueOf(cent);
_handler.sendMessage(message);
}
}
private void doRedraw(Canvas canvas, float cent) {
// put actual painting logic here
}
#Override
public boolean handleMessage(Message msg) {
float lastCent = _processOnlyLast ? _lastCent : ((Float) msg.obj).floatValue();
boolean shouldRedraw = (MSG_FORCE_REDRAW == msg.what)
|| ((MSG_DRAW == msg.what) && (_lastDrawnCent != lastCent));
if (shouldRedraw) {
Canvas canvas = null;
synchronized (_lock) {
if (_surfaceHolder != null)
canvas =_surfaceHolder.lockCanvas();
}
if (canvas != null) {
doRedraw(canvas, lastCent);
_surfaceHolder.unlockCanvasAndPost(canvas);
_lastDrawnCent = lastCent;
}
return true;
}
return false;
}
}
And then in your activity class you do something like
private SurfaceView surfaceView;
private SurfacePitchDrawingHelper surfacePitchDrawingHelper = new SurfacePitchDrawingHelper();
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceView.getHolder().addCallback(surfacePitchDrawingHelper);
int sr = 44100;//The sample rate
int bs = 2048;
AudioDispatcher d = AudioDispatcherFactory.fromDefaultMicrophone(sr, bs, 0);
PitchDetectionHandler printPitch = new PitchDetectionHandler() {
#Override
public void handlePitch(final PitchDetectionResult pitchDetectionResult, AudioEvent audioEvent) {
final float p = pitchDetectionResult.getPitch();
float cent = (float) (1200 * Math.log(p / 8.176) / Math.log(2)) % 12;
System.out.println(cent);
surfacePitchDrawingHelper.postRedraw(cent);
}
};
PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.YIN; //use YIN
AudioProcessor pitchEstimator = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(pitchEstimator);
// d.run();//starts the dispatching process <-- this was another bug in the original code (see update)!
AudioProcessor p = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(p);
new Thread(d, "Audio Dispatcher").start();
...
}
Note that SurfacePitchDrawingHelper encapsulates most of the logic related to drawing and there is no need in your subclass MySurfaceView (which I think is a bad idea anyway).
The main idea is that SurfacePitchDrawingHelper creates are dedicated HandlerThread when new Surface is created. HandlerThread + Looper + Handler provide a useful infrastructure of running (in an efficient way) an infinite loop on a separate thread that waits for incoming messages and handles them one by one. So its effective public API besides SurfaceHolder.Callback2 consists of single postRedraw method that might be used to ask the drawing thread to do another redraw and this is exactly what is used by custom PitchDetectionHandler. "Asking" is done by putting a message in the queue to be processed by the drawing thread (more specifically our custom Handler on that thread). I didn't bother with reducing real public API to "effective" one because it makes code a bit more complicated and I'm too lazy. But of course both "implements" can be moved to inner classes.
There is one important decision to be made by you: whether drawing thread should produce each inbound message (all cent values) in order the came or just the latest at the moment drawing occurs. This might become especially important in case PitchDetectionHandler produces events much faster then the "drawing thread" can update Surface. I believe that for most of the cases it is OK to handle just the last value from the PitchDetectionHandler but I left both version in the code for illustration. This distinction is currently implemented in the code by _processOnlyLast field. Most probably you should make this decision ones and just get rid of this almost-constant-field and the code in the irrelevant branches.
And of course don't forget to put your actual drawing logic inside doRedraw
Update (why Back button doesn't work)
TLDR version
The offending line is
d.run();//starts the dispatching process
Just comment it out!
Longer version
Looking at your example we can see that d is AudioDispatcher which implements Runnable and thus run method is a method to be called on a new thread. You may notice that this is important because inside this method does some IO and blocks the thread it runs on. So in your case it blocked the main UI thread. A few lines down you do
new Thread(d, "Audio Dispatcher").start();
and this seems to be a correct way to use AudioDispatcher
This can easily be seen from the stack traces that I asked in the comments for.
I have implemented a gameloop to work on SurfaceView, it works perfectly but when scaling down or moving canvas it renders snapshots of scaling behind the canvas which is annoying.
So I want to clear my view background after onDraw() calls. I tried using view.setBackgroundColor() from the gameloop or inside onDraw() but android claims that I can use it from another thread.
Is there any equivalent method to do so?
public class GameLoopThread extends Thread
{
private MapView view;
private boolean running = false;
static final long FPS = 30;
public GameLoopThread(MapView view)
{ this.view = view; }
public void setRunning(boolean run)
{ running = run; }
#Override
public void run()
{
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running)
{
Canvas c = null;
startTime = System.currentTimeMillis();
try
{
c = view.getHolder().lockCanvas();
synchronized (view.getHolder())
{
view.onDraw(c);
view.setBackgroundColor(Color.WHITE); // fails on this
}
}
finally
{
if (c != null)
view.getHolder().unlockCanvasAndPost(c);
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try
{
if (sleepTime > 0) sleep(sleepTime);
else sleep(10);
}
catch (Exception e){}
}
}
}
Update
I don't want to use view.setBackgroundColor() instead I want to use something equivalent to draw behin the canvas
You can't update UI from a separate thread instead you can use runOnUiThread
You can only "Change" views from the UIThread. So a possible solution would be passing your Activity-Context and use
activityContext.runOnUiThread
Hope it helps
here it is better to use the Handler, because GameLoopThread has no Context
android.os.Handler handler = new android.os.Handler();
handler.post(new Runnable() {
#Override
public void run() {
//update here
}
});
make private MapView view; to final
You can update UI thread from handlers, asynctasks, or runOnUIThread method. You cannot do that from a normal thread
Thanks all for posting answers.
I have found my need which in its turn works as setBackgroundColor()
It's as simple as calling drawColor()
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE); // fill the whole canvas with white
// rest of drawing
}
I wish to find out what dimensions my canvas is going to be long before I ever need to create one or draw on it.
The only canvas code I know (or have a flimsy knowledge of) is this:
final SurfaceHolder holder = getHolder();
try
{
Canvas canvas = holder.lockCanvas();
if(canvas != null)
{
onDraw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
catch (Exception e)
{
e.printStackTrace();
}
But this looks like it is doing far too much for simply getting the height. Is there a function like WhatHeightWillMyCanvasBeWhenIMakeOne()?
EDIT: ...and if there isn't such a function, then what is a minimal piece of code for temporarily getting a canvas for long enough to ask its height and then get rid of it (if needed).
You cannot get a canvas sized to your drawable-screen-area until you override the onDraw and do some canvas locking and posting. (from what I understand anyways)
#Override
public void run() {
while (running) {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
try {
sleep(10);
} catch (Exception e) {}
}
}
Now when you are calling on draw in your loop, you are sending it a canvas. now just implement the onDraw method in your custom view.
#Override
protected void onDraw(Canvas canvas) {
int h = canvas.getHeight();
int w = canvas.getWidth();
}
This can also be done w/o a canvas. first of all declare a class with some static holders.
public class Window {
public static int WINDOW_HEIGHT; // = to screen size
public static int WINDOW_WIDTH; // = to screen size
}
Next call some special features in your main start up activity. we request these because i've never seen anyone want to draw to a canvas and not want a full screen with no bar. If you plan on not using a full screen ignore the options. So to get an accurate measure we need to hide them here as well.
public void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.main); // <--- or here you can call your custom view
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Window.WINDOW_HEIGHT = height;
Window.WINDOW_WIDTH = width;
}
from inside your custom view you could make a call like this
private void CreateWindow(Context context) {
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Window.WINDOW_HEIGHT = height; // = to screen size
Window.WINDOW_WIDTH = width; // = to screen size
}
I would like to ask about a problem that has been addressed here once or twice, but none of information I found could help me overcome the problem I faced a few days ago.
I want to make a live wallpaper for android using canvases - it is not graphically complicated enough to require OpenGL. For simplicity assume it consists of solid background and two smaller rectangles.
Drawing consists of three separate stages (in single thread):
backgroundDraw() requests entire canvas lock and draws on it solid color
draw1() requests partial (Rect r1) lock and draws only on locked rectangle
draw2() requests partial (Rect r2) lock and draws only on locked rectangle
I tested it on multiple Android versions (both emulators and devices): 2.1, 2.2, 2.3.3. It seems to be working properly only on the latter one (here: http://home.elka.pw.edu.pl/~pgawron/include/Android/android_233.jpg). On previous Android versions SurfaceHolder.lockCanvas(Rect dirty) resizes(!) dirty passed as parameter to size of the full screen and further drawing using it results drawing on the whole screen (here: http://home.elka.pw.edu.pl/~pgawron/include/Android/android_22.jpg). I can in fact see how each rectangle is being ill-drawn (full-screen): whole screen changes it's color very quickly.
Unluckily google could not find for me any proper example of lockCanvas(Rect dirty) usage. Below I attach my complete and only class used for testing purposes. Full eclipse project is accessible just where provided screenshots are placed.
I would be extremely grateful if somebody could finally help me and correct my code (if only the problem is in my code). I really wasted too much time on it.
BR,
petrelli
package sec.polishcode.test;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.SystemClock;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.SurfaceHolder;
public class TestLiveWallpaper extends WallpaperService{
#Override
public Engine onCreateEngine() {
return new MyEngine();
}
class MyEngine extends Engine implements SurfaceHolder.Callback {
private final String LOGTAG = MyEngine.class.getSimpleName();
private Paint backgroundPaint = new Paint();
private Paint mPaint1 = new Paint();
private Paint mPaint2 = new Paint();
private long lastVisibilityOnChange;
private final Rect r1 = new Rect(20, 20, 60, 280);
private final Rect r2 = new Rect(70, 20, 110, 280);
public MyEngine() {
getSurfaceHolder().addCallback(this);
backgroundPaint.setColor(Color.YELLOW);
mPaint1.setColor(Color.LTGRAY);
mPaint2.setColor(Color.MAGENTA);
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
drawSurface();
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
Log.i(LOGTAG, "surfaceCreated");
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
Log.i(LOGTAG, "surfaceDestroyed");
}
#Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
setTouchEventsEnabled(true);
}
#Override
public void onVisibilityChanged(boolean visible) {
if (!visible)
return;
lastVisibilityOnChange = SystemClock.elapsedRealtime();
drawSurface();
}
#Override
public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
float yStep, int xPixels, int yPixels) {
if (SystemClock.elapsedRealtime() - lastVisibilityOnChange > 30)
return;
Log.i(LOGTAG, "onOffsetsChanged filtered");
drawSurface();
}
private void drawSurface() {
backgroundDraw();
draw1();
draw2();
}
private void backgroundDraw() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
c.drawRect(holder.getSurfaceFrame(), backgroundPaint);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
}
private void draw1() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas(r1);
if (c != null) {
c.drawRect(r1, mPaint1);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
}
private void draw2() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas(r2);
if (c != null) {
c.drawRect(r2, mPaint2);
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
}
}
}
lockCanvas(Rect dirty) is pretty simple. Remember that on Android surfaces are double-buffered by default. This means that you need to not only repaint the dirty region of the current surface, but also the dirty region of the previous surface to make it work properly. This is why lockCanvas() will resize the rectangle you pass: it tells what the real dirty region is. The dirty region might also change because the surface was discarded and recreated, etc. The correct way to use lockCanvas(Rect) is to pass your dirty rectangle and then check its new values and honor them.
I started implementation of android live wallpaper, following the examples and tutorials found on the internet, and I can't include png background as wallpaper. Also checked with similar problems here, and still can't make it work.
This is the code:
public class LiveWallpaper extends WallpaperService {
/* IDs of recurces needed for animations*/
private SurfaceHolder holder;
private static final String TAG = "MyActivity";
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public Engine onCreateEngine() {
return new WallpaperEngine();
}
class WallpaperEngine extends Engine {
public final Runnable mDrawWallpaper = new Runnable(){
public void run(){
drawWallpaper();
}
};
#Override
public void onCreate(SurfaceHolder surfaceHolder){
super.onCreate(surfaceHolder);
setTouchEventsEnabled(false);
loadImagesIntoMemory(R.drawable.wallpaper);
holder = getSurfaceHolder();
}
void drawWallpaperContent(Canvas c, int resourceId){
Bitmap decodeResoure = BitmapFactory.decodeResource (getResources(), resourceId);
c.drawBitmap(decodeResoure, 0, 0, null);
}
void drawWallpaper(){
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
c = holder.lockCanvas();
if(c!=null){
c.save();
drawWallpaperContent(c, R.drawable.wallpaper);
c.restore();
}
}
private void loadImagesIntoMemory(int resourceId){
Resources res = getResources();
BitmapFactory.decodeResource(res, resourceId);
}
#Override
public void onDestroy(){
super.onDestroy();
mHandler.removeCallbacks(mDrawWallpaper);
}
}
}
Bitmap is stored in drawable folder, and the version of android sdk is 2.2.
After launching the live wallpaper, I only get 'Loading Wallpaper' without showing the wallpaper image.
Does anyone knows what could be the problem?
Thank you.
Dj.
use this in your draw
' Bitmap image = BitmapFactory.decodeResource(getResources(),R.drawable.image);'
canvas.drawBitmap(image, 0, 0, paint);
you can pass null in paint parameter. m using this and its working
I struggled with a similar problem, c.drawColor(0xff000000); before drawing the bitmap was the solution for me.