接着上一篇Android自定义开发系列(一)继续:
由于上一篇的例子做得比较粗糙,也是因为那个只是个简单的入门,只是演示了一下通过组合已有控件开发自定义控件的几个途径。那样的例子连自己都感觉不好意思啦,那么今天我来做一个稍微美观、实用、性能更好的自定义控件,也是大家在平时开发中使用广泛的控件——自定义一个带清除按钮的输入框,先看一下效果:
呵呵,还不错吧(自恋一下),其实从图片上看不明显,还是看看作品内涵吧,看完以后你就会发现——咦?这个自定义控件蛮好用啊!让我们开始吧:
首先,我们要创建一个布局文件,我的叫 mould_layout.xml,创建待组合的TextView和ImageView:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/imgClean" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_margin="5dp" android:contentDescription="@null" android:src="@drawable/icon_clean" /> <EditText android:id="@+id/et" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:paddingLeft="10dp" android:layout_toLeftOf="@id/imgClean" android:background="@null" /> </RelativeLayout>
然后我们要稍微思考一下:这个控件要实现什么样的功能呢?(1)既然是输入框,就应该有hint提示语、输入方式(密码、数字....)、输入长度、字体大小、字体颜色 ... ...等等,还要让清除按钮在有输入时显示,无输入时不显示,点击清除按钮清除已输入的内容,哦,还有,一定不能少了要能获取到输入值,要不然这不就是个废物?(2)既然作为一个自定义控件,就应该自己完成自己的相关设置。那么,设置这些属性应该用什么类型的参数呢?我们在 alues 目录下创建attrs.xml声明参数的资源文件(当然,如果已经有了就不用创建了,直接在后边追加就好):
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="EditTextCanClean"> <!-- *记住* declare-styleable的name就是自定义控件的名字 --> <attr name="cleanIconDrawable" format="reference" /> <attr name="hint" format="reference|string" /> <attr name="text" format="reference|string" /> <attr name="textSize" format="reference|dimension" /> <attr name="textColor" format="reference|color" /> <attr name="maxLength" format="integer" /> </declare-styleable> </resources>
这里怎么没有 inputType 属性呢?我们现往后看,后边有具体说明。
到这里以后,只要我们创建自定义控件类,我的叫EditTextCanClean,继承RelativeLayout,根据提示补上构造函数(一定不能忘了两个参数的那个,否则运行时会报错!)剩下的什么都不做,我们就可以在布局文件中创建这个自定义控件了(要声明自己的命名空间哦!如果不知道可以查看Android自定义开发系列(一)):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:wangj="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/activity_vertical_margin"> <com.example.wangj.myedittext.EditTextCanClean android:id="@+id/et_account" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/background_layout" wangj:hint="测试hint是否设置正确" wangj:text="测试文字颜色和字号大小" wangj:textColor="#273590" wangj:maxLength="10"/> </RelativeLayout>
但是自定义设置参数还没有起作用。接下来我们就让这些参数起作用(嘴拙,代码有详细注释,如果原理不清楚就看Android自定义开发系列(一)基础):
public class EditTextCanClean extends RelativeLayout { private EditText et; private ImageView img; public EditTextCanClean(Context context) { super(context); } public EditTextCanClean(Context context, AttributeSet attrs) { super(context, attrs); int resourceId = -1; View view = LayoutInflater.from(context).inflate(R.layout.mould_layout, this, true); et = (EditText) view.findViewById(R.id.et); //给et添加输入内容变化的监听,控制清除按钮的显示与否 et.addTextChangedListener(textWatcher); img = (ImageView) view.findViewById(R.id.imgClean); //初始无输入值时,清除按钮隐藏 img.setVisibility(View.INVISIBLE); //清除按钮的点击响应 img.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { et.setText(""); } }); //获取自定义参数,对自定义控件进行初始化设置 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EditTextCanClean); int n = ta.getIndexCount(); for (int i = 0; i < n; i++) { int attr = ta.getIndex(i); switch (attr) { //若读到hint属性设置,否则走不到该case case R.styleable.EditTextCanClean_hint: resourceId = ta.getResourceId(R.styleable.EditTextCanClean_hint, 0); //若resourceId>0,说明读到hint设置为reference类型,则找到资源字段进行设置 //否则,说明读到为hint设置了String字段,获取从读到的字段中取出对应String设置给et et.setHint(resourceId > 0 ? getResources().getText(resourceId) : ta.getString(R.styleable.EditTextCanClean_hint)); break; //若读到text属性设置,否则走不到该case case R.styleable.EditTextCanClean_text: resourceId = ta.getResourceId(R.styleable.EditTextCanClean_text, 0); et.setText(resourceId > 0 ? getResources().getText(resourceId) : ta.getString(R.styleable.EditTextCanClean_text)); break; //若没有设置cleanIconDrawable属性,则走不到该case,cleanIconDrawable显示ImageView中默认的图标 case R.styleable.EditTextCanClean_cleanIconDrawable: resourceId = ta.getResourceId(R.styleable.EditTextCanClean_cleanIconDrawable, 0); img.setImageResource(resourceId > 0 ? resourceId : R.drawable.icon_clean); break; case R.styleable.EditTextCanClean_textColor: resourceId = ta.getResourceId(R.styleable.EditTextCanClean_textColor, 0); et.setTextColor(resourceId > 0 ? getResources().getColor(resourceId) : ta.getColor(R.styleable.EditTextCanClean_textColor, Color.BLACK)); break; case R.styleable.EditTextCanClean_textSize: resourceId = ta.getResourceId(R.styleable.EditTextCanClean_textSize, 0); et.setTextSize(resourceId > 0 ? getResources().getDimension(resourceId) : ta.getDimension(R.styleable.EditTextCanClean_textSize, 20)); break; case R.styleable.EditTextCanClean_maxLength: // et.setMaxEms(ta.getInt(R.styleable.EditTextCanClean_maxLength, 0)); et.setFilters(new InputFilter[]{new InputFilter.LengthFilter(ta.getInt(R.styleable.EditTextCanClean_maxLength, 0))}); Log.e("WangJie", ta.getInt(R.styleable.EditTextCanClean_maxLength, 0)+"个"); break; default: break; } } ta.recycle(); //一定要对TypedArray资源进行释放 } //设置Hint提示字符串的方法,以便通过Java代码进行设置 protected void setHint(String string){ et.setHint(string); } //设置编辑框显示文字的方法,以便通过Java代码进行设置 protected void setText(String string) { et.setText(string); } //获取输入值 protected String getText(){ return et.getText().toString(); } //设置清除按钮的图标,以便通过Java代码进行设置 protected void setCleanIcon(int drawableId) { Drawable drawable = getResources().getDrawable(drawableId); img.setImageDrawable(drawable); } //***重要 //暴露出EditText,以便设置EditText的更多属性,如inputType属性等等,是为了增加灵活性和兼容性 protected EditText getEditText(){ return et; } //输入框文本变化监听器 private TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void afterTextChanged(Editable s) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { //在输入框没有输入时不显示清除图标,有输入后显示 if (TextUtils.isEmpty(s)){ //为什么不用View.GONE呢?——GONE会使被隐藏的控件不再占用它原本的位置 // 为了放置在隐藏和显示变化过程中由于位置有无导致自定义控件外观产生抖动或是变化,所以用了View.INVISIBLE img.setVisibility(View.INVISIBLE); }else { img.setVisibility(View.VISIBLE); } } }; }
Activity中代码:
public class MainActivity extends Activity { private EditTextCanClean etCanClean; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etCanClean = (EditTextCanClean) findViewById(R.id.et_account); //测试代码设置文字方法 // etCanClean.setText("飒飒"); //有效 //测试代码设置清除图标样式 // etCanClean.setCleanIcon(R.mipmap.ic_launcher); //有效 //获取自定义空间中EditText进行特殊属性设置 etCanClean.getEditText().setInputType(InputType.TYPE_CLASS_PHONE); //有效 //setBackground方法也是可用的,因为EditTextCanClean继承Layout //etCanClean.setBackgroundResource(R.drawable.xx); Toast.makeText(this, etCanClean.getText(), Toast.LENGTH_SHORT).show(); } }
按照这个步骤做出来的控件是没有外边那个圆角边框的,但是所有功能都完美实现。下边做几点设计和实现的说明,让大家更明白我的思路: