目录
4.2.调用兼容AppCompatActivity代理类AppCompatDelegate实现xml布局到View视图的转换
4.3.AppCompatActivity定义实现不同版本AppCompatDelegate实现类
4.4.setContentView()方法最终调用代理类AppCompatDelegateImplV7中的方法
4.5.在LayoutInflater中实现基于XmlPullParser解析xml布局文件
4.6.xml解析调用createViewFromTag()通过控件名称和属性集合创建视图
4.7.createViewFromTag()真正执行解析xml布局文件以后创建View视图
4.8.mFactory2来源(调试发现最终调用的是AppCompatActivity代理类的onCreateView()方法)
6.1SkinFactory实现Factory2接口,完成视图的创建,视图的缓存,全部视图的换肤操作;
6.2SkinEngine加载插件apk的对应的Resources,(Resources用插件apk的资源)实现更换皮肤的各种方法(例如:getColor等)
实现换肤的方案:
a.静态修改theme主题方式
设置多套皮肤的theme;
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="defaultcolor">@color/defalultcolor</item>
</style>
<style name="NewTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="defaultcolor">@color/newcolor</item>
</style>
</resources>
声明属性需要动态替换的样式属性
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="defaultcolor" format="reference|color" />
</resources>
为控件设置属性样式?attr/defaultcolor
<TextView
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是第一个fragment"
android:textColor="?attr/defaultcolor"
android:textSize="36dp" />
动态设置主题
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if( 1 == 1){
setTheme(R.style.AppTheme);
}else {
setTheme(R.style.NewTheme);
}
setContentView(R.layout.activity_main);
}
缺点是设置主题在setContentView之前,设置以后需要重启Activity;
b.动态修改样式(应用内/插件化-皮肤apk),不需要重启Activity
应用内
采用通过相同名称+不同皮肤后缀来区分不同皮肤,如实现黑白皮肤,文本颜色item_text_color有一套默认皮肤,一套黑色皮肤定义资源item_text_color,item_text_color_black;
String resName = mOutResource.getResourceEntryName(resId);
int outResId = mOutResource.getIdentifier(resName, "drawable", mOutPkgName);
resName+"_"+不同皮肤后缀
缺点:应用内多套皮肤时可能导致安装包过大;
插件化-皮肤apk
皮肤apk和主apk有相同皮肤资源名称,获取皮肤apk下的资源名称,需要和主apk下资源名称一致,设置在皮肤apk下皮肤资源 ;
String resName = mOutResource.getResourceEntryName(resId);
int outResId = mOutResource.getIdentifier(resName, "drawable", mOutPkgName);
mOutResource皮肤apk资源Resources;
动态修改视图皮肤需要解决两个问题:
1).缓存获取全部需要换肤的视图,换肤(资源)时执行换肤操作;
2).获取Resources资源(插件apk资源Resources),动态设置皮肤;
1.什么是一键换肤
所谓”一键换肤“就是通过一个接口调用,实现app范围内所有资源文件替换,包括文本,颜色,图片,动画等;
2.界面上那些东西可以换肤
例如TextView文字颜色,字体大小,ImageView的background等等;
res目录所有的资源几乎都可以替换,具体如下:
动画
背景图片
字体
字体颜色
字体大小
音频
视频
3.利用Hook实现一键换肤
什么是hook
如题,我是用hook实现一键换肤。那么什么是hook?
hook,钩子. 安卓中的hook技术,其实是一个抽象概念:对系统源码的代码逻辑进行"劫持",插入自己的逻辑,然后放行。注意:hook可能频繁使用java反射机制···
"一键换肤"中的hook思路
- "劫持"系统创建View的过程,我们自己来创建View
系统原本自己存在创建View的逻辑,我们要了解这部分代码,以便为我所用. - 收集我们需要换肤的View(用自定义view属性来标记一个view是否支持一键换肤),保存到变量中
劫持了 系统创建view的逻辑之后,我们要把支持换肤的这些view保存起来 - 加载外部资源包,调用接口进行换肤
外部资源包,是.apk
后缀的一个文件,是通过gradle
打包形成的。里面包含需要换肤的资源文件,但是必须保证,要换的资源文件,和原工程里面的文件名完全相同
.
4.Android创建视图源码分析
自己定义Activity继承自兼容AppCompatActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
创建Activity经常在onCreate()方法中调用setContentView(R.layout.xxx);设置要显示视图,那么如何将我们创建的xml布局文件转换为要显示View视图呢?
源码执行流程:
4.1.自定义Activity设置要显示的布局文件xml
setContentView(R.layout.activity_main);
4.2.调用兼容AppCompatActivity代理类AppCompatDelegate实现xml布局到View视图的转换
由于Android版本比较分散,需要兼容各个版本Android系统,AppCompatActivity定义实现不同版本AppCompatDelegate实现类;
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
4.3.AppCompatActivity定义实现不同版本AppCompatDelegate实现类
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV7(context, window, callback);
}
}
4.4.setContentView()方法最终调用代理类AppCompatDelegateImplV7中的方法
通过如下实现将layout的xml布局文件转换为View视图添加到父视图上contentParent;
LayoutInflater.from(mContext).inflate(resId, contentParent);
4.5.在LayoutInflater中实现基于XmlPullParser解析xml布局文件
4.6.xml解析调用createViewFromTag()通过控件名称和属性集合创建视图
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
View result = root;
//找到跟节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//获取跟节点名字
final String name = parser.getName();
//创建xml的layout布局文件跟视图
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//递归调用创建子视图
rInflateChildren(parser, temp, attrs, true);
// 添加到xml布局文件视图到父视图
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//将xml布局的跟视图添加做为父视图
if (root == null || !attachToRoot) {
result = temp;
}
...部分源码
return result;
}
}
//创建xml布局文件的跟视图
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//递归创建xml布局文件的子视图,最后调用createView