Android 打造自己的个性化应用(四):仿墨迹天气实现-->自定义扩展名的zip格式的皮肤

在这里谈一下墨迹天气的换肤实现方式,不过首先声明我只是通过反编译以及参考了一些网上其他资料的方式推测出的换肤原理, 在这里只供参考. 若大家有更好的方式, 欢迎交流.

墨迹天气下载的皮肤就是一个zip格式的压缩包,在应用的时候把皮肤资源释放到墨迹天气应用的目录下,更换皮肤时新的皮肤资源会替换掉老的皮肤资源每次加载的时候就是从手机硬盘上读取图片,这些图片资源的命名和程序中的资源的命名保持一致,一旦找不到这些资源,可以选择到系统默认中查找。这种实现是直接读取了外部资源文件,在程序运行时通过代码显示的替换界面的背景资源。这种方式的优点是:皮肤资源的格式定义很随意可以是zip也可以是自定义的格式,只要程序中能够解析到资源就行,缺点是效率上的问题.

这里需要注意的一点是,再这里对压缩包的解压,借助了第三方工具: ant. jar进行解压和压缩文件. 关于ant工具的使用,我在稍后的文章中会具体介绍.

主要技术点:

如何去读取zip文件中的资源以及皮肤文件存放方式

实现方案:如果软件每次启动都去读取SD卡上的皮肤文件,速度会比较慢。较好的做法是提供一个皮肤设置的界面,用户选择了哪一个皮肤,就把那个皮肤文件解压缩到”/data/data/[package name]/skin”路径下(读取的快速及安全性),这样不需要跨存储器读取,速度较快,而且不需要每次都去zip压缩包中读取,不依赖SD卡中的文件,即使皮肤压缩包文件被删除了也没有关系。

实现方法:

1. 在软件的帮助或者官网的帮助中提示用户将皮肤文件拷贝到SD卡指定路径下。
2. 在软件中提供皮肤设置界面。可以在菜单或者在设置中。可参考墨迹、搜狗输入法、QQ等支持换肤的软件。
3. 加载指定路径下的皮肤文件,读取其中的缩略图,在皮肤设置界面中显示,将用户选中的皮肤文件解压缩到”/data/data/[package name]/skin”路径下。
4. 软件中优先读取”/data/data/[package name]/skin/”路径下的资源。如果没有则使用apk中的资源。

效果图:

具体代码:

1. AndroidManifest.xml:

[java] view plaincopy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.tony.skin" android:versionCode="1" android:versionName="1.0">
  4. <uses-sdk android:minSdkVersion="7" />
  5. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  6. <application android:icon="@drawable/icon" android:label="@string/app_name">
  7. <activity android:name=".Re_Skin2Activity"
  8. android:label="@string/app_name">
  9. <intent-filter>
  10. <action android:name="android.intent.action.MAIN" />
  11. <category android:name="android.intent.category.LAUNCHER" />
  12. </intent-filter>
  13. </activity>
  14. </application>
  15. </manifest>

2.布局文件main.xml

[java] view plaincopy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:background="#d2d2d2"
  7. android:id="@+id/layout">
  8. <Button android:text="导入皮肤" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
  9. <Button android:text="换肤" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
  10. <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content"
  11. android:text="请先点击“导入皮肤”,会将/sdcard/skin.zip导入到/sdcard/Skin_kris目录下,然后点击‘换肤’会将sdcard里面的素材用作皮肤"
  12. android:textColor="#000"></TextView>
  13. </LinearLayout>

3. Re_Skin2Activity:

[java] view plaincopy

  1. package com.tony.skin;
  2. import android.app.Activity;
  3. import android.graphics.Bitmap;
  4. import android.graphics.BitmapFactory;
  5. import android.graphics.drawable.BitmapDrawable;
  6. import android.os.Bundle;
  7. import android.view.View;
  8. import android.view.View.OnClickListener;
  9. import android.widget.Button;
  10. import android.widget.LinearLayout;
  11. import android.widget.Toast;
  12. import com.tony.skin.utils.ZipUtil;
  13. /**
  14. *
  15. * @author Tony
  16. *
  17. */
  18. public class Re_Skin2Activity extends Activity implements OnClickListener{
  19. private Button  btnSet;
  20. private Button  btnImport;
  21. private LinearLayout layout;
  22. /** Called when the activity is first created. */
  23. @Override
  24. public void onCreate(Bundle savedInstanceState) {
  25. super.onCreate(savedInstanceState);
  26. setContentView(R.layout.main);
  27. btnSet = (Button)findViewById(R.id.button1);
  28. btnSet.setOnClickListener(this);
  29. btnImport = (Button)findViewById(R.id.button2);
  30. btnImport.setOnClickListener(this);
  31. layout = (LinearLayout)findViewById(R.id.layout);
  32. }
  33. @Override
  34. public void onClick(View v) {
  35. switch (v.getId()) {
  36. case R.id.button1:
  37. Bitmap bitmap= BitmapFactory.decodeFile("/sdcard/tony/skin/skin.png");
  38. BitmapDrawable bd=new BitmapDrawable(bitmap);
  39. btnSet.setBackgroundDrawable(bd);
  40. layout.setBackgroundDrawable(new BitmapDrawable(BitmapFactory.decodeFile("/sdcard/Skin_kris/skin/bg/bg.png")));
  41. break;
  42. case R.id.button2:
  43. ZipUtil zipp = new ZipUtil(2049);
  44. System.out.println("begin do zip");
  45. zipp.unZip("/sdcard/skin.zip","/sdcard/Skin_kris");
  46. Toast.makeText(this, "导入成功", Toast.LENGTH_SHORT).show();
  47. break;
  48. default:
  49. break;
  50. }
  51. }
  52. }

4. ZipUtil 解压缩处理ZIP包的工具类

