В новой версии Android появилась возможность создавать интерактивные заставки. Когда устройство находится в режиме ожидания или заряжается, оно может отображать фотографии из галереи, данные из интернета (новости, фотографии и так далее) или просто анимацию. Для активации заставки нужно в настройках, в категории Дисплей выбрать заставку и указать, когда ее отображать, к примеру во время зарядки устройства.
Архитектура 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.
Автор: ZZnOB