分析Android App中内置换肤功能的实现方式_Android

Android平台api没有特意为换肤提供一套简便的机制,这可能是外国的软件更注重功能和易用,不流行换肤。系统不提供直接支持,只能自行研究。
换肤,可以认为是动态替换资源(文字、颜色、字体大小、图片、布局文件……)。这个使用编程语言来动态设置是可以做到的,例如使用View的setBackgroundResource、setTextSize、setTextColor等函数。但我们不可能在每个activity里对页面里的所有控件都通过调用这些函数来换肤,这样的程序代码难以维护、扩展,也违背了UI和代码分离的原则(android开发中UI以xml文件的方式布局)。 通常,皮肤资源会在主程序apk之外提供,以减少主程序的大小,以及方便随时提供新的皮肤扩展。
简单的来说,软件皮肤包括图标、字体、布局、交互风格等,换肤就是换掉皮肤包括的部分或所有资源。

主流应用程序换肤方式:
国内有很多的软件都支持皮肤定制,这也是与国外软件重大不同之一,国外用户注重社交、邮件等功能,国内用户则重视音乐、小说、皮肤等功能. 在写这个换肤系列之前,我也参考了其他人的一些总结. 知识点比较散,因此我对其进行了整理和进一步的优化和扩展. 当然,由于精力有限,部分换肤方式我也只写了主体功能的实现. 如果出现有误或者不够详细的地方,希望大家提出意见或者自行进行扩展.
关于其中提到的几种主流实现方式,接下来的文章里我会具体提供代码进行解释, 此次先做一个整体的概述.
目前主流的换肤从功能上可以划分几种实现方式,

1) 软件内置多个皮肤,不可由用户增加或修改:
最低的自由度,软件实现相对于后面的几种相对简单.
如果你的程序和资源都很小,可以在主程序apk中放入足够的皮肤资源.
典型应用:平板电脑Apad上QQ空间的换肤功能,实际上只是改变了Activity的背景,或这部分的资源.

2) 官方提供皮肤供下载,用户可以使用下载的皮肤:

用户可选择下载自己喜欢的皮肤,有些玩家会破解皮肤的定制方法,自己做皮肤使用,或者传到网上给大家用。
典型应用: 墨迹天气下载的皮肤就是一个zip格式的压缩包,在应用的时候把皮肤资源释放到墨迹天气应用的目录下,更换皮肤时新的皮肤资源会替换掉老的皮肤资源每次加载的时候就是从手机硬盘上读取图片,这些图片资源的命名和程序中的资源的命名保持一致,一旦找不到这些资源,可以选择到系统默认中查找。
这种实现是直接读取了外部资源文件,在程序运行时通过代码显示的替换界面的背景资源。
这种方式的优点是:皮肤资源的格式定义很随意可以是zip也可以是自定义的格式,只要程序中能够解析到资源就行,缺点是需要读取并解析文件,导致效率上会比较差.

3) 皮肤资源存在于主程序之类的APK中,即实现了APK的拆分. 这里类似于浏览器与插件的关系. 当考虑应用程序需要扩展时,则需要采用本方式实现.,这不仅仅体现在换肤的APK中.
典型应用: 手机QQ换肤的实现方式
Q的皮肤是一个无界面APK应用,这个皮肤应用中的资源和主程序的资源命名一致,通过主程序和皮肤程序共享进程实现主程序对皮肤程序中资源的访问,在程序运行时通过代码显示指定皮肤资源,缺点是在主程序中每个activity要增加复杂的使用哪种皮肤逻辑,优点是效率比较快,且使应用程序具有了良好的扩展性,降低了程序的耦合性. 包括其他类似的扩展功能,都可以利用此方式实现.

4) 官方提供皮肤制作工具或方法,用户可自制皮肤:
大致分为两种情况:
a. 应用程序主列表为一个GridView,用户可通过在设置中选择背景的颜色和按钮的风格. 直接进行替换即可.
b. 另外一种是有可视化带向导的工具。用户只要自己找一些图片、修改文字的字体替换就可以了。用户可以上传自制的皮肤,提供其他用户下载. 一般都是打包为.zip格式的,扩展名可由公司需求自定义. 例如墨迹天气皮肤扩展名是mja,搜狗输入法的皮肤扩展名是sga,它们的文件格式实际上都是zip。之后的应用就和第二种换肤方式类似了.
这种方式优点是:使用户有参与感,自由度较高。用户可根据自己的喜好定制软件的皮肤。
      
