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

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

转载于:https://www.cnblogs.com/dongweiq/p/4249931.html

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

  1. Android之高仿墨迹天气桌面组件(AppWidgetProvider) .

    Android之高仿墨迹天气桌面组件(AppWidgetProvider) . 点击:382 发布时间:2012-10-03 更多0 相信墨迹天气,大家都见过,他在时间显示和天气界面上,很吸引人,今天 ...

  2. 墨迹天气php,Android_仿墨迹天气在Android App中实现自定义zip皮肤更换,在这里谈一下墨迹天气的换肤 - phpStudy...

    仿墨迹天气在Android App中实现自定义zip皮肤更换 在这里谈一下墨迹天气的换肤实现方式,不过首先声明我只是通过反编译以及参考了一些网上其他资料的方式推测出的换肤原理, 在这里只供参考. 若大 ...

  3. (4.0.24.2)Android之桌面组件App Widget案例之高仿墨迹天气桌面组件

    相信墨迹天气,大家都见过,他在时间显示和天气界面上,很吸引人,今天我就来模仿一下墨迹天气的桌面组件,但是由于谷歌在天朝频频被墙的缘故,所以我在今天测试的时候,解析xml文件的网页打不开,所以天气显示出 ...

  4. android 自定义皮肤,仿墨迹天气在Android App中实现自定义zip皮肤更换

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

  5. 高仿墨迹天气 白天晴天

    简介 一直对墨迹天气的绚丽的场景蛮感兴趣的,趁有时间,自己就高仿了其中的一个场景,其他场景呢,也是类似的,主要是写对象的AI也就是逻辑了. 先看看效果吧,动态效果比较坑,太模糊 高清图 代码分析 来看 ...

  6. android 打造炫酷导航栏(仿UC头条)

    年后开始上班甚是清闲,所以想捣鼓一些东西.在翻阅大神杰作Android 教你打造炫酷的ViewPagerIndicator 不仅仅是高仿MIUI 的时候看到下面有一条评论说,如果导航栏能滑动就更好了. ...

  7. Android一步一步剖析+实现仿支付宝手势密码自定义View

    最近项目需求:要求在项目中添加手势密码和指纹验证,恰巧最近在苦练自定义View,于是参考了网上轮子和自己的理解,实现了如下的效果. 国际惯例:Without pic you say a JB(奖杯). ...

  8. Android 仿墨迹天气24小时预报

    效果目标: 1.现在到未来24小时温度点连成贝塞尔曲线 2.指示器跟随选中每小时空气质量移动 3.合并相同风力与天气icon 4.显示区域的icon在屏幕可视范围内左右居中 5.显示区域的风力等级在屏 ...

  9. android 墨迹天气背景,GitHub - xiangzhihong/android: 仿墨迹天气折线,以及背景滚动效果...

    ##实现效果 ##开源协议 Copyright (C) 2016 This program is free software; you can redistribute it and/or modif ...

最新文章

  1. Ubuntu管理开机自启动程序
  2. 查看chrome驱动以及下载对应谷歌浏览器版本
  3. python项目归纳总结-这4个Python实战项目,让你瞬间读懂Python!
  4. 【全文搜索引擎】Elasticsearch相关介绍与linux系统安装
  5. opencv3——ANN算法的使用
  6. Python os.path路径模块中的操作方法总结
  7. C#中的结构和类之间的区别
  8. 微型计算机频繁死机的原因,电脑经常死机是什么原因|电脑经常死机的解决方法...
  9. 如何通过像素点找到世界坐标_如何通过阅读来找到自己理论研究的“视域”?...
  10. java 虚基类_重拾C++之虚函数和虚基类以及抽象类
  11. 使用 XHGui 对 PHP 应用进行性能诊断
  12. 外参矩阵(旋转矩阵+平移向量)以及外方位元素的关系
  13. mapreduce分组统计_mongodb中使用mapreduce进行分组统计
  14. js回文数的四种判断方法
  15. 推荐一个C缓冲区的结构
  16. access百科 pc_PC Access SMART
  17. vs点击方法跳不到对于的地方_田宫四驱车 狂牛 配VS底盘改装制作全攻略
  18. 任务栏优化工具TrueLaunchBar_我是亲民_新浪博客
  19. 在线报表设计实战系列 – ②制作表格类报表
  20. 多智能体中的图论——多智能体的一致性(二)

热门文章

  1. java length()函数_小猿圈介绍java函数式编码结构及优势
  2. 【赠送】IT技术视频教程,白拿不谢!思科、华为、红帽、数据库、云计算等等
  3. java jdbc in list_javaweb_JDBC
  4. 分享你的见解与经验|RocketMQ Summit 2022 议题征集中
  5. 炫界 (978) -(建工发现应用克隆漏)_湖南建工装配式建筑迈入“加速度”
  6. Jmeter之断言操作
  7. java 俄语 工作_工作俄语怎么说
  8. php 文字换行,用Php中的Fpdf换行文本
  9. 【Python进阶】你真的明白NumPy中的ndarray吗?
  10. 中国飞机制造及修理行业未来需求预测及发展态势研究报告2021版