android自定义控件(二),简易的数值输入器
前言:前几天看到了迅雷app上有个一元夺宝,那时无聊就参加了几次,到后来中奖结果出来了,哎,根本就没我的份啊(意料之中),还好投的钱不多,就一块两块,不过,我看到了那里有个数值输入的效果,我当时想了想这个实现挺简单了,应该有很多种方式,于是做了个决定,如果没中奖,我就把那个数值输入器做出来以惩罚自己(开玩笑~),现在,该是兑现自己承诺的时候了。
先看看我们即将要实现的效果图:
大家看到这个效果,要实现这么一个控件,我们可以怎么做?
一开始,我心想,这还不简单,自定义一个view,然后分别画出左边的减号按钮,中间的输入控件,和右边的加法按钮,包括最外边的边框,最后重新view的事件监听方法,在里面判断点击的坐标,如果是点击了左边按钮的坐标范围,那么让中间的输入控件数值递减,如果是点击了右边按钮的坐标范围,那么让中间的输入按钮数值递增。
嗯,这听起来不错,好吧,我动手试着按照这种思路开始做了,当我在画中间的输入按钮的时候,我懵了,怎么才能画出一个可以让用户输入的控件?于是,我放弃了这种思路,开始了另一种思路。
接着,我想,这用类似组合控件的形式不是很简单吗,干嘛搞得那么复杂啊,这样一来,不是不仅不用自己画一个输入控件了,而且还不用计算点击坐标了。所以,下面将用组合控件的形式实现这个控件。
1. 自定义属性
?为了提供控件的可定制性,我们需要自定义属性,观察上面的效果图,我们可以定义下面9个属性供用户使用(当然,你也可以定义其他的属性供用户定制)
border_color:边框的颜色 border_width:边框的宽度 minus_color:减号的颜色 minus_width:… plus_color:加号的颜色 plus_width:… min_value:允许的最小值 initial_value:初始值 max_value:允许的最大值2. 自定义加减按钮
?为了解决耦合问题,那个减号,加号按钮使用自定义view绘图来实现,不引用任何资源,这里以加号按钮为例,做个说明(在文章结尾将会贴上全部代码)。
class PlusView extends View{
private Paint mPlusPaint;
public PlusView(Context context) {
this(context, null);
}
public PlusView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PlusView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPlusPaint = new Paint();
mPlusPaint.setAntiAlias(true);
mPlusPaint.setStrokeWidth(mPlusWidth);
mPlusPaint.setStyle(Paint.Style.STROKE);
mPlusPaint.setStrokeCap(Paint.Cap.ROUND);
mPlusPaint.setColor(mPlusColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthMode == MeasureSpec.AT_MOST){
widthSize = Math.min(widthSize,100);
}
if(heightMode == MeasureSpec.AT_MOST){
heightSize = Math.min(heightSize,100);
}
setMeasuredDimension(widthSize,heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
canvas.drawLine(mWidth - mWidth / 5, mHeight / 2, mWidth / 5, mHeight / 2, mPlusPaint);
canvas.save();
canvas.rotate(90, mWidth / 2, mHeight / 2);
canvas.drawLine(mWidth / 5, mHeight / 2, mWidth - mWidth / 5, mHeight / 2, mPlusPaint);
canvas.restore();
}
}
这里自定义view,就是在测量的时候判断一些测量模式,如果是AT_MOST模式,那么给个最小值。测量完了以后,在onDraw()
方法中画个加号就行了,对于自定义view不熟悉的可以参考这篇文章:android自定义view(一),打造绚丽的验证码
3. 继承LinearLayout实现控件组合
?观察上面的效果图,可以看出这个控件的三部分是等宽的,所以,使用LinearLayout实现非常简单(weight属性),下面,定义类继承LinearLayout:
public class NumberInput extends LinearLayout {
public NumberInput(Context context) {
this(context, null);
}
public NumberInput(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberInput(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// viewGroup必须设置背景,这样才会调用onDraw()方法
setBackgroundColor(Color.TRANSPARENT);
init(context,attrs);
initView(context);
}
}
这里,三个构造方法的调用情况就不多说了,不懂的可以问度娘~。我们在构造方法中做了一些初始化的工作,包括获取布局属性值,添加子view等等。这里,要注意的事,viewGroup默认是不会调用onDraw()
方法的,我们可以给它设置背景强制viewGroup调用onDraw()
方法。
获取属性值:
/**
* 获取自定义属性值
*/
private void init(Context context,AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.NumberInput);
mBorderColor = typedArray.getColor(R.styleable.NumberInput_border_color, Color.GRAY);
mBorderWidth = typedArray.getDimension(R.styleable.NumberInput_border_width, 6);
mMinusColor = typedArray.getColor(R.styleable.NumberInput_minus_color, Color.GRAY);
mMinusWidth = typedArray.getDimension(R.styleable.NumberInput_minus_width, 5);
mPlusColor = typedArray.getColor(R.styleable.NumberInput_plus_color, Color.GRAY);
mPlusWidth = typedArray.getDimension(R.styleable.NumberInput_plus_width, 5);
mInitialValue = typedArray.getInteger(R.styleable.NumberInput_initial_value, 0);
mMinValue = typedArray.getInteger(R.styleable.NumberInput_min_value, Integer.MIN_VALUE);
mMaxValue = typedArray.getInteger(R.styleable.NumberInput_max_value, Integer.MAX_VALUE);
// 回收资源
typedArray.recycle();
}
添加子View:
private void initView(Context context) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
params.setMargins(20, 6, 20, 6);
params.weight = 1;
mMinusButton = new MinusView(context);
mMinusButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int currentNum = Integer.valueOf(mEditText.getText().toString());
currentNum--;
mEditText.setText(currentNum + "");
}
});
if(mInitialValue<=mMinValue){
mMinusButton.setEnabled(false);
}
addView(mMinusButton, params);
mEditText = new EditText(context);
// 做个判断,如果给的初始值小于最小值,那么初始值就是最小值
// 如果给的初始值大于最大值,那么初始值就是最大值
if(mInitialValue<=mMinValue){
mEditText.setText(mMinValue + "");
}else if(mInitialValue>=mMaxValue){
mEditText.setText(mMaxValue + "");
}else {
mEditText.setText(mInitialValue + "");
}
mEditText.requestFocus();
mEditText.setGravity(Gravity.CENTER);
mEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
// 去除默认的下划线
mEditText.setBackground(null);
mEditText.addTextChangedListener(mWatcher);
addView(mEditText, params);
mPlusButton = new PlusView(context);
params.weight = 1;
mPlusButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int currentNum = Integer.valueOf(mEditText.getText().toString());
currentNum++;
mEditText.setText(currentNum + "");
}
});
if(mInitialValue>=mMaxValue){
mPlusButton.setEnabled(false);
}
addView(mPlusButton,params