Android动态壁纸解析

TomGrantham 3年前
   <h2>阅读之前</h2>    <ul>     <li>建议 <a href="/misc/goto?guid=4959750917859281067" rel="nofollow,noindex">下载使用</a> <em>Style动态壁纸</em> 应用</li>     <li>文章后面会给出相应引用的链接</li>    </ul>    <h2>Android动态壁纸</h2>    <p>动态壁纸是Android主屏幕中,可以动的、交互的背景。自Android 2.1开始支持。例如双击屏幕(Style中双击屏幕壁纸会变清晰)。相关的api在 android.service.wallpaper 包中。</p>    <p>动态壁纸应用实际上和其他应用是很相似的。下面我们一步一步来学习怎么创建一款动态壁纸应用。最终的实现效果如下:</p>    <p><img src="https://simg.open-open.com/show/d05c3cc17e6a019b80f70208a42af3f7.gif"></p>    <p>如何创建动态壁纸应用</p>    <p>要创建壁纸应用,首先你需要在 /res/xml 文件夹下面创建一个XML文件。这个文件包含了这个应用的描述、图标、以及应用指定的壁纸设置页面等。在壁纸设置页面会显示这些信息(右下角)。 <img src="https://simg.open-open.com/show/e24926230c09725c9410e4689699ab0f.png"></p>    <p>同时,你也需要创建一个 Service ,继承自 WallpaperService 类。 WallpaperService 这个类是系统所有动态壁纸等基类。你必须实现 onCreateEngine() 方法,返回一个 android.service.wallpaper.WallpaperService.Engine 对象。这个对象处理动态壁纸生命周期中的事件,壁纸的动画和绘制。 Engine 类定义了一些生命周期方法,例如: onCreate() , onSurfaceCreated() , onVisibilityChanged() , onOffsetsChanged() , onTouchEvent() 和 onCommand() 。</p>    <p>另外,这个 Service 需要 android.permission.BIND_WALLPAPER 权限,它必须被注册到一个 IntentFilter 中,并且这个 IntentFilter 的action是 android.service.wallpaper.WallpaperService 。</p>    <p>打开壁纸设定的Intent</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>上代码</p>    <p>以下代码可以在 <a href="/misc/goto?guid=4959750917947425728" rel="nofollow,noindex">这里</a> 找到。</p>    <p>创建一个新的Project,可以选择不要Activity。但是为了让用户直接跳转到壁纸设置页面,我们创建了一个 MainActivity 。让用户能够对我们提供的壁纸进行设置,我们再创建一个 SettingActivity 。</p>    <p>在 /res/xml 文件夹下创建 <em>wallpaper.xml</em> ,当然名字可以自取。包含如下内容。注意 android:settingsActivity 的值,是刚才创建的 SettingActivity 的包名,可能你需要修改。</p>    <pre>  <code class="language-java"><?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" /></code></pre>    <p>这个文件包含了壁纸的描述和图标,同时包含一个设置页面(设置页面是可选的)。</p>    <p>这个文件会在 AndroidManifest.xml 中用到。</p>    <p>创建一个 NormalWallpaperService 类,暂时不用实现里面的方法。</p>    <pre>  <code class="language-java">public class NormalWallpaperService extends WallpaperService {      @Override      public Engine onCreateEngine() {          return null;      }  }</code></pre>    <p>同时在 AndroidManifest.xml 中声明它。</p>    <pre>  <code class="language-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></code></pre>    <p>我们还必须在 AndroidManifest.xml 中增加下面的代码:</p>    <pre>  <code class="language-java"><uses-feature          android:name="android.software.live_wallpaper"          android:required="true" >  </uses-feature></code></pre>    <p>到此我们的基本配置已经OK了。下来我们来一步步实现动态壁纸的绘制。</p>    <p>我们创建一个 MyPoint 类,用来存储我们绘制过的点。</p>    <pre>  <code class="language-java">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;      }  }</code></pre>    <p>在 /res/xml 文件夹下创建 <em>prefs.xml</em> 。用于对动态壁纸的设置。</p>    <pre>  <code class="language-java"><?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></code></pre>    <p>在我们创建的 SettingActivity 中增加如下代码:</p>    <pre>  <code class="language-java">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;          }      };    }</code></pre>    <p>当然不能忘了在 AndroidManifest.xml 中注册。</p>    <pre>  <code class="language-java"><activity              android:name=".SettingActivity"              android:exported="true"              android:label="@string/app_name">  </activity></code></pre>    <p>在我们的壁纸 Service 即 WallpaperService 中,实现 Engine 。完整代码可以看 <a href="/misc/goto?guid=4959750918043914980" rel="nofollow,noindex">这里</a> 。</p>    <pre>  <code class="language-java">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();              }            };      ......</code></pre>    <p>最后我们在 MainActivity 中,增加按钮让用户跳转到壁纸设置页面。</p>    <pre>  <code class="language-java">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));      }  }</code></pre>    <p>这样,我们的第一个壁纸应用创建好了。每秒钟会随机画一个圆。并且支持自定义设置,可以设置圆的最大数量、是否支持触摸事件。</p>    <p>运行之后的效果图:</p>    <p><img src="https://simg.open-open.com/show/2446daedf0f4cb28c6661ece827fac5b.gif"></p>    <p>GLWallpaperService</p>    <p>GL就是OpenGL,它是一个高性能的二维和三维的图形绘制库。这里我不再详细的介绍,有兴趣的同学可以戳 <a href="/misc/goto?guid=4959750918121866631" rel="nofollow,noindex">这里</a> 。</p>    <p>GLWallpaperService是早年(Android 2.2时期,为什么不是2.1?因为2.2开始支持OpenGL2.0)一位美国同学开发的,这位同学自发布了这一款开源项目之后在开源界就默默无闻了。当然,你不要觉得代码太老,没人维护。可是它就是那么的好用,而且没有问题。市面上的动态壁纸使用它的数不胜数。</p>    <p>为什么GLWallpaperService</p>    <p>知道什么是OpenGL,那么原因就很明了了。高性能、高性能、还是高性能。动态壁纸在主屏幕可见的时候就一直在绘制,那么用OpenGL是最适合不过了。</p>    <p>让我们开始吧</p>    <p>开始之前,需要我们重复上面创建动态壁纸的几个基本步骤。这里直接省略,同学们自己创建。</p>    <p>接下来重要的,当然是把代码拿过来。代码也是简单,就一个类,直接放到项目里就行了。 <a href="/misc/goto?guid=4959750918206476478" rel="nofollow,noindex">还是在这里</a> 。可以看到代码的第一行写着 <em>2008年</em> ,你没有看错。</p>    <p>现在我们需要实现里面的两个主要的类, Service 类和 GLSurfaceView.Renderer 类。这里的 Service 需要继承 GLWallpaperService ,它的行为和Android的 WallpaperService 类似,都是需要实现 onCreateEngine() 这个方法。但是为了使用OpenGL,我们需要返回一个 GLEngine 对象。 GLEngine 里面有一个 GLThread 对象,渲染操作都会在 GLThread 中执行,从而保证了高效。</p>    <p>还是上代码</p>    <p>我们还是由一个简单的demo开始,篇幅原因,我就用最简单的demo。</p>    <p>创建 MyRenderer 继承自 GLSurfaceView.Renderer 。逻辑很简单,就是用OpenGL画个背景。</p>    <pre>  <code class="language-java">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() {      }  }</code></pre>    <p>创建 MyGLWallpaperService 继承自 GLWallpaperService 。</p>    <pre>  <code class="language-java">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;          }      }  }</code></pre>    <p>demo创建好了,运行之前需要确保前面的基本配置都做好了。</p>    <p>接下来,我们着手实现最前面的效果。</p>    <p>先从 <a href="/misc/goto?guid=4959750918299440672" rel="nofollow,noindex">这里</a> 拿到 Cube 类,放到工程中,它用OpenGL接口画出一个立方体,并且每一面都是一张 Bitmap 。具体怎么绘制的,有兴趣自己研究一下,这里不多介绍了。</p>    <p>创建一个 AdvanceRenderer 实现 GLSurfaceView.Renderer 。</p>    <pre>  <code class="language-java">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() {      }  }</code></pre>    <p>代码中充斥着各种OpenGL的调用,看不懂没关系,简单理解成在绘制就行了。</p>    <p>接着,创建 AdvanceGLWallpaperService 继承自 GLWallpaperService 。</p>    <pre>  <code class="language-java">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;          }      }  }</code></pre>    <p>目前两个demo的Service基本没有什么区别,区别在于 Renderer 。运行代码,效果如下:</p>    <p><img src="https://simg.open-open.com/show/46a0963d40c4a3825d01e5aefdd4a4c4.png"></p>    <p>雏形已经出来了,可是它还不能跟着手势滚动。那么下面我们来处理触摸事件。</p>    <p>首先,我们需要在 AdvanceGLWallpaperService 中的 AdvanceEngine 中实现 onCreate(SurfaceHolder surfaceHolder) 方法,并且通过 setTouchEventsEnabled(true) 设置它能够接受触摸事件。 同时实现 onTouchEvent(MotionEvent event) 方法来处理触摸事件。</p>    <pre>  <code class="language-java">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;          }      }</code></pre>    <p>触摸事件我们是交给 Renderer 处理的。 Renderer 中的实现如下:</p>    <pre>  <code class="language-java">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;      }</code></pre>    <p>可以看到, Renderer 中仅仅是通过触摸的位置设置了它的一些变量。前面说过动态壁纸会不停的绘制,因此在不断根据这些变量进行绘制,变量一改变,绘制的位置、方向等等就改变了,从而达到了动态的效果。用户看来就是跟着自己的手势动了起来。</p>    <p>另外,上一个demo中我们绘制时没有对这些变量进行处理,现在我们加上两句代码。</p>    <pre>  <code class="language-java">@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);      }</code></pre>    <p>跟前面的对比发现,还真只加了两句代码。聪明的你能看出是哪两句么?</p>    <p>运行效果:</p>    <p><img src="https://simg.open-open.com/show/dd7472414eb390ff014e5cd035b6b0ac.gif"></p>    <p>What fk</p>    <p>同学们可能会问,最上面的效果是不需要触摸就自动动的,现在的效果不一样啊。</p>    <p>其实仔细想一想,触摸我们都解决了,自动的难道会难么?这个就当留了个课后作业给大家。</p>    <p>提示:有几句代码为给注释掉了。</p>    <h2>结论</h2>    <p><a href="/misc/goto?guid=4959750918385748307" rel="nofollow,noindex">前一篇文章</a> 讲述Android的架构方面的知识,很多同学说根本看不懂。想当年我语文高考87分,差三分及格,以后我们还是多上代码吧。</p>    <p>当然写这篇文章的目的不是为了让大家都去写动态壁纸应用,因为已经有一款非常优秀的了,没错,那就是 <a href="/misc/goto?guid=4959750918471499717" rel="nofollow,noindex">Style</a> , <em> <a href="/misc/goto?guid=4959750918471499717" rel="nofollow,noindex">Style</a> </em> , <strong> <a href="/misc/goto?guid=4959750918471499717" rel="nofollow,noindex">Style</a> </strong> 。</p>    <p>这是一个典型的OpenGL应用场景,通过这篇文章大家也能对动态壁纸开发有一定的了解。我更希望的是,大家能动手将代码跑起来,动手的过程就是强化学习的过程。</p>    <h2>引用</h2>    <p><a href="/misc/goto?guid=4959750918579128402" rel="nofollow,noindex">Style艺术壁纸</a></p>    <p><a href="/misc/goto?guid=4959750918121866631" rel="nofollow,noindex">OpenGL</a> (需KX上网)</p>    <p><a href="/misc/goto?guid=4959750918673019682" rel="nofollow,noindex">Android动态壁纸支持</a> (需KX上网)</p>    <p>感谢各位,感谢开源!</p>    <p> </p>    <p>来自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0725/8242.html</p>    <p> </p>