美文网首页
android 皮肤换肤

android 皮肤换肤

作者: BigBigArvin | 来源:发表于2022-11-30 16:40 被阅读0次

很多app都会设置夜间和白天的模式,而实现换肤的方法有很多种,有的必须重新进入才能有效果,有的是动态的,设置了就马上就可以显示。
首先看看通过设置主题的方式来实现换肤
通过设置setTheme(R.style.BlackTheme);来改变字体颜色,背景灯
<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">

<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>

<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>

<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>

</style>

可以设置多个主题,然后在进入activity改变主题,但是这中方法,不能时时有效,必须重新进入才能。
所以实际项目中运用更多的是动态换肤。
换皮肤我们需要解决的问题是找到view对象,然后和获取到需要替换的资源文件
第一步获取需要换肤的view,
一种是在Activity的oncreat方法setContentView方法后获取view,但是此时view
已经加载了,我们再去获取修改,就要重新设置一次,而且每个activity都要写大量代码,体验和性能都不好。
我们都知道activity加载布局文件是在
setContentView里添加的。无论是继承 AppCompatActivity 或者Activity最后
都是会执行到LayoutInflater.from(mContext).inflate(resId, contentParent);

然后这里面就是循环解析xml文件最后会执行到

public final View tryCreatView(View parent,String name,Context context,AttributeSet attrs){
if(mFactoty2!=null){
view =mFactoty2.onCreatView(parent,name,context,attrs)
}else if(mFactory != null){
view =mFactoty.onCreatView(name,context,attrs)
}
if(view ==null && mPrivateFacory){
view =mPrivateFacory.onCreatView(parent,name,context,attrs)
}
}

可以看到最终会执行到这个地方,这里面默认mFactoty2 和mFactory 都是空
最后执行到mPrivateFacory 这个地方,但是在这里没有看到这个new出来,但是我们看activity的启动时候在ActivityThread里面的的activity.attach
方法mwindow.getLayoutInflater.setprivateFactory(this),设置了,所以如果我们想
在可以在getLayoutInflater ,设置factory2活着factory去在我们自定义的LayoutInflater.factory 里面去获取view去设置对应的资源属性。

然后重写这个方法。

public View onCreateView(@Nullable View view, @NonNull String name, @NonNull Context context, @NonNull AttributeSet set) {
        Log.d("jun","------>"+name);


        View realView= null;

        if (name.contains(".")) {//表示不是常用的TextView这些控件,不包括自定义,
            // 第三方。v7 ,v4,androidx等库的控件,这些需要单独去适配这里只是说原理,其他的都是差不多的
            realView = createView(name, context, set);
        } else {//系统控件
            for (int i = 0; i < sClassPrefixList.length; i++) {
                realView = createView(sClassPrefixList[i] + name, context, set);
                if (realView != null) {
                    break;
                }
            }
        }
        List<SkinAttr> skinAttrsList = new ArrayList<>();
        for (int i=0;i<set.getAttributeCount();i++){
            String attributeName = set.getAttributeName(i);//属性的名字background
            String attributeValue = set.getAttributeValue(i);//属性的值

            //在这里收集的属性主要是皮肤换肤需要的一些属性,例如background,textColor,src等
            if(isSupportSkinAttr(attributeName)){
                //资源的id,实际就是R文件的id
                int resId = Integer.parseInt(attributeValue.substring(1));//截取@2131361811 ,拿到实际的在R文件中的值
                String resName = context.getResources().getResourceName(resId);//这个是完整的路径
                String res = context.getResources().getResourceEntryName(resId);//资源的名字
                String attrType = context.getResources().getResourceTypeName(resId);
                Log.i("jun","res"+res+"name:"+resName+"----attrType"+attrType+"---rId");
                SkinAttr attr = new SkinAttr(attributeName,attrType,res,resId);
                skinAttrsList.add(attr);
            }
        }
        SkinView skinView = new SkinView(view,skinAttrsList);
        skinViews.add(skinView);
        skinView.skinApply();
        return realView;
    }

这样我们获取了所有的view对象和它的属性
然后需要解决的就是如何获取到我们的替换资源。
我们都知道资源的获取是通过context.getResource获取的。

而context是在什么时候创建的了,我们在看activity启动流程时候
在ActivityThread的performlaunchActivity方法中

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
      //下面省略

}

