(Android)
I am working on adapting a full-screen analog clock app to a live wallpaper.
The app use three separate ImageView for hour, min, sec hands and RotateAnimation.
I have been looking around for a method to use ImageView in live wallpaper. this and this indicates that this should be possible with measure() and layout(), but I
don't really know how to use it.
For example, I use the code below to load clock_background.png into a ImageView.
public class MyWallpaperService extends WallpaperService {
#Override
public Engine onCreateEngine() {
return new MyWallpaperEngine();
}
....
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
mContext = getBaseContext();
backgroundImage = new ImageView(mContext);
backgroundImage.setImageDrawable(mContext.getResources().getDrawable(R.drawable.clock_background));
}
}
Later I have:
backgroundImage.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
backgroundImage.layout(0, 0, 150, 150);
I expect the code above would display clock_background.png in the upper-left corner of
the screen. I tried to put the two lines above in OnSurfaceCreated and OnSurfaceChanged. Unfortunately it does not work.
As I understand, live wallpaper gives a Surface (not SurfaceView), from which one can get
canvas and draw things on it.
I apologise for being very new at Android: Am I doing it all wrong? I just don't want to
mess with drawBitmap or likes, and I don't care about battery anyway.
Any help is highly appreciated. Thank you very much!
I short, yes you're doing it wrong.
Use the supplied canvas and its drawX() methods
Related
Recently in project I faced challenge of resizing VirtualDisplay "on flight". So the use case is :
Start stream
In undetermined period of stream there may come specific data which indicates that my streaming capabilities have changed
Update VirtualDisplay's parameters without recreation, so that state loss is avoided
I've found in documentation for VirtualDisplay resize method, though it seems to have no effect on new parameters incoming. For implementation I am using
virtualDisplay = mDisplayManager.createVirtualDisplay("DispName",
getResolution().getResolutionWidth(), getResolution().getResolutionHeight(),
getDisplayDensity(), inputSurface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
where inputSurface is created by mediaEncoder.createInputSurface() and cofigured properly by this moment. So, the question is, how can I resize VirtualDisplay? I also didn't find any examples how to do it in official sources, would appreciate any help!
UPDATE
Just forgot to mention, I've put Listener for VirtualDisplays and onChange method is triggered, though check if actual metrics were changed shows negative results
Answering to my own question
The resize method of VirtualDisplay works pretty fine, though it was from my side misunderstanding of how to achieve very specific behaviour, when only underlying layout changes it's size, though elements are keeping their properties on smaller window
So, in case if you want to get some kind of "scalar" resize (like everything comes bigger or smaller) you should call resize
But, whenewer your project demands some kind of resizing and making your controls
bigger, though screen comes smaller you should check your extention of your concrete Presentation class linked with VirtualDisplay and just update your layout manually without having VirtualDisplay resized
public void resizeView(final int newWidth, final int newHeight) {
uiHandler.post(new Runnable() {
#Override
public void run() {
try {
Constructor<? extends ViewGroup.LayoutParams> ctor =
mainView.getLayoutParams().getClass().getDeclaredConstructor(int.class, int.class);
mainView.setLayoutParams(ctor.newInstance(newWidth, newHeight));
mainView.requestLayout();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
where uiHandler could be simply obtained even in background with
Handler uiHandler = new Handler(Looper.getMainLooper());
I hope this would be useful for somebody!
I have a UI where the root layout is a RelativeLayout. It has a number of Views, such as fields, buttons, etc.
There are also two other panels that are initially invisible. When the user clicks a button, one of these panels slides in from the left, another slides in from the bottom. The problem is the frame rate is pathetic on a Nexus S.
I want to use setDrawingCacheEnabled(true) in order to (I hope) speed up the animation of these two panels. As a simplified example, here is roughly how I slide in one of these panels. I'll call it "detailPanel".
public class MyActivity extends Activity {
// Initially invisible.
private View detailPanel;
// A simple slide animation.
private Animation detailEnterAnimation;
...
public void someButtonClicked(View view) {
detailPanel.startAnimation(detailEnterAnimation);
detailPanel.setVisibility(View.VISIBLE);
detailPanel.setDrawingCacheEnabled(true);
}
}
Now in the DetailPanel I try to use the cache. I was hoping that bitmap rendering via the cache would improve performance:
public class DetailPanel extends LinearLayout {
public void draw(Canvas canvas) {
Bitmap cache = getDrawingCache();
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, null);
} else {
super.draw(canvas);
}
}
}
The Bitmap is non-null and is the right size, but it is completely black.
Why is my bitmap black? That code may be obscenely wrong; I've tried a million different things and just cannot figure it out.
From Android documentation (http://developer.android.com/reference/android/view/View.html#setDrawingCacheEnabled%28boolean%29):
Enabling the drawing cache is similar to setting a layer when hardware acceleration is turned off. When hardware acceleration is turned on, enabling the drawing cache has no effect on rendering because the system uses a different mechanism for acceleration which ignores the flag. If you want to use a Bitmap for the view, even when hardware acceleration is enabled, see setLayerType(int, android.graphics.Paint) for information on how to enable software and hardware layers.
Maybe this is your case?
You may want to build your drawing cache before using it, otherwise It will give you a blank bitmap
public void someButtonClicked(View view) {
detailPanel.startAnimation(detailEnterAnimation);
detailPanel.setVisibility(View.VISIBLE);
detailPanel.setDrawingCacheEnabled(true);
detailPanel.buildDrawingCache(); //may be you need to add this?
}
I'm just trying to figure out the best approach for running a scolling background on an android device. The method I have so far.... its pretty laggy. I use threads, which I believe is not the best bet for android platforms
#Override
public void run() {
// Game Loop
while(runningThread){
//Scroll background down
bgY += 1;
try {
this.postInvalidate();
t.sleep(10);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
where postinvalidate in the onDraw function simply pushings the background image down
canvas.drawBitmap(backgroundImage, bgX, bgY, null);
Thanks in advance
UPDATE
I've identified the problem. And it is the fact that my player updates the same rate as the background scrolls (making it look choppy). from top to bottom. This is because both get drawn in the same function. I'm not really sure how to tackle this and would be grateful for any help. i.e so that player movement is handled separately from the map scrolling
Also how can I control the speed at which onDraw(canvas) get called?
Thanks in advance.
However, I have patched together a different run loop for anyone having the same problem. This is partially from the jetboy example on google.
Below is my inner class in my surfaceview
class MapThread extends Thread{
private Map map;
private SurfaceHolder holder;
private boolean run = false;
public MapThread(Map map, SurfaceHolder holder){
this.holder = holder;
this.map = map;
setRunning(true);
}
public void setRunning(boolean run){
this.run = run;
}
#Override
public void run(){
while(run){
try{
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
map.onDraw(c);
}
} finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
}
}
SOLUTION
https://gamedev.stackexchange.com/questions/8127/android-game-scrolling-background
Use the SurfaceView implementation draw on the screen. It allows you more control of what to draw and when.
The SurfaceView is a special subclass of View that offers a dedicated drawing surface within the View hierarchy. The aim is to offer this drawing surface to an application's secondary thread, so that the application isn't required to wait until the system's View hierarchy is ready to draw.
The basic design is to have a surfaceview that draws continuously in a while loop. Then add an if-statement whose condition is to be true if a timer thread tells you its time to draw. Say, every 30ms, draw the bitmap. This will give you about 33 fps.
Now you may also have another timer thread that tells you when to update the the bgX or bgY values. Say at every 60ms, it will set a boolean updateFlag = true; Then in your main thread, you have an if-statement check this flag, set it to false, and update your bgX and bgY values. By accurately controlling the timer and the bgX/bgY increments, you should be able to produce smooth animations.
It would be a good idea to look at the LunarLander source code provided by Google.
One thing to keep in mind is that sleep is very inaccurate. To work around this, you can keep track of exactly how much time passed during the sleep and update how much you move things accordingly.
Its not clear from you code, but you need to make sure that all of your UI updates happen in the UI thread.
You do need to do your timing outside of the UI thread, because otherwise the UI will never update. There are other methods of timing, like using a Handler that can be a little bit cleaner, but I think the overhead on them might be a bit much for what you are trying to do. I think a simple thread has the least amount of overhead.
I am using this method on the second level of my SpaceQuestAlpha game. This makes a seemless scroll.
I used the 2 lines below to set original position.
moony=0;
moon2y=-(heighty);
Then these lines increment both versions of the background image. One starts at 0 and one starts at negative screen height. Every time one of the images goes below the bottom of the screen it is moved up twice the height to move it back into position. I am using surface view with no latency issues.
moony+=5;
moon2y+=5;
if(moon2y>=heighty) {moon2y=moon2y-(heighty*2);}
canvas.drawBitmap(lavabackground, 0, moon2y, null);
if(moony>=heighty){moony=moony-(heighty*2);}
canvas.drawBitmap(lavabackground, 0, moony, null);
I would like to know what the best way would be to draw a background image (this would be pretty much static) and a foreground object which would move depending on user input in Android?
Sort of like a side-scroller game (when the foregroudn object needs to animate and change a lot more than the background).
TIA
You could set the background image as main view with setContentView. For drawing foreground image You could use custom class that extends View and do drawing in it "onDraw" method. Something like this:
class ForegroundImage extends View
{
public Foreground(Context ctx) {}
public void onDraw(Canvas c)
{
//here You draw image anything You want on canvas
}
}
ImageView lBackgroundImage = (ImageView)findViewById(R.id.BackgroundView);
setContentView(lBackgrounImage);
ForegroundImage lForegroundImage = new ForegroundImage(this);
addContentView (lForegroundImage);
I hope this helped
I'm new to Android.
I am drawing bitmaps, lines and shapes onto a Canvas inside the OnDraw(Canvas canvas) method of my view. I am looking for help on how to implement smooth scrolling in response to a drag by the user. I have searched but not found any tutorials to help me with this.
The reference for Canvas seems to say that if a Canvas is constructed from a Bitmap (called bmpBuffer, say) then anything drawn on the Canvas is also drawn on bmpBuffer. Would it be possible to use bmpBuffer to implement a scroll ... perhaps copy it back to the Canvas shifted by a few pixels at a time? But if I use Canvas.drawBitmap to draw bmpBuffer back to Canvas shifted by a few pixels, won't bmpBuffer be corrupted? Perhaps, therefore, I should copy bmpBuffer to bmpBuffer2 then draw bmpBuffer2 back to the Canvas.
A more straightforward approach would be to draw the lines, shapes, etc. straight into a buffer Bitmap then draw that buffer (with a shift) onto the Canvas but so far as I can see the various methods: drawLine(), drawShape() and so on are not available for drawing to a Bitmap ... only to a Canvas.
Could I have 2 Canvases? One of which would be constructed from the buffer bitmap and used simply for plotting the lines, shapes, etc. and then the buffer bitmap would be drawn onto the other Canvas for display in the View?
I should welcome any advice!
Answers to similar questions here (and on other websites) refer to "blitting". I understand the concept but can't find anything about "blit" or "bitblt" in the Android documentation. Are Canvas.drawBitmap and Bitmap.Copy Android's equivalents?
I seem to have found an answer. I have put the bulk of the drawing code (which was previously in onDraw()) in a new doDrawing() method. This method starts by creating a new bitmap larger than the screen (large enough to hold the complete drawing). It then creates a second Canvas on which to do the detailed drawing:
BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
Canvas BufferCanvas = new Canvas(BufferBitmap);
The rest of the doDrawing() method is taken up with detailed drawing to BufferCanvas.
The entire onDraw() method now reads as follows:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
}
The position variables, posX and posY, are initialised at 0 in the application's onCreate()method. The application implements OnGestureListener and uses the distanceX and distanceY arguments returned in the OnScroll notification to increment posX and posY.
That seems to be about all that's needed to implement smooth scrolling. Or am I over-looking something!?
I had this problem too,
I did the drawing like this:
Canvas BigCanvas = new Canvas();
Bitmap BigBitmap = new Bitmap(width,height);
int ScrollPosX , ScrollPosY // (calculate these with the onScrollEvent handler)
void onCreate()
{
BigCanvas.SetBitmap(BigBitmap);
}
onDraw(Canvas TargetCanvas)
{
// do drawing stuff
// ie. BigCanvas.Draw.... line/bitmap/anything
//draw to the screen with the scrolloffset
//drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
TargetCanvas.DrawBitmap(BigBitmap(new Rect(ScrollPosX,ScrollPosY,ScrollPosX + BigBitmap.getWidth(),ScrollPosY + BigBitmap.getHeight(),new Rect(0,0,ScreenWidth,ScreenHeight),null);
}
for smooth scrolling you'd need to make some sort of method that takes a few points after scrolling (i.e the first scroll point and the 10th) , subtract those and scroll by that number in a for each loop that makes it gradually slower ( ScrollAmount - turns - Friction ).
I Hope this gives some more insight.
Continuation of reply to Viktor ...
In fact, the situation is more complicated. Because the doDrawing process is quite slow (taking 2-3 seconds on my slow old HTC Hero phone) I found it desirable to pop up a Toast message to advise the user that it was happening and to indicate the reason. The obvious way to do this was to create a new method containing just 2 lines:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");`
doDrawing();
}
and to call this method from other places in my program instead of doDrawing().
However, I found that the Toaster either never appeared or flashed up so briefly that it could not be read. My workaround has been to use a time check Handler to force the program to sleep for 200 milliseconds between displaying the Toast and calling doDrawing(). Although this slightly delays the start of a redraw I feel this is a price worth paying in terms of the program's usability because the user knows what is going on.
reDrawBuffer() now reads:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");
mTimeCheckHandler.sleep(200);
}`
and the Handler code (which is nested within my View class) is:
private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();
class timeCheckHandler extends Handler {
#Override
public void handleMessage(Message msg) {
doDrawing();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
}`
No need for the activity to be restarted! (Per prepbgg's Jan 27 10 reply to his Jan 17 10 'answer') Rather than recycling the bitmap and incurring the overhead of having the activity reloaded, you can avoid having the application loaded by putting the 'android:configChanges' attribute shown below, in the 'activity' element of the AndroidManifest.xml file for the app. This tells the system the the app will handle orientation changes and that it doesn't need to restart the app.
<activity android:name=".ANote"
android:label="#string/app_name"
android:configChanges="orientation|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This method can be used to get a notification when the orienation is changed:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
prt("onConfigurationChanged: "+newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
prt(" PORTRAIT");
} else {
prt(" LANDSCAPE");
}
} // end of onConfigurationChanged
prepbgg: I don't think the code will work because canvas.drawBitmap does not draw into the bitmap but draws the bitmap on-to the canvas.
Correct me if I am wrong!