terça-feira, 5 de junho de 2012

Live Wallpaper

Live Wallpaper, ou Papel de Parede Animado conforme a tradução, foram introduzidos na versão 2.1 (API 7) do Android. Como o nome sugere, ele nada mais é do que um fundo que se mexe. Mas ao contrário do que se imagina ele pode ser muito mais poderoso do que se imagina. É possível fazer praticamente qualquer coisa que um aplicativo comum faz, inclusive interagir com toques do usuário.

Mas, como dizia Tio Ben, com grandes poderes vêm grandes responsabilidades. Lembre-se que o Live Wallpaper irá rodar boa parte do tempo, então este não deve fazer muitas operações pesadas para não consumir muitos dados ou energia do aparelho.


O exemplo mostrado aqui pode ser baixado clicando aqui.


O Live Wallpaper não passa de um Service que rodará infinitamente enquanto você manter o Live Wallpaper. Então vamos começar definindo isso no AndroidManifest:


<uses-feature android:name="android.software.live_wallpaper" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        
        <service
            android:label="@string/app_name"
            android:name=".ExempleWallpaper"
            android:permission="android.permission.BIND_WALLPAPER">
            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService" />
            </intent-filter>
            <meta-data android:name="android.service.wallpaper" android:resource="@xml/wall" />
        </service>
        
    </application>


Note o uses-feature que é necessário e a definição do Service. Assim como na definição de um app widget aqui também definimos um meta-data que está em xml/wall. Vamos cria-lo:


<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"/>


Só isso. Dentro dessa tag poderíamos definir android:settingsActivity que seria a Activity de configuração, semelhante a Activity de configuração do app widget, mas por enquanto não iremos fazer isso. A seguir vamos criar nosso service ExempleWallpaper propriamente dito, como definido no Manifest:

package com.gdacarv.tutoriandroid.livewallpaper;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class ExempleWallpaper extends WallpaperService {

    // Handler utilizado para redesenhar a tela
    private final Handler mHandler = new Handler();

    @Override
    public Engine onCreateEngine() {
        return new ExempleEngine();
    }

    class ExempleEngine extends Engine {

        // Frames Por Segundo
        private static final int FPS = 30;

        // Velocidade da animação
        private static final float SPEED = 3;
        
        // Utilizado quando a tela é movida
        private float mOffset;
        
        // Registra toques na tela
        private float mTouchX = -1;
        private float mTouchY = -1;
        
        
        private int height, width;
        
        private Bitmap mBitmap;
        private float x,y;
        private int dirHorizontal = 1, dirVertical = 1;

        private final Runnable mDrawCube = new Runnable() {
            public void run() {
                drawFrame();
            }
        };
        
        private boolean mVisible;

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);

            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
            //setTouchEventsEnabled(true); // Ativa eventos de toque (Atualmente desativados por default)
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacks(mDrawCube);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            mVisible = visible;
            if (visible) {
                drawFrame();
            } else {
                mHandler.removeCallbacks(mDrawCube);
            }
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
            // Salva largura e altura da tela
            this.width = width;
            this.height = height;
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            mVisible = false;
            mHandler.removeCallbacks(mDrawCube);
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset,
                float xStep, float yStep, int xPixels, int yPixels) {
            mOffset = xOffset;
        }

       
        @Override
        public void onTouchEvent(MotionEvent event) {
            // Guarda toques na tela. Só é executado se comando setTouchEventsEnabled(true) for executado previamente.
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                mTouchX = event.getX();
                mTouchY = event.getY();
            } else {
                mTouchX = -1;
                mTouchY = -1;
            }
            super.onTouchEvent(event);
        }

        void drawFrame() {
            final SurfaceHolder holder = getSurfaceHolder();

            Canvas c = null;
            try {
                c = holder.lockCanvas();
                if (c != null) {
                    mover();
                    onDraw(c);
                }
            } finally {
                if (c != null) holder.unlockCanvasAndPost(c);
            }

            // Agenda proximo re-desenho
            mHandler.removeCallbacks(mDrawCube);
            if (mVisible) {
                mHandler.postDelayed(mDrawCube, 1000 / FPS);
            }
        }

        private void mover() {
            // Move a imagem
            x += SPEED*dirHorizontal;
            y += SPEED*dirVertical;
            if(y < 0 || y > height-mBitmap.getHeight())
                dirVertical *= -1;
            if(x < 0 || x > width-mBitmap.getWidth())
                dirHorizontal *= -1;
        }

        private void onDraw(Canvas c) {
            // Limpa tela e desenha a imagem
            c.drawARGB(255, 0, 0, 0);
            c.drawBitmap(mBitmap, x, y, null);
        }

    }
}



O que importa nesse Service é a Engine. Estendemos a classe Engine para que possamos definir nossos próprios métodos. Note que Engine fornece métodos de criação, de alteração da Surface, de mudança (deslizamento) de tela, de toque na tela, entre outros. Você deve sempre fazer algo no onVisibilityChanged para que quando o wallpaper não estiver visível ele pause o seu funcionamento, assim economizando recursos.

Acesse a documentação oficial e esse exemplo para mais informações.


Qualquer dúvida é só perguntar.

3 comentários:

Heráclito Thiago at 28 de janeiro de 2013 13:13 disse...

E para usar um arquivo *swf ao invés de *bmp, qual o procedimento?

Gustavo Carvalho at 4 de fevereiro de 2013 11:43 disse...

@Heráclito: Creio que não seja possível usar swf em Live Wallpapers, talvez seja possível usar num aplicativo com WebView.

Alisson Monteiro at 22 de junho de 2013 01:16 disse...

Gustavo, descobrir seu blog hoje.

Na moral, seu blog e muito bom. E o melhor VC responde as duvidas de todos, muito bom mesmo.

Todos esses scripts são java? Como postei antes aguardo resposta sobre movimentos touch.

Eu estava fazendo meus experimentos com unity e parei por não conseguir esses scripts.
Tenho um xperia mini pro e consegui movimentos através do teclado físico, e não ficou legal.

Se eu conseguir, vou retornar os testes. Vlw

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog