GithubHelp home page GithubHelp logo

androidskin's Introduction

AndroidSkin

1引用

1.1 Add it in your root build.gradle at the end of repositories:

allprojects {

    repositories {
    
    
        ...
        maven { url 'https://jitpack.io' }
        
        
    }
    
    
}

1.2 Add the dependency

dependencies { implementation 'com.github.lkxiaojian:AndroidSkin:1.0.2' }

2使用

1.如果使用静态皮肤包 就别集成框架了 直接设置

delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES. //日间

delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_NO//夜间

即可 (查看3.1静态换肤)

2.2
在Application 中调用 SkinManager.init(this) Activity Activity 继承 SkinActivity
加载动态皮肤包 skinDynamic(skinPath, R.color.skin_item_color) //1外部路径 2主题颜色 还原皮肤包 defaultSkin(R.color.colorPrimary)

自定义VIew
需要 继承 ViewsMatch 查看domo 中的用法(可以封装一个BaseView ,看项目需求) com.zky.myskin.views.CustomCircleView

Fragment

调用 SkinManager.instance?.applyViews(view)

RecycleView
两种方式 1.把recycleView 复用关闭 holder.setIsRecyclable(true) (不建议) 在viewHodle 中调用 SkinManager.instance?.applyViews(view)

public class BaseHolder extends RecyclerView.ViewHolder{

public BaseHolder(@NonNull View itemView) {
    super(itemView);
    SkinManager.Companion.getInstance().applyViews(itemView);
}  

}

3原理 排版有问题 查看 Android Skin 换肤.html

3.1静态换肤

//Kotlin code

delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES. //日间

delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_NO//夜间

在对应的src 中新建 drawable-night values-night 即可

3.2 动态换肤 在Acvitity 创建布局都需要 setContentView()

@Override public void setContentView(int resId) { ensureSubDecor(); //android 总布局 是个FrameLayout 我们所有的布局都加载这个上面 R.id.content 是系统的ID ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); LayoutInflater.from(mContext).inflate(resId, contentParent); mAppCompatWindowCallback.getWrapped().onContentChanged(); }

LayoutInflater.from(mContext).inflate(resId, contentParent); 这个才是加载布局实际操作的

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" (" + Integer.toHexString(resource) + ")"); }

//如果创建过这个view ,内部通过反射 得到 
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
    return view;
}

//得到xml 解析器 XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }

inflate(parser, root, attachToRoot)

由于代码过多 简略下 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

    final Context inflaterContext = mContext;
     // 资源管理器
    final AttributeSet attrs = Xml.asAttributeSet(parser);
    Context lastContext = (Context) mConstructorArgs[0];
    mConstructorArgs[0] = inflaterContext;
    View result = root;

...

  rInflate(parser, root, inflaterContext, attrs, false);

... return result; } }

//解析xml 布局 用的是 Pull解析xml(while 循环 把view 添加到viewGroup)

void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;


while (((type = parser.next()) != XmlPullParser.END_TAG ||
        parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {


..

       final View view = createViewFromTag(parent, name, context, attrs);
        final ViewGroup viewGroup = (ViewGroup) parent;
        final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
        rInflateChildren(parser, view, attrs, true);
        viewGroup.addView(view, params);

..

}

rInflate 其中调用 tryCreateView() 方法

public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); }

View view;
 //这两个才是 创建view的方法  布局加载工厂
 //mFactory 是mFactory2 的父类
if (mFactory2 != null) {

    view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs);
} else {
    view = null;
}
if (view == null && mPrivateFactory != null) {
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;

}

大致流程是

通过布局ID (resID) —> XmlResourceParser —> 通过while 循环添加 { createViewFromTag() 这个方法里面区分是否是自定义view(if (-1 == name.indexOf('.')) {})。 不是 android.view.* 这样的路径 不过都通过反射实例化对象 (如果有缓冲,冲缓存中读取) 然后 根布局.addView }

在看 super.onCreate override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

}

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory(); delegate.onCreate(savedInstanceState); super.onCreate(savedInstanceState); }

@Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { LayoutInflaterCompat.setFactory2(layoutInflater, this); } else { if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } } }

这个方法设置 Factory 的 如果是null 抛出异常 所以我们在 super.onCreate(savedInstanceState) 添加 val layoutInflater = LayoutInflater.from(this) LayoutInflaterCompat.setFactory2(layoutInflater, this) 这样就不会抛出异常了

然后在onCreateView 中收集资源就可以了 override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? { if (openChangeSkin()) { if (viewInflater == null) { viewInflater = CustomAppCompatViewInflater(context) } viewInflater!!.setName(name) viewInflater!!.setAttrs(attrs) return viewInflater!!.autoMatch() } return super.onCreateView(parent, name, context, attrs) }

如TextView

设置textColor 属性 // 根据自定义属性,获取styleable中的textColor属性 key = R.styleable.SkinnableTextView[R.styleable.SkinnableTextView_android_textColor] val textColorResourceId = attrsBean.getViewResource(key) if (textColorResourceId > 0) { if (instance!!.isDefaultSkin) { val color = ContextCompat.getColorStateList(context, textColorResourceId) setTextColor(color) } else { val color = instance!!.getColorStateList(textColorResourceId) setTextColor(color) } }

读取外部皮肤包 fun loaderSkinResources(skinPath: String?) { // 如果没有皮肤包或者没做换肤动作,方法不执行直接返回! if (TextUtils.isEmpty(skinPath)) { isDefaultSkin = true return } // 优化:app冷启动、热启动可以取缓存对象 if (cacheSkin.containsKey(skinPath)) { isDefaultSkin = false val skinCache = cacheSkin[skinPath] if (null != skinCache) { skinResources = skinCache.skinResources skinPackageName = skinCache.skinPackageName return } } try { // 创建资源管理器(此处不能用:application.getAssets()) val assetManager = AssetManager::class.java.newInstance() // 由于AssetManager中的addAssetPath和setApkAssets方法都被@hide,目前只能通过反射去执行方法 val addAssetPath = assetManager.javaClass.getDeclaredMethod(ADD_ASSET_PATH, String::class.java) // 设置私有方法可访问 addAssetPath.isAccessible = true // 执行addAssetPath方法 addAssetPath.invoke(assetManager, skinPath)

  // 创建加载外部的皮肤包(mySkin.skin.apk  名字随便去)文件Resources(注:依然是本应用加载)
    skinResources = Resources(assetManager,
            appResources.displayMetrics, appResources.configuration)
    // 根据apk文件路径(皮肤包也是apk文件),获取该应用的包名。兼容5.0 - 9.0(亲测)
    skinPackageName = application.packageManager
            .getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES).packageName
    // 无法获取皮肤包应用的包名,则加载app内置资源
    isDefaultSkin = TextUtils.isEmpty(skinPackageName)
    if (!isDefaultSkin) {
        cacheSkin[skinPath] = SkinCache(skinResources, skinPackageName)
    }
    Log.e("skinPackageName >>> ", skinPackageName)
} catch (e: Exception) {
    e.printStackTrace()
    // 发生异常,预判:通过skinPath获取skinPacakageName失败!
    isDefaultSkin = true
}

}

androidskin's People

Contributors

lkxiaojian avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.