自定义控件之一:自定义属性

来源:互联网 发布:举例说明算法的可行性 编辑:程序博客网 时间:2024/05/21 12:08

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="white-space:pre"></span>想进阶成为android高手,自定义控件是必须要玩转的,那么就从入门开始,记录自己的学习过程。为啥要使用自定义控件呢?我的理解是可以类比一下style的抽取。在应用中,比如字体,可能有多处字体的大小,样式,颜色等等属性都是一样的,这样就可以进行抽取公共的样式,在xml中使用@style可以大大的简化这些繁琐的属性。应用中也有可能有这样的组件,在多处出现,但又不是系统提供的,那么就需要用到自定义控件了,将这个控件功能进行抽取,自定义出来,就能大大提高开发效率。</span>

一、自定义属性

自定义属性,比如TextView的text属性,在使用的时候,只需要在TextView中直接使用 Android:text="xxx"就可以了。简单来说,就是要能直接在xml文件中声明出来的属性。那么如何自定义属性呢?在values文件夹下面建立attrs.xml文件,根节点是<Resource>。然后我们参考系统的属性文件是怎么写的。打开SDK->platforms->android.xx->data->res->values->attr.xml。发现有这样一个节点 <declare-styleable name="Theme">,下面是系统自己的一些属性。那么我们就可以仿造来定义自己的控件属性。

<resources>     <declare-styleable name="custom">         <attr name="title" format="string"/>         <attr name="content" format="integer"/>         <attr name="key" format="boolean"/>              </declare-styleable>    </resources>
attrs属性的format格式有这么几种:

"reference" //引用  "color" //颜色  "boolean" //布尔值  "dimension" //尺寸值  "float" //浮点值  "integer" //整型值  "string" //字符串  "fraction" //百分数,比如200% 


还有一种枚举型:

< attr name="orientation">    < enum name="horizontal" value="0" />    < enum name="vertical" value="1" />  < /attr>

name就是你自定义的属性名,format用来指定该属性的格式。


现在我们来自定义一个这样的设置控件,两个textview+一个checkbox组合而成,给它取名为SettingView。既然它包含了这样几个子控件,我们在构造这个SettingView的时候,必须继承ViewGroup。我这里继承的是RelativeGroup。

public class SettingView extends RelativeLayout implements OnCheckedChangeListener {public SettingView(Context context) {super(context);// TODO Auto-generated constructor stub}public SettingView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}public SettingView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub}

这三个构造方法有何区别?

构造一是从java代码中构造视图而不是xml布局中填充视图时使用的,简单来说,就是new出来的时候要用的

构造二是在xml创建但没有指定style时调用的

构造三是在xml创建,又指定style调用

这里只需要调用2个参数的构造就行了。当然也可以全部实现。那有何区别,下文会说到。

那么如何如何将xml布局和SettingView关联起来?当然要是用布局填充。在构造中将xml布局填充进来

View.inflate(context, R.layout.view_setting, null);

可能下意识就这样写了,可是这时xml和settingview有关联吗?肯定是没有的。那么这里第三个参数就不能为null,而是this。

二者有何区别?为null时,是将参数二指定的布局文件填充成视图返回,而不为空时,是将布局文件填充成视图,然后添加到参数三当中,参数三此时是一个跟布局root,然后将参数三返回。可以点进去阅读源码。也就是说,这里将view_setting填充成视图,添加到了SettingView当中,所以就不需要返回值了,添加过程可阅读源码。

然后自定义属性,这里定义了三个属性 title 、content和key。它们的format都是string类型。

接着就要在activity_main当中引用我们自定义的属性了:

<LinearLayout     xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:vladivostok="http://schemas.android.com/apk/res/com.vladivostok.customview"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity"     android:orientation="vertical"    > <com.vladivostok.customview.SettingView     android:id="@+id/sv"     android:layout_height="wrap_content"     android:layout_width="wrap_content"     vladivostok:title="我的自动更新设置"     vladivostok:content="自动更新已经开启"     vladivostok:key="toggle"          />

这里注意,要为自定义的控件声明命名控件:

