自定义View的流程##
- 一般我们新建一个View,继承自现有View,或直接继承自View/ViewGroup,为了让自定义的View有更多属性可配置,类似于textview 的textSize,textColor等属性,我们会定义一个attrs文件中,新建一个declare-styleable,用来定义该控件的自定义属性,如
<declare-styleable name="DivideTextView">
<attr name="dt_drawable" format="reference|color" />
<attr name="dt_dividerSize" format="dimension" />
<!-- 分割线padding,左右上下padding,上下左右padding -->
<attr name="dt_dividerPadding" format="dimension" />
<attr name="dt_divideGravity">
<flag name="none" value="0x01" />
<flag name="left" value="0x02" />
<flag name="top" value="0x04" />
<flag name="right" value="0x08" />
<flag name="bottom" value="0x10" />
</attr>
</declare-styleable>
- 在layout文件中,使用自定义属性,如:
<ui.better.DivideTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="开始放大招了"
android:textSize="20sp"
app:dt_divideGravity="top|right|bottom|left"
app:dt_dividerPadding="2dp"
app:dt_dividerSize="1dp"
app:dt_drawable="#22bbcc" />
- 在自定义View类中,通过obtainStyledAttributes来获取自定义属性值:
public DivideTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
// 初始化属性
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DivideTextView, defStyleAttr, 0);
try {
setDivideDrawable(a.getDrawable(R.styleable.DivideTextView_dt_drawable));
setDivideGravityInner(a.getInt(R.styleable.DivideTextView_dt_divideGravity, NONE));
setDivideSize((int) a.getDimension(R.styleable.DivideTextView_dt_dividerSize, 0f));
setDividePadding((int) a.getDimension(R.styleable.DivideTextView_dt_dividerPadding, 0f));
} finally {
if (a != null) {
a.recycle();
}
}
}
obtainStyledAttributes说明##
自定义View中通过 obtainStyledAttributes来获取自定义属性的值;
她有4个重载方法,如图,
最终都会调用第一个,也就是4个参数的那个方法;
public final TypedArray obtainStyledAttributes(
AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
参数的意思是:
第4个参数:defStyleRes:####
英文说明:
A resource identifier of a style resource that supplies default values for the TypedArray, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
用于指定一个style,即默认的style,意思是:如果没有在layout中设置属性,就从style中获取属性的默认值;
比如在style文件中,新建style如下:
<style name="Def_Style_DividerTextView">
<item name="dt_divideGravity">none</item>
<item name="dt_dividerPadding">0dp</item>
<item name="dt_dividerSize">1dp</item>
<item name="dt_drawable">#88cc00</item>
</style>
修改 获取属性的代码 如下:
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DivideTextView, defStyleAttr,
R.style.Def_Style_DividerTextView);
Log.e("better", "dividerPadding: " + padding);
Log.e("better", "dividerSize: " + size);
Log.e("better", "Drawable: " + a.getColor(R.styleable.DivideTextView_dt_drawable, 0));
输出如下:
第3个参数:defStyleAttr:####
英文说明:
An attribute in the current theme that contains a reference to a style resource that supplies defaults values for the TypedArray. Can be 0 to not look for defaults.
说明它是一个引用类型的属性,指向一个style,并且在当前的theme中进行设置;
使用步骤:
- 在attrs.xml中,声明一个attr,创建一个新的引用格式属性‘Def_Style_Attr_DividerTextView’, 如:
<!-- 指向style的引入类型 -->
<attr name="Def_Style_Attr_DividerTextView" format="reference" />
- 在styles.xml文件里面,找到我们所使用的theme,添加一条item:
<!-- 在当前使用的主题中,配置 Def_Style_Attr_DividerTextView -->
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!-- 在当前使用的主题中,配置 Def_Style_Attr_DividerTextView -->
<item name="Def_Style_Attr_DividerTextView">@style/Def_Style_DividerTextView</item>
</style>
- 修改 获取属性的代码 如下
// final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DivideTextView, defStyleAttr, R.style.Def_Style_DividerTextView);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DivideTextView, R.attr.Def_Style_Attr_DividerTextView, 0);
Log.e("better", "dividerPadding: " + padding);
Log.e("better", "dividerSize: " + size);
Log.e("better", "Drawable: " + a.getColor(R.styleable.DivideTextView_dt_drawable, 0));
输出:
对于第三个参数:defStyleAttr
系统中的Button,EdiText,他们会在构造函数中,指定第3个参数:
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
通过在Theme里,设置参数的样式,比如:background、textColor等,当切换不同的主题时,控件的样式会发生变化,这是因为,不同的主题设置了不同的style;
** 系统的button defStyleAttr目录:**
默认Theme:
D:\workTools\sdk\platforms\android-25\data\res\values\attrs.xml, 有如下:
<!-- Normal Button style. -->
<attr name="buttonStyle" format="reference" />
D:\workTools\sdk\platforms\android-25\data\res\values\theme.xml, 有如下:
<!-- Button styles -->
<item name="buttonStyle">@style/Widget.Button</item>
D:\workTools\sdk\platforms\android-25\data\res\values\styles.xml, 有如下:
<!---->
<style name="Widget.Button">
<item name="background">@drawable/btn_default</item>
<item name="focusable">true</item>
<item name="clickable">true</item>
<item name="textAppearance">?attr/textAppearanceSmallInverse</item>
<item name="textColor">@color/primary_text_light</item>
<item name="gravity">center_vertical|center_horizontal</item>
</style>
Holo_Theme:
D:\workTools\sdk\platforms\android-25\data\res\values\themes_holo.xml, 有如下:
<!-- Button styles -->
<item name="buttonStyle">@style/Widget.Holo.Button</item>
D:\workTools\sdk\platforms\android-25\data\res\values\styles_holo.xml, 有如下:
<!--可看到继承自:Widget.Button-->
<style name="Widget.Holo.Button" parent="Widget.Button">
<item name="background">@drawable/btn_default_holo_dark</item>
<item name="textAppearance">?attr/textAppearanceMedium</item>
<item name="textColor">@color/primary_text_holo_dark</item>
<item name="minHeight">48dip</item>
<item name="minWidth">64dip</item>
</style>
** 参考系统的实现,如果我们自定义的View,的自定义属性非常多,也可以去提供默认的style,然后让用户去设置到theme里面即可**
自定义View的构造方法##
- 第一种:
public DivideTextView(Context context) {
this(context, null);
}
public DivideTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DivideTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
- 第二种:
public DivideTextView(Context context) {
super(context);
init();
}
public DivideTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DivideTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
}
使用场景的区别:
- 如果需要设置 obtainStyledAttributes 的第3个参数,‘defStyleAttr’,使用第一种方式,没有这个需求,2种没区别;
- 继承系统已有控件去定义View时,使用第一种方式,会覆盖掉系统的在theme里面设置的style,如上面的 button的构造函数的第3个参数,这样的话,使用第二种,会继续使用系统提供的style,如果非要用第一种,也可以,将系统的 defStyleAttr copy进来即可,如下示例代码:
public ClearEditText(Context context) {
super(context);
this(context, null);
}
public ClearEditText(Context context, AttributeSet attrs) {
super(context, attrs);
// 注意这里,将系统的样式拿过来,如果传递0,将丢失样式,切记!!!
this(context, attrs, android.R.attr.editTextStyle);
}
public ClearEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
获取自定义属性的2种写法##
- 第一种方式,如上:
// 这种形式
setDivideDrawable(a.getDrawable(R.styleable.DivideTextView_dt_drawable));
2.第二种形式:
// 第二种获取自定义属性的方式,通过循环遍历所有属性:
final int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
if (i == R.styleable.DivideTextView_dt_drawable) {
} else if (i == R.styleable.DivideTextView_dt_divideGravity) {
setDivideDrawable(a.getDrawable(i));
} else if (i == R.styleable.DivideTextView_dt_dividerSize) {
setDivideGravityInner(a.getInt(i, NONE));
} else if (i == R.styleable.DivideTextView_dt_dividerPadding) {
setDividePadding((int) a.getDimension(i, 0f));
}
}
区别
第一种写法,不管有没有在布局文件中使用该属性,都会执行getXXX方法赋值;
第二种写法,只有在布局文件中,设置该属性后,才去调用getXXX方法(indexCount);
注意
父view已经对某个属性赋值了,如果采用第一种方式,可能用户根本没有设置值,但是会执行getXXX方法,可能就把父View的赋值给覆盖了;