1.在attrs.xml声明好控件属性,这里控件用到了3个文字类描述属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SettingSwitchCompat">
<attr name="title" format="string" />
<attr name="desc_on" format="string" />
<attr name="desc_off" format="string" />
</declare-styleable>
</resources>
2.view_setting_switchcompat.xml
一般情况会提前写出组合控件的布局,包括组成和摆放位置...
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/selector_setting_switchcompat"
android:minHeight="?android:listPreferredItemHeight"
android:padding="16dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@android:color/black"
android:textSize="18sp"
tools:text="某某开关"/>
<TextView
android:id="@+id/tvDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tvTitle"
android:layout_marginTop="5dp"
android:singleLine="true"
android:textColor="#888888"
android:textSize="14sp"
tools:text="开启和关闭的描述"/>
<android.support.v7.widget.SwitchCompat
android:id="@+id/sc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:layout_alignParentRight="true"
android:layout_centerVertical="true">
</android.support.v7.widget.SwitchCompat>
</RelativeLayout>
为了更好的点击用户体验,其中用到了一个背景selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- setting_switchcompat_press 为 #216b74ff -->
<item android:state_pressed="true" android:drawable="@color/setting_switchcompat_press"/>
<item android:drawable="@android:color/white"/>
</selector>
3.SettingSwitchCompat 控件的Java类
package com.yao.mytestproject;
import android.content.Context;
import android.support.annotation.StringRes;
import android.support.v7.widget.SwitchCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* Created by Administrator on 2016/10/1.
*/
public class SettingSwitchCompat extends RelativeLayout {
private static final String NAMESPACE = "http://schemas.android.com/apk/res-auto";
private GestureDetector mGestureDetector;
private TextView tvTitle;
private TextView tvDesc;
private SwitchCompat sc;
private CharSequence title;
private CharSequence descOn;
private CharSequence descOff;
public SettingSwitchCompat(Context context) {
super(context);
initView();
}
public SettingSwitchCompat(Context context, AttributeSet attrs) {
super(context, attrs);
//获取自定义控件中xml中的属性
title = attrs.getAttributeValue(NAMESPACE, "title");
descOn = attrs.getAttributeValue(NAMESPACE, "desc_on");
descOff = attrs.getAttributeValue(NAMESPACE, "desc_off");
initView();
}
public SettingSwitchCompat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
//inflate出自定义的布局
View.inflate(getContext(), R.layout.view_setting_switchcompat, this);
tvTitle = (TextView) findViewById(R.id.tvTitle);
tvDesc = (TextView) findViewById(R.id.tvDesc);
sc = (SwitchCompat) findViewById(R.id.sc);
setTitle(title);
setChecked(false);
}
/**
* 设置控件标题
*
* @param title 标题字符串
*/
public void setTitle(CharSequence title) {
tvTitle.setText(title);
}
/**
* 设置控件标题
*
* @param resid 字符资源id
*/
public void setTitle(@StringRes int resid) {
tvTitle.setText(title);
}
/**
* 设置控件开关时候的描述
*
* @param descOn 开启时的描述
* @param descOff 关闭时的描述
*/
public void setDesc(CharSequence descOn, CharSequence descOff) {
this.descOn = descOn;
this.descOff = descOff;
}
/**
* 设置控件开关时候的描述
*
* @param descOnResId 开启时的描述
* @param descOffResId 关闭时的描述
*/
public void setDesc(@StringRes int descOnResId, @StringRes int descOffResId) {
setDesc(getContext().getResources().getText(descOnResId), getContext().getResources().getText(descOffResId));
}
/**
* 获取当前开关状态
*
* @return
*/
public boolean isChecked() {
return sc.isChecked();
}
/**
* 设置当前开关状态
*
* @param check
*/
public void setChecked(boolean check) {
sc.setChecked(check);
if (check) {
tvDesc.setText(descOn);
} else {
tvDesc.setText(descOff);
}
}
@Override
public boolean performClick() {
Log.e("YAO", "SettingSwitchCompat.java - performClick() ---------- " );
setChecked(!isChecked());
return super.performClick();
}
}
自定义组合控件中,如果有属性,都会重写2个参数的那个构造方法来获取xml里面的属性值。
接下来普遍做法就是inflate出组合控件的布局,再根据xml传来的属性值给控件设置一些参数。比如文字,按钮状态。
里面的代码都挺简单,比较重要的是。
第一在我android 事件分发机制源码解析文章里最后部分说了,View在onTouchEvent里会根据传来的down、up事件和一些判断。最终得出这是点击事件(不是长按,不是拖动),然后执行performClick()方法。所以可以重写performClick()方法,在里面写SwitchCompat开关的逻辑。
第二因为View里面的performClick()会执行到setOnClickListener传进去的点击回调事件。所以在需要在最后调用一下super.performClick()而不是直接return true消费这个点击事件。这样以后在使用这个SettingSwitchCompat时依旧可以用setOnClickListener方法来写一些业务逻辑。
使用方法:
1.在xml中写出控件 因为有3个新属性不属于原生的,注意写出res-auto这个命名空间。app是我取的,名字不限定。
<?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="fill_parent"
android:layout_height="fill_parent">
<com.yao.mytestproject.SettingSwitchCompat
android:id="@+id/settingSwitchCompat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
app:desc_off="已关闭"
app:desc_on="已开启"
app:title="缓存开关"/>
</RelativeLayout>
2.代码中使用
public class TestActivity extends AppCompatActivity {
private SettingSwitchCompat settingSwitchCompat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
settingSwitchCompat = (SettingSwitchCompat) findViewById(R.id.settingSwitchCompat);
settingSwitchCompat.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("YAO", "TestActivity.java - onClick() ---------- settingSwitchCompat.isChecked()" + settingSwitchCompat.isChecked() );
}
});
}
}
由于重写的performClick方法里,先执行了改变状态的setChecked(!isChecked())方法,然后才执行传过去onClick回调。
所以在onClick方法里,我们就能准备的获取控件当前状态,以便保存或者使用。