@linux1s1s
2016-09-22T11:12:14.000000Z
字数 9421
阅读 5853
AndroidView
在展开这个话题前,我们先来看看顶级View类的构造器,构造器有5个重载方法,我们只看带有AttributeSet
参数的构造器,如果你不知道AttributeSet
这货是干甚的,你只需简单的理解为数组或者集合即可
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public View(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
...
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.View_padding:
padding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = padding;
mUserPaddingRightInitial = padding;
leftPaddingDefined = true;
rightPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingLeft:
leftPadding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = leftPadding;
leftPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingTop:
topPadding = a.getDimensionPixelSize(attr, -1);
break;
case com.android.internal.R.styleable.View_paddingRight:
rightPadding = a.getDimensionPixelSize(attr, -1);
mUserPaddingRightInitial = rightPadding;
rightPaddingDefined = true;
break;
...
}
}
}
}
我们重点看一下L15com.android.internal.R.styleable.View
,然后再接着往下看是不是看到一些平时非常熟悉的View的属性了,没错就是这些属性:
paddingLeft,paddingTop等等
我们进一步找一下这个styleable文件在
D:\...\android-sdk-windows\platforms\android-22\data\res\values\attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="View">
<!-- Supply an identifier name for this view, to later retrieve it
with {@link android.view.View#findViewById View.findViewById()} or
{@link android.app.Activity#findViewById Activity.findViewById()}.
This must be a
resource reference; typically you set this using the
<code>@+</code> syntax to create a new ID resources.
For example: <code>android:id="@+id/my_id"</code> which
allows you to later retrieve the view
with <code>findViewById(R.id.my_id)</code>. -->
<attr name="id" format="reference" />
<!-- Supply a tag for this view containing a String, to be retrieved
later with {@link android.view.View#getTag View.getTag()} or
searched for with {@link android.view.View#findViewWithTag
View.findViewWithTag()}. It is generally preferable to use
IDs (through the android:id attribute) instead of tags because
they are faster and allow for compile-time type checking. -->
<attr name="tag" format="string" />
<!-- The initial horizontal scroll offset, in pixels.-->
<attr name="scrollX" format="dimension" />
<!-- The initial vertical scroll offset, in pixels. -->
<attr name="scrollY" format="dimension" />
<!-- A drawable to use as the background. This can be either a reference
to a full drawable resource (such as a PNG image, 9-patch,
XML state list description, etc), or a solid color such as "#ff000000"
(black). -->
<attr name="background" format="reference|color" />
<!-- Sets the padding, in pixels, of all four edges. Padding is defined as
space between the edges of the view and the view's content. A views size
will include it's padding. If a {@link android.R.attr#background}
is provided, the padding will initially be set to that (0 if the
drawable does not have padding). Explicitly setting a padding value
will override the corresponding padding found in the background. -->
<attr name="padding" format="dimension" />
<!-- Sets the padding, in pixels, of the left edge; see {@link android.R.attr#padding}. -->
<attr name="paddingLeft" format="dimension" />
<!-- Sets the padding, in pixels, of the top edge; see {@link android.R.attr#padding}. -->
<attr name="paddingTop" format="dimension" />
<!-- Sets the padding, in pixels, of the right edge; see {@link android.R.attr#padding}. -->
<attr name="paddingRight" format="dimension" />
<!-- Sets the padding, in pixels, of the bottom edge; see {@link android.R.attr#padding}. -->
<attr name="paddingBottom" format="dimension" />
<!-- Sets the padding, in pixels, of the start edge; see {@link android.R.attr#padding}. -->
<attr name="paddingStart" format="dimension" />
<!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->
<attr name="paddingEnd" format="dimension" />
</declare-styleable>
</resources>
这个就是系统定义的com.android.internal.R.styleable.View 样式文件,如果我们想获得其中的某个属性怎么办?很好办,看源码怎么做的,我们就怎么破。
比如想获得paddingTop
属性,源码中这么做的L36
case com.android.internal.R.styleable.View_paddingTop:
topPadding = a.getDimensionPixelSize(attr, -1);
break;
规则就是declare-styleable name + '_' + attr name
即可。
所以按照源码的思路,我们也来自定义一下CustomView,同理,先来写个styleable文件如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="customViewTest">
<attr name="text" format="string" />
<attr name="textAttr" format="integer" />
</declare-styleable>
</resources>
然后我们定义一个CustomView类继承自View,模仿View的构造器,我们也来读取styleable文件中的属性值
public class CustomView extends View
{
private static final String TAG = "Custom_View_Tag";
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs, 0);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.customViewTest);
final String text = a.getString(R.styleable.customViewTest_text);
final int textAttr = a.getInt(R.styleable.customViewTest_textAttr, -1);
Log.i(TAG, "text: " + text + " " + "textAttr: " + textAttr);
a.recycle();
}
}
代码中L10和L12处就遵循了上面的规则:declare-styleable name + '_' + attr name
。
接下来就可以在layout 文件中使用我们自定义的CustomView以及自定义的属性了
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:linroid="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.linroid.com.viewattrs.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/my_id"
linroid:textAttr="520"
linroid:text="@string/hello_world" />
</RelativeLayout>
注意这里的命名空间是如何命名的,看一下android的命名规律
xmlns:android="http://schemas.android.com/apk/res/android"
然后在定义View的属性时
android:layout_width="wrap_content"
所以如果我们这样命名空间:
xmlns:linroid="http://schemas.android.com/apk/res-auto"
那么在自定义View的属性时,是不是应该这样
linroid:text="@string/hello_world"
接下来我们验证一下上面做的对不对?
run一下,log打印出来是不是验证上面的推理没有问题了。
在上节View的构造器中我们了解了啥是AttributeSet,但是他究竟是干甚的我们至此尚不清楚,另外还有一个二货TypedArray,又是干甚,更是不清楚,下面带着大家一起来看看,这俩兄弟是干甚的
修改上面的layout文件,将Hard-Code的值修改成引用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:linroid="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.linroid.com.viewattrs.CustomView
android:layout_width="@dimen/custom_view_width"
android:layout_height="@dimen/custom_view_height"
android:id="@+id/my_id"
linroid:textAttr="520"
linroid:text="@string/hello_world" />
</RelativeLayout>
然后我们接着修改Custom的构造器,来看看AttributeSet究竟能干甚
public class CustomView extends View
{
private static final String TAG = "Custom_View_Tag";
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs, 0);
final int account = attrs.getAttributeCount();
for (int i = 0; i < account; i++)
{
String name = attrs.getAttributeName(i);
String value = attrs.getAttributeValue(i);
Log.i(TAG, "name: " + name + " " + "value: " + value);
}
}
}
我们run一下,看一下Log
很明显,这货能够获取View的所有属性值,那么这些值看起来是不是有点奇怪,因为我们在前面将这些属性值都修改成了引用,所以只能看到引用值了,所以看起来不太直观,所以这个时候TypedArray粉墨登场了,这个例子我们就不做了,因为上面一节中已经对TypedArray做好很好的宣传了。所以TypedArray就是直接获得属性值,而非引用值。省去了先获得id,在通过这个id获得属性值。
我们已经解决了两个问题,接下来,我们看看布局文件,我们有一个属性叫做:linroid:text。
总所周知,系统提供了一个属性叫做:android:text,那么我觉得直接使用android:text更nice,这样的话,考虑问题:
如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?
答案是可以的,怎么做呢?
直接在attrs.xml中使用android:text属性。
<declare-styleable name="customViewTest">
<attr name="android:text" />
<attr name="textAttr" format="integer" />
</declare-styleable>
注意,这里我们是使用已经定义好的属性,不需要去添加format属性(注意声明和使用的区别,差别就是有没有format)。
然后在类中这么获取:a.getString(R.styleable.customViewTest_android_text);布局文件中直接android:text="@string/hello_world"即可。
ok,接下来,我在想,既然declare-styleable
这个标签的name都能随便写,那么索性declare-styleable
这个标签不要好了,可不可以?
我们来实验一下:
简单粗暴,直接删除declare-styleable
这个标签
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="text" format="string" />
<attr name="textAttr" format="integer" />
</resources>
吆西,so清爽
接下来我们来看看代码如何写
public class CustomView extends View
{
private static final String TAG = "Custom_View_Tag";
private static final int[] mAttr = {R.attr.text, R.attr.textAttr};
private static final int ATTR_ANDROID_TEXT = 0;
private static final int ATTR_TEXT_ATTR = 1;
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs, 0);
final TypedArray a = context.obtainStyledAttributes(mAttr);
final String text = a.getString(ATTR_ANDROID_TEXT);
final int textAttr = a.getInt(ATTR_TEXT_ATTR, -1);
Log.i(TAG, "text: " + text + " " + "textAttr: " + textAttr);
a.recycle();
}
}
貌似多了些代码,可以看到我们声明了一个int数组,数组中的元素就是我们想要获取的attr的id。并且我们根据元素的在数组中的位置,定义了一些整形的常量代表其下标,然后通过TypedArray进行获取。
下面一起来验证一下这样做是否可行?
run一下,看log:
所以这是行得通的。
那么其实呢,android在其内部也会这么做,按照传统的写法,它会在R.java生成如下代码:
public static final class attr {
public static final int testAttr=0x7f0100a9;
}
public static final class styleable {
public static final int test_android_text = 0;
public static final int test_testAttr = 1;
public static final int[] test = {
0x0101014f, 0x7f0100a9
};
}
ok,根据上述你应该发现了什么。styleale的出现系统可以为我们完成很多常量(int[]数组,下标常量)等的编写,简化我们的开发工作(想想如果一堆属性,自己编写常量,你得写成什么样的代码)。那么大家肯定还知道declare-styleable的name属性,一般情况下写的都是我们自定义View的类名。主要为了直观的表达,该declare-styleable的属性,都是该View所用的。
接下来我们来小结一下,自定义View属性该怎么做
接下来我们温习一下前面提及的问题,这里只提问题,不回答,如果还回答不了,那么请你从新读一遍这篇博客
能完全理解上面提及的四个问题,那么恭喜你,你已经理解了View自定义属性。