[关闭]
@linux1s1s 2016-09-22T11:12:14.000000Z 字数 9421 阅读 5888

Android View 自定义属性

AndroidView


View构造器

在展开这个话题前,我们先来看看顶级View类的构造器,构造器有5个重载方法,我们只看带有AttributeSet参数的构造器,如果你不知道AttributeSet这货是干甚的,你只需简单的理解为数组或者集合即可

  1. public class View implements Drawable.Callback, KeyEvent.Callback,
  2. AccessibilityEventSource {
  3. public View(Context context, AttributeSet attrs) {
  4. this(context, attrs, 0);
  5. }
  6. public View(Context context, AttributeSet attrs, int defStyleAttr) {
  7. this(context, attrs, defStyleAttr, 0);
  8. }
  9. public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  10. this(context);
  11. final TypedArray a = context.obtainStyledAttributes(
  12. attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
  13. ...
  14. final int N = a.getIndexCount();
  15. for (int i = 0; i < N; i++) {
  16. int attr = a.getIndex(i);
  17. switch (attr) {
  18. case com.android.internal.R.styleable.View_background:
  19. background = a.getDrawable(attr);
  20. break;
  21. case com.android.internal.R.styleable.View_padding:
  22. padding = a.getDimensionPixelSize(attr, -1);
  23. mUserPaddingLeftInitial = padding;
  24. mUserPaddingRightInitial = padding;
  25. leftPaddingDefined = true;
  26. rightPaddingDefined = true;
  27. break;
  28. case com.android.internal.R.styleable.View_paddingLeft:
  29. leftPadding = a.getDimensionPixelSize(attr, -1);
  30. mUserPaddingLeftInitial = leftPadding;
  31. leftPaddingDefined = true;
  32. break;
  33. case com.android.internal.R.styleable.View_paddingTop:
  34. topPadding = a.getDimensionPixelSize(attr, -1);
  35. break;
  36. case com.android.internal.R.styleable.View_paddingRight:
  37. rightPadding = a.getDimensionPixelSize(attr, -1);
  38. mUserPaddingRightInitial = rightPadding;
  39. rightPaddingDefined = true;
  40. break;
  41. ...
  42. }
  43. }
  44. }
  45. }

