自定义 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,慢慢体会吧~
相关文章推荐
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 自定义组件<六>:深入理解ViewGroup
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 在Android中,可以自定义类,继承ViewGroup等容器类,以实现自己需要的布局显示。
- Android 手把手教您自定义ViewGroup(二) 实战篇 -> 实现FlowLayout
- Android-->FlowRadioGroup(流式布局RadioGroup, 自定义View的简单使用)
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 关于Android自定义view 你所需要知道的基本函数
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 自定义ViewGroup控件(二)----->流式布局进阶(二)
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 自定义view你需要知道的
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 【Android】 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 自定义ViewGroup控件(四)----->流式布局进阶(四)
- 自定义ViewGroup控件(一)----->流式布局进阶(一)
- Android笔记【3】--ViewGroup & 自定义view
- 关于Android自定义view 你所需要知道的基本函数
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout