Android学习笔记---重新学习自定义View#01

   2016-09-12 0
核心提示:最近发现自己对Android的学习只在表面,并没有深入的理解,我不喜欢这种感觉,而且没有自己的理解,学习到的内容也很难为我所用.所以从本次开始,我要写点自己理解的东西,但要对知识有自己的理解,那就必须深入了解它的原理.而我觉得Android的自定义View是一个很好

最近发现自己对Android的学习只在表面,并没有深入的理解,我不喜欢这种感觉,而且没有自己的理解,学习到的内容也很难为我所用.

所以从本次开始,我要写点自己理解的东西,但要对知识有自己的理解,那就必须深入了解它的原理.而我觉得Android的自定义View是一个很好的入口.

学会如何自定义View,能够了解Android系统中View使如何创建和维护的.这有助于我们学习Android的View的基本机制,也能解决我们日常开发的需求.

接下来我们一起来对自定义View中的各个部分做详细的研究,现在我们先从View的构造函数入手.

自定义View的构造函数

自定义View最基础的方式就是创建一个继承于 View 或其子类的类,并且最少重写父类的一个构造函数.

/**
*/

public class MyView extends View {
/**
* 在代码中使用new关键字创建View时会调用
* @param context
*/

public MyView(Context context) {
super(context);
}

/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
* @param context
* @param attrs
* @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
*/

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}

第一个构造函数比较简单,我们从第二个构造函数开始研究.

当我们需要在xml布局文件中使用我们的View时,我们就必须实现第二个构造函数.第二个构造函数的参数列表为 MyView(Context context, AttributeSet attrs) ,这里的 context 不用多说,就是View所在的上下文,而第二个参数就是 AttributeSet 类型的一个Set集合.它是一个保存了View的自定义属性的集合,即我们在xml布局文件中为View所设置的属性可以通过这个参数获取.

通常我们会在 res/values/ 文件下创建一个 attrs.xml 文件来声明我们的自定义属性.该文件是一个资源文件.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="myview_text" format="string"/>
</declare-styleable>
</resources>

上面我们在 attrs.xml 中定义了我们的自定义属性,其中 <declare-styleable> 标签表示一组自定义的属性,对应着一个View的自定义属性.其中的 name 可以是任何值,但为了方便和规范,建议 name 与对应的View同名.

<attr> 标签就是一个具体的属性了,它的 name 代表该属性的名字. format 代表该属性的值的格式.者两个是必须要有的. <attr> 标签支持的 formatstring,enum,boolean,dimension,color,float... 等多种格式.

有了自定义的属性后,在xml布局文件中使用View时就能使用我们的自定义属性了.但我们需要在使用自定义属性前,指定自定义属性的命名空间,这样系统才能准确的找到你的自定义属性.

而它们的命名空间为 http://schemas.android.com/apk/res/[your package name] ,这里不同的View可能会有不同的 package name ,这样一来就比较麻烦.但是在 Android Studio 中我们只需指定一个统一的命名空间即可 http://schemas.android.com/apk/res-auto .剩下的 AS 会帮我们完成.

<?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.source.kevin.costomviewlib.costomview.MyView
app:myview_text="Hello"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

</RelativeLayout>

为了验证第二个参数 AttributeSet 能否得到我们的自定义属性,我们在第二个构造函数中测试一下.

/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
try{
String string = a.getString(R.styleable.MyView_myview_text);
Log.e("RESULT",string);
}finally {
a.recycle();//回收资源
}
}

先运行一下应用,然后在控制台的Log中我们看到了输出

Android学习笔记---重新学习自定义View#01

我们很简单的就得到了自定义的属性.代码中我们使用了 context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs) 这个函数获取我们的自定义属性集合.返回的是一个 TypeArray 类型的对象,这个对象包含了View的自定义属性.第1个参数便是要解析的 AttributeSet ,第2个参数 int[] attrs 就是我们 attrs.xml 文件中定义的一组属性资源.其实我们可以打开自动生成的 R.java 文件,在文件中搜索 MyView 关键字,定位到相应的行数,我们可以看到下面的代码:

....
public static final class attr {
....
public static final int myview_text=0x7f0100a7;
....
}
....
public static final class styleable {
....
public static final int[] MyView = {
0x7f0100a7
};
public static final int MyView_myview_text = 0;
....
}

可以看出 R.styleable.MyView 是一个int型数组,里面的内容是与 R.attr.myview_text 相对应的.这就表明了 R.style.MyView 是一个存放了 attrs.xml 文件中声明的一组自定义属性的id集合.而 R.styleable.MyView_myview_text 则是一个属性在数组中对应的下标索引.

如果还是存在疑惑,可以在 attrs.xmlMyView 属性节点下多添加几个自定义的属性,然后重新编译代码,按照我上面的方法查看相应的代码,我相信你能够明白其中的关系.

我们可以点到 context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs) 这个函数的源码中去,发现这个函数调用的是 context.getTheme().obtainStyledAttributes(set, attrs, 0, 0) 这个函数.可以看到这是一个4个参数的函数,它的函数原型如下:

/**
*
* @param set 需要解析的属性集合
* @param attrs 属性集合对应的id资源数组
* @param defStyleAttr 当前Theme中包含的一个指向style样式的引用.
* 当我们没有设置自定义属性时,默认会从该集合中查找布局文件的属性配置值(0代表不向defStyleAttr查找属性默认值)
* @param defStyleRes 也是一个指向Style的资源ID
* 当defStyleAttr==0 或 defStyleAttr!=0 但Theme中没有为defStyleAttr赋值,该参数才起作用.
* @return
*/

public TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)

