您的位置:首页 > 编程语言 > PHP开发

自定义 View & ViewGroup,你需要知道的事

2017-03-14 19:46 405 查看

构造函数

View
有四个构造函数

public View(Context context) {}

public View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
// ...
}


在平常的开发中,用的最多的是第一个和第二个构造方法,而后面的两个构造方法就很陌生了,但是我们还是要知道后面两个怎么使用。

一个参数的构造方法,适用于动态创建,例如
new Button(context)


二个参数的构造方法,适用于从
XML
文件创建。

三个参数的构造方法,是为了用
Theme
统一控制
View
的一些属性,例如

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="buttonStyle">@style/MyBtnStyle</item>
</style>

<style name="MyBtnStyle" parent="Widget.AppCompat.Button">
<item name="android:gravity">start</item>
</style>


AppTheme
中为
Button
重新设置
buttonStyle
,我们把它的
android:gravity
属性设置为
start
,这样的话,如果你的布局方向是
LTR
,那么
Button
中的文字应该从左边开始显示,而不是居中(正常情况下)。

在布局中添加一个带
android:text
Button
试试

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ckt.materialdesigncontactsexample.TestActivity">

<Button
android:text="Text Gravity is start"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>


它的效果如下图



而默认情况下,是居中显示的,如下图



四个参数的构造方法,其实是为了不让系统统一用
Theme
控制属性,而用自己定义的
Style
。构造方法中有个获取属性的方法

final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);


这个可能很多人不明白什么意思,看官方文档也是一知半解,先看下参数的意思:

参数
com.android.internal.R.styleable.View
定义了属性集,类型为
int[]


参数
attrs
是从
XML
中解析出来的属性,类型为
AttributeSet


参数
defStyleAttr
在刚才的三个参数中的构造方法已经解释过,是为了用
Theme
统一控制

参数
defStyleRes
是自定义的
Style
,但是这个参数只有在
defStyleAttr
0
的时候才起作用,也就是说它只是一个替补。

那么
context.obtainStyledAttributes()
的意思就是用获取
com.android.internal.R.styleable.View
属性集的值,先从
XML
解析出来的属性
attrs
中获取,然后从系统的
Theme
defStyleAttr
获取,而如果
defStyleAttr
0
,就用自定义的
defStyleRes


还是很懵逼? 没关系,用一个例子带你全面了解这四个构造函数。

自定义一个
View
,名为
XView
,先看下自定义的属性,路径为
res/values/attrs.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="XView">
<attr name="XText" format="string|reference"/>
<attr name="bg" format="color|reference"/>
</declare-styleable>

<attr name="XStyle" format="reference"/>

</resources>


这里定义了一个属性集为
XView
,还单独定义了一个属性
XStyle
,这个单独的属性就是为了用
Theme
统一控制
XView
的属性的,后面就会看到作用。

然后,看下自定义的
XView


public class XView extends View {
private String mText;
private int mBgColor;
private TextPaint mPaint;
private Rect mTextBound;

public XView(Context context) {
this(context, null);
}

public XView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.XStyle);
}

public XView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}

public XView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XView, defStyleAttr, 0);
mText = a.getString(R.styleable.XView_XText);
mBgColor = a.getColor(R.styleable.XView_bg, 0xff000000);
a.recycle();

mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(24);

mTextBound = new Rect();
}

@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(mBgColor);
mPaint.getTextBounds(mText, 0, mText.length(), mTextBound);
canvas.drawText(mText, (getWidth() - mTextBound.width()) / 2,
(getHeight() - mTextBound.height()) / 2, mPaint);
}
}


XView
非常简单,只是绘制了一个背景,和一个居中显示的文字而已。

再来看看构造方法,注意,在第二构造方法中,我们调用了第三个构造方法,其中传入的第三个参数为
R.attr.XStyle
,这个就是我们在自定义的属性中单独定义的那个属性。

那么,我们怎么用
XStyle
来统一控制
XView
呢? 正如前面所言,需要在
Theme
中加入这个属性,路径为
res/values/styles.xml


<resources>

<!-- 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>
<item name="XStyle">@style/XViewStyle</item>
</style>

<style name="XViewStyle">
<item name="bg">@color/colorPrimary</item>
</style>

</resources>


AppTheme
中,我们把自定义的属性
XStyle
指定为了一个名为
XViewStyle
Style
,其中我们把自定义的属性
bg
设置了一个颜色值
#3F51B5


