Android换肤功能

Android换肤原理和Android-Skin-Loader框架解析

换肤类型

1.白天/黑夜主题切换 一般通过本地theme来做。
使用相同的资源id,但在不同的Theme下自定义不同的资源,通过主动切换不同的Theme从而切换界面元素创建时使用的资源,这种方案适合代码量不多的情况。缺点是对于已创建界面的皮肤,必须重新加载界面元素。
2.多种主题切换 一般作为线上服务,皮肤资源在下载后进行使用。

方案

1.拦截系统创建View的过程,交由自己来创建
2.收集需要换肤的View
如何辨别那个View需要换肤?
可以用自定义view属性来标记,需要的话,将其保存起来
3.加载外部资源包,进行换肤

加载资源包

换肤相应的API

Resources提供了可以通过@+id、Type、PackageName这三个参数就可以在AssetManager中寻找相应的PackageName中有没有Type类型并且id值都能与参数对应上的id,进行返回,然后通过这个id再调用Resource的获取资源的api就可以得到相应的资源。
这里我们需要注意的一点是getIdentifier(String name,String defType,String defPackage)方法和getString(int id)方法所调用Resources对象的mAssets对象必须是同一个,并且包含有PackageName这个资源包。

AssetManager构造

AssetManager的构造函数来看有{@hide}的注解,所以在其他类里面是直接创建AssetManager实例,但不要忘记Java中还有反射机制可以创建类对象。

AssetManager assetManager = AssetManager.class.newInstance();

如何让创建的assetManager包含特定的PackageName的资源信息
需要使用@hide注解的addAssetPath()方法
只能通过反射的方法来调用

String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk";
AssetManager assetManager = null;
try {
	AssetManager assetManager = AssetManager.class.newInstance();
	AssetManager.class.getDeclaredMethod("addAssetPath,String.class).invoke(assetManager,apkPath);
} catch(Throwable th) {
th.printStackTrace();
}

换肤Resources构造

public Resources getSkinResources(Context context){ 
/**
     * 插件apk路径
     */ 
     String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk"; AssetManager assetManager = null; 
     try { 
     AssetManager assetManager =AssetManager.class.newInstance(); 
     AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath); 
     } catch (Throwable th) {
      th.printStackTrace(); 
      } 
      return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); }

使用资源包中的资源换肤

public Resources getSkinResources(Context context){ 
/**
     * 插件apk路径
     */ 
     String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk"; 
     AssetManager assetManager = null;
      try { 
      AssetManager assetManager = AssetManager.class.newInstance(); 
      AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath);
       } catch (Throwable th) { 
       th.printStackTrace(); 
       }
        return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
         } 
         @Override 
         protected void onCreate(Bundle savedInstanceState) { 
         super.onCreate(savedInstanceState); 
         setContentView(R.layout.activity_main); 
         ImageView imageView = (ImageView) findViewById(R.id.imageView); 
         TextView textView = (TextView) findViewById(R.id.text); 
         /**
     * 插件资源对象
     */ 
     Resources resources = getSkinResources(this); 
     /**
     * 获取图片资源
     */ 
     Drawable drawable = resources.getDrawable(resources.getIdentifier("night_icon", "drawable","com.tzx.skin"));
      /**
     * 获取Color资源
     */ 
     int color = resources.getColor(resources.getIdentifier("night_color","color","com.tzx.skin")); 
     imageView.setImageDrawable(drawable); textView.setText(text); 
     }

LayoutInflater.Factory

Android给我们在View生产的时候做修改提供了法门。

public abstract class LayoutInflater { 
/***部分代码省略****/ 
public interface Factory { 
public View onCreateView(String name, Context context, AttributeSet attrs); 
} 
public interface Factory2 extends Factory { 
public View onCreateView(View parent, String name, Context context, AttributeSet attrs); 
} 
/***部分代码省略****/ }

我们可以给当前页面的Windos对象在创建的时候设置Factory,那么在Window中的View进行创建时候就会通过自己设置的Factory进行创建。

