简介
在Android开发中除了View的自定义绘制以外,组合控件也是比较常见的一种自定义View的方式,这种方式通过把系统提供的一些常见View组合起来,添加一些自定义的属性;很少或者根本不需要进行手动的额外绘制,操作相对简单一些,也能大幅度提升开发效率。
在绝大多数app中都有这样的页面:
需求整理
从上图我们可以看到这种常见的UI效果中包含了几种情况:
1.选择效果,例如时间的选择,点击右边弹出选择框或者页面跳转,整个item是不可编辑的。
2.输入效果,左边是显示输入框的文本属性,右边是输入框,用户可以输入文本,例如修改个人信息的时候会用到。
3.显示效果,左右都不可编辑,只是单纯的显示一些信息。
除此之外还有一些细节方面的属性,例如:左边和右边文本的属性,颜色,大小,padding值等;item之间的divider分隔线,分隔线的颜色,宽度,padding值;文本框的hint以及hint的细节属性;文字的Gravity属性以及item的点击事件响应等等。
之前为了实现这些效果,大多数人常用的方法就是直接在UI里面进行系统控件的嵌套组合;这是一种最直接也最简单的方法。但是如果类似的效果少了还好,多的话就让人比较头疼了,只能一遍一遍复制粘贴。这样就会造成页面布局文件很长很长;系统解析起来费劲,自己看着也烦。
这时候自定义组合控件就派上用场了。
实现原理
先通过分析,把类似的效果抽取到一个通用的UI中。
总的来说就是一个TextView+一个EditText的组合,或者两个TextView的组合;或者可以把右箭头图标单独拎出来放进一个ImageView中。
布局抽取出来之后就开始进行控件组合代码的编写:
先继承LinearLayout并添加构造方法,添加自定义的属性:
public class ChooseItemView extends LinearLayout
{
public ChooseItemView(Context context)
{
this(context, null, 0);
}
public ChooseItemView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public ChooseItemView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
this.context = context;
//抽取需要的自定义属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChooseItemView);
itemNameText = typedArray.getString(R.styleable.ChooseItemView_item_name_text);
itemNameTextPaddingLeft = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_name_text_padding_left, dip2px(15));
itemNameTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_name_text_color, Color.BLACK);
itemNameTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_name_text_size, 16);
itemChooseText = typedArray.getString(R.styleable.ChooseItemView_item_choose_text);
itemChooseTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_choose_text_color, Color.BLACK);
itemChooseTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_choose_text_size, 16);
itemChooseGravity = typedArray.getInt(R.styleable.ChooseItemView_item_choose_gravity, 2);
editTextGravity = typedArray.getInt(R.styleable.ChooseItemView_item_edit_text_gravity, 2);
iconVisible = typedArray.getBoolean(R.styleable.ChooseItemView_item_choose_icon_visible, true);
editMode = typedArray.getBoolean(R.styleable.ChooseItemView_item_edit_mode, false);
editHint = typedArray.getString(R.styleable.ChooseItemView_item_edit_hint);
editHintColor = typedArray.getColor(R.styleable.ChooseItemView_item_edit_hint_color, Color.LTGRAY);
editText = typedArray.getString(R.styleable.ChooseItemView_item_edit_text);
editTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_edit_text_color, Color.BLACK);
editTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_edit_text_size, 16);
dividerColor = typedArray.getColor(R.styleable.ChooseItemView_item_divider_color, Color.LTGRAY);
dividerHeight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_height, dip2px(2));
dividerMarinLeft = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_margin_left, dip2px(15));
dividerMarinRight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_margin_right, 0);
itemHeight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_height, dip2px(50))+dividerHeight/2;
typedArray.recycle();
}
进行子View的绑定和属性初始化:
private void initView()
{
//准备绘制分隔线的画笔
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(dividerColor);
paint.setStrokeWidth(dividerHeight);
//子控件绑定
View view = LayoutInflater.from(context).inflate(R.layout.layout_choose_item, this);
llContainer = (LinearLayout) view.findViewById(R.id.ll_view_container);
tvItemName = (TextView) view.findViewById(R.id.tv_choose_item_name);
tvItemChoose = (TextView) view.findViewById(R.id.tv_choose_item_choose);
etItemChoose = (EditText) view.findViewById(R.id.et_choose_item_choose);
//初始化各个控件的属性
tvItemName.setText(itemNameText);
tvItemName.setTextColor(itemNameTextColor);
tvItemName.setTextSize(itemNameTextSize);
tvItemName.setPadding(itemNameTextPaddingLeft, dip2px(15), dip2px(15), dip2px(15));
tvItemChoose.setText(itemChooseText);
tvItemChoose.setTextColor(itemChooseTextColor);
tvItemChoose.setTextSize(itemChooseTextSize);
//item整体高度的自定义
ViewGroup.LayoutParams params = llContainer.getLayoutParams();
params.height = itemHeight;
llContainer.setLayoutParams(params);
etItemChoose.setText(editText);
etItemChoose.setHint(editHint);
etItemChoose.setHintTextColor(editHintColor);
etItemChoose.setTextSize(editTextSize);
etItemChoose.setTextColor(editTextColor);
setEditMode(editMode);
setIconVisible(iconVisible);
setItemChooseGravity(itemChooseGravity);
setEditTextGravity(editTextGravity);
}
绘制item之间的分隔线:
@Override
protected void dispatchDraw(Canvas canvas)
{
//绘制分隔线 一定要在父控件绘制完成之后
super.dispatchDraw(canvas);
canvas.drawLine(dividerMarinLeft, itemHeight, getWidth() - dividerMarinRight, itemHeight, paint);
}
添加一些常用的api以便在代码中修改相关属性:
//自定义属性相关的api
public String getItemNameText()
{
return itemNameText;
}
public void setItemNameText(String itemNameText)
{
this.itemNameText = itemNameText;
tvItemName.setText(itemNameText);
}
public void setItemNameTextSize(int itemNameTextSize)
{
this.itemNameTextSize = itemNameTextSize;
tvItemName.setTextSize(TypedValue.COMPLEX_UNIT_SP, itemNameTextSize);
}
public void setItemChooseTextColor(int itemChooseTextColor)
{
this.itemChooseTextColor = itemChooseTextColor;
tvItemChoose.setTextColor(ContextCompat.getColor(context, itemChooseTextColor));
}
public int getItemChooseTextSize()
{
return itemChooseTextSize;
}
public void setItemChooseTextSize(int itemChooseTextSize)
{
this.itemChooseTextSize = itemChooseTextSize;
tvItemChoose.setTextSize(TypedValue.COMPLEX_UNIT_SP, itemChooseTextSize);
}
//与绘制相关的属性修改完成后要记得通知系统进行重绘
public void setDividerColor(int dividerColor)
{
this.dividerColor = dividerColor;
paint.setColor(ContextCompat.getColor(context, dividerColor));
invalidate();
}
//根据需要添加对应的监听事件
public void setIconClickListener(OnClickListener iconClickListener)
{
tvItemChoose.setOnClickListener(iconClickListener);
}
public void setTextChangeListener(TextWatcher textChangeListener)
{
etItemChoose.addTextChangedListener(textChangeListener);
}
}
....
效果和总结
使用:
这样完了之后,一个通用的UI控件就完成了,可以适用于大多数类似的页面,仅仅需要设置几个不同的属性;成倍的减少了代码量,布局文件再也不会像以前那样臃肿了。并且代码的扩展性也增强了不少。以后遇到类似的场景就可以参考这种实现方式,而不必一次次的复制粘贴。
完整代码参考
GitHub