5) 改写SDK的Resource类:
此处提供一些思路:
在Android系统中,资源主要指图片和MP3类型的文件,也是用户UI包含的所有元素。谷歌在设计Android系统时,将UI界面和逻辑代码分开组建:界面设计通过XML的形式描述,具体的程序和应用逻辑则通过代码来实现;前端工程师只负责HTML和CSS的设计与架构,后端工程师则专门考虑JSP和Java的代码执行.
资源访问在Android性能架构中处于何种地位?在进行Android开发时,开发者经常用到Framework提供的资源包Framework.jar与Framework-res.apk,以及与核心资源相关的组件“Resource Manager”文件系统。
APK本身是一个简单的文件格式,也是一个压缩文件包。通过解压文件包可以释放APK文件:首先需要APK的原数据Meta INF、Manifest以及RES目录。一部分包含图片资源的应用,在资源释放时也会用到Layout。
在安装文件时,系统会将文件取出、解压后放在Dalvik Cache中。该缓存下有许多dex文件,当用户打开应用时系统会自动加载相应的类。在加载过程中,系统如需访问APK,则需对其进行解压,这样通常导致效率较为低下。而如果将dex文件放入Dalvik Cache中,则能够令加载的效率大大提升。
每个进程都有一份关于Framework的共享类和共享资源,但物理内存空间中的系统级别资源只有一份。Framework类和资源是只读的,而Android操作系统设计之初并没有硬盘的虚拟内存和换进换出机制,所以节省内存空间是非常重要的工作。

应用程序内置资源实现换肤功能
通过应用程序内置资源实现换肤,典型的应用为QQ空间中换肤的实现. 应用场景为: 应用一般不大,且页面较少,风格相对简单,一般只用实现部分资源或者只用实现背景的更换.
此种换肤方式实现的思路:
1. 把几套皮肤放在res/drawable目录里,然后用SharedPreferences来记录当前皮肤的资源id.然后在程序启动时加载Activity背景。
2. 主要的实现在皮肤管理器SkinManager类中. 将皮肤资源的ID加入集合中. 由该类同一调度皮肤更换,如初始化皮肤,获取当前皮肤符号以及具体的对应资源的更换皮肤.

接下来看一下效果图:

内置皮肤的实现相对比较简单,下面直接上代码:

AndroidMainfest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.tony.skindemo"
 android:versionCode="1"
 android:versionName="1.0" > 

 <uses-sdk android:minSdkVersion="8" /> 

 <application
  android:icon="@drawable/ic_launcher"
  android:label="@string/app_name" >
  <activity
   android:label="@string/app_name"
   android:name="com.tony.skindemo.SkinDemoActivity" >
   <intent-filter >
    <action android:name="android.intent.action.MAIN" /> 

    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity> 

 </application> 

</manifest> 

布局文件:
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical" > 

 <TextView
  android:textColor="#ff00ff"
  android:text="程序皮肤更换"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" />
<RadioGroup
  android:id="@+id/skin_options"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
   > 

  <RadioButton
   android:layout_weight="1"
   android:id="@+id/radioButton1"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="风格1" /> 

  <RadioButton
   android:layout_weight="1"
   android:id="@+id/radioButton2"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="风格2" /> 

  <RadioButton
   android:layout_weight="1"
   android:id="@+id/radioButton3"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="风格3" /> 

  <RadioButton
   android:layout_weight="1"
   android:id="@+id/radioButton4"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="风格4" /> 

  <RadioButton
   android:layout_weight="1"
   android:id="@+id/radioButton5"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="风格5" />
 </RadioGroup> 

</LinearLayout> 

程序主Activity

package com.tony.skindemo; 

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener; 

public class SkinDemoActivity extends Activity { 

