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下载地址

阅读全文
3 0
原创粉丝点击