到这里还并没有完,因为这个
AppTheme
你必须要设置才能生效,我们要在
AndroidManifest.xml
中为
Appplication
设置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ckt.materialdesigncontactsexample">

<application
// ...
android:theme="@style/AppTheme">
// ...
</application>

</manifest>


那么现在把它添加到布局中

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.ckt.materialdesigncontactsexample.XView
android:layout_width="300dp"
android:layout_height="300dp"
app:XText="XView"/>
</RelativeLayout>


效果如下



很显然我们已经成功地把背景显示出来了,现在我们在
Theme
中去改变这个值

<style name="XViewStyle">
<item name="bg">@color/colorAccent</item>
</style>


我们改变为了一个粉红色
#FF4081
,现在的效果为



那么现在构造函数中的第四个参数怎么用呢? 前面说过,它的作用是在第三参数为
0
的时候,才起作用,也就说,如果你并不想用
Theme
来统一控制属性,就可以把第三个参数设置为0,然后设置自己的第四个参数。

public XView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XView, 0, R.style.AnotherXViewStyle);
// ...
}


注意
obtainStyledAttributes()
方法的第三个参数一定要为
0
,否则第四个参数不起作用。现在看下这个
AnotherXViewStyle
是如何定义的

<style name="AnotherXViewStyle">
<item name="bg">@android:color/holo_green_light</item>
</style>


很明显,定义了一个原谅色,如下图



其实除了上述方法能设置自定义属性的值外,还有一种方法

<com.ckt.materialdesigncontactsexample.XView
style="@style/XViewStyle"
android:layout_width="300dp"
android:layout_height="300dp"
app:XText="XView"/>


<style name="XViewStyle">
<item name="bg">@color/colorAccent</item>
</style>


XML
XView
设置
style
属性也可以达到效果,效果就不展示了。

那么有这么多的地方可以重复设置属性,到底哪个优先呢? 我们看下源码注释就知道了。

/**
* Perform inflation from XML and apply a class-specific base style from a
* theme attribute or style resource. This constructor of View allows
* subclasses to use their own base style when they are inflating.
* <p>
* When determining the final value of a particular attribute, there are
* four inputs that come into play:
* <ol>
* <li>Any attribute values in the given AttributeSet.
* <li>The style resource specified in the AttributeSet (named "style").
* <li>The default style specified by <var>defStyleAttr</var>.
* <li>The default style specified by <var>defStyleRes</var>.
* <li>The base values in this theme.
* </ol>
* <p>
* Each of these inputs is considered in-order, with the first listed taking
* precedence over the following ones. In other words, if in the
* AttributeSet you have supplied <code><Button * textColor="#ff000000"></code>
* , then the button's text will <em>always</em> be black, regardless of
* what is specified in any of the styles.
*/
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {}


优先级为 XML 中的
AtrributeSet
-> XML 中的
style
属性 ->
defStyleAttr
->
defStyleRes
->
Theme


在这个顺序中,是把
Theme
defStyleAttr
分开了,而例子中是结合到一起的。 由于篇幅原因,我并不能把这个顺序演示出来,有个印象就好。

自定义属性

属性集

<resources>

<declare-styleable name="XView">
<attr name="XText" format="string|reference"/>
<attr name="bg" format="color|reference"/>
</declare-styleable>

</resources>


每个属性集会生成一个
R.styleable.name
,以及为每一个属性生成一个
R.styleable.name_attribute
。例如上面的代码会生成一个
R.styleable.XView
R.styleable.XView_XText
R.styleable.XView_bg


R.styleable.XView
代表所有属性的一个数组,类型为
int[]
,而
R.styleable.XView_XText
R.styleable.XView_bg
代表数组中的
index
值,类型为
int
。 那么,按如下的方法解析属性就并不奇怪了。

public XView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XView, defStyleAttr, 0);
mText = a.getString(R.styleable.XView_XText);
mBgColor = a.getColor(R.styleable.XView_bg, 0xff000000);
a.recycle();

// ...
}


属性的 format

那么现在,你肯定很关心的是属性的
format
如何设置呢。我们可以参数源码中的
frameworks/base/core/res/res/values/attrs.xml
。我们就列举非常常用的例子来学习如何写格式

integer

可以为
ProgressBar
设置进度
android:progressbar
,这个进度是
integer


<attr name="progress" format="integer" />


float