 private SkinSettingManager mSettingManager;
 private RadioButton radioButton1;
 private RadioButton radioButton2;
 private RadioButton radioButton3;
 private RadioButton radioButton4;
 private RadioButton radioButton5; 

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState); 

  // 取消标题栏
  requestWindowFeature(Window.FEATURE_NO_TITLE);
  // 完成窗体的全屏显示 // 取消掉状态栏
  getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN); 

  setContentView(R.layout.main);
  // 初始化皮肤
  mSettingManager = new SkinSettingManager(this);
  mSettingManager.initSkins(); 

  //通过单选按钮设置皮肤(可自定义更换的方式,如导航栏,也可以加上预览功能,此处不再实现)
  radioButton1 = (RadioButton) findViewById(R.id.radioButton1);
  radioButton2 = (RadioButton) findViewById(R.id.radioButton2);
  radioButton3 = (RadioButton) findViewById(R.id.radioButton3);
  radioButton4 = (RadioButton) findViewById(R.id.radioButton4);
  radioButton5 = (RadioButton) findViewById(R.id.radioButton5);
  RadioGroup radioGroup = (RadioGroup) findViewById(R.id.skin_options);
  radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { 

   @Override
   public void onCheckedChanged(RadioGroup group, int checkedId) { 

    switch (checkedId) {
    case R.id.radioButton1:
     mSettingManager.changeSkin(1);
     break; 

    case R.id.radioButton2:
     mSettingManager.changeSkin(2);
     break;
    case R.id.radioButton3:
     mSettingManager.changeSkin(3);
     break;
    case R.id.radioButton4:
     mSettingManager.changeSkin(4);
     break;
    case R.id.radioButton5:
     mSettingManager.changeSkin(5);
     break;
    default:
     break;
    }
   }
  });
 } 

 // 这里为了简单实现,实现换肤
 public boolean onTouchEvent(MotionEvent event) {
  mSettingManager.toggleSkins();
  return super.onTouchEvent(event);
 } 

} 

皮肤管理器: 
 

import android.app.Activity;
import android.content.SharedPreferences; 

/**
 * 皮肤管理器
 * @author tony
 *
 */
public class SkinSettingManager { 

 public final static String SKIN_PREF = "skinSetting"; 

 public SharedPreferences skinSettingPreference; 

 private int[] skinResources = { R.drawable.default_wallpaper,
   R.drawable.wallpaper_c,R.drawable.wallpaper_d,R.drawable.wallpaper_f,
   R.drawable.wallpaper_g
 }; 

 private Activity mActivity; 

 public SkinSettingManager(Activity activity) {
  this.mActivity = activity;
  skinSettingPreference = mActivity.getSharedPreferences(SKIN_PREF, 3);
 } 

 /**
  * 获取当前程序的皮肤序号
  *
  * @return
  */
 public int getSkinType() {
  String key = "skin_type";
  return skinSettingPreference.getInt(key, 0);
 } 

 /**
  * 把皮肤序号写到全局设置里去
  *
  * @param j
  */
 public void setSkinType(int j) {
  SharedPreferences.Editor editor = skinSettingPreference.edit();
  String key = "skin_type"; 

  editor.putInt(key, j);
  editor.commit();
 } 

 /**
  * 获取当前皮肤的背景图资源id
  *
  * @return
  */
 public int getCurrentSkinRes() {
  int skinLen = skinResources.length;
  int getSkinLen = getSkinType();
  if(getSkinLen >= skinLen){
   getSkinLen = 0;
  } 

  return skinResources[getSkinLen];
 } 

 public void toggleSkins(){ 

  int skinType = getSkinType();
  if(skinType == skinResources.length - 1){
   skinType = 0;
  }else{
   skinType ++;
  }
  setSkinType(skinType);
  mActivity.getWindow().setBackgroundDrawable(null);
  try {
   mActivity.getWindow().setBackgroundDrawableResource(getCurrentSkinRes());
  } catch (Throwable e) {
   e.printStackTrace(); 

  } 

 } 

 /**
  * 用于初始化皮肤
  */
 public void initSkins(){
  mActivity.getWindow().setBackgroundDrawableResource(getCurrentSkinRes());
 } 

 /**
  * 随即切换一个背景皮肤
  */
 public void changeSkin(int id) { 

  setSkinType(id);
  mActivity.getWindow().setBackgroundDrawable(null);
  try {
   mActivity.getWindow().setBackgroundDrawableResource(getCurrentSkinRes());
  } catch (Throwable e) {
   e.printStackTrace();
  }
 } 

}

就这样,通过程序内置皮肤的基本功能完成了.若想在自己的应用中实现,仍需注意以下几点(实现起来并不复杂,此处不再写具体实现):
1.  实现多个activity的更换皮肤. 需要利用自定义MyApplication类,继承自Application. 并加入activity的集合属性.用于存储应用所有的activity。修改SkinManager,在更换皮肤时,从application中取出该集合,进行遍历并更换皮肤。
2.可以优化用户体验,通过导航栏方式进入更换皮肤界面,并可以加入预览功能,当确定修改配置后,才完成更换皮肤功能.
3. 加入style.theme等资源,实现更加复杂的皮肤更换. 具体实现同更换背景.
 

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
, 安卓
换肤
android 换肤功能实现、android 换肤实现、前端换肤功能如何实现、ios 换肤功能实现、js实现换肤功能,以便于您获取更多的相关知识。

时间: 2024-11-03 14:16:25

分析Android App中内置换肤功能的实现方式_Android的相关文章

