在Android上使用布局资源制作动态壁纸

5
当您在Android 2.2+中创建LiveWallpaper时,您会获得一个画布(或3D等效物)来进行绘制。我希望使用内置的Android UI工具绘制一些元素,而不是使用画布命令或加载预渲染的UI位图从头开始构建所有内容。
将单个视图转换为位图可以正常工作。例如,以下内容非常有效:
// 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);

但是,转换带有子元素的LinearLayout会引起问题。您只会得到LinearLayout本身,而没有它的任何子元素。例如,如果我将LinearLayout设置为白色背景,我会得到一个漂亮渲染的白色框,但是其中的TextView子元素都不在位图中。我还尝试使用DrawingCache,结果类似。

我正在使用的代码是立方体示例,唯一的更改是额外的绘制命令。LinearLayout作为toast或常规视图正常工作(即所有内容都很好地显示出来),但在LiveWallpaper上,我只能看到LinearLayout的背景。

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);

有没有人知道如何让我的子项正确地渲染到位图中?也就是说,我是否需要采取某些特殊措施来使布局呈现其余的子项?我是否应该编写一个递归函数来对所有子级执行某些操作?

我可以自己合成所有内容,但由于显示区域相当静态(即我只需绘制一次,并保存位图的副本以在背景上绘制),这似乎更容易实现且仍然非常高效。

编辑: 在进一步研究布局状态时,看起来布局未沿着视图树向下进行(即LinearLayout在调用layout()时计算其布局,但子项的大小为null(0x0))。 根据Romain Guy在2008年的文章中的说法android developer post,您必须等待布局通行证或强制自己布局。问题在于,我如何在不连接到根视图组的LinearLayout上等待布局通行证?如果布局需要在我不知道这些值应该是什么的情况下设置左、上、右、下,我如何手动布置每个子元素。

我尝试调用子项上的forceLayout,但似乎也没有帮助。我不确定布局框架在幕后是如何工作的(除了它执行两个传递的布局)。是否有一种方法可以手动使其进行布局通行证,即立即?由于它不是Activity,我认为许多正常的后台操作并不完全按照我想要的方式进行。


你实现了建议的解决方案吗?我正在评估相同的方法,但想从你的经验中学习。 - dparnas
2个回答

11

动态壁纸是专门设计的,不使用标准的UI控件。然而,您仍然可以使用它们。您需要先调用视图上的measure()方法,然后调用layout()方法来自行强制进行布局。您可以在我的演示文稿中找到更多信息。


14
@JustinBuser,我是Android团队上设计和实现动态壁纸的工程师之一。你提到的那些类与我们正在讨论的标准UI小部件无关。问题在于你无法直接向动态壁纸中添加视图(使用setContentView()、addView()等方法)。例如,ListView。我提供的演示文稿是关于如何手动测量和布局视图的。 - Romain Guy
1
该演示介绍了如何在视图上调用measure()和layout()方法。一旦视图被测量和布局,它就可以被绘制到画布上,例如在动态壁纸上。 - Romain Guy

3
这是一个在Live Wallpapers中布局和显示的视图组、按钮和ImageView的示例。如果将窗口类型设置为0,则还可以通过WindowManager直接添加视图,从而解决空窗口令牌错误。您必须捕获它抛出的异常,结果有些不稳定,但大部分情况下都能正常工作。
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 )
                    {
                    }

            }

    }

这就是为什么我们不能拥有好东西的原因... "你必须捕获它抛出的异常,结果有些不稳定,但大部分情况下它能工作。" 真的吗? - Dan
5
我的回答主要是对另一个说法的回应:“动态壁纸非常明确地设计成不使用标准的用户界面小部件”,这是一个荒谬且误导性的回答。这就像说飞机“特别设计为不能潜水”一样。我的观点基本上是,仅仅因为你没有使某物防水并不意味着它不能实现。我花了15分钟写那个示例代码证明它实际上是可以实现的,但在证明我的观点之后,我并没有真的想倾力排除所有错误,因此警告可能会有错误出现。 - Justin Buser
这个解决方案真的有效!:D 代码有点乱,缺少一些代码,但确实起作用了。不过我有一个问题。我有一个子类扩展GLSurfaceView,其中渲染了一个3D对象,我该如何使它们同时显示,其中3D对象在后台,ImageView在顶部?谢谢! - ponnex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接