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

<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 implements
		OnCheckedChangeListener {

	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));

	}

	@Override
	public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
		SPutils.putValue(getContext(), key, isChecked);
		tv_content.setText(content + (isChecked ? "开启" : "关闭"));

	}

}

MainActivity中的代码:

public class MainActivity extends Activity {

	@Override
	protected 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,系统的很多空间都使用了第三个参数。第三个参数的优先级更高。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值