阅读之前
- 建议 下载使用 Style动态壁纸 应用
- 文章后面会给出相应引用的链接
Android动态壁纸
动态壁纸是Android主屏幕中,可以动的、交互的背景。自Android 2.1开始支持。例如双击屏幕(Style中双击屏幕壁纸会变清晰)。相关的api在 android.service.wallpaper 包中。
动态壁纸应用实际上和其他应用是很相似的。下面我们一步一步来学习怎么创建一款动态壁纸应用。最终的实现效果如下:
如何创建动态壁纸应用
要创建壁纸应用,首先你需要在 /res/xml 文件夹下面创建一个XML文件。这个文件包含了这个应用的描述、图标、以及应用指定的壁纸设置页面等。在壁纸设置页面会显示这些信息(右下角)。
同时,你也需要创建一个 Service ,继承自 WallpaperService 类。 WallpaperService 这个类是系统所有动态壁纸等基类。你必须实现 onCreateEngine() 方法,返回一个 android.service.wallpaper.WallpaperService.Engine 对象。这个对象处理动态壁纸生命周期中的事件,壁纸的动画和绘制。 Engine 类定义了一些生命周期方法,例如: onCreate() , onSurfaceCreated() , onVisibilityChanged() , onOffsetsChanged() , onTouchEvent() 和 onCommand() 。
另外,这个 Service 需要 android.permission.BIND_WALLPAPER 权限,它必须被注册到一个 IntentFilter 中,并且这个 IntentFilter 的action是 android.service.wallpaper.WallpaperService 。
打开壁纸设定的Intent
public void onClick(View view) {
Intent intent = new Intent(
WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
new ComponentName(this, MyWallpaperService.class));
startActivity(intent);
}
上代码
以下代码可以在 这里 找到。
创建一个新的Project,可以选择不要Activity。但是为了让用户直接跳转到壁纸设置页面,我们创建了一个 MainActivity 。让用户能够对我们提供的壁纸进行设置,我们再创建一个 SettingActivity 。
在 /res/xml 文件夹下创建 wallpaper.xml ,当然名字可以自取。包含如下内容。注意 android:settingsActivity 的值,是刚才创建的 SettingActivity 的包名,可能你需要修改。
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/normal_wallpaper_des"
android:settingsActivity="com.yalin.wallpaper.demo.SettingActivity"
android:thumbnail="@drawable/ic_launcher_round" />
这个文件包含了壁纸的描述和图标,同时包含一个设置页面(设置页面是可选的)。
这个文件会在 AndroidManifest.xml 中用到。
创建一个 NormalWallpaperService 类,暂时不用实现里面的方法。
public class NormalWallpaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return null;
}
}
同时在 AndroidManifest.xml 中声明它。
<service
android:name=".normal.NormalWallpaperService"
android:enabled="true"
android:label="@string/wallpaper"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter android:priority="1">
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/normal_wallpaper" />
</service>
我们还必须在 AndroidManifest.xml 中增加下面的代码:
<uses-feature
android:name="android.software.live_wallpaper"
android:required="true" >
</uses-feature>
到此我们的基本配置已经OK了。下来我们来一步步实现动态壁纸的绘制。
我们创建一个 MyPoint 类,用来存储我们绘制过的点。
public class MyPoint {
String text;
int x;
int y;
public MyPoint(String text, int x, int y) {
this.text = text;
this.x = x;
this.y = y;
}
}
在 /res/xml 文件夹下创建 prefs.xml 。用于对动态壁纸的设置。
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="touch"
android:title="Enable Touch" />
<EditTextPreference
android:key="numberOfCircles"
android:title="Number of Circles" />
</PreferenceScreen>
在我们创建的 SettingActivity 中增加如下代码:
public class SettingActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs01);
// add a validator to the "numberofCircles" preference so that it only
// accepts numbers
Preference circlePreference = getPreferenceScreen().findPreference(
"numberOfCircles");
// add the validator
circlePreference.setOnPreferenceChangeListener(numberCheckListener);
}
/**
* Checks that a preference is a valid numerical value
*/
Preference.OnPreferenceChangeListener numberCheckListener =
new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// check that the string is an integer
if (newValue != null && newValue.toString().length() > 0
&& newValue.toString().matches("d*")) {
return true;
}
// If now create a message to the user
Toast.makeText(SettingActivity.this, "Invalid Input",
Toast.LENGTH_SHORT).show();
return false;
}
};
}
当然不能忘了在 AndroidManifest.xml 中注册。
<activity
android:name=".SettingActivity"
android:exported="true"
android:label="@string/app_name">
</activity>
在我们的壁纸 Service 即 WallpaperService 中,实现 Engine 。完整代码可以看 这里 。
public class NormalWallpaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new MyWallpaperEngine();
}
private class MyWallpaperEngine extends Engine {
private final Handler handler = new Handler();
private final Runnable drawRunner = new Runnable() {
@Override
public void run() {
draw();
}
};
......
最后我们在 MainActivity 中,增加按钮让用户跳转到壁纸设置页面。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void setting(View view) {
Intent intent = new Intent(
WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
new ComponentName(this, NormalWallpaperService.class));
startActivity(intent);
}
public void customSetting(View view) {
startActivity(new Intent(this, SettingActivity.class));
}
}
这样,我们的第一个壁纸应用创建好了。每秒钟会随机画一个圆。并且支持自定义设置,可以设置圆的最大数量、是否支持触摸事件。
运行之后的效果图:
GLWallpaperService
GL就是OpenGL,它是一个高性能的二维和三维的图形绘制库。这里我不再详细的介绍,有兴趣的同学可以戳 这里 。
GLWallpaperService是早年(Android 2.2时期,为什么不是2.1?因为2.2开始支持OpenGL2.0)一位美国同学开发的,这位同学自发布了这一款开源项目之后在开源界就默默无闻了。当然,你不要觉得代码太老,没人维护。可是它就是那么的好用,而且没有问题。市面上的动态壁纸使用它的数不胜数。
为什么GLWallpaperService
知道什么是OpenGL,那么原因就很明了了。高性能、高性能、还是高性能。动态壁纸在主屏幕可见的时候就一直在绘制,那么用OpenGL是最适合不过了。
让我们开始吧
开始之前,需要我们重复上面创建动态壁纸的几个基本步骤。这里直接省略,同学们自己创建。
接下来重要的,当然是把代码拿过来。代码也是简单,就一个类,直接放到项目里就行了。 还是在这里 。可以看到代码的第一行写着 2008年 ,你没有看错。
现在我们需要实现里面的两个主要的类, Service 类和 GLSurfaceView.Renderer 类。这里的 Service 需要继承 GLWallpaperService ,它的行为和Android的 WallpaperService 类似,都是需要实现 onCreateEngine() 这个方法。但是为了使用OpenGL,我们需要返回一个 GLEngine 对象。 GLEngine 里面有一个 GLThread 对象,渲染操作都会在 GLThread 中执行,从而保证了高效。
还是上代码
我们还是由一个简单的demo开始,篇幅原因,我就用最简单的demo。
创建 MyRenderer 继承自 GLSurfaceView.Renderer 。逻辑很简单,就是用OpenGL画个背景。
public class MyRenderer implements GLSurfaceView.Renderer {
public void onDrawFrame(GL10 gl) {
// Your rendering code goes here
gl.glClearColor(0.2f, 0.4f, 0.2f, 1f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
/**
* Called when the engine is destroyed. Do any necessary clean up because
* at this point your renderer instance is now done for.
*/
public void release() {
}
}
创建 MyGLWallpaperService 继承自 GLWallpaperService 。
public class MyGLWallpaperService extends GLWallpaperService {
@Override
public Engine onCreateEngine() {
MyEngine engine = new MyEngine();
return engine;
}
private class MyEngine extends GLEngine {
MyRenderer renderer;
public MyEngine() {
super();
// handle prefs, other initialization
renderer = new MyRenderer();
setRenderer(renderer);
setRenderMode(RENDERMODE_CONTINUOUSLY);
}
public void onDestroy() {
super.onDestroy();
if (renderer != null) {
renderer.release();
}
renderer = null;
}
}
}
demo创建好了,运行之前需要确保前面的基本配置都做好了。
接下来,我们着手实现最前面的效果。
先从 这里 拿到 Cube 类,放到工程中,它用OpenGL接口画出一个立方体,并且每一面都是一张 Bitmap 。具体怎么绘制的,有兴趣自己研究一下,这里不多介绍了。
创建一个 AdvanceRenderer 实现 GLSurfaceView.Renderer 。
public class AdvanceRenderer implements GLSurfaceView.Renderer {
private Cube cube;
private Context context;
private float z = -5.0f; // Depth Into The Screen
public AdvanceRenderer(Context context) {
this.cube = new Cube();
this.context = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glEnable(GL10.GL_LIGHT0); // Enable Light 0
// Blending
gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f); // Full Brightness. 50% Alpha ( NEW )
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); // Set The Blending Function For Translucency ( NEW )
gl.glDisable(GL10.GL_DITHER); // Disable dithering
gl.glEnable(GL10.GL_TEXTURE_2D); // Enable Texture Mapping
gl.glShadeModel(GL10.GL_SMOOTH); // Enable Smooth Shading
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
gl.glClearDepthf(1.0f); // Depth Buffer Setup
gl.glEnable(GL10.GL_DEPTH_TEST); // Enables Depth Testing
gl.glDepthFunc(GL10.GL_LEQUAL); // The Type Of Depth Testing To Do
// Really Nice Perspective Calculations
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
cube.loadGLTexture(gl, context);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
if (height == 0) { // Prevent A Divide By Zero By
height = 1; // Making Height Equal One
}
gl.glViewport(0, 0, width, height); // Reset The Current Viewport
gl.glMatrixMode(GL10.GL_PROJECTION); // Select The Projection Matrix
gl.glLoadIdentity(); // Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW); // Select The Modelview Matrix
gl.glLoadIdentity(); // Reset The Modelview Matrix
}
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity(); // Reset The Current Modelview Matrix
// Check if the light flag has been set to enable/disable lighting
gl.glEnable(GL10.GL_LIGHTING);
// Check if the blend flag has been set to enable/disable blending
gl.glEnable(GL10.GL_BLEND); // Turn Blending On ( NEW )
gl.glDisable(GL10.GL_DEPTH_TEST); // Turn Depth Testing Off ( NEW )
// Drawing
gl.glTranslatef(0.0f, 0.0f, z); // Move z units into the screen
// Scale the Cube to 80 percent, otherwise it would be too large for the screen
gl.glScalef(0.8f, 0.8f, 0.8f);
cube.draw(gl, 0);
}
/**
* Called when the engine is destroyed. Do any necessary clean up because
* at this point your renderer instance is now done for.
*/
public void release() {
}
}
代码中充斥着各种OpenGL的调用,看不懂没关系,简单理解成在绘制就行了。
接着,创建 AdvanceGLWallpaperService 继承自 GLWallpaperService 。
public class AdvanceGLWallpaperService extends GLWallpaperService {
@Override
public Engine onCreateEngine() {
return new AdvanceEngine();
}
private class AdvanceEngine extends GLEngine {
AdvanceRenderer renderer;
public AdvanceEngine() {
super();
renderer = new AdvanceRenderer(AdvanceGLWallpaperService.this);
setRenderer(renderer);
setRenderMode(RENDERMODE_CONTINUOUSLY);
}
public void onDestroy() {
super.onDestroy();
if (renderer != null) {
renderer.release();
}
renderer = null;
}
}
}
目前两个demo的Service基本没有什么区别,区别在于 Renderer 。运行代码,效果如下:
雏形已经出来了,可是它还不能跟着手势滚动。那么下面我们来处理触摸事件。
首先,我们需要在 AdvanceGLWallpaperService 中的 AdvanceEngine 中实现 onCreate(SurfaceHolder surfaceHolder) 方法,并且通过 setTouchEventsEnabled(true) 设置它能够接受触摸事件。 同时实现 onTouchEvent(MotionEvent event) 方法来处理触摸事件。
private class AdvanceEngine extends GLEngine {
AdvanceRenderer renderer;
public AdvanceEngine() {
super();
renderer = new AdvanceRenderer(AdvanceGLWallpaperService.this);
setRenderer(renderer);
setRenderMode(RENDERMODE_CONTINUOUSLY);
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
// Add touch events
setTouchEventsEnabled(true);
}
@Override
public void onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
renderer.onTouchEvent(event);
}
@Override
public void onDestroy() {
super.onDestroy();
if (renderer != null) {
renderer.release();
}
renderer = null;
}
}
触摸事件我们是交给 Renderer 处理的。 Renderer 中的实现如下:
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
// If a touch is moved on the screen
if (event.getAction() == MotionEvent.ACTION_MOVE) {
// Calculate the change
float dx = x - oldX;
float dy = y - oldY;
// Define an upper area of 10% on the screen
int upperArea = 0;
// Zoom in/out if the touch move has been made in the upper
if (y < upperArea) {
z -= dx * TOUCH_SCALE / 2;
// Rotate around the axis otherwise
} else {
xrot += dy * TOUCH_SCALE;
yrot += dx * TOUCH_SCALE;
}
}
// Remember the values
oldX = x;
oldY = y;
// We handled the event
return true;
}
可以看到, Renderer 中仅仅是通过触摸的位置设置了它的一些变量。前面说过动态壁纸会不停的绘制,因此在不断根据这些变量进行绘制,变量一改变,绘制的位置、方向等等就改变了,从而达到了动态的效果。用户看来就是跟着自己的手势动了起来。
另外,上一个demo中我们绘制时没有对这些变量进行处理,现在我们加上两句代码。
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity(); // Reset The Current Modelview Matrix
// Check if the light flag has been set to enable/disable lighting
gl.glEnable(GL10.GL_LIGHTING);
// Check if the blend flag has been set to enable/disable blending
gl.glEnable(GL10.GL_BLEND); // Turn Blending On ( NEW )
gl.glDisable(GL10.GL_DEPTH_TEST); // Turn Depth Testing Off ( NEW )
// Drawing
gl.glTranslatef(0.0f, 0.0f, z); // Move z units into the screen
// Scale the Cube to 80 percent, otherwise it would be too large for the screen
gl.glScalef(0.8f, 0.8f, 0.8f);
// Rotate around the axis based on the rotation matrix (rotation, x, y, z)
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f); // X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f); // Y
cube.draw(gl, 0);
}
跟前面的对比发现,还真只加了两句代码。聪明的你能看出是哪两句么?
运行效果:
What fk
同学们可能会问,最上面的效果是不需要触摸就自动动的,现在的效果不一样啊。
其实仔细想一想,触摸我们都解决了,自动的难道会难么?这个就当留了个课后作业给大家。
提示:有几句代码为给注释掉了。
结论
前一篇文章 讲述Android的架构方面的知识,很多同学说根本看不懂。想当年我语文高考87分,差三分及格,以后我们还是多上代码吧。
当然写这篇文章的目的不是为了让大家都去写动态壁纸应用,因为已经有一款非常优秀的了,没错,那就是 Style , Style , Style 。
这是一个典型的OpenGL应用场景,通过这篇文章大家也能对动态壁纸开发有一定的了解。我更希望的是,大家能动手将代码跑起来,动手的过程就是强化学习的过程。
引用
OpenGL (需科学上网)
Android动态壁纸支持 (需科学上网)
感谢各位,感谢开源!
来自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0725/8242.html
扩展阅读
Android 开发中的日常积累Android Launcher开发之动态壁纸(LiveWallPaper)气泡流动效果
一份旨在帮助 Android 初学者快速入门以及找到适合自己学习的资料
上百个Android开源项目分享
使用最新最酷的Android开发技术
为您推荐
Android的3D引擎:Rajawali[Android技术专题]应用开发进阶必经之路之性能优化
Android应用: 3D旋转球
Android代码速查,写给新手的朋友们
在Android应用中实现3D圆柱体
更多
安卓开发Android
Android开发