В новой версии Android появилась возможность создавать интерактивные заставки. Когда устройство находится в режиме ожидания или заряжается, оно может отображать фотографии из галереи, данные из интернета (новости, фотографии и так далее) или просто анимацию. Для активации заставки нужно в настройках, в категории Дисплей выбрать заставку и указать, когда ее отображать, к примеру во время зарядки устройства.
Каждая реализация заставки является подклассом android.service.dreams.DreamService. Когда вы создаете DreamService, вы получаете доступ к API похожему на API жизненного цикла Activity. Основные методы DreamService для переопределения в подклассе (не забудьте вызвать в реализации суперкласса):
Важные методы DreamService, которые вы можете вызывать:
Для создания заставки, необходимо создать
Тэг
Следующий код, создает классическую заставку с прыгающим логотипом. Анимация реализована с помощью TimeAnimator, что дает эффект гладкой анимации.

Создадим новый проект с минимальной версией SDK – 17. Отредактируем AndroidManifest.xml, указав ему, что мы хотим сделать заставку.
Главный класс нашей заставки будет выглядеть следующим образом:
Так же нам нужно добавить два класса. Первый будет устанавливать URL адрес страницы, которая отобразится в нашей заставке. Второй, делать то же самое что первый плюс делать нашу заставку интерактивной.
Итак, первый класс – SetURL.java:
И второй – SetURLInteractive.java:
Файл стиля, в данном случае, не содержит ничего кроме WebView:
Последним штрихом, будет создание html файла default.html в папке assets. В нем напишем, приветственное сообщение и небольшие инструкции.
Вот в принципе и все, можно компилировать и тестировать.
В следующей статье, планирую поведать о создании заставок при помощи libGDX.
Архитектура Daydream
Каждая реализация заставки является подклассом android.service.dreams.DreamService. Когда вы создаете DreamService, вы получаете доступ к API похожему на API жизненного цикла Activity. Основные методы DreamService для переопределения в подклассе (не забудьте вызвать в реализации суперкласса):
- onAttachedToWindow() — используется для начальной установки, также как вызов setContentView()
- onDreamingStarted() — вызывается, когда создается и отображается окно Daydream, теперь можно запускать анимацию.
- onDreamingStopped() — останавливает анимацию.
- onDetachedFromWindow() – удаляет все, что было создано в onAttachedToWindow().
Важные методы DreamService, которые вы можете вызывать:
- setContentView() – устанавливает сцену Daydream, ведет себя точно так же как setContentView() в активити. Включает использование MATCH_PARENT для отображения ширины и высоты.
- setInteractive(boolean) – по умолчанию, Daydream закроется, когда пользователь коснется экрана. Если нужно, чтобы пользователь взаимодействовал с заставкой, используем setInteractive(true).
- setFullscreen(boolean) – удобный метод, скрывающий статусбар.
- setScreenBright(boolean) – по умолчанию, заставка устанавливает полную яркость дисплея. setScreenBrith(false) – установит яркость на минимум.
Для создания заставки, необходимо создать
service в файле манифеста:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.app"> <uses-sdk android:targetSdkVersion="17" android:minSdkVersion="17" /> <application> <service android:name=".TestDaydream" android:exported="true" android:label="@string/my_daydream_name"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.service.dream" android:resource="@xml/dream_info" /> </service> </application> </manifest>
Тэг
<meta-data> необязательный. Он позволяет указать на ресурс XML в котором, описаны параметры, характерные для Daydream. Пользователь имеет к ним доступ, для этого он должен нажать на значок настроек рядом с названием заставки.<!-- res/xml/dream_info.xml --> <?xml version="1.0" encoding="utf-8"?> <dream xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.example.app/.ExampleDreamSettingsActivity" />
Следующий код, создает классическую заставку с прыгающим логотипом. Анимация реализована с помощью TimeAnimator, что дает эффект гладкой анимации.