分析Android App中内置换肤功能的实现方式

Android平台api没有特意为换肤提供一套简便的机制,这可能是外国的软件更注重功能和易用,不流行换肤.系统不提供直接支持,只能自行研究. 换肤,可以认为是动态替换资源(文字.颜色.字体大小.图片.布局文件--).这个使用编程语言来动态设置是可以做到的,例如使用View的setBackgroundResource.setTextSize.setTextColor等函数.但我们不可能在每个activity里对页面里的所有控件都通过调用这些函数来换肤,这样的程序代码难以维护.扩展,也违背了UI和代

Android App中实现向右滑动销毁功能的要点解析_Android

今天给大家带来一个向右滑动销毁Activity的效果,Activtiy随着手指的移动而移动,该效果在Android应用中还是比较少见的,在IOS中就比较常见了,例如"网易新闻" ,"美食杰" , "淘宝"等应用采用此效果,而Android应用中"知乎"采用的也是这种滑动切换Activity的效果, 不过我发现"淘宝"并没有随着手势的移动而移动,只是捕捉到滑动手势,然后产生平滑切换界面的动画效果,这个在And

Android平台中实现数据存储的5种方式_Android

本文介绍Android中的5种数据存储方式,具体内容如下   数据存储在开发中是使用最频繁的,在这里主要介绍Android平台中实现数据存储的5种方式,分别是:  1 使用SharedPreferences存储数据  2 文件存储数据  3 SQLite数据库存储数据  4 使用ContentProvider存储数据  5 网络存储数据   下面将为大家一一详细介绍.   第一种:使用SharedPreferences存储数据  SharedPreferences是Android平台上一个轻量级

Android App中实现向右滑动销毁功能的要点解析

今天给大家带来一个向右滑动销毁Activity的效果,Activtiy随着手指的移动而移动,该效果在Android应用中还是比较少见的,在IOS中就比较常见了,例如"网易新闻" ,"美食杰" , "淘宝"等应用采用此效果,而Android应用中"知乎"采用的也是这种滑动切换Activity的效果, 不过我发现"淘宝"并没有随着手势的移动而移动,只是捕捉到滑动手势,然后产生平滑切换界面的动画效果,这个在And

详解Android App中使用VideoView来实现视频播放的方法_Android

通过VideoView播放视频的步骤: 1.在界面布局文件中定义VideoView组件,或在程序中创建VideoView组件 2.调用VideoView的如下两个方法来加载指定的视频 (1)setVidePath(String path):加载path文件代表的视频 (2)setVideoURI(Uri uri):加载uri所对应的视频 3.调用VideoView的start().stop().psuse()方法来控制视频的播放 VideoView通过与MediaController类结合使用,

Android应用中内嵌SQLite数据库的基本操作指南_Android

一.首先写一个类继承SQLiteOpenHelper类 重写他的方法指定db的名称.版本,重写oncreat和onUpgrade方法,写SQL语句创建表 public class MySQLiteOpenhelper extends SQLiteOpenHelper { private static String name = "person.db"; private static int version = 1; public MySQLiteOpenhelper(Context c

Android App中使用Glide加载图片的教程_Android

与其他图片加载库相同,Glide除了可以加载网络图片之外,也可以加载本地图片.甚至还可以从各种各样奇葩的数据源中加载图片. 加载网络图片很多情况下,我们使用图片加载库就是为了加载网络图片.网络操作是一个很复杂的东西.试想一下,如果没有图片加载库,我们就要手动去下载图片,缓存图片,最后再从文件里面读取bitmap并设置到Imageview里面.这还算好的,要是在Listview里面你会更头疼的.原因我就不说了,你懂的~~再加上各种各样的Bitmap操作,保准你再也不想撸代码了.而且Bitmap这东

rfid-请问Android开发中一般带有NFC功能的手机能否实现NFID识别入场证id信息

问题描述 请问Android开发中一般带有NFC功能的手机能否实现NFID识别入场证id信息 请问Android开发中一般带有NFC功能的手机能否实现NFID识别入场证id信息 是否需要相应的物理模块? 比如在应用中实现读取员工卡的id来实现某个app的登陆功能. 解决方案 已解决. 从硬件厂家要了驱动接口

技术-android APP想做一个发帖功能,向有经验的大熊弟求教!!!

问题描述 android APP想做一个发帖功能,向有经验的大熊弟求教!!! 我想做的是 点击BUTTON 填写帖子的相关内容 发布后就会在列表中刷新,求相关技术或实例教程!! 万分拜谢!! 解决方案 这个很简单呀,列表和那个发布是在同一个界面吗?