[java] view plaincopy

    1. package com.tony.skin.utils;
    2. import java.io.BufferedOutputStream;
    3. import java.io.File;
    4. import java.io.FileInputStream;
    5. import java.io.FileOutputStream;
    6. import java.io.IOException;
    7. import java.io.InputStream;
    8. import java.util.Enumeration;
    9. import java.util.zip.Deflater;
    10. import org.apache.tools.zip.ZipEntry;
    11. import org.apache.tools.zip.ZipFile;
    12. import org.apache.tools.zip.ZipOutputStream;
    13. /**
    14. * Zip包压缩,解压处理工具类
    15. * @author a
    16. *
    17. */
    18. public class ZipUtil {
    19. private ZipFile         zipFile;
    20. private ZipOutputStream zipOut;     //压缩Zip
    21. private  int            bufSize;    //size of bytes
    22. private byte[]          buf;
    23. private int             readedBytes;
    24. public ZipUtil(){
    25. this(512);
    26. }
    27. public ZipUtil(int bufSize){
    28. this.bufSize = bufSize;
    29. this.buf = new byte[this.bufSize];
    30. }
    31. /**
    32. *
    33. * @param srcFile  需要 压缩的目录或者文件
    34. * @param destFile 压缩文件的路径
    35. */
    36. public void doZip(String srcFile, String destFile) {// zipDirectoryPath:需要压缩的文件夹名
    37. File zipDir;
    38. String dirName;
    39. zipDir = new File(srcFile);
    40. dirName = zipDir.getName();
    41. try {
    42. this.zipOut = new ZipOutputStream(new BufferedOutputStream(
    43. new FileOutputStream(destFile)));
    44. //设置压缩的注释
    45. zipOut.setComment("comment");
    46. //设置压缩的编码,如果要压缩的路径中有中文,就用下面的编码
    47. zipOut.setEncoding("GBK");
    48. //启用压缩
    49. zipOut.setMethod(ZipOutputStream.DEFLATED);
    50. //压缩级别为最强压缩,但时间要花得多一点
    51. zipOut.setLevel(Deflater.BEST_COMPRESSION);
    52. handleDir(zipDir, this.zipOut,dirName);
    53. this.zipOut.close();
    54. } catch (IOException ioe) {
    55. ioe.printStackTrace();
    56. }
    57. }
    58. /**
    59. *  由doZip调用,递归完成目录文件读取
    60. * @param dir
    61. * @param zipOut
    62. * @param dirName  这个主要是用来记录压缩文件的一个目录层次结构的
    63. * @throws IOException
    64. */
    65. private void handleDir(File dir, ZipOutputStream zipOut,String dirName) throws IOException {
    66. System.out.println("遍历目录:"+dir.getName());
    67. FileInputStream fileIn;
    68. File[] files;
    69. files = dir.listFiles();
    70. if (files.length == 0) {// 如果目录为空,则单独创建之.
    71. // ZipEntry的isDirectory()方法中,目录以"/"结尾.
    72. System.out.println("压缩的 Name:"+dirName);
    73. this.zipOut.putNextEntry(new ZipEntry(dirName));
    74. this.zipOut.closeEntry();
    75. } else {// 如果目录不为空,则分别处理目录和文件.
    76. for (File fileName : files) {
    77. // System.out.println(fileName);
    78. if (fileName.isDirectory()) {
    79. handleDir(fileName, this.zipOut,dirName+File.separator+fileName.getName()+File.separator);
    80. } else {
    81. System.out.println("压缩的 Name:"+dirName + File.separator+fileName.getName());
    82. fileIn = new FileInputStream(fileName);
    83. this.zipOut.putNextEntry(new ZipEntry(dirName + File.separator+fileName.getName()));
    84. while ((this.readedBytes = fileIn.read(this.buf)) > 0) {
    85. this.zipOut.write(this.buf, 0, this.readedBytes);
    86. }
    87. this.zipOut.closeEntry();
    88. }
    89. }
    90. }
    91. }
    92. /**
    93. * 解压指定zip文件
    94. * @param unZipfile 压缩文件的路径
    95. * @param destFile   解压到的目录 
    96. */
    97. public void unZip(String unZipfile, String destFile) {// unZipfileName需要解压的zip文件名
    98. FileOutputStream fileOut;
    99. File file;
    100. InputStream inputStream;
    101. try {
    102. this.zipFile = new ZipFile(unZipfile);
    103. for (Enumeration entries = this.zipFile.getEntries(); entries
    104. .hasMoreElements();) {
    105. ZipEntry entry = (ZipEntry) entries.nextElement();
    106. file = new File(destFile+File.separator+entry.getName());
    107. if (entry.isDirectory()) {
    108. file.mkdirs();
    109. } else {
    110. // 如果指定文件的目录不存在,则创建之.
    111. File parent = file.getParentFile();
    112. if (!parent.exists()) {
    113. parent.mkdirs();
    114. }
    115. inputStream = zipFile.getInputStream(entry);
    116. fileOut = new FileOutputStream(file);
    117. while ((this.readedBytes = inputStream.read(this.buf)) > 0) {
    118. fileOut.write(this.buf, 0, this.readedBytes);
    119. }
    120. fileOut.close();
    121. inputStream.close();
    122. }
    123. }
    124. this.zipFile.close();
    125. } catch (IOException ioe) {
    126. ioe.printStackTrace();
    127. }
    128. }
    129. // 设置缓冲区大小
    130. public void setBufSize(int bufSize) {
    131. this.bufSize = bufSize;
    132. }
    133. }
时间: 2024-08-04 17:32:22

Android 打造自己的个性化应用(四):仿墨迹天气实现-->自定义扩展名的zip格式的皮肤的相关文章

Android 打造自己的个性化应用(五):仿墨迹天气实现续--&gt; 使用Ant实现zip/tar的压缩与解压

上一篇中提到对于Zip包的解压和压缩需要借助Ant 实现,我经过参考了其他的资料,整理后并加上了一些自己的看法: 这里就具体地讲下如何使用Ant进行解压缩及其原因: java中实际是提供了对  zip等压缩格式的支持,但是为什么这里会用到ant呢?   原因主要有两个: 1. java提供的类对于包括有中文字符的路径,文件名支持不够好,你用其它第三方软件解压的时候就会存在乱码.而ant.jar就支持文件名或者路径包括中文字符. 2. ant.jar提供了强大的工具类,更加方便于我们对压缩与解压的

