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

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

package="com.tony.skin" android:versionCode="1" android:versionName="1.0">

android:label="@string/app_name">

2.布局文件main.xml

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:background="#d2d2d2"

android:id="@+id/layout">

android:text="请先点击“导入皮肤”,会将/sdcard/skin.zip导入到/sdcard/Skin_kris目录下,然后点击‘换肤'会将sdcard里面的素材用作皮肤"

android:textColor="#000">

3. Re_Skin2Activity:

package com.tony.skin;

import android.app.Activity;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.drawable.BitmapDrawable;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.Toast;

import com.tony.skin.utils.ZipUtil;

/**

*

* @author Tony

*

*/

public class Re_Skin2Activity extends Activity implements OnClickListener{

private Button btnSet;

private Button btnImport;

private LinearLayout layout;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

btnSet = (Button)findViewById(R.id.button1);

btnSet.setOnClickListener(this);

btnImport = (Button)findViewById(R.id.button2);

btnImport.setOnClickListener(this);

layout = (LinearLayout)findViewById(R.id.layout);

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.button1:

Bitmap bitmap= BitmapFactory.decodeFile("/sdcard/tony/skin/skin.png");

BitmapDrawable bd=new BitmapDrawable(bitmap);

btnSet.setBackgroundDrawable(bd);

layout.setBackgroundDrawable(new BitmapDrawable(BitmapFactory.decodeFile("/sdcard/Skin_kris/skin/bg/bg.png")));

break;

case R.id.button2:

ZipUtil zipp = new ZipUtil(2049);

System.out.println("begin do zip");

zipp.unZip("/sdcard/skin.zip","/sdcard/Skin_kris");

Toast.makeText(this, "导入成功", Toast.LENGTH_SHORT).show();

break;

default:

break;

}

}

}

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

package com.tony.skin.utils;

import java.io.BufferedOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.Enumeration;

import java.util.zip.Deflater;

import org.apache.tools.zip.ZipEntry;

import org.apache.tools.zip.ZipFile;

import org.apache.tools.zip.ZipOutputStream;

/**

* Zip包压缩,解压处理工具类

* @author a

*

*/

public class ZipUtil {

private ZipFile zipFile;

private ZipOutputStream zipOut; //压缩Zip

private int bufSize; //size of bytes

private byte[] buf;

private int readedBytes;

public ZipUtil(){

this(512);

}

public ZipUtil(int bufSize){

this.bufSize = bufSize;

this.buf = new byte[this.bufSize];

}

/**

*

* @param srcFile 需要 压缩的目录或者文件

* @param destFile 压缩文件的路径

*/

public void doZip(String srcFile, String destFile) {// zipDirectoryPath:需要压缩的文件夹名

File zipDir;

String dirName;

zipDir = new File(srcFile);

dirName = zipDir.getName();

try {

this.zipOut = new ZipOutputStream(new BufferedOutputStream(

new FileOutputStream(destFile)));

//设置压缩的注释

zipOut.setComment("comment");

//设置压缩的编码,如果要压缩的路径中有中文,就用下面的编码

zipOut.setEncoding("GBK");

//启用压缩

zipOut.setMethod(ZipOutputStream.DEFLATED);

//压缩级别为最强压缩,但时间要花得多一点

zipOut.setLevel(Deflater.BEST_COMPRESSION);

handleDir(zipDir, this.zipOut,dirName);

this.zipOut.close();

} catch (IOException ioe) {

ioe.printStackTrace();

}

}

/**

* 由doZip调用,递归完成目录文件读取

* @param dir

* @param zipOut

* @param dirName 这个主要是用来记录压缩文件的一个目录层次结构的

* @throws IOException

*/

private void handleDir(File dir, ZipOutputStream zipOut,String dirName) throws IOException {

System.out.println("遍历目录:"+dir.getName());

FileInputStream fileIn;

File[] files;

files = dir.listFiles();

if (files.length == 0) {// 如果目录为空,则单独创建之.

// ZipEntry的isDirectory()方法中,目录以"/"结尾.

System.out.println("压缩的 Name:"+dirName);

this.zipOut.putNextEntry(new ZipEntry(dirName));

this.zipOut.closeEntry();

} else {// 如果目录不为空,则分别处理目录和文件.

for (File fileName : files) {

// System.out.println(fileName);

if (fileName.isDirectory()) {

handleDir(fileName, this.zipOut,dirName+File.separator+fileName.getName()+File.separator);

} else {

System.out.println("压缩的 Name:"+dirName + File.separator+fileName.getName());

fileIn = new FileInputStream(fileName);

this.zipOut.putNextEntry(new ZipEntry(dirName + File.separator+fileName.getName()));

while ((this.readedBytes = fileIn.read(this.buf)) > 0) {

this.zipOut.write(this.buf, 0, this.readedBytes);

}

this.zipOut.closeEntry();

}

}

}

}

/**

* 解压指定zip文件

* @param unZipfile 压缩文件的路径

* @param destFile   解压到的目录

*/

public void unZip(String unZipfile, String destFile) {// unZipfileName需要解压的zip文件名

FileOutputStream fileOut;

File file;

InputStream inputStream;

try {

this.zipFile = new ZipFile(unZipfile);

for (Enumeration entries = this.zipFile.getEntries(); entries

.hasMoreElements();) {

ZipEntry entry = (ZipEntry) entries.nextElement();

file = new File(destFile+File.separator+entry.getName());

if (entry.isDirectory()) {

file.mkdirs();

} else {

// 如果指定文件的目录不存在,则创建之.

File parent = file.getParentFile();

if (!parent.exists()) {

parent.mkdirs();

}

inputStream = zipFile.getInputStream(entry);

fileOut = new FileOutputStream(file);

while ((this.readedBytes = inputStream.read(this.buf)) > 0) {

fileOut.write(this.buf, 0, this.readedBytes);

}

fileOut.close();

inputStream.close();

}

}

this.zipFile.close();

} catch (IOException ioe) {

ioe.printStackTrace();

}

}

// 设置缓冲区大小

public void setBufSize(int bufSize) {

this.bufSize = bufSize;

}

}