代码中的注释已经说的很清楚,其中第3个参数和第4个参数分别与View的3参数的构造函数中的第3个参数和4参数构造函数中的第4个参数的意义是一样的.最后当我们使用完了TypeArray,我们需要将它回收,因为它是一个共享的资源.

由上面的函数就可以看出属性可以在很多的地方进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

下面我们来尝试一下如何从第三个参数获取属性值.首先我们在 attr.xml 文件中添加一个单独的属性 MyViewDefStyleAttr ,格式为 reference ,就是资源引用类型.并在 MyView 属性组下添加一个属性.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="myview_text" format="string"/>
<attr name="myview_attr" format="string"/>
</declare-styleable>
<attr name="MyViewDefStyleAttr" format="reference"/>
</resources>

接下来我们修改 style.xml 文件,添加一个自定义的style,作为 MyViewDefStyleAttr 的实现,并在 AppTheme style下引用它.

<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="MyViewDefStyleAttr">@style/MyViewDefStyleaAttrImpl</item>
</style>


<style name="MyViewDefStyleaAttrImpl">
<item name="myview_attr">attr</item>
</style>


</resources>

然后我们就可以在第三个构造函数中获取自定义属性.

/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/

public MyView(Context context, AttributeSet attrs) {
this(context, attrs,R.attr.MyViewDefStyleAttr);

}

/**
* 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
* @param context
* @param attrs
* @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
*/

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0);
try {
String s = a.getString(R.styleable.MyView_myview_attr);
Log.e("RESULT",s);
}finally {
a.recycle();
}

}

这里我们通过两个参数的构造函数调用3参数的构造函数,并传入 R.attr.MyViewDefStyleAttr 作为默认样式属性值资源,在第三个构造函数中,若要获取第3个参数的默认属性值,必须通过显式的调用 context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyView,defStyleAttr, 0) 这个4个参数的 obtainStyledAttributes() 函数.我们运行代码后得到的相应的结果:

Android学习笔记---重新学习自定义View#01

通过对View的构造函数的研究,基本了解了View在创建时是通过什么方式获取自定义属性的,并且也知道了该如何实现View的自定义属性.

 
标签: XML 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • View绘制详解
    View绘制详解
    在介绍View绘制之前先来介绍一下LayoutInflater,而介绍LayoutInflater之前,先介绍一种单例实现模式:使用容器实现单例模式public class SingletonManager {   private static MapString, Object objMap = new HashMapString,Object();  private Singlet
  • Android 中处理 XML 的四种方式-XPath
    适用场景:只取 XML中的部分节点值非常方便,我很喜欢 XPath,关于 XPath语法请参考SelectNodes 与 XPath,这是 C#中的,但是 XPath语法是通用的。import org.xml.sax.InputSource;import java.io.ByteArrayInputStream;import java.io.IOException;import ja
  • Android 中处理 XML 的四种方式-PULL
    PULL和 SAX很相像,都是在节点中走,然后遇到开始节点了、结束节点会触发事件,此时就可以获取值。import org.xmlpull.v1.XmlPullParser;import org.xmlpull.v1.XmlPullParserException;import java.io.ByteArrayInputStream;import java.io.IOException;impo
  • Android 中处理 XML 的四种方式-SAX
    SAX(Simple API for XML)解析速度快,占用内存少。适用为 SAX的场景:映射为对象很方便。流程SAX通过一个 Handler将 XML“映射”到一个对象。XML - Handler - ObjectXML示例?xml version=1.0?rootsiteName千一网络/siteNamesiteUrlhttp://www.cftea.com//si
  • Android 中处理 XML 的四种方式-DOM
    Android 中处理 XML 的几种方式连载中,我们就不介绍合成 XML了,因为合成 XML可以直接拼接字符串,虽然看起很不高大上,但却很有效。我们主要介绍如何取 XML中的值。适用 DOM的场景:只取 XML中的部分节点值方便,但还不如 XPath方便。DOM解析小 XML很快,大
  • 使用FileProvider
    像这样的代码:privatevoidinstall(File apkFile){ Uri uri = Uri.fromFile(apkFile); Intent intent = newIntent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(uri, "application/vnd.android.package-arch
  • 炫酷:一句代码实现标题栏、导航栏滑动隐藏。ByeBurger库的使用和实现
    炫酷:一句代码实现标题栏、导航栏滑动隐藏。By
    其实上周五的时候已经发过一篇文章。基本实现了底部导航栏隐藏的效果。但是使用起来可能不是很实用。因为之前我实现的方式是继承了系统的导航栏,并且提供了响应的隐藏显示方法。这样就变相等于强制使用这个view,体验不是很好。所以抽时间把他优化了一下。因
  • LayoutInflater踩坑日记
    Android开发中ayoutInflater还是比较常见的,例如在Adapter或是Fragment中加载Layout布局,拿Adapter来说,在onCreateViewHolder中加载布局然后传给自定义的ViewHolder,通常我们都是这么用的:@Override public CustomViewHolder onCreateViewHolder(ViewGro
  • Android - 自定义View冷知识之动态替换layout.x
    在开发迭代中,有这么一个场景:我们给TextView定制了不少功能,在下一个版本,需要把程序中的所有TextView都替换成我自己的CustomTextView,这个时候你会怎么做?有没有一种方法在不改动布局文件的情况下就能实现动态替换呢?原理:layout.xml - Java 对象首
    10-10 安卓开发
  • Gank中的MVP模式
    Gank中的MVP模式
    第一次看到 Gank,还是源于 drakeet 的 Meizhi 项目,后来各种干货项目层出不穷,自己的项目中也借鉴了其中不少的写法,恰好最近公司决定让我做点前端了,同时项目经理也和我一样偏爱 Material Design,在说服了老板后,我就开始边学边用 Materialize 框架重
    10-01 MVC模式XML
点击排行