Android应用源码之仿墨迹天气插件

Android应用源码之仿墨迹天气插件 仿照墨迹天气的桌面小插件例子源码. 下载地址:http://www.dwz.cn/Cox82 运行截图:

高仿墨迹天气黄历

本项目是高仿墨迹天气的黄历功能模块,可插件化安装

Android 打造自己的个性化应用(一):应用程序换肤主流方式的分析与概述

Android平台api没有特意为换肤提供一套简便的机制,这可能是外国的软件更注重功能和易用,不流行换肤.系统不提供直接支持,只能自行研究. 换肤,可以认为是动态替换资源(文字.颜色.字体大小.图片.布局文件……).这个使用编程语言来动态设置是可以做到的,例如使用View的setBackgroundResource.setTextSize.setTextColor等函数.但我们不可能在每个activity里对页面里的所有控件都通过调用这些函数来换肤,这样的程序代码难以维护.扩展,也违背了UI和代

高仿墨迹天气 白天晴天

简介 一直对墨迹天气的绚丽的场景蛮感兴趣的,趁有时间,自己就高仿了其中的一个场景,其他场景呢,也是类似的,主要是写对象的AI也就是逻辑了. 先看看效果吧,动态效果比较坑,太模糊 高清图 代码分析 来看看代码结构吧 这里使用了SurfaceView而不是用的view,其实这个天气的场景绘制更像是游戏开发,使用SurfaceView会更灵活. public SceneSurfaceView(Context context, AttributeSet attrs) { super(context, a

高仿墨迹天气下拉拉伸图片

简介 最近比较闲,就多学习了下,关键是不看点东西,就犯困啊.墨迹天气这个应用有不少地方需要学习的,这篇文章呢, 说一下他的"我"Tab页下拉拉伸图片展示效果,如果留意的话, 像QQ的好友动态也有差不多的效果. 代码分析 代码比较简单了,就重写了一个ScrollView类,先说说他的原理吧,我是先根据id拿到这个ImageView,然后获得他的TopMargin也就是遮掩后的偏移值,在触摸的时候,对ImageView的TopMargin进行改变产生效果,松手的时候搞个属性动画让他还原到以

Android 打造自己的个性化应用(三):应用程序的插件化

在android的项目开发中,都会遇到后期功能拓展增强与主程序代码变更的现实矛盾,也就是程序的灵活度. 由于linux平台的安全机制,再加上dalvik的特殊机制,各种权限壁垒,使得开发一个灵活多变的程序,变得比较困难,不像pc平台下那么容易. 这里实际上可以借鉴传统软件中扩展程序的方法: 也就是插件的实现. 如目前所有的浏览器,比如我们使用的eclipse,以及很多优秀的软件,都使用了此种方式. 这样轻松实现了软件的功能扩展,而升级功能时只用更新对应插件, 而不是需要更新整个应用,降低了程序的

Android 查看项目依赖树的四种方式

Android 查看项目依赖树的四种方式: 方式一: ./gradlew 模块名:dependencies //查看单独模块的依赖 ./gradlew :app:dependencies --configuration compile //查看项目的编译依赖 方式二:使用Gradle Project 方式三:安装Android Studio插件 gradle view 方式四:如果你嫌在命令行窗口展示观看不友好,这里还有一种体验更好的方式. 输入下面命令行: ./gradlew build --

Android打造通用的下拉刷新组件

还记得上一篇 blog 的内容吗?如果不记得建议先去了解一下,Android 事件处理全面剖析 ,因为下拉刷新需要用到手势的处理,而上一篇文章中,对事件处理做了很详细的说明,了解了事件的处理机制,对理解本篇文章有很大的帮助.好了,这里就当大家都已经对事件处理有了一定的了解,开始我们的下拉刷新征程. 还是老规矩,先上效果图,再根据效果图来分析实现的原理: 一 .分析原理 我们都知道,listView 控件为我们提供了 addHeaderView.和 addFootView 的方法,我们通过此方法可