Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Layout resources in a LiveWallpaper on Android

When you create a LiveWallpaper in Android 2.2+ you get a canvas (or whatever the 3D equivalent is) to draw on. I'd like to draw some elements using the built-in Android UI tools rather than building everything from scratch using canvas commands or a loading a pre-rendered UI bitmap.

Converting a single View to a Bitmap works fine. i.e. this works great:

// For example this works:
TextView view = new TextView(ctx);
view.layout(0, 0, 200, 100);
view.setText("test");
Bitmap b = Bitmap.createBitmap( 200, 100, Bitmap.Config.ARGB_8888);                
Canvas tempC = new Canvas(b);
view.draw(tempC);
c.drawBitmap(b, 200, 100, mPaint);

But, converting a LinearLayout with children causes problems. You only get the LinearLayout itself and none of it's children. For example, if I set the LinearLayout to have a white background I get a nicely rendered white box, but none of the TextView children are in the Bitmap. I've also tried using DrawingCache with similar results.

The code I'm using is the cube example with the only changes being an extra draw command. The LinearLayout works fine as a toast or as a regular view (i.e. everything nicely shows up), on the LiveWallpaper all I get is the LinearLayout's background rendered.

inflater = (LayoutInflater)getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (LinearLayout) inflater.inflate(com.example.android.livecubes.R.layout.testLinearLayout, null);
layout.layout(0, 0, 400, 200);

Bitmap b = Bitmap.createBitmap( 400, 200, Bitmap.Config.ARGB_8888);
Canvas tempC = new Canvas(b);
layout.draw(tempC);
c.drawBitmap(b, 10, 200, mPaint);

Does anyone know if you need to do anything special to get the children rendered properly to my bitmap? i.e. do I need to somehow do something special to make the layout render the rest of the children? Should I write a function to recursively do something to all the children?

I could composite everything myself but, since the display is fairly static (i.e. I draw this once and keep a copy of the bitmap to draw on the background) this seems easier on me and still pretty efficient.

Edit: While digging a bit more into the state of the Layout it looks as though the layout is not progressing down the view tree (i.e. the LinearLayout gets its layout computed when I call layout() but the children have a null (0x0) size). According to the Romain Guy's post in 2008 android developer post. You have to wait for the layout pass or force the layout yourself. The problem is how can I wait for a layout pass from a wall paper engine for a LinearLayout that is not attached to the root view group? And how can I manually layout each child element if layout requires you to set the left, top, right, bottom when I don't know what these should be.

I've tried calling forceLayout on the children but it doesn't seem to help either. I'm not sure how the layout framework works behind the scenes (besides that it does a two pass layout). Is there a way to manually make it do the layout pass, i.e. right now? Since it's not an Activity I don't think a lot of the normal background stuff is happening quite the way I'd like.

like image 550
William Avatar asked Nov 13 '10 21:11

William


2 Answers

Live Wallpapers were very specifically designed to NOT use standard UI widgets. However it is possible to use them anyway. You will have to force a layout pass yourself by first calling measure() on the View, then layout(). You can find more information in my presentation.

like image 142
Romain Guy Avatar answered Nov 01 '22 02:11

Romain Guy


Here's an example of a view group, button and imageview laid out and displayed in a Live Wallpapers. You can also work around the null window token bug and add views directly via WindowManager if you set the the Window type to 0. You have to catch the exception it throws and the results are somewhat erratic but it works for the most part.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class UIWidgetWallpaper extends WallpaperService
    {

        private final String    TAG         = getClass().getSimpleName();
        final static int        pixFormat   = PixelFormat.RGBA_8888;
        protected ImageView     imageView;
        protected WindowManager windowManager;
        protected LayoutParams  layoutParams;
        protected WidgetGroup   widgetGroup;
        protected SurfaceHolder surfaceHolder;
        protected Button        button;

        @Override
        public Engine onCreateEngine()
            {
                Log.i( TAG, "onCreateEngine" );
                return new UIWidgetWallpaperEngine();
            }

        public class WidgetGroup extends ViewGroup
            {

                private final String    TAG = getClass().getSimpleName();

                public WidgetGroup( Context context )
                    {
                        super( context );
                        Log.i( TAG, "WidgetGroup" );
                        setWillNotDraw( true );
                    }

                @Override
                protected void onLayout( boolean changed, int l, int t, int r, int b )
                    {
                        layout( l, t, r, b );
                    }

            }

        public class UIWidgetWallpaperEngine extends Engine implements Callback
            {

                private final String    TAG = getClass().getSimpleName();

                @Override
                public void onCreate( SurfaceHolder holder )
                    {
                        Log.i( TAG, "onCreate" );
                        super.onCreate( holder );
                        surfaceHolder = holder;
                        surfaceHolder.addCallback( this );
                        imageView = new ImageView( getApplicationContext() );
                        imageView.setClickable( false );
                        imageView.setImageResource( R.drawable.icon );
                        widgetGroup = new WidgetGroup( getApplicationContext() );
                        widgetGroup.setBackgroundDrawable( getWallpaper() );
                        widgetGroup.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ) );
                        widgetGroup.setAddStatesFromChildren( true );
                        holder.setFormat( pixFormat );
                        LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT );
                        imageParams.weight = 1.0f;
                        imageParams.gravity = Gravity.CENTER;
                        widgetGroup.addView( imageView, imageParams );
                        button = new Button( getApplicationContext() );
                        button.setText( "Test Button" );
                        LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT );
                        buttonParams.weight = 1.0f;
                        buttonParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
                        widgetGroup.addView( button, buttonParams );
                    }

                @Override
                public void surfaceChanged( SurfaceHolder holder, int format, int width, int height )
                    {
                        Log.i( TAG, "surfaceChanged: " );
                        synchronized( surfaceHolder )
                            {
                                Canvas canvas = surfaceHolder.lockCanvas();
                                widgetGroup.layout( 0, 0, width, height );
                                imageView.layout( 0, 0, width / 2, height );
                                button.layout( width / 2, height - 100, width, height );
                                widgetGroup.draw( canvas );
                                surfaceHolder.unlockCanvasAndPost( canvas );
                            }
                    }

                @Override
                public void surfaceCreated( SurfaceHolder holder )
                    {
                    }

                @Override
                public void surfaceDestroyed( SurfaceHolder holder )
                    {
                    }

            }

    }
like image 27
Justin Buser Avatar answered Nov 01 '22 04:11

Justin Buser