Android插件化开发之动态加载本地皮肤包进行换肤
来源:互联网 发布:知贤装饰怎么样 编辑:程序博客网 时间:2024/06/05 03:27
Android插件化开发之动态加载本地皮肤包进行换肤
前言: 本文主要讲解如何用开源换肤框架 android-skin-loader-lib来实现加载本地皮肤包文件进行换肤,具体可自行参考框架原理进行更改!
实现:
1. https://github.com/fengjundev/Android-Skin-Loader 框架地址,下载文件,根据自己需要进行删减得到自己的文件. 我的文件主要有android-skin-loader-lib依赖包文件,android-skin-loader-skin自己的皮肤包文件,其它则可以不要
2.创建项目,导入依赖包和皮肤包文件
注意点 :
a. 依赖包,皮肤包的build.gradle里的版本相关需要和自己app的build.gradle里的一致!
b.依赖包,皮肤包的app name要和自己app的app name一直!
**c.**app里要替换的颜色,图片,在皮肤包目录也要有,且id要一直,才可以找到,并更换!
d.要换肤的界面需继承换肤依赖包里的base里的相关界面元素,当前有activity,fragment,fragmentActivity,在布局文件需添加相关标识,如下:
...xmlns:skin="http://schemas.android.com/android/skin"... <TextView ... skin:enable="true" ... />
e. 需要在自己App的Application文件中设置初始化:
SkinManager.getInstance().init(this); SkinManager.getInstance().load();
f.自定义view换肤需要实现IDynamicNewView 接口:
public interface IDynamicNewView { void dynamicAddView(View view, List<DynamicAttr> pDAttrs);}
3.编译皮肤包文件
因为我们的皮肤包文件是android-skin-loader-skin文件,它就是一个没有Java文件,只有资源文件的项目,所以我们要生成皮肤包文件也是由它生成的,就是一个apk啦,但是我们需要对它修改后缀名,防止用户点击.
当我们制定了皮肤包的相关文件时,修改皮肤包文件里的build.gradle,把编译生成的Apk文件进行重命名,并放到主项目的assets目录下.build.gradle目录如下:
apply plugin: 'com.android.application'def skinName = "BlackFantacy.skin"android { signingConfigs { config { keyAlias 'fengjun' keyPassword 'fengjun' storeFile file('keystore.key') storePassword 'fengjun' } } compileSdkVersion 23 buildToolsVersion '25.0.0' defaultConfig { applicationId "com.example.android_skin_laoder_skin" minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1'}final def TARGET_SKIN_DIR = '../app/src/main/assets/'gradle.projectsEvaluated { assembleRelease.doLast { println("=====================assembleRelease.doLast.begin.=========================") def dir = new File(TARGET_SKIN_DIR) if (!dir.exists()) { dir.mkdirs() } def f = new File(TARGET_SKIN_DIR + skinName) if (f.exists()) { f.delete() } copy { from('build/outputs/apk') into(TARGET_SKIN_DIR) include '*.apk' exclude '**/*-unaligned.apk' rename ('android-skin-loader-skin-release.apk', skinName) } println("=====================assembleRelease.doLast success.=========================") } assembleDebug.doLast { println("=====================assembleDebug.doLast.begin.=========================") def dir = new File(TARGET_SKIN_DIR) if (!dir.exists()) { dir.mkdirs() } def f = new File(TARGET_SKIN_DIR + skinName) if (f.exists()) { f.delete() } copy { from('build/outputs/apk') into(TARGET_SKIN_DIR) include '*.apk' exclude '**/*-unaligned.apk' rename ('android-skin-loader-skin-debug.apk', skinName) } println("=====================assembleDebug.doLast success.=========================") }}
然后进行编译:
当编译完成时这时我们的主项目assets目录下会出现一个BlackFantacy.skin的文件,这就是我们要换肤的文件:
4.实现换肤
创建Application文件:
package com.example.administrator.replaceappskin;import android.app.Application;import cn.feng.skin.manager.loader.SkinManager;/** * Created by Administrator on 2017/5/19. */public class ReplaceAppSkinApplication extends Application { public void onCreate() { super.onCreate(); initSkinLoader(); } /** * Must call init first */ private void initSkinLoader() { SkinManager.getInstance().init(this); SkinManager.getInstance().load(); }}
在注册文件添加指定的application文件:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.replaceappskin"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <application android:name=".ReplaceAppSkinApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
在要实现的activity或者fragment,fragmentActivity继承换肤的基类,我这里是把assets里的换肤文件写入sd卡中,生成自己的目录,方便以后置换,activity代码如下:
package com.example.administrator.replaceappskin;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import cn.feng.skin.manager.base.BaseActivity;import cn.feng.skin.manager.listener.ILoaderListener;import cn.feng.skin.manager.loader.SkinManager;import cn.feng.skin.manager.util.L;public class MainActivity extends BaseActivity { private static final String DATAPATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "我的主题包"; /** * 在DATAPATH中新建这个目录,TessBaseAPI初始化要求必须有这个目录。 */ private static final String tessdata = DATAPATH + File.separator + "主题"; /** * TessBaseAPI初始化测第二个参数,就是识别库的名字不要后缀名。 */ private static final String DEFAULT_LANGUAGE = "BlackFantacy"; /** * assets中的文件名 */ private static final String DEFAULT_LANGUAGE_NAME = DEFAULT_LANGUAGE + ".skin"; /** * 保存到SD卡中的完整文件名 */ private static final String LANGUAGE_PATH = tessdata + File.separator + DEFAULT_LANGUAGE_NAME; private TextView titleText; private Button setOfficalSkinBtn; private Button setNightSkinBtn; private boolean isOfficalSelected = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initSkinData(); initView(); } private void initSkinData() { //如果存在就删掉 File f = new File(LANGUAGE_PATH); if (f.exists()) { f.delete(); } if (!f.exists()) { File p = new File(f.getParent()); if (!p.exists()) { p.mkdirs(); } try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } InputStream is = null; OutputStream os = null; try { is = this.getAssets().open(DEFAULT_LANGUAGE_NAME); File file = new File(LANGUAGE_PATH); os = new FileOutputStream(file); byte[] bytes = new byte[2048]; int len = 0; while ((len = is.read(bytes)) != -1) { os.write(bytes, 0, len); } os.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) is.close(); if (os != null) os.close(); } catch (IOException e) { e.printStackTrace(); } } } private void initView() { titleText = (TextView) findViewById(R.id.title_text); titleText.setText("设置皮肤"); setOfficalSkinBtn = (Button) findViewById(R.id.set_default_skin); setNightSkinBtn = (Button) findViewById(R.id.set_night_skin); isOfficalSelected = !SkinManager.getInstance().isExternalSkin(); if (isOfficalSelected) { setOfficalSkinBtn.setText("官方默认(当前)"); setNightSkinBtn.setText("黑色幻想"); } else { setNightSkinBtn.setText("黑色幻想(当前)"); setOfficalSkinBtn.setText("官方默认"); } setNightSkinBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onSkinSetClick(); } }); setOfficalSkinBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onSkinResetClick(); } }); } protected void onSkinResetClick() { if (!isOfficalSelected) { SkinManager.getInstance().restoreDefaultTheme(); Toast.makeText(getApplicationContext(), "切换成功", Toast.LENGTH_SHORT).show(); setOfficalSkinBtn.setText("官方默认(当前)"); setNightSkinBtn.setText("黑色幻想"); isOfficalSelected = true; } } private void onSkinSetClick() { if (!isOfficalSelected) return; File skin = new File(LANGUAGE_PATH); if (skin == null || !skin.exists()) { Toast.makeText(getApplicationContext(), "请检查" + LANGUAGE_PATH + "是否存在", Toast.LENGTH_SHORT).show(); return; } SkinManager.getInstance().load(skin.getAbsolutePath(), new ILoaderListener() { @Override public void onStart() { L.e("startloadSkin"); } @Override public void onSuccess() { L.e("loadSkinSuccess"); Toast.makeText(getApplicationContext(), "切换成功", Toast.LENGTH_SHORT).show(); setNightSkinBtn.setText("黑色幻想(当前)"); setOfficalSkinBtn.setText("官方默认"); isOfficalSelected = false; } @Override public void onFailed() { L.e("loadSkinFail"); Toast.makeText(getApplicationContext(), "切换失败", Toast.LENGTH_SHORT).show(); } }); }}
相关的方法和原理可以去看看它的方式,这里不细讲!
效果如下:
Demo下载地址
- Android插件化开发之动态加载本地皮肤包进行换肤
- Android插件化系列第(二)篇---动态加载技术之应用换肤
- Android 使用动态加载框架DL进行插件化开发
- Android--使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- JSP 动作元素
- 关于String类型遗漏点与StringBuilder、StringBuffer
- Service进阶篇(bindService)
- HibernateTemplate常用方法详解
- onunload、onbeforeunload事件详解
- Android插件化开发之动态加载本地皮肤包进行换肤
- easyUi排序
- Android代码入侵原理解析(一)
- 微信扫码
- C#中的值传递
- 页面jQuery和controller
- Linux下VoltDB-5.8.7安装指南
- 字符串string
- 连接oracle数据库提示:用户名/口令无效的解决方案