美文网首页
高级UI<第三十八篇>:Attrs之狸猫换太子

高级UI<第三十八篇>:Attrs之狸猫换太子

作者: NoBugException | 来源:发表于2020-02-02 18:51 被阅读0次

狸猫换太子可以理解为偷天换日,其实本质上是修改源码的原有逻辑从而实现自己的需求,本章主要讲解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_textColorapp:textview_text这两个属性。

然而熟悉ConstraintLayout(约束布局)的人都知道,它的父类是ViewGroup,相当于自定义ViewGroup,它的属性可以在其子视图中使用,比如下面代码:

image.png

以上画红线的属性就是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;
        }
    }
}

总结

在实际项目中,有些需求的实现思路必须借助源码分析来寻找,在上文字就是一个典型的例子,文章中的需求,利用重写源码方法改变默认行为,这就是典型的狸猫换太子艺术。

[本章完...]

相关文章

网友评论

      本文标题:高级UI<第三十八篇>:Attrs之狸猫换太子

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