 xmlns:vladivostok="http://schemas.android.com/apk/res/com.vladivostok.customview"

很好理解,因为可能你定义的属性名和系统的属性名重复,声明命名空间就是为了区分自定义的属性和系统属性.命名空间最后是你工程的包名。

OK,属性声明好了,那如何在代码中去获取?也就是说使用者在xml中为你定义的属性符的值怎么获取?

有两种方式:

//方式一、获取命名空间,根据自定义的属性名获取String namespace="http://schemas.android.com/apk/res/com.vladivostok.customview";String title = attrs.getAttributeValue(namespace, "title");content = attrs.getAttributeValue(namespace, "content");key = attrs.getAttributeValue(namespace, "key");


//方式二、根据自定义的styleable的名字获取属性集,再根据对应的索引获取自定义属性的值TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.custom);String title = array.getString(0);content=array.getString(1);key=array.getString(2); //array要记得recycle()掉array.recycle();


接下来就是具体逻辑代码,这里全部贴出:

package com.vladivostok.customview;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.widget.CheckBox;import android.widget.CompoundButton;import android.widget.CompoundButton.OnCheckedChangeListener;import android.widget.RelativeLayout;import android.widget.TextView;public class SettingView extends RelativeLayout implementsOnCheckedChangeListener {private CheckBox cb;private String key;private TextView tv_content;private String content;// xml中使用的public SettingView(Context context, AttributeSet attrs) {super(context, attrs);View.inflate(context, R.layout.view_setting, this);TextView tv_title = (TextView) findViewById(R.id.tv_title);tv_content = (TextView) findViewById(R.id.tv_content);cb = (CheckBox) findViewById(R.id.cb);// 获取自定义属性的2种方式// 方式一、获取命名空间,根据自定义的属性名获取String namespace = "http://schemas.android.com/apk/res/com.vladivostok.customview";String title = attrs.getAttributeValue(namespace, "title");content = attrs.getAttributeValue(namespace, "content");key = attrs.getAttributeValue(namespace, "key");// 方式二、根据自定义的styleable的名字获取属性集,再根据对应的索引获取自定义属性的值TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.custom, 0, R.style.style_CustomView);String title = array.getString(0);content = array.getString(1);key = array.getString(2);// array要记得recycle()掉array.recycle();tv_title.setText(title);tv_content.setText(content);//将content切割使用,避免硬编码content = content.split("已经")[0];cb.setOnCheckedChangeListener(this);}public void setChecked() {cb.setChecked(SPutils.getBoolean(getContext(), key));}@Overridepublic void onCheckedChanged(CompoundButton arg0, boolean isChecked) {SPutils.putValue(getContext(), key, isChecked);tv_content.setText(content + (isChecked ? "开启" : "关闭"));}}

MainActivity中的代码:

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SettingView sv= (SettingView) findViewById(R.id.sv);sv.setChecked();}}
控件的使用就很简单了。实现的功能就是点击checkbox实现开启/关闭状态的切换,并将状态记录。下次重新进入可将checkbox状态和文字还原。
其实脑中有了思路,按照逻辑一步步来,也不会很难,但是每个细节有力求弄透彻。


上文中提到获取属性集的两种方式,其中方式二:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.custom);

其实最终调用的是:

 public final TypedArray obtainStyledAttributes( AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {        return getTheme().obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);    }
那后两个参数defStyleAttr和defStyleRes分别代表什么??先看参数4。

在style.xml中定义一个

 <span style="white-space:pre"></span></style>        <style name="style_CustomView">        <item name="title">i am title </item>        <item  name="content">content is here</item>        <item name="key">it is key</item>            </style>
引用到方法中,TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.custom,0,R.style.style_CustomView);  

在使用SettingView时没有定义任何属性,

 <com.vladivostok.customview.SettingView     android:id="@+id/sv_1"     android:layout_height="wrap_content"     android:layout_width="wrap_content"     />


很显然,在不设置任何属性的情况下,会从这个style中读取相关属性


参数3:defStyleAttr 它是一个引用类型的属性,上文提到过。并且指向一个style,并且要在当前Theme中设置。于是在attr中添加一条属性

  <attr name="settingViewStyleRef" format="reference"/>
并在style.xml的Theme添加这样一条item:

 <!-- Application theme. -->    <style name="AppTheme" parent="AppBaseTheme">        <!-- All customizations that are NOT specific to a particular API-level can go here. -->        <item name="settingViewStyleRef">@style/style_CustomView</item>    </style>
修改一句代码:
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.custom,R.attr.settingViewStyleRef,0);
再次运行,效果同上。
这就是为什么切换不同的样式时,控件的样式也会发生变化,就是应为不同的主题设置了不同的style,系统的很多空间都使用了第三个参数。第三个参数的优先级更高。







0 0
原创粉丝点击