仿墨跡天氣在Android App中實(shí)現(xiàn)自定義zip皮膚更換
在這里談一下墨跡天氣的換膚實(shí)現(xiàn)方式,不過(guò)首先聲明我只是通過(guò)反編譯以及參考了一些網(wǎng)上其他資料的方式推測(cè)出的換膚原理, 在這里只供參考. 若大家有更好的方式, 歡迎交流.
墨跡天氣下載的皮膚就是一個(gè)zip格式的壓縮包,在應(yīng)用的時(shí)候把皮膚資源釋放到墨跡天氣應(yīng)用的目錄下,更換皮膚時(shí)新的皮膚資源會(huì)替換掉老的皮膚資源每次加載的時(shí)候就是從手機(jī)硬盤(pán)上讀取圖片,這些圖片資源的命名和程序中的資源的命名保持一致,一旦找不到這些資源,可以選擇到系統(tǒng)默認(rèn)中查找。這種實(shí)現(xiàn)是直接讀取了外部資源文件,在程序運(yùn)行時(shí)通過(guò)代碼顯示的替換界面的背景資源。這種方式的優(yōu)點(diǎn)是:皮膚資源的格式定義很隨意可以是zip也可以是自定義的格式,只要程序中能夠解析到資源就行,缺點(diǎn)是效率上的問(wèn)題.
這里需要注意的一點(diǎn)是,再這里對(duì)壓縮包的解壓,借助了第三方工具: ant. jar進(jìn)行解壓和壓縮文件. 關(guān)于ant工具的使用,我在稍后的文章中會(huì)具體介紹.
主要技術(shù)點(diǎn):
如何去讀取zip文件中的資源以及皮膚文件存放方式
實(shí)現(xiàn)方案:如果軟件每次啟動(dòng)都去讀取SD卡上的皮膚文件,速度會(huì)比較慢。較好的做法是提供一個(gè)皮膚設(shè)置的界面,用戶選擇了哪一個(gè)皮膚,就把那個(gè)皮膚文件解壓縮到”/data/data/[package name]/skin”路徑下(讀取的快速及安全性),這樣不需要跨存儲(chǔ)器讀取,速度較快,而且不需要每次都去zip壓縮包中讀取,不依賴SD卡中的文件,即使皮膚壓縮包文件被刪除了也沒(méi)有關(guān)系。
實(shí)現(xiàn)方法:
1. 在軟件的幫助或者官網(wǎng)的幫助中提示用戶將皮膚文件拷貝到SD卡指定路徑下。
2. 在軟件中提供皮膚設(shè)置界面。可以在菜單或者在設(shè)置中。可參考墨跡、搜狗輸入法、QQ等支持換膚的軟件。
3. 加載指定路徑下的皮膚文件,讀取其中的縮略圖,在皮膚設(shè)置界面中顯示,將用戶選中的皮膚文件解壓縮到”/data/data/[package name]/skin”路徑下。
4. 軟件中優(yōu)先讀取”/data/data/[package name]/skin/”路徑下的資源。如果沒(méi)有則使用apk中的資源。
效果圖:

具體代碼:
1. AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tony.skin" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Re_Skin2Activity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2.布局文件main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#d2d2d2"
android:id="@+id/layout">
<Button android:text="導(dǎo)入皮膚" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
<Button android:text="換膚" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
<TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="請(qǐng)先點(diǎn)擊“導(dǎo)入皮膚”,會(huì)將/sdcard/skin.zip導(dǎo)入到/sdcard/Skin_kris目錄下,然后點(diǎn)擊‘換膚'會(huì)將sdcard里面的素材用作皮膚"
android:textColor="#000"></TextView>
</LinearLayout>
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, "導(dǎo)入成功", 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)));
//設(shè)置壓縮的注釋
zipOut.setComment("comment");
//設(shè)置壓縮的編碼,如果要壓縮的路徑中有中文,就用下面的編碼
zipOut.setEncoding("GBK");
//啟用壓縮
zipOut.setMethod(ZipOutputStream.DEFLATED);
//壓縮級(jí)別為最強(qiáng)壓縮,但時(shí)間要花得多一點(diǎn)
zipOut.setLevel(Deflater.BEST_COMPRESSION);
handleDir(zipDir, this.zipOut,dirName);
this.zipOut.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* 由doZip調(diào)用,遞歸完成目錄文件讀取
* @param dir
* @param zipOut
* @param dirName 這個(gè)主要是用來(lái)記錄壓縮文件的一個(gè)目錄層次結(jié)構(gòu)的
* @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) {// 如果目錄為空,則單獨(dú)創(chuàng)建之.
// ZipEntry的isDirectory()方法中,目錄以"/"結(jié)尾.
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 {
// 如果指定文件的目錄不存在,則創(chuàng)建之.
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();
}
}
// 設(shè)置緩沖區(qū)大小
public void setBufSize(int bufSize) {
this.bufSize = bufSize;
}
}
- Android實(shí)現(xiàn)zip文件壓縮及解壓縮的方法
- Android實(shí)現(xiàn)下載zip壓縮文件并解壓的方法(附源碼)
- Android GZip的使用-開(kāi)發(fā)中網(wǎng)絡(luò)請(qǐng)求的壓縮實(shí)例詳解
- Android Zipalign工具優(yōu)化Android APK應(yīng)用
- Android APK優(yōu)化工具Zipalign詳解
- Android中實(shí)現(xiàn)下載和解壓zip文件功能代碼分享
- Android zip文件下載和解壓實(shí)例
- 在Android系統(tǒng)中使用gzip進(jìn)行數(shù)據(jù)傳遞實(shí)例代碼
- Android RxJava創(chuàng)建操作符Interval
相關(guān)文章
Android 中自定義ContentProvider與ContentObserver的使用簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 中自定義ContentProvider與ContentObserver的使用簡(jiǎn)單實(shí)例的相關(guān)資料,這里提供實(shí)例幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下2017-09-09
Android開(kāi)發(fā)獲取手機(jī)Mac地址適配所有Android版本
這篇文章主要介紹了Android開(kāi)發(fā)獲取手機(jī)Mac地址適配所有Android版本,需要的朋友可以參考下2020-03-03
Android sharedPreferences實(shí)現(xiàn)記住密碼功能
這篇文章主要為大家詳細(xì)介紹了Android sharedPreferences實(shí)現(xiàn)記住密碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
android如何添加桌面圖標(biāo)和卸載程序后自動(dòng)刪除圖標(biāo)
android如何添加桌面圖標(biāo)和卸載程序后自動(dòng)刪除桌面圖標(biāo),這是一個(gè)應(yīng)用的安裝與卸載過(guò)程對(duì)桌面圖標(biāo)的操作,下面與大家分享下具體是如何實(shí)現(xiàn)的,感興趣的朋友可以參考下哈2013-06-06
揭秘雙十一手機(jī)淘寶圖標(biāo)如何被動(dòng)態(tài)更換
這篇文章主要介紹了每到雙十一十二的時(shí)候Android手機(jī)動(dòng)態(tài)更換手機(jī)圖標(biāo)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
快速解決Android適配底部返回鍵等虛擬鍵盤(pán)的問(wèn)題
今天小編就為大家分享一篇快速解決Android適配底部返回鍵等虛擬鍵盤(pán)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
android viewpager實(shí)現(xiàn)豎直滑動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了android viewpager實(shí)現(xiàn)豎直滑動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android開(kāi)發(fā)應(yīng)用第一步 安裝及配置模擬器Genymotion
這篇文章主要介紹了Android開(kāi)發(fā)應(yīng)用第一步,即安裝及配置模擬器Genymotion,感興趣的小伙伴們可以參考一下2015-12-12
談?wù)凙ndroid的三種網(wǎng)絡(luò)通信方式
Android平臺(tái)有三種網(wǎng)絡(luò)接口可以使用,他們分別是:java.net.*(標(biāo)準(zhǔn)Java接口)、Org.apache接口和Android.net.*(Android網(wǎng)絡(luò)接口)。本文詳細(xì)的介紹,有興趣的可以了解一下。2017-01-01