android 自定义皮肤,仿墨迹天气在Android App中实现自定义zip皮肤更换相关推荐

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

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

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

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

  3. Android自定义view--SurfaceView实现墨迹天气的风车效果

    Android自定义view--SurfaceView实现墨迹天气的风车效果 SurfaceView也是继承自View,它和我们以前接触到的View(Button.TextView等)最大的不同是,S ...

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

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

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

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

  6. Android使用SurfaceView实现墨迹天气的风车效果

    SurfaceView也是继承自View,它和我们以前接触到的View(Button.TextView等)最大的不同是,SurfaceView可以有一个单独的线程进行绘制,这个线程区别于UI线程(主线 ...

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

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

  8. 高仿墨迹天气“我”页面

    看到墨迹天气的"我"页面比较炫酷,处于好奇,就写了一个demo模仿一下. 1,实现效果 实现的效果分三个部分来说明,首先是下拉到最大高度,个人信息界面会产生一个回弹的效果,然后是滚 ...

  9. 在iPhone App中使用自定义字体

    如何在iPhone程序中使用自定义字体?比如要添加方正兰亭字体,首先需要一个ttf的字体库.到一些字体库的网站上去下载吧 然后把这个文件添加到工程里面  在plist里面添加 添加成功后,就可以使用啦 ...

最新文章

  1. Variational Bayes
  2. 【DS】排序算法之归并排序(Merge Sort)
  3. Spring Boot2.x-10 基于Spring Boot 2.1.2 + Mybatis 2.0.0实现多数据源,支持事务
  4. flex 下对齐_Flex 布局教程:语法篇
  5. 软件项目周报模板_一份高质量的职场工作周报,要这样写
  6. windows版一键绕id工具_Windows免费版一键绕过IOS13.6激活锁工具XgRiNdA,完美重启!...
  7. What's the QPSK?
  8. 如何切换计算机用户界面,让我来教大家从WIN10界面切换到WIN 7界面吧!嘻嘻
  9. mysql btree fulltext_Mysql主要索引方式:FULLTEXT,HASH,BTREE,RTREE。
  10. Ztree加载完成后显示勾选节点
  11. 中铁二局全面推行建筑工地务工人员实名制管理
  12. 电线的粗细与电流的大小怎么算?电流的大小与电器的功率有什么关系? 如何根据电流的大小选择铜质电线的粗细...
  13. 搜索引擎背后的经典数据结构和算法
  14. k8s(四)—service服务(暴露方式:ClusterIP、NodePort、LoadBalancer、ExternalName、ingress)
  15. php 保持内容换行符,PHP 将内容写入word pdf 换行符不生效咋办
  16. MoCo v1 文献研究 [自监督学习]
  17. android qq底部图片选择器,Android 高仿QQ图片选择器
  18. TPMS胎压传感器烧录器
  19. 408-数据结构-树的应用-哈夫曼树并查集
  20. python实现截竹竿

热门文章

  1. 浅谈style,currentStyle以及getComputedStyle
  2. pickle模块 python_Python之Pickle模块
  3. C4D R19学习之 001界面认识
  4. 深度学习(十) Unsupervised Learning 理论部分
  5. win10 + Qt5.14.0(mingw73_64) 下的 OpenCV4.5.1源码编译 注意事项
  6. 时光笔记 android,时光笔记app-时光笔记下载 1.6.2 手机版 - 河东软件园
  7. iOS分享微信小程序无反应、失败;iOS多种分享形式(以微信为例:链接+图片+小程序)
  8. torch.device
  9. IaaS、PaaS、SaaS学习
  10. Ora:12154 PLsql连接报错