前言
最近做一个小App的时候用到了数值增减功能,主要用途就是在将商品添加到购物车时指定购买商品数量;遇到这个需求后首先想到的就是去网上找个现成的来用,但寻找很久也没找到合适的;可能太简单没人觉得这是什么大不了的,或者根本不需要自定义一个控件,这样只能自己动手了。
我本身是个Android新手,很多地方都还是理解不清楚;这次自定义的控件也不知道怎么样,但勉强能满足自己的需求。
效果截图
上图中的“数量“即是本控件的样子,可以点击左右的加减号来控制TextView中数值的增减。
上图是在点击TextView时弹出的对话框,在对话框中的TextView中输入想要的数字后,确定即可退出对话框,然后将数字填写入控件的TextView。
在控件数值改变后可以触发监听器,用于回调。
主要代码
attrs.xml
首先在attrs.xml文件中定义所要用到的属性,如下所示:
<declare-styleable name="IncreaseReduceTextView">
<attr name="textBackground" format="reference"/>
<attr name="textSize" format="dimension"/>
<attr name="verticalPadding" format="dimension"/>
<attr name="horizontalPadding" format="dimension"/>
<attr name="viewSpace" format="dimension"/>
</declare-styleable>
简单解释一下:
- textBackground
控件TextView文本域背景 - textSize
文本域字体大小, 默认为14sp - verticalPadding
文本域垂直方向上的内边距, 默认为2dp - horizontalPadding
文本域水平方向上的内边距, 默认为2dp - viewSpace
TextView左右Margin
IncreaseReduceTextView
接下来是主控件代码,代码较多,但逻辑很简单,如下所示:
package com.witmoon.xmb.ui.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.support.v4.app.FragmentActivity;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.witmoon.xmb.R;
import com.witmoon.xmb.activity.common.fragment.PurchaseQuantityDialogFragment;
import com.witmoon.xmb.util.DensityUtils;
/**
* 自定义数值增减控件, 用于购买数量
* Created by zhyh on 2015/5/26.
*/
public class IncreaseReduceTextView extends ViewGroup implements View.OnClickListener {
private Context mContext;
private int number = 1;
private int mTargetWidth;
private ImageView mMinusImageButton;
private TextView mNumberTextView;
private ImageView mAddImageButton;
private int mBackground; // TextView背景资源
private int mTextSize = 14; // 字体大小, 默认为14sp
private int mVerticalPadding = 2; // TextView上下内边距, 默认为2dp
private int mHorizontalPadding = 2; // TextView左右内边距, 默认为2dp
private int mViewSpace = 0; // TextView左右Margin
public IncreaseReduceTextView(Context context) {
this(context, null);
}
public IncreaseReduceTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IncreaseReduceTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (getChildCount() > 0) {
throw new RuntimeException("IncreaseReduceTextView不允许有子元素.");
}
this.mContext = context;
// 读取自定义属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.IncreaseReduceTextView);
mBackground = ta.getResourceId(R.styleable.IncreaseReduceTextView_textBackground, 0);
mTextSize = ta.getDimensionPixelSize(R.styleable.IncreaseReduceTextView_textSize,
DensityUtils.sp2px(context, mTextSize));
mVerticalPadding = ta.getDimensionPixelSize(R.styleable
.IncreaseReduceTextView_verticalPadding, DensityUtils.dp2px(context,
mVerticalPadding));
mHorizontalPadding = ta.getDimensionPixelSize(R.styleable
.IncreaseReduceTextView_horizontalPadding, DensityUtils.dp2px(context,
mHorizontalPadding));
mViewSpace = ta.getDimensionPixelSize(R.styleable.IncreaseReduceTextView_viewSpace,
mViewSpace);
ta.recycle();
Paint paint = new Paint();
paint.setTextSize(mTextSize);
mTargetWidth = (int) paint.measureText("00");
initializeView();
}
// 初始化视图
private void initializeView() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mMinusImageButton = new ImageView(mContext);
mMinusImageButton.setLayoutParams(params);
mMinusImageButton.setImageResource(R.mipmap.icon_minus_rounded_square);
mMinusImageButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (number > 1) {
number--;
numberChanged();
}
}
});
addView(mMinusImageButton);
mNumberTextView = new TextView(mContext);
LayoutParams editLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams
.WRAP_CONTENT);
mNumberTextView.setLayoutParams(editLayoutParams);
mNumberTextView.setBackgroundResource(mBackground);
mNumberTextView.setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding,
mVerticalPadding);
mNumberTextView.setGravity(Gravity.CENTER);
mNumberTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mNumberTextView.setText(String.valueOf(number));
mNumberTextView.setOnClickListener(this);
addView(mNumberTextView);
mAddImageButton = new ImageView(mContext);
mAddImageButton.setLayoutParams(params);
mAddImageButton.setImageResource(R.mipmap.icon_add_rounded_square);
mAddImageButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (number < 999) {
number++;
numberChanged();
}
}
});
addView(mAddImageButton);
numberChanged();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSum; // 总宽度, 最终结果为自定义组件的宽度
// 处理中间的TextView
View middleView = getChildAt(1);
int childHeight = middleView.getMeasuredHeight();
int childWidth = middleView.getMeasuredWidth();
widthSum = childWidth + mTargetWidth;
middleView.measure(MeasureSpec.makeMeasureSpec(childWidth + mTargetWidth, MeasureSpec
.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
// 处理左右ImageView,重新计算其尺寸
int count = getChildCount();
for (int i = 0; i < count; i++) {
if (i == 1) continue;
View child = getChildAt(i);
int ms = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
child.measure(ms, ms);
widthSum = widthSum + childHeight;
}
// 设置组件自身尺寸, 总宽度再加上两个间距(间距默认为0)
setMeasuredDimension(widthSum + mViewSpace * 2, childHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int left = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
childView.layout(left, 0, left + childWidth, childHeight);
left += childWidth;
if (i != childCount - 1) {
left += mViewSpace;
}
}
}
public void setNumber(int number) {
this.number = number;
numberChanged(false);
}
private void numberChanged() {
numberChanged(true);
}
private void numberChanged(boolean isTriggerChanged) {
mNumberTextView.setText(String.valueOf(number));
if (mOnNumberChangeListener != null && isTriggerChanged) {
mOnNumberChangeListener.onNumberChange(number);
}
if (number <= 1) {
mMinusImageButton.setImageResource(R.mipmap.icon_minus_rounded_square_grey);
} else {
mMinusImageButton.setImageResource(R.mipmap.icon_minus_rounded_square);
}
}
public int getNumber() {
return this.number;
}
@Override
public void onClick(View v) {
PurchaseQuantityDialogFragment pqFragment = new PurchaseQuantityDialogFragment();
pqFragment.setInitNumber(this.number);
pqFragment.setOnInputCompleteListener(new PurchaseQuantityDialogFragment
.OnInputCompleteListener() {
@Override
public void onInputComplete(String data) {
number = Integer.parseInt(data);
numberChanged();
}
});
pqFragment.show(((FragmentActivity) mContext).getSupportFragmentManager(),
"DialogFragment");
}
// ------------------- 数值更改回调接口 -------------------
private OnNumberChangeListener mOnNumberChangeListener;
public void setOnNumberChangeListener(OnNumberChangeListener listener) {
mOnNumberChangeListener = listener;
}
public interface OnNumberChangeListener {
void onNumberChange(int number);
}
}
代码还算清晰,首先是读取自定义属性;然后初始化控件内容,测量各内部控件的尺寸,并将它们摆放好;最后注册事件监听器。
弹出对话框DialogFragment
在点击控件TextView时将弹出一个AlertDialog,用户可以直接输入一个数字;点击确定可以将用户输入更新到控件的TextView中,弹出对话框在这里使用DialogFragment来实现,代码如下所示:
package com.witmoon.xmb.activity.common.fragment;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.AppCompatEditText;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import com.witmoon.xmb.R;
import com.witmoon.xmb.util.DensityUtils;
/**
* 数量增减对话框
* Created by zhyh on 2015/6/6.
*/
public class PurchaseQuantityDialogFragment extends DialogFragment {
private OnInputCompleteListener mOnInputCompleteListener;
public void setOnInputCompleteListener(OnInputCompleteListener listener) {
mOnInputCompleteListener = listener;
}
private EditText mEditText;
private String mInitInput;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.layout_purchase_quantity, null);
// View view = createView(getActivity());
mEditText = (EditText) view.findViewById(R.id.edit);
mEditText.setText(mInitInput);
mEditText.selectAll();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(view).setTitle("修改购买数量").setPositiveButton(R.string.text_confirm, new
DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String data = mEditText.getText().toString();
if (isInputValid(data)) {
if (mOnInputCompleteListener != null) {
mOnInputCompleteListener.onInputComplete(data);
}
dialog.dismiss();
}
}
}).setNegativeButton(R.string.text_cancel, null);
return builder.create();
}
// 创建对话框View
private View createView(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
layout.setGravity(Gravity.CENTER);
AppCompatEditText editText = new AppCompatEditText(context);
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(3)});
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
editText.setId(R.id.edit);
editText.setGravity(Gravity.CENTER_HORIZONTAL);
editText.setPadding(12, 0, 12, 0);
editText.setMinWidth(DensityUtils.dp2px(context, 40));
layout.addView(editText, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
return layout;
}
// 设置初始值
public void setInitNumber(int number) {
this.mInitInput = String.valueOf(number);
}
private boolean isInputValid(String input) {
if (TextUtils.isEmpty(input)) {
return false;
}
if (!input.matches("\\d+")) {
return false;
}
try {
return Integer.parseInt(input) > 0;
} catch (NumberFormatException e) {
return false;
}
}
public interface OnInputCompleteListener {
void onInputComplete(String data);
}
}
DiaglogFragment所使用的xml布局文件
接下来还有一个上面DialogFragment要使用的XML布局文件,内容很简单,仅仅是一个EditText控件而已,如下所示:
<?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="wrap_content"
android:gravity="center"
android:paddingTop="12dp"
android:orientation="vertical">
<android.support.v7.widget.AppCompatEditText
android:id="@+id/edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="number"
android:maxLength="3"
android:background="@drawable/bg_input_area"
android:minWidth="48dp"
android:padding="4dp"/>
</LinearLayout>