前言
自定义View这种东西相信大家也不陌生了,毕竟没吃过猪肉也看过猪跑了吧!
首先看下效果图对比(知道你们找代码主要看效果)
出来混久了,一是不满足于官方控件反复写,一个是因为人人都是产品经理,需求就多了...
通常,Android实现自定义View有如下三种方式:
- 将现有控件进行组合,实现功能更加强大控件。
- 继承现有控件,对其控件的功能进行拓展。
- 重写View实现全新的控件。
今天就来实践一下简单的自定义View之组合。自定义View这种东西,说难也难,说简单也简单。俗话说得好,纸上得来终觉浅,绝知此事要躬行。看了一下支付宝的界面,觉得有很多地方值得学习,这次就拿一个比较简单的点出来模仿,实现简单自定义View。
效果图已经贴出来了,其实这种效果很常见,几乎每个软件都会有,大同小异而已了。总不能每次都止步于用一个LinearLayout或者RelativeLayout写来写去。
实践
布局
首先按照套路写一个布局,实现我们自定义View需要的效果:
这里也没什么特别的,就是普通的布局文件,但是要注意一下边界条件,比如TextView的长度、行数之类的,避免越界。
分析
简单的分析一下,我们要做一个自定义控件,那应该像Android的原生控件一样,能够方便调用者设置控件的属性。比如说这里,我们可以除了常规的设置文字、文字颜色外还能设置左边的icon、右边的icon、左边文字的颜色、右边文字的颜色等。
设置属性也不是很复杂,只需要在res资源目录下的values目录下创建一个attrs.xml属性文件,并在该文件定义你所需要的属性即可。这个自定义控件自定义属性如下:
上面的几种属性(format)的意思是:
- reference:参考某一资源ID
- color:颜色值
- boolean:布尔值
- dimension:尺寸值
- float:浮点值
- integer:整型值
- string:字符串
- fraction:百分数
- enum:枚举值
- flag:位或运算
目的就是为了告诉系统,这个属性代表了什么。
接下来,我们继承RelativeLayout(为什么是它,因为刚才我们的布局根节点就是RelativeLayout),实现最重要的一步。
/**
* Created by fdm on 2017/4/2.
*/
public class MenuItem extends RelativeLayout {
ImageView iconLeft;
ImageView iconRight;
TextView menuTextRight;
TextView menuTextLeft;
public MenuItem(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MenuItem(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
inflate(context, R.layout.menuitem_layout, this);
iconLeft = getView(R.id.icon_left);
iconRight = getView(R.id.icon_right);
menuTextLeft = getView(R.id.menu_text_left);
menuTextRight = getView(R.id.menu_text_right);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MenuItemView);
int rightTextColor = ta.getColor(R.styleable.MenuItemView_rightTextColor, Color.GRAY);
int leftTextColor = ta.getColor(R.styleable.MenuItemView_leftTextColor, Color.BLACK);
menuTextRight.setTextColor(rightTextColor);
menuTextLeft.setTextColor(leftTextColor);
float textSize = ta.getDimension(R.styleable.MenuItemView_textSize, getResources().getDimension(R.dimen.default_text_size));
menuTextLeft.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
menuTextRight.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
int leftIconId = ta.getResourceId(R.styleable.MenuItemView_leftIcon, R.mipmap.icon0);
iconLeft.setImageResource(leftIconId);
int rightIconId = ta.getResourceId(R.styleable.MenuItemView_rightIcon, R.mipmap.icon_right);
iconRight.setImageResource(rightIconId);
String text = ta.getString(R.styleable.MenuItemView_text);
if (text != null) {
menuTextLeft.setText(text);
// menuTextRight.setText(text);
}
ta.recycle();
}
public void setMenuTextRight(String text) {
menuTextRight.setText(text);
}
public CharSequence getMenuTextRight() {
return menuTextRight.getText();
}
public void setMenuTextLeft(String text) {
menuTextLeft.setText(text);
}
public CharSequence getMenuTextLeft() {
return menuTextLeft.getText();
}
public T getView(int id) {
return (T) findViewById(id);
}
}
首先我们获取到了TypedArray的值:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MenuItemView);
然后就可以获取到具体的属性并设置默认值,最后不要忘记回收TypedArray。这里我们也可以看到,正是R.styleable.MenuItemView将我们自定义的属性一一对应起来。
为了扩展性,我们还可以提供几个方法,这里示例了设置文字内容等。
使用
前面已经准备完毕了,接下来看下怎么使用,就像普通控件那样使用就行了,不过需要带上完整包名。
这里的app是个命名空间(也可以自定义成abc之类的),需要手动引入
xmlns:app="http://schemas.android.com/apk/res-auto"
注意,自定义属性在AS上默认没有智能提示,直接写就对了。这里看到我们可以设置左右两边的icon,文字颜色了。
Activity文件(注:经评论提醒,这里将declare-styleable name="MenuItemView" 跟自定义View的类名MenuItemView统一对应起来就可以智能提示了,疏忽了这个。)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
MenuItem menuItem;
MenuItem menuItem1;
MenuItem menuItem2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
menuItem = getView(R.id.menu);
menuItem1 = getView(R.id.menu1);
menuItem2 = getView(R.id.menu2);
menuItem.setOnClickListener(this);
menuItem1.setOnClickListener(this);
menuItem2.setOnClickListener(this);
menuItem1.setMenuTextRight("0.00元");
menuItem2.setMenuTextRight("古铜色会员");
}
public T getView(int id) {
return (T) findViewById(id);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.menu:
Toast.makeText(this, "蚂蚁花呗", Toast.LENGTH_SHORT).show();
break;
case R.id.menu1:
Toast.makeText(this, "余额", Toast.LENGTH_SHORT).show();
break;
case R.id.menu2:
Toast.makeText(this, "蚂蚁会员", Toast.LENGTH_SHORT).show();
break;
}
}
}
运行一下:
还是有几分神似吧,毕竟我们重要的是实现,不变的是技术,细节随着需求而改变,这里就不深究了吧。
总结
其实按照套路一步步走下来,自定义View的组合大法其实也不是很难,遇到困难就查查资料,相信有付出就有收获。
国际惯例:源码送上https://github.com/brucevanfdm/CustomViewDemo 欢迎star/fork,并提出宝贵意见。
注:程序部分图标来源:阿里巴巴矢量图标库 表示感谢,也顺便打个广告,自来水。