Поиск по этому блогу

пятница, 6 февраля 2015 г.

Живые обои с GIF анимацией

Сегодня я хочу вам рассказать как сделать красоту и не набраться гемороя. Обычно живые обои делают какая то фоновая картинка, какой-то живой движущийся объект и летают или появляются кружки или линии. Это все можно сделать стандартным канвасом, а вот как сделать что бы вместо тупых летающих квадратов на экране бегала анимация с nyan котом или например анимация с горящим костром? А просто, нужно всего лишь заюзать тот же канвас, только немного не стандартно.

Как создавать живые обои я рассказывал вам раньше еще в далеком феврале 2012 года. Вот эта статья Создаем живые обои для Android. Но это было давно, да и там рассматривается немного иная ситуация, кому интересно переходите смотрите, правда там почему то картинки убились… Не знаю как восстановить, в исходниках все есть в принципе :)

И так начнем, мы сделаем простые живые обои в которых у нас будет бегать по экрану nyan котэ и все, их можно будет установить как фоновую картинку и как бы все. Никаких настроек и так далее не будет ибо нет смысла, это я рассматривал в предыдущей статье про живые обои.

Вот такая красота будет бегать по экрану после того как мы закончим


Создайте пустой проект без активностей и леяутов. Будем делать все с нуля, нам нужно написать класс наследник WallpaperService который будет нашим проводником в работе с живыми обоями.

Засуньте картинку в папку res/raw/, в дальнейшем мы будем ее брать оттуда.

Создайте файл \ класс VideoLiveWallpaperService, ну или назовите как то по своему, не важно. И впишите следующий код который будет собственно отображать сами обои, растягивать и проигрывать анимацию в них же.

VideoLiveWallpaperService.java
import android.graphics.Canvas;
import android.graphics.Movie;
import android.os.Handler;
import android.os.SystemClock;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.SurfaceHolder;

import java.io.IOException;
import java.io.InputStream;

public class VideoLiveWallpaperService extends WallpaperService {

    static final String TAG = "NYAN";
    static final Handler mNyanHandler = new Handler();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    //создаем вью которое будет отображать нашу анимацию
    @Override
    public Engine onCreateEngine() {
        try {
            return new NyanEngine();
        } catch (IOException e) {
            Log.w(TAG, "Error creating NyanEngine", e);
            stopSelf();
            return null;
        }
    }

    //само класс с анимауией
    class NyanEngine extends Engine {
        //объявляем переменные, Moview класс для отображения
        //анимаций как вы сам я думаю поняли. Остальные переменные
        //объявлялись для обределения продолжительности проигрыша
        //для runnuble для потока, дальше размер и начало и конец старта проигрыша
        private final Movie mNyan;
        private final int mNyanDuration;
        private final Runnable mNyanNyan;
        float mScaleX;
        float mScaleY;
        int mWhen;
        long mStart;

        // открываем файл из raw папки, декодируем его и смотрим насколько он
        //длительный по проигрышу
        NyanEngine() throws IOException {
            InputStream is = getResources().openRawResource(R.raw.nyan);
            if (is != null) {
                try {
                    mNyan = Movie.decodeStream(is);
                    mNyanDuration = mNyan.duration();
                } finally {
                    is.close();
                }
            } else {
                throw new IOException("Unable to open R.raw.nyan");
            }

            mWhen = -1;
            mNyanNyan = new Runnable() {
                public void run() {
                    nyan();
                }
            };
        }

        //уничтожаем поток если выходят из обоев
        @Override
        public void onDestroy() {
            super.onDestroy();
            mNyanHandler.removeCallbacks(mNyanNyan);
        }

