我们经常会遇到这么一个问题,同一个界面中有几个相同的布局,如果这些布局是一些简单的控件,重复写几遍也无所谓,但是如果这些布局里边嵌套了很多控件,甚至布局里边嵌套布局,重复写工作量很大,这时我们需要用自定义View来减少工作量,这里利用自定义View实现设置中心的功能视图。
我有这么一个需求,在设置中心可以设置对电话短信的拦截是否开启,如下图:
实现上面视图,需要的布局文件如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView style="@style/text_title_style" android:text="设置中心" /> <RelativeLayout android:id="@+id/rl_sms_block_setting" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginTop="5dp"> <TextView android:id="@+id/tv_sms_block_setting" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="电话短信拦截设置" android:textColor="#000000" android:textSize="23sp" /> <TextView android:id="@+id/tv_sms_block_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/tv_sms_block_setting" android:layout_marginTop="3dp" android:text="黑名单拦截没有开启" android:textSize="18sp" /> <CheckBox android:id="@+id/cb_sms_block" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:clickable="false" android:focusable="false" /> </RelativeLayout></LinearLayout>
实现电话短信拦截是否开启就需要以上几十行代码,如果在下面还需实现版本更新是否开启,我们可能会复制以上几十行代码,修改其中的文本即可,但是如果还有同样的10个功能呢,我们也继续复制吗?显然没必要,使用自定义View,相同的布局只需写一次。
把上边布局文件中的需要重复书写的布局提取出来,放到一个单独的布局文件中,以供自定义View的引用。
item布局文件:ui_item_setting.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_sms_block_setting" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginTop="5dp"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#000000" android:textSize="23sp" /> <TextView android:id="@+id/tv_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/tv_title" android:layout_marginTop="3dp" android:textSize="18sp" /> <CheckBox android:id="@+id/cb_checked" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:clickable="false" android:focusable="false" /></RelativeLayout>
自定义View的Java代码:SettingView.java
public class SettingsView extends RelativeLayout { private TextView tvTitle; private TextView tvDesc; private CheckBox cbCheaked; public SettingsView(Context context) { super(context); initView(context); } //如果使用布局文件创建view对象,会调用第二个构造方法 public SettingsView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { View.inflate(context, R.layout.ui_item_setting, this); tvTitle = (TextView) this.findViewById(R.id.tv_title); tvDesc = (TextView) this.findViewById(R.id.tv_desc); cbCheaked = (CheckBox) this.findViewById(R.id.cb_checked); } /** * 设置文本标题 * @param text */ public void setTitle(String text) { tvTitle.setText(text); } /** * 设置文本描述 * @param text */ public void setDesc(String text) { tvDesc.setText(text); } /** * 设置checkbox * @param checked */ public void setChecked(boolean checked) { cbCheaked.setChecked(checked); } /** * 获取文本标题 */ public String getTitle() { return tvTitle.getText().toString(); } /** * 获取文本描述 */ public String getDesc() { return tvDesc.getText().toString(); } /** * 获取checkbox状态 */ public boolean getChecked() { return cbCheaked.isChecked(); } }
从最上边的布局文件中我们可以看出,需要重复书写的布局是一个RelativeLayout,所以新建一个类SettingView继承RelativeLayout,并实现SettingView的三个构造方法,为了保证一创建或引用SettingView就能将布局转化为View对象,在三个构造方法中都实现了一个初始化View对象的initView方法。
在initView方法中,利用View.inflate(Context context, int resource, ViewGroup root)方法将布局转化为View对象,这个方法有三个参数,前两个参数分别为上下文和抽取出来新建的布局文件,第三个参数是一个ViewGroup(View的容器),如果创建一个单独的View对象,用null即可,如果把一个布局转化为View对象,并挂载在自定义View中,则用this。所以View.inflate(context, R.layout.ui_item_setting, this);的意思是将ui_item_setting显示到SettingView中。
我们还可以看到SettingView中还定义了一些getter和setter方法,这些方法是为了操作(设置和获取)文本而创建的。
自定义View基本搞定,接下来就可以在布局文件中使用自定义的SettingView了。
页面布局文件:activity_setting.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView style="@style/text_title_style" android:text="设置中心" /> <com.trampcr.mobilesafer.ui.SettingsView android:id="@+id/sv_sms_block" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.trampcr.mobilesafer.ui.SettingsView></LinearLayout>
把SettingsView的全路径作为一个标签写到布局文件当中,就已经完了自定义View的引用。
接下来就可以创建SettingsView 对象了,并操作该SettingsView 对象,例如为其添加点击事件等等。
主活动代码:SettingActivity.java
public class SettingsActivity extends Activity { private SettingsView svSmsBlock; private SharedPreferences sp; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); svSmsBlock = (SettingsView) findViewById(R.id.sv_sms_block); sp = getSharedPreferences("config", MODE_PRIVATE); if (sp.getBoolean("smsblock", false)){ svSmsBlock.setChecked(true); svSmsBlock.setDesc("黑名单拦截已经开启"); }else { svSmsBlock.setChecked(false); svSmsBlock.setDesc("黑名单拦截没有开启"); } svSmsBlock.setTitle("电话短信拦截设置"); svSmsBlock.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SharedPreferences.Editor editor = sp.edit(); if (svSmsBlock.getChecked()) { svSmsBlock.setChecked(false); svSmsBlock.setDesc("黑名单拦截没有开启"); editor.putBoolean("smsblock", false); } else { svSmsBlock.setChecked(true); svSmsBlock.setDesc("黑名单拦截已经开启"); editor.putBoolean("smsblock", true); } editor.commit(); } }); } }
上面这部分主要实现了SettingsView 对象的点击事件,将Checkbox的状态保存在SharedPreferences中,通过判断SharedPreferences保存的状态信息来显示相应的文本。
到这里还有一个缺陷,那就是设置文本,需要先获取自定义控件对象,然后通过setTitle和setDesc来设定,加一个控件就需要加一段代码,显然有些繁琐,那么如何实现像TextView那样,直接在属性里就可以定义文本呢?
首先先了解一下我们平时用的属性是哪里来的,观察xml文件发现每个头布局中都有这么一行代码:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns意思是xml命名空间,android就是命名空间的名称,所以每个属性都是android:开头的,而后面的一串字符是依赖的android.jar的路径。
AndroidStudio中不用自定义命名空间,在下边创建好attrs.xml后即可自动生成一个命名空间:
xmlns:app="http://schemas.android.com/apk/res-auto"
要使用属性,需要先声明属性。通过查资料得知android系统的这些属性放在sdk/platform/android-version/data/res/values/attrs.xml中,这里展示一下TextView控件的部分属性定义:
<declare-styleable name="TextView"> <attr name="bufferType"> <enum name="normal" value="0" /> <enum name="spannable" value="1" /> <enum name="editable" value="2" /> </attr> <attr name="text" format="string" localization="suggested" /> <attr name="hint" format="string" />...
模仿系统控件定义属性的方法,在values下新建一个attrs.xml:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="SettingsView"> <attr name="title_text" format="string"/> <attr name="desc_on" format="string"/> <attr name="desc_off" format="string"/> </declare-styleable></resources>
接下来就可以使用自定义控件的属性了。
<com.trampcr.mobilesafer.ui.SettingsView android:id="@+id/sv_sms_block" app:title_text="电话短信拦截设置" app:desc_on="黑名单拦截已经开启" app:desc_off="黑名单拦截没有开启" android:layout_width="match_parent" android:layout_height="wrap_content"></com.trampcr.mobilesafer.ui.SettingsView>
自定义控件的属性可以使用了,接下来就是如何将属性设定的内容显示到界面上,回到自定义控件的三个构造方法,前面提到过如果使用布局文件创建View对象,会调用那个含有两个参数的构造方法,这个构造方法的第二个参数是一个AttributeSet,要想获取属性中设定的文本可以通过AttributeSet的getAttributeValue方法,该方法的第一个参数是命名空间,第二个参数是属性名称,那么获取属性中设定的文本的代码如下:
...//如果使用布局文件创建view对象,会调用第二个构造方法public SettingsView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); title = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "title_text"); descOn = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "desc_on"); descOff = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "desc_off"); setTitle(title); setDesc(descOff); } ...public void setChecked(boolean checked) { if (checked){ setDesc(descOn); }else { setDesc(descOff); } }
在构造方法中获取属性中设定的文本,并设置默认的标题和描述信息,这样在代码中就不用再显示设置文本了;并通过判断勾选状态来设置描述信息。
到目前为止,到目前为止,到目前为止,通过自定义View实现了文章刚开始的那个界面,你可能会说自定义View也就如此,但是接下来你就会体验到它的强大和方便了,比如还需要一个更新状态是否开启的设置,只需在布局文件中添加一个SettingView标签,然后在主界面添加该控件的点击事件即可。xml文件和Java代码如下:
<com.trampcr.mobilesafer.ui.SettingsView android:id="@+id/sv_update" android:layout_width="match_parent" android:layout_height="wrap_content" app:desc_off="更新设置没有开启" app:desc_on="更新设置已经开启" app:title_text="更新状态设置"/> svUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SharedPreferences.Editor editor = sp.edit(); if (svUpdate.getChecked()) { svUpdate.setChecked(false); editor.putBoolean("update",false); }else { svUpdate.setChecked(true); editor.putBoolean("update",true); } editor.commit(); } });
添加后的界面如下:
如果再添加其他的设置,只需添加以上两段代码即可,简单方便。
转载出处:本文章(教程)为本站原创,未经许可、禁止转载!