我们重点看一下L15com.android.internal.R.styleable.View,然后再接着往下看是不是看到一些平时非常熟悉的View的属性了,没错就是这些属性:
paddingLeft,paddingTop等等 我们进一步找一下这个styleable文件在
D:\...\android-sdk-windows\platforms\android-22\data\res\values\attrs.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="View">
  4. <!-- Supply an identifier name for this view, to later retrieve it
  5. with {@link android.view.View#findViewById View.findViewById()} or
  6. {@link android.app.Activity#findViewById Activity.findViewById()}.
  7. This must be a
  8. resource reference; typically you set this using the
  9. <code>@+</code> syntax to create a new ID resources.
  10. For example: <code>android:id="@+id/my_id"</code> which
  11. allows you to later retrieve the view
  12. with <code>findViewById(R.id.my_id)</code>. -->
  13. <attr name="id" format="reference" />
  14. <!-- Supply a tag for this view containing a String, to be retrieved
  15. later with {@link android.view.View#getTag View.getTag()} or
  16. searched for with {@link android.view.View#findViewWithTag
  17. View.findViewWithTag()}. It is generally preferable to use
  18. IDs (through the android:id attribute) instead of tags because
  19. they are faster and allow for compile-time type checking. -->
  20. <attr name="tag" format="string" />
  21. <!-- The initial horizontal scroll offset, in pixels.-->
  22. <attr name="scrollX" format="dimension" />
  23. <!-- The initial vertical scroll offset, in pixels. -->
  24. <attr name="scrollY" format="dimension" />
  25. <!-- A drawable to use as the background. This can be either a reference
  26. to a full drawable resource (such as a PNG image, 9-patch,
  27. XML state list description, etc), or a solid color such as "#ff000000"
  28. (black). -->
  29. <attr name="background" format="reference|color" />
  30. <!-- Sets the padding, in pixels, of all four edges. Padding is defined as
  31. space between the edges of the view and the view's content. A views size
  32. will include it's padding. If a {@link android.R.attr#background}
  33. is provided, the padding will initially be set to that (0 if the
  34. drawable does not have padding). Explicitly setting a padding value
  35. will override the corresponding padding found in the background. -->
  36. <attr name="padding" format="dimension" />
  37. <!-- Sets the padding, in pixels, of the left edge; see {@link android.R.attr#padding}. -->
  38. <attr name="paddingLeft" format="dimension" />
  39. <!-- Sets the padding, in pixels, of the top edge; see {@link android.R.attr#padding}. -->
  40. <attr name="paddingTop" format="dimension" />
  41. <!-- Sets the padding, in pixels, of the right edge; see {@link android.R.attr#padding}. -->
  42. <attr name="paddingRight" format="dimension" />
  43. <!-- Sets the padding, in pixels, of the bottom edge; see {@link android.R.attr#padding}. -->
  44. <attr name="paddingBottom" format="dimension" />
  45. <!-- Sets the padding, in pixels, of the start edge; see {@link android.R.attr#padding}. -->
  46. <attr name="paddingStart" format="dimension" />
  47. <!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->
  48. <attr name="paddingEnd" format="dimension" />
  49. </declare-styleable>
  50. </resources>

这个就是系统定义的com.android.internal.R.styleable.View 样式文件,如果我们想获得其中的某个属性怎么办?很好办,看源码怎么做的,我们就怎么破。
比如想获得paddingTop属性,源码中这么做的L36

  1. case com.android.internal.R.styleable.View_paddingTop:
  2. topPadding = a.getDimensionPixelSize(attr, -1);
  3. break;

规则就是declare-styleable name + '_' + attr name即可。

所以按照源码的思路,我们也来自定义一下CustomView,同理,先来写个styleable文件如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="customViewTest">
  4. <attr name="text" format="string" />
  5. <attr name="textAttr" format="integer" />
  6. </declare-styleable>
  7. </resources>

然后我们定义一个CustomView类继承自View,模仿View的构造器,我们也来读取styleable文件中的属性值

  1. public class CustomView extends View
  2. {
  3. private static final String TAG = "Custom_View_Tag";
  4. public CustomView(Context context, AttributeSet attrs)
  5. {
  6. super(context, attrs, 0);
  7. final TypedArray a = context.obtainStyledAttributes(
  8. attrs, R.styleable.customViewTest);
  9. final String text = a.getString(R.styleable.customViewTest_text);
  10. final int textAttr = a.getInt(R.styleable.customViewTest_textAttr, -1);
  11. Log.i(TAG, "text: " + text + " " + "textAttr: " + textAttr);
  12. a.recycle();
  13. }
  14. }

代码中L10和L12处就遵循了上面的规则:declare-styleable name + '_' + attr name

接下来就可以在layout 文件中使用我们自定义的CustomView以及自定义的属性了

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:linroid="http://schemas.android.com/apk/res-auto"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <android.linroid.com.viewattrs.CustomView
  6. android:layout_width="wrap_content"
  7. android:layout_height="wrap_content"
  8. android:id="@+id/my_id"
  9. linroid:textAttr="520"
  10. linroid:text="@string/hello_world" />
  11. </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打印出来是不是验证上面的推理没有问题了。

AttributeSet & TypedArray

在上节View的构造器中我们了解了啥是AttributeSet,但是他究竟是干甚的我们至此尚不清楚,另外还有一个二货TypedArray,又是干甚,更是不清楚,下面带着大家一起来看看,这俩兄弟是干甚的
修改上面的layout文件,将Hard-Code的值修改成引用

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:linroid="http://schemas.android.com/apk/res-auto"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <android.linroid.com.viewattrs.CustomView
  6. android:layout_width="@dimen/custom_view_width"
  7. android:layout_height="@dimen/custom_view_height"
  8. android:id="@+id/my_id"
  9. linroid:textAttr="520"
  10. linroid:text="@string/hello_world" />
  11. </RelativeLayout>

然后我们接着修改Custom的构造器,来看看AttributeSet究竟能干甚

  1. public class CustomView extends View
  2. {
  3. private static final String TAG = "Custom_View_Tag";
  4. public CustomView(Context context, AttributeSet attrs)
  5. {
  6. super(context, attrs, 0);
  7. final int account = attrs.getAttributeCount();
  8. for (int i = 0; i < account; i++)
  9. {
  10. String name = attrs.getAttributeName(i);
  11. String value = attrs.getAttributeValue(i);
  12. Log.i(TAG, "name: " + name + " " + "value: " + value);
  13. }
  14. }
  15. }

我们run一下,看一下Log

此处输入图片的描述

很明显,这货能够获取View的所有属性值,那么这些值看起来是不是有点奇怪,因为我们在前面将这些属性值都修改成了引用,所以只能看到引用值了,所以看起来不太直观,所以这个时候TypedArray粉墨登场了,这个例子我们就不做了,因为上面一节中已经对TypedArray做好很好的宣传了。所以TypedArray就是直接获得属性值,而非引用值。省去了先获得id,在通过这个id获得属性值。

declare-styleable

我们已经解决了两个问题,接下来,我们看看布局文件,我们有一个属性叫做:linroid:text。
总所周知,系统提供了一个属性叫做:android:text,那么我觉得直接使用android:text更nice,这样的话,考虑问题:

如果系统中已经有了语义比较明确的属性,我可以直接使用嘛?

答案是可以的,怎么做呢?
直接在attrs.xml中使用android:text属性。

  1. <declare-styleable name="customViewTest">
  2. <attr name="android:text" />
  3. <attr name="textAttr" format="integer" />
  4. </declare-styleable>

注意,这里我们是使用已经定义好的属性,不需要去添加format属性(注意声明和使用的区别,差别就是有没有format)。
然后在类中这么获取:a.getString(R.styleable.customViewTest_android_text);布局文件中直接android:text="@string/hello_world"即可。

ok,接下来,我在想,既然declare-styleable这个标签的name都能随便写,那么索性declare-styleable这个标签不要好了,可不可以?
我们来实验一下:
简单粗暴,直接删除declare-styleable这个标签

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <attr name="text" format="string" />
  4. <attr name="textAttr" format="integer" />
  5. </resources>

吆西,so清爽

接下来我们来看看代码如何写

  1. public class CustomView extends View
  2. {
  3. private static final String TAG = "Custom_View_Tag";
  4. private static final int[] mAttr = {R.attr.text, R.attr.textAttr};
  5. private static final int ATTR_ANDROID_TEXT = 0;
  6. private static final int ATTR_TEXT_ATTR = 1;
  7. public CustomView(Context context, AttributeSet attrs)
  8. {
  9. super(context, attrs, 0);
  10. final TypedArray a = context.obtainStyledAttributes(mAttr);
  11. final String text = a.getString(ATTR_ANDROID_TEXT);
  12. final int textAttr = a.getInt(ATTR_TEXT_ATTR, -1);
  13. Log.i(TAG, "text: " + text + " " + "textAttr: " + textAttr);
  14. a.recycle();
  15. }
  16. }

貌似多了些代码,可以看到我们声明了一个int数组,数组中的元素就是我们想要获取的attr的id。并且我们根据元素的在数组中的位置,定义了一些整形的常量代表其下标,然后通过TypedArray进行获取。

下面一起来验证一下这样做是否可行?
run一下,看log:
此处输入图片的描述

所以这是行得通的。

那么其实呢,android在其内部也会这么做,按照传统的写法,它会在R.java生成如下代码:

  1. public static final class attr {
  2. public static final int testAttr=0x7f0100a9;
  3. }
  4. public static final class styleable {
  5. public static final int test_android_text = 0;
  6. public static final int test_testAttr = 1;
  7. public static final int[] test = {
  8. 0x0101014f, 0x7f0100a9
  9. };
  10. }

ok,根据上述你应该发现了什么。styleale的出现系统可以为我们完成很多常量(int[]数组,下标常量)等的编写,简化我们的开发工作(想想如果一堆属性,自己编写常量,你得写成什么样的代码)。那么大家肯定还知道declare-styleable的name属性,一般情况下写的都是我们自定义View的类名。主要为了直观的表达,该declare-styleable的属性,都是该View所用的。

小结

接下来我们来小结一下,自定义View属性该怎么做

  1. 自定义一个CustomView(extends View )类
  2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
  3. 在布局文件中CustomView使用自定义的属性(注意namespace)
  4. 在CustomView的构造方法中通过TypedArray获取

接下来我们温习一下前面提及的问题,这里只提问题,不回答,如果还回答不了,那么请你从新读一遍这篇博客

能完全理解上面提及的四个问题,那么恭喜你,你已经理解了View自定义属性。

此篇博客编辑自CSDN-HongYang的博客,在此向该大牛致敬

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注