        //если экран включен то запускаем, если выключен то тормазим
        //и ждем когда включится снова
        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);
            if (visible) {
                nyan();
            } else {
                mNyanHandler.removeCallbacks(mNyanNyan);
            }
        }

        //создаем объект сюрфейса для отображения гифки, растягиваем ее во весь экран
        // и запускаем на экране
        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
            mScaleX = width / (1f * mNyan.width());
            mScaleY = height / (1f * mNyan.height());
            nyan();
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
                                     float yOffsetStep, int xPixelOffset, int yPixelOffset) {
            super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset);
            nyan();
        }

        //класс запускающий анимацию пройгрыша гифки
        void nyan() {
            //частота пройгрыша анимации циклим все.
            tick();
            //создаем сюрфейс с канвасом, в принципе как и в статьях в которых
            // мы созадвали игру, пока все стандартно
            SurfaceHolder surfaceHolder = getSurfaceHolder();
            Canvas canvas = null;
            try {
                canvas = surfaceHolder.lockCanvas();
                if (canvas != null) {
                    //запускаем отрисовку с помощью канвы
                    nyanNyan(canvas);
                }
             //по завершению удаляем все к чертовой бабушке
             // если это когда нибудь случиться конечно
            } finally {
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
            mNyanHandler.removeCallbacks(mNyanNyan);
            if (isVisible()) {
                // 25 - количество фпс, и в целом это количество фпс за секунду
                // то есть 1000 это секунда 1 секунда делим на 25 кадров
                mNyanHandler.postDelayed(mNyanNyan, 1000L/25L);
            }
        }

        //таймер для заасечения времени проигрыша
        void tick() {
            if (mWhen == -1L) {
                mWhen = 0;
                mStart = SystemClock.uptimeMillis();
            } else {
                long mDiff = SystemClock.uptimeMillis() - mStart;
                mWhen = (int) (mDiff % mNyanDuration);
            }
        }

        // рисуем нашего кода на канвасе
        void nyanNyan(Canvas canvas) {
            canvas.save();
            //растягиваем на весь экран
            canvas.scale(mScaleX, mScaleY);
            //указываем частоту проигрыша
            mNyan.setTime(mWhen);
            //устанавливаем в левый верхний угол картинку и рисуем ее
            mNyan.draw(canvas, 0, 0);
            canvas.restore();
        }
    }
}

Прокомментировал как мог, надеюсь будет понятно. 

По сути мы просто рисуем картинку на Surface при помощи Canvas, проигрываем ее с помощью класса Movie который как раз предназначен именно для таких целей, растягиваем ее и задаем частоту проигрыша. Это если в кратце.

Но это еще не все, теперь нам нужно этот класс инициализировать в минифесте, для этого переходим в AndroidManifest и смотрим, сейчас у нас там нет ничего если вы не создавали активности, сейчас там пусто. Для того что бы наши обои отображались как обои, в манифейсте пишем следующее:

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <service
            android:name=".VideoLiveWallpaperService"
            android:label="@string/app_name"
            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/nyan" />
        </service>
    </application>

</manifest>

Очень важный момент, что бы обои были как обои и отображались как надо нам обязательно нужно прописать премишен.

android:permission="android.permission.BIND_WALLPAPER"

Без него не будет никакого счастья, а только непонятки, почему и зачем вообще мы это делаем и че оно не работает.

Ну а так по сути стандартно, объявили сервис и теперь оно будет работать в фоне постоянно пока мы это позволяем ему делать.

Да, хочу еще обратить внимание на эту строку:
<meta-data
                android:name="android.service.wallpaper"
                android:resource="@xml/nyan" />

Она у вас должна подкрашиваться красным, это файл который обязателен для живых обоев, но так как у нас там по сути ничего нет и мы рисуем программно на экране, этот файл у нас просто содержит ничего. 

Создайте папку res/xml/ и в ней файл nyan.xml. И впишите в него следующую разметку:

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

И все. Теперь можете запускать обои, заходите в меню с обоями и у вас там должны появиться ваши обои, как вы их назвали у себя в проекте.



Google Play

Исходники
DropBox && GitHub