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

墨迹天气下载的皮肤就是一个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:

  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

  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:

  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包的工具类

    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. }

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

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

    上一篇中提到对于Zip包的解压和压缩需要借助Ant 实现,我经过参考了其他的资料,整理后并加上了一些自己的看法: 这里就具体地讲下如何使用Ant进行解压缩及其原因: java中实际是提供了对  zip ...

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

    Android平台api没有特意为换肤提供一套简便的机制,这可能是外国的软件更注重功能和易用,不流行换肤.系统不提供直接支持,只能自行研究. 换肤,可以认为是动态替换资源(文字.颜色.字体大小.图片. ...

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

    在android的项目开发中,都会遇到后期功能拓展增强与主程序代码变更的现实矛盾,也就是程序的灵活度. 由于linux平台的安全机制,再加上dalvik的特殊机制,各种权限壁垒,使得开发一个灵活多变的 ...

  4. Android 打造自己的个性化应用(二):应用程序内置资源实现换肤功能

    通过应用程序内置资源实现换肤,典型的应用为QQ空间中换肤的实现. 应用场景为: 应用一般不大,且页面较少,风格相对简单,一般只用实现部分资源或者只用实现背景的更换. 此种换肤方式实现的思路: 1. 把 ...

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

    Android 查看项目依赖树的四种方式: 方式一: ./gradlew 模块名:dependencies //查看单独模块的依赖 ./gradlew :app:dependencies --conf ...

  6. Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单

    原文:Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43131133, ...

  7. 【转】android 欢迎界面翻页成效,仿微信第一次登陆介绍翻页界面

    android 欢迎界面翻页效果,仿微信第一次登陆介绍翻页界面 本实例做的相对比较简单主要是对翻页控件的使用,有时候想要做一些功能是主要是先了解下是否有现成的控件可以使用,做起来比较简单不用费太大的劲 ...

  8. Android 打造完美的侧滑菜单/侧滑View控件

    概述 Android 打造完美的侧滑菜单/侧滑View控件,完全自定义实现,支持左右两个方向弹出,代码高度简洁流畅,兼容性高,控件实用方便. 详细 代码下载:http://www.demodashi. ...

  9. android指纹识别、拼图游戏、仿MIUI长截屏、bilibili最美创意等源码

    Android精选源码 一个动画效果的播放控件,播放,暂停,停止之间的动画 用 RxJava 实现 Android 指纹识别代码 Android仿滴滴打车(滴滴UI)源码 Android高仿哔哩哔哩动 ...

随机推荐

  1. [转]iOS开发使用半透明模糊效果方法整理

    转自:http://www.molotang.com/articles/1921.html 虽然iOS很早就支持使用模糊效果对图片等进行处理,但尤其在iOS7以后,半透明模糊效果得到大范围广泛使用.包 ...

  2. getAttribute()与getParameter的区别

    当两个Web组件之间为转发关系时,转发源会将要共享 request范围内的数据先用setAttribute将数据放入到HttpServletRequest对象中,然后转发目标通过 getParamet ...

  3. 先装Net Framework 后 装 IIS的处理办法

    先装IIS话,后面装Net Framework时候会自动注册 处理aspx和ashx等的处理扩展程序 先装Net Framework 后 装 IIS.扩展程序注册在命令:aspnet_regiis - ...

  4. FpSpread添加标注

    先看效果 实现: FarPoint.Web.Spread.StyleInfo Errorcss = new FarPoint.Web.Spread.StyleInfo(); Errorcss.Bord ...

  5. iOS中UISearchBar(搜索框)使用总结

    http://my.oschina.net/u/2340880/blog/509756

  6. 定义了重复的system.web.extensions/scripting/scriptResourceHandler怎么办

    今天移转系统,都配置好之后,系统报错说我的web服务下的web.config 定义了重复的 system.web.extensions/scripting/scriptResourceHandler ...

  7. lightoj 1236 正整数唯一分解定理

    A - (例题)整数分解 Crawling in process... Crawling failed Time Limit:2000MS     Memory Limit:32768KB     6 ...

  8. c++ 编译期计算 (一)

    编译期就是编译器进行编译,产生.obj文件的所处的那一段时间(如果是广义的编译期,那么一般还包括了链接期,因为现在很多编译器都会自动调用链接器进行链接)执行期就是你执行某个已经链接好的程序的那段时间. ...

  9. HDU 3966 dfs序+LCA+树状数组

    题目意思很明白: 给你一棵有n个节点的树,对树有下列操作: I c1 c2 k 意思是把从c1节点到c2节点路径上的点权值加上k D c1 c2 k 意思是把从c1节点到c2节点路径上的点权值减去k ...

  10. php随机获取金山词霸每日一句

    header('Content-Type:text/html; charset=utf-8'); $nowyear=date("Y"); $nowmouth = date('m') ...