Android-Skin-Loader解析

初始化

初始化换肤框架,导入需要换肤的资源包(当前为一个apk文件,其中只有资源文件)。

public class SkinApplication extends Application {
	public void onCreate() {
		super.onCreate();
		initSkinLoader();
		}
		/*Must call init first*/
		private void initSkinLoader() {
			SkinManager.getInstance().init(this);
			SkinManager.getInstance().load();
			}
			}

黑夜模式的简单流程

styles.xml

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="clockBackground">@android:color/white</item>
        <item name="clockTextColor">@android:color/black</item>
    </style>

attrs.xml

<?xml version="1.0" encoding="utf-8" ?>
<resources>
    <attr name="clockBackground" format="color" />
    <attr name="clockTextColor" format="color" />
</resources>

TextView里的android:textColor="?attr/clockTextColor"是让字体颜色跟随所设置的Theme.


setTheme(R.style.NightTheme);

TypedValue background = new TypedValue();
        TypedValue textColor = new TypedValue();
        Resources.Theme theme = getTheme();
        theme.resolveAttribute(R.attr.clockBackground,background,true);
        theme.resolveAttribute(R.attr.clockTextColor,textColor,true);

        mHeaderLayout.setBackgroundResource(background.resourceId);

获取截屏的相关操作

View view = getWindow.getDecorView();
view.setDrawingCacheEnable(true);
final Bitmap drawingCache = view.getDrawingCahe();

将截图显示上去,演示动画

final View view = new View(this);
view.setBackground(new BitmapDrawable(getResources(),cacheBitmap));
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
((ViewGroup) decorView).addView(view,layoutParams);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view,"alpha",1f,0f);
            objectAnimator.setDuration(300);
            objectAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    ((ViewGroup) decorView).removeView(view);
                }
            });
            objectAnimator.start();

让RecyclerView缓存的Item失效

/*
        * 让RecylerView 缓存在Pool中的Item失效
        * 那么,如果是ListView,要怎么做呢?这里的思路是通过反射拿到AbsListView类中的RecycleBin对象
        * 然后同样再用反射去调用clear方法
        * */
        Class<RecyclerView> recyclerViewClass = RecyclerView.class;
        try {
            Field declaredField = recyclerViewClass.getDeclaredField("mRecycler");
            declaredField.setAccessible(true);
            Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear",(Class<?>[]) new Class[0]);
            declaredMethod.setAccessible(true);
            declaredMethod.invoke(declaredField.get(mRecyclerView),new Object[0]);
            RecyclerView.RecycledViewPool recycledViewPool = mRecyclerView.getRecycledViewPool();
            recycledViewPool.clear();
        } catch (Exception e) {
            e.printStackTrace();
        }

代码的分析

skintest

MyApplication

onCreate()方法中调用,super.onCreate()语句之后
SkinManager.get().init(this);
get()方法,用于创建SkinManager对象

@MainThread
    public void init(Context context) {
    	//使用传进来的应用程序的上下文对象
        mContext = context.getApplicationContext();
        mSkinResourceManager = new SkinResourceManagerImpl(mContext, null, null);
        /*
        *public SkinResourceManagerImpl(Context context, String pkgName, Resources resources) {
        mDefaultResources = context.getResources();
        mSkinPluginPackageName = pkgName;
        mSkinPluginResources = resources;
    }
        */

		//加载已经用户默认设置的皮肤资源
        load();
        /*
        String skinApkPath = SkinConfig.getSkinPath();
        此处返回的是String类型的mSaveSkinFilePath = "/storage/emulated/0/Android/skins/default_skin.txt";
        if (TextUtils.isEmpty(skinApkPath)) {
            restoreToDefaultSkin();
            /*
            mSkinResourceManager.setPluginResourcesAndPkgName(null, null);
            notifySkinChanged();//更换皮肤时,通知view更换资源
            */
        } else {
            loadNewSkin(skinApkPath);//加载新皮肤
        }
        */
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值