public class TestDreamer extends DreamService { @Override public void onDreamingStarted() { super.onDreamingStarted(); // Our content view will take care of animating its children. final Bouncer bouncer = new Bouncer(this); bouncer.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); bouncer.setSpeed(200); // pixels/sec // Add some views that will be bounced around. // Here I'm using ImageViews but they could be any kind of // View or ViewGroup, constructed in Java or inflated from // resources. for (int i=0; i<5; i++) { final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); final ImageView image = new ImageView(this); image.setImageResource(R.drawable.android); image.setBackgroundColor(0xFF004000); bouncer.addView(image, lp); } setContentView(bouncer); } } public class Bouncer extends FrameLayout implements TimeAnimator.TimeListener { private float mMaxSpeed; private final TimeAnimator mAnimator; private int mWidth, mHeight; public Bouncer(Context context) { this(context, null); } public Bouncer(Context context, AttributeSet attrs) { this(context, attrs, 0); } public Bouncer(Context context, AttributeSet attrs, int flags) { super(context, attrs, flags); mAnimator = new TimeAnimator(); mAnimator.setTimeListener(this); } /** * Start the bouncing as soon as we’re on screen. */ @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mAnimator.start(); } /** * Stop animations when the view hierarchy is torn down. */ @Override public void onDetachedFromWindow() { mAnimator.cancel(); super.onDetachedFromWindow(); } /** * Whenever a view is added, place it randomly. */ @Override public void addView(View v, ViewGroup.LayoutParams lp) { super.addView(v, lp); setupView(v); } /** * Reposition all children when the container size changes. */ @Override protected void onSizeChanged (int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; for (int i=0; i<getChildCount(); i++) { setupView(getChildAt(i)); } } /** * Bouncing view setup: random placement, random velocity. */ private void setupView(View v) { final PointF p = new PointF(); final float a = (float) (Math.random()*360); p.x = mMaxSpeed * (float)(Math.cos(a)); p.y = mMaxSpeed * (float)(Math.sin(a)); v.setTag(p); v.setX((float) (Math.random() * (mWidth - v.getWidth()))); v.setY((float) (Math.random() * (mHeight - v.getHeight()))); } /** * Every TimeAnimator frame, nudge each bouncing view along. */ public void onTimeUpdate(TimeAnimator animation, long elapsed, long dt_ms) { final float dt = dt_ms / 1000f; // seconds for (int i=0; i<getChildCount(); i++) { final View view = getChildAt(i); final PointF v = (PointF) view.getTag(); // step view for velocity * time view.setX(view.getX() + v.x * dt); view.setY(view.getY() + v.y * dt); // handle reflections final float l = view.getX(); final float t = view.getY(); final float r = l + view.getWidth(); final float b = t + view.getHeight(); boolean flipX = false, flipY = false; if (r > mWidth) { view.setX(view.getX() - 2 * (r - mWidth)); flipX = true; } else if (l < 0) { view.setX(-l); flipX = true; } if (b > mHeight) { view.setY(view.getY() - 2 * (b - mHeight)); flipY = true; } else if (t < 0) { view.setY(-t); flipY = true; } if (flipX) v.x *= -1; if (flipY) v.y *= -1; } } public void setSpeed(float s) { mMaxSpeed = s; } }
Попробуем создать что-нибудь своими руками.
Создадим новый проект с минимальной версией SDK – 17. Отредактируем AndroidManifest.xml, указав ему, что мы хотим сделать заставку.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.habrdreamer" > <!-- uses-permission android:name="android.permission.WRITE_SETTINGS" --> <uses-permission android:name="android.permission.INTERNET" /> <uses-sdk android:minSdkVersion="17" android:maxSdkVersion="17" android:targetSdkVersion="17"/> <application android:label="HabrDreamer"> <service android:name="ScreensaverHabr" android:exported="true" android:label="ScreensaverHabr"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> <activity android:name="SetURL" android:label="Dream URL" android:theme="@android:style/Theme.Translucent.NoTitleBar"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity> <activity android:name="SetURLInteractive" android:label="Dream URL (Interactive)" android:theme="@android:style/Theme.Translucent.NoTitleBar"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity> </application> </manifest>
Главный класс нашей заставки будет выглядеть следующим образом:
package com.example.habrdreamer; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.graphics.PorterDuff; import android.net.Uri; import android.os.BatteryManager; import android.os.Handler; import android.provider.Settings; import android.service.dreams.DreamService; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.widget.TextView; public class ScreensaverHabr extends DreamService { private class LinkLauncher extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); return true; } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); setContentView(R.layout.screensaver_habr); setFullscreen(true); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final String url = prefs.getString("url", "http://habrahabr.ru"); final boolean interactive = prefs.getBoolean("interactive", false); Log.v("WebViewDream", String.format("loading %s in %s mode", url, interactive ? "interactive" : "noninteractive")); setInteractive(interactive); WebView webview = (WebView) findViewById(R.id.webview); webview.setWebViewClient(new LinkLauncher()); WebSettings webSettings = webview.getSettings(); webSettings.setJavaScriptEnabled(true); webview.loadUrl(url); } }
Так же нам нужно добавить два класса. Первый будет устанавливать URL адрес страницы, которая отобразится в нашей заставке. Второй, делать то же самое что первый плюс делать нашу заставку интерактивной.
Итак, первый класс – SetURL.java:
package com.example.habrdreamer; import android.util.Log; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.widget.Toast; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.widget.Toast; public class SetURL extends Activity { @Override public void onCreate(Bundle stuff) { super.onCreate(stuff); final Intent intent = getIntent(); final String action = intent.getAction(); String url = intent.getStringExtra(Intent.EXTRA_TEXT); if (url == null) { finish(); } else if (Intent.ACTION_SEND.equals(action)) { set(url); finish(); } } protected void set(String url) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final Editor editor = prefs.edit(); editor.putString("url", url); editor.putBoolean("interactive", false); editor.commit(); Toast.makeText(this, "WebView dream URL set to: " + url, Toast.LENGTH_SHORT).show(); } }
И второй – SetURLInteractive.java:
package com.example.habrdreamer; import android.util.Log; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.widget.Toast; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.widget.Toast; public class SetURLInteractive extends Activity { @Override public void onCreate(Bundle stuff) { super.onCreate(stuff); final Intent intent = getIntent(); final String action = intent.getAction(); String url = intent.getStringExtra(Intent.EXTRA_TEXT); if (url == null) { finish(); } else if (Intent.ACTION_SEND.equals(action)) { set(url); finish(); } } protected void set(String url) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final Editor editor = prefs.edit(); editor.putString("url", url); editor.putBoolean("interactive", true); editor.commit(); Toast.makeText(this, "WebView dream URL set to: " + url, Toast.LENGTH_SHORT).show(); } }
Файл стиля, в данном случае, не содержит ничего кроме WebView:
<?xml version="1.0" encoding="utf-8"?> <WebView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" > </WebView>
Последним штрихом, будет создание html файла default.html в папке assets. В нем напишем, приветственное сообщение и небольшие инструкции.
<html> <head> <style> body { background-color: black; color: gray; font-size: 16pt; font-family: "Roboto-Light", Roboto, sans-serif; text-align: center; } #text { margin-left: auto; margin-right: auto; margin-top: 96pt; } </style> </head> <body> <div id="text"> Используйте опцию вашего браузера “Отправить страницу (Share)”, чтобы выбрать страницу для отображения в заставке. </div> </body> </html>
Вот в принципе и все, можно компилировать и тестировать.
Полезные ссылки:
- DreamService API
- BounceDreamer — Полный исходный код заставки с прыгающим логотипом.
- Исходный код заставки с WebView
- Заставка демонстрирующая работу с OpenGL ES 2.0 и TextureView
В следующей статье, планирую поведать о создании заставок при помощи libGDX.