View
设置透明度
android:alpha
,值是
0
1
,它的类型是
float


<attr name="alpha" format="float" />


string

TextView
设置的
android:text
属性应该为
string


<attr name="text" format="string" localization="suggested" />


boolean

可以为
View
设置可点击属性
android:clickable
,它就是
boolean
类型

<attr name="clickable" format="boolean" />


color

View
设置
android:background
的时候,可以设置一个颜色值

<attr name="background" format="color" />


例如
android:background="#ffff0000"


reference

View
设置
android:background
的时候,还可以指定一个
drawable
或者图片,它们都属于引用

<attr name="background" format="reference" />


例如,设置一个
drawable
android:background="@drawable/thumb"
, 设置一个图片为
android:background="@mipmap/ic_launcher"


复合型

既然
android:background
既可以为
color
型,也可以为
reference
型,那么可以结合起来

<attr name="background" format="color|reference" />


dimenssion

通常我们会为
View
设置一个宽高,我们可以指定为固定尺寸,例如
100dp
,那么类型就为
dimenssion


<attr name="layout_height" format="dimension" />


enum

View
的宽高除了指定固定尺寸外,还可以设置
match_parent
,
wrap_content
,这就是枚举型

<attr name="layout_height" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>


flag

flag
定义的是位运算,例如为
TextView
可以设置文字的位置
android:gravity
,默认的值是
center_vertical|center_horizontal
,也就是居中

<attr name="gravity">
<flag name="top" value="0x30" />
<flag name="bottom" value="0x50" />
<flag name="left" value="0x03" />
<flag name="right" value="0x05" />
<flag name="center_vertical" value="0x10" />
// ...
</attr>


fraction

比例用的最多的是在
XML
中定义动画的时候,例如位移动画,会定义位移的起始位置
android:fromXDelta
-100%


<attr name="fromXDelta" format="float|fraction" />


LayoutParams

ViewGroup
中定义了一个
LayoutParams
类和它的一个子类
MarginLayoutParams


ViewGroup.LayoutParams
只封装了一个
width
height
,也就是说它只管宽高。

ViewGroup.MarginLayoutParams
ViewGroup.LayoutParams
的基础上,增加了好多个
margin
,例如
leftMargin
,
topMargin
等等。

LinearLayout.LayoutParams
继承自
ViewGroup.MarginLayoutParams
,在它的基础上又增加了
gravity
,所以在
LinearLayout
可以为子
View
设置
android:layout_gravity
属性,而在
RelativeLayout
就不行。

那么,如果你想在自定义的
ViewGroup
中设置自己的
LayoutParams
来达到有趣的位置效果,就需要自己定义了。那么该如何做呢? 有四个方法需要你做的。

checkLayoutParams()
:这是一个验证参数是否有效的方法,例如调用
addView(View child, int index, LayoutParams params)
的时候,系统会调用
checkLayoutParams()
方法来检测传入的布局参数是否合理。

generateDefaultLayoutParams()
:它会为我们生成一个默认的布局参数。 例如调用
addView(View child)
的时候没有指定布局参数,系统就会调用这个方法为我们自动生成一个参数。

generateLayoutParams(ViewGroup.LayoutParams p)
:用参数来生成一个安全可靠的正确的布局参数。例如调用
addView(View child, int index, LayoutParams params)
的时候,用
checkLayoutParams()
方法检测返回了
false
,那么系统就会调用
generateLayoutParams(ViewGroup.LayoutParams p)
为我们生成一个可靠的布局参数。说白了,就是抽取有用的信息,其它默认。

generateLayoutParams(AttributeSet attrs)
:参数
AttributeSet attrs
在构造函数中就知道是什么意思了,这里就是为
XML
定义的
Child View
生成
LayoutParams


说了这么多,你肯定有点懵逼,看看
LinearLayout
中的实现,你或许就明白了

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}

@Override
protected LayoutParams generateDefaultLayoutParams() {
if (mOrientation == HORIZONTAL) {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} else if (mOrientation == VERTICAL) {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
return null;
}

@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (sPreserveMarginParamsInLayoutParamConversion) {
if (lp instanceof LayoutParams) {
return new LayoutParams((LayoutParams) lp);
} else if (lp instanceof MarginLayoutParams) {
return new LayoutParams((MarginLayoutParams) lp);
}
}
return new LayoutParams(lp);
}

@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LinearLayout.LayoutParams;
}


OK,慢慢体会吧~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息