狸猫换太子
可以理解为偷天换日
,其实本质上是修改源码的原有逻辑从而实现自己的需求,本章主要讲解Attrs的高级用法。
【Attrs的基本用法】
首先,我们先来了解一下Attrs的基本用法:
- 声明属性
<declare-styleable name="CustomTextView">
<!--颜色值-->
<attr name="textview_textColor" format="color"/>
<!--字符串-->
<attr name="textview_text" format="string"/>
</declare-styleable>
- 自定义View
public class CustomTextView extends AppCompatTextView {
//将要填写的内容
private String content = "我是中国人!";
private Paint mPaint;
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr){
mPaint = new Paint();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyleAttr, 0);
//获取颜色值
int textColor = a.getColor(R.styleable.CustomTextView_textview_textColor, Color.GRAY);
//如果xml中没有设置颜色值,就使用默认,否则就使用xml中设置的颜色值
mPaint.setColor(textColor);
//获取字符串
content = a.getString(R.styleable.CustomTextView_textview_text);
a.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(content, 100, 100, mPaint);
}
}
以上自定义View比较简单,就是绘制一个文本。
- 在布局中使用该自定义View
<com.example.attrdemo.CustomTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:textview_textColor="@color/colorAccent"
app:textview_text="我是中国人!" />
Attrs的基本用法比较简单,但是在布局中,只能在CustomTextView
标签内使用app:textview_textColor
和app:textview_text
这两个属性。
然而熟悉ConstraintLayout(约束布局)
的人都知道,它的父类是ViewGroup,相当于自定义ViewGroup,它的属性可以在其子视图中使用,比如下面代码:
以上画红线的属性就是ConstraintLayout(约束布局)
的属性,那么在我们的自定义视图中是否也可以实现这样的效果呢?
答案是可以的。
下面开始给出实现步骤以及解释。
【第一步】
声明属性
<declare-styleable name="CustomViewGroup">
<!--颜色值-->
<attr name="super_textcolor" format="color"/>
<!--字符串-->
<attr name="super_text" format="string"/>
<!--背景色-->
<attr name="super_background" format="color"/>
</declare-styleable>
为了简单起见,就声明三个属性了。
【第二步】
自定义ViewGroup
写好ViewGroup备用,怎么简单怎么来,这里就继承LinearLayout了。
public class CustomViewGroup extends LinearLayout {
public CustomViewGroup(Context context) {
this(context, null);
}
public CustomViewGroup(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(LinearLayout.VERTICAL);
}
}
在构造方法里,我设置了布局方向,接下来需要遍历CustomViewGroup下的子视图以及将自定义属性
强行添加到子视图中。
假设,xml中的布局是这样的,在CustomViewGroup布局下有两个子视图,如下:
<?xml version="1.0" encoding="utf-8"?>
<com.example.attrdemo.CustomViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp"/>
</com.example.attrdemo.CustomViewGroup>
那么,这两个子视图是如何被添加到CustomViewGroup布局中,这两个子视图本身的属性又是如何被添加的呢?
【第三步】
源码解析ViewGroup中子视图的添加以及子视图属性的配置
我们都知道,Activity添加布局的方法是:
setContentView(R.layout.activity_main)
其源码分析流程我已经绘制好了,如下:
image.png其中,核心最为核心的一句代码是:
//在布局中添加子视图
root.addView(temp, params)
temp是子视图,params是属性,生成params的关键代码如下:
//自动生成布局属性
params = root.generateLayoutParams(attrs)
所以,经过源码分析,要想在自定义布局下的子视图中使用自定义属性需要重写generateLayoutParams
方法生成自己想要的属性,以及重写addView(child, myCustomParam)
方法将属性设置到子视图中。
【第四步】
生成自己想要的属性
先看下generateLayoutParams
方法的源码
ViewGroup.java
/**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @param attrs the attributes to build the layout parameters from
*
* @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
ViewGroup的属性其实就是new一个LayoutParams对象,attrs是默认属性,那么我们可以狸猫换太子
,重写generateLayoutParams
方法替换LayoutParams对象。
代码如下:
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
//狸猫换太子
return new MyCustomParam(getContext(), attrs);
}
MyCustomParam是自定义布局的内部类,代码如下:
class MyCustomParam extends LinearLayout.LayoutParams{
//xml中自定义属性的个数
private int paramCount = 0;
//文本
private String text;
//文本颜色
private int textColor;
//背景色
private int background;
public MyCustomParam(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewGroup);
//xml中使用到的属性个数
paramCount = a.getIndexCount();
//获取颜色值
background = a.getColor(R.styleable.CustomViewGroup_super_background, Color.parseColor("#00ffffff"));
//文本
text = a.getString(R.styleable.CustomViewGroup_super_text);
//文本颜色
textColor = a.getColor(R.styleable.CustomViewGroup_super_textcolor, Color.BLACK);
//释放
a.recycle();
}
public String getText() {
return text;
}
public int getTextColor() {
return textColor;
}
public int getBackground() {
return background;
}
public int getParamCount() {
return paramCount;
}
}
【第五步】
将新的子视图添加到自定义布局中
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
//params的类型必然是MyCustomParam
MyCustomParam myCustomParam = (MyCustomParam) params;
//当子视图没有使用自定义属性时,默认调用底层
if(myCustomParam.getParamCount() == 0){
super.addView(child, myCustomParam);
}else{//将修饰之后的clild添加到自定义布局
//设置子视图的背景色
child.setBackgroundColor(myCustomParam.getBackground());
if(child instanceof TextView){
TextView textView = (TextView) child;
//设置文件颜色
textView.setTextColor(myCustomParam.getTextColor());
//设置文本
textView.setText(myCustomParam.getText());
}
super.addView(child, myCustomParam);
}
}
在第四步中,自定生成的属性对象是MyCustomParam,所以重写addView方法时传递过来的param参数必然是MyCustomParam类型。
接着在MyCustomParam中获取自定义的属性值,将这些属性值设置到child中,最后将新的child添加到自定义布局。
【第六步】
xml中的使用
<?xml version="1.0" encoding="utf-8"?>
<com.example.attrdemo.CustomViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp"
app:super_textcolor="@color/colorAccent"
app:super_text="文本测试1!" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp"
app:super_text="文本测试2!"
app:super_background="@color/colorAccent"/>
</com.example.attrdemo.CustomViewGroup>
效果如下:
image.png自定义布局的全部代码如下:
public class CustomViewGroup extends LinearLayout {
public CustomViewGroup(Context context) {
this(context, null);
}
public CustomViewGroup(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(LinearLayout.VERTICAL);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
//params的类型必然是MyCustomParam
MyCustomParam myCustomParam = (MyCustomParam) params;
//当子视图没有使用自定义属性时,默认调用底层
if(myCustomParam.getParamCount() == 0){
super.addView(child, myCustomParam);
}else{//将修饰之后的clild添加到自定义布局
//设置子视图的背景色
child.setBackgroundColor(myCustomParam.getBackground());
if(child instanceof TextView){
TextView textView = (TextView) child;
//设置文件颜色
textView.setTextColor(myCustomParam.getTextColor());
//设置文本
textView.setText(myCustomParam.getText());
}
super.addView(child, myCustomParam);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
//狸猫换太子
return new MyCustomParam(getContext(), attrs);
}
class MyCustomParam extends LinearLayout.LayoutParams{
//xml中自定义属性的个数
private int paramCount = 0;
//文本
private String text;
//文本颜色
private int textColor;
//背景色
private int background;
public MyCustomParam(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewGroup);
//xml中使用到的属性个数
paramCount = a.getIndexCount();
//获取颜色值
background = a.getColor(R.styleable.CustomViewGroup_super_background, Color.parseColor("#00ffffff"));
//文本
text = a.getString(R.styleable.CustomViewGroup_super_text);
//文本颜色
textColor = a.getColor(R.styleable.CustomViewGroup_super_textcolor, Color.BLACK);
//释放
a.recycle();
}
public String getText() {
return text;
}
public int getTextColor() {
return textColor;
}
public int getBackground() {
return background;
}
public int getParamCount() {
return paramCount;
}
}
}
总结
在实际项目中,有些需求的实现思路必须借助源码分析来寻找,在上文字就是一个典型的例子,文章中的需求,利用重写源码方法改变默认行为,这就是典型的狸猫换太子
艺术。
[本章完...]
网友评论