createBaseContextForActivity 会创建basecontext ,可以看出实际 的创建是在
ContextImpl 这里完成的 走到了 createActivityContext 这个方法

 static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        String[] splitDirs = packageInfo.getSplitResDirs();
        ClassLoader classLoader = packageInfo.getClassLoader();
  //省略
   context.setResources(resourcesManager.createBaseTokenResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getOverlayPaths(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader,
                packageInfo.getApplication() == null ? null
                        : packageInfo.getApplication().getResources().getLoaders()));


}

这里设置资源可以得知resourcesManager.createBaseTokenResources( 这里面创建的

最后走到resourcesManager 的

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        final AssetManager assets = createAssetManager(key, apkSupplier);
        if (assets == null) {
            return null;
        }

        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);

        final Configuration config = generateConfig(key);
        final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);

        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

我们看到这个点地方会创建AssetManager ,不过33版本的创建Assertmanger 方式改变了,

 private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        final AssetManager.Builder builder = new AssetManager.Builder();

        final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
        for (int i = 0, n = apkKeys.size(); i < n; i++) {
            final ApkKey apkKey = apkKeys.get(i);
            try {
                builder.addApkAssets(
                        (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
            } catch (IOException e) {
                if (apkKey.overlay) {
                    Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
                } else if (apkKey.sharedLib) {
                    Log.w(TAG, String.format(
                            "asset path '%s' does not exist or contains no resources",
                            apkKey.path), e);
                } else {
                    Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
                    return null;
                }
            }
        }

        if (key.mLoaders != null) {
            for (final ResourcesLoader loader : key.mLoaders) {
                builder.addLoader(loader);
            }
        }

        return builder.build();
    }

之前的是调用AssetManager的这个方法去把资源路径传递
public int addAssetPath(String path) {
}
把path传到apkkey里面然后添加到这个方法
新版的是 builder.addApkAssets( apkkey)
不过原来的addAssetPath被标为过时的,还可以用。
我们这个地方还是可以按照

首先获取 资源文件

  public boolean loadSkin(String skinPath) {
        //------------拿到skinPackageName----------
        boolean isSuccess =false;
        PackageInfo packageArchiveInfo = mContext.getPackageManager().getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES);
        if (packageArchiveInfo == null) {
        } else {
            //----------拿到skin中的Resource对象----------
            AssetManager assets = null;
            skinPackageName = packageArchiveInfo.packageName;
            try {
                assets = AssetManager.class.newInstance();
                Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assets, skinPath);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            mChooseResources = new Resources(assets, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
            isSuccess =true;

        }
        return isSuccess;
    }

然后将这个mChooseResources 保存,换肤获取资源就通过这个去获取。

结合上面获取到的view,就可以直接进行换肤了。
demo 在这里 代码.

相关文章

  • android 皮肤换肤

    很多app都会设置夜间和白天的模式,而实现换肤的方法有很多种,有的必须重新进入才能有效果,有的是动态的,设置了就马...

  • 换肤

    Android换肤功能 什么是换肤?app的皮肤,比如说黑夜模式,切换之后整体风格改变成以黑色为主题色 换了什么?...

  • Android App换肤实现 - 准备工作

    从网易云音乐换肤开始 Android 模拟器或者Android手机(需要root权限) 安装网易云音乐并选择皮肤进...

  • Android 换肤框架, 极低的学习成本, 极好的用户体验.

    制作皮肤插件: 新建Android application工程 将需要换肤的资源放到res目录下(同名资源) 打包...

  • 个人博客—换肤

    个人博客—换肤 点击换肤按钮 获取后台皮肤列表 点击皮肤列表中图片 换肤成功 html部分 js部分 php部分 ...

  • 2.3 Android 换肤原理

    Android 换肤原理 制作皮肤包,皮肤包相当于一个apk,不过只包含了资源文件 获取到皮肤包的Resource...

  • Android主题换肤框架 无缝切换

    Android-Skin-Loader 一个通过动态加载本地皮肤包进行换肤的皮肤框架 工程目录介绍 用法 1. 在...

  • 夜间模式实践

    现状 夜间模式是android换肤的一种,关于换肤的相关知识总结,大家可以参考这篇文章Android换肤技术总结-...

  • Android主题换肤 无缝切换

    今天再给大家带来一篇干货。 Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重启直接实现无缝...

  • 漂亮的东西谁不爱呢?Android主题换肤,阿里大牛教你一招无缝

    今天再给大家带来一篇干货。 Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重启直接实现无缝...

网友评论

      本文标题:android 皮肤换肤

      本文链接:https://www.haomeiwen.com/subject/nxxrfdtx.html