在這裏談一下墨跡天氣的換膚實現方式,不過首先聲明我只是通過反編譯以及參考了一些網上其他資料的方式推測出的換膚原理, 在這裏只供參考. 若大家有更好的方式, 歡迎交流.
墨跡天氣下載的皮膚就是一個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:
- <?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="導入皮膚" 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="請先點擊“導入皮膚”,會將/sdcard/skin.zip導入到/sdcard/Skin_kris目錄下,然後點擊‘換膚’會將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/Skin_kris/skin/google.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;
- }
- }