需求
在Android中系统给我们提供了非常多的好用的控件。如:TextView,ImageView等。但是有时候我们的需求原生的控件并不能满足我们的要求,或者操作起来很麻烦,要写一堆代码,没法像控件本身的属性(如:textSize,textCOlor等)一样方便配置。
那么怎么才可以做到把我们需要的一些属性直接配置到系统控件里呢?
像这样:
android:layout_height="wrap_content"
android:text="可以跳的TextView"
/>
给TextView配置一个我们自定义的jump属性,我们动态的配置高度,实现一个可以跳跃的TextView。
准备
首先来分析下需求
1:首先jump这个属性是自己提出的,所以需要自定义属性
2:TextView本身并不具备执行我们自定义属性的功能,所以需要自定义一个FrameLayout,把TextView包裹在这个FrameLayout里,然后让这个FrameLayout执行jump属性
3:为了方便控制可以自定义一个LinearLayout,在布局里引用把TextView放在里面
1:首先需要在attrs里自定义jump属性,这个不用多说
2:定义一个可以包裹TextView的ViewGroup-JumpWrap ,具有jump的属性和方法
public class JumpWrap extends FrameLayout{
private float jump;
public JumpWrap(Context context) {
super(context);
}
public void setJump(float jump){
this.jump=jump;
}
//跳跃功能用补间动画简单实现
public void jump( ){
TranslateAnimation translateAnimation = new TranslateAnimation(0,0,0,-jump);
translateAnimation.setDuration(400);
startAnimation(translateAnimation);
}
}
3:自定义一个LinearLayout
OK,分析到这里,下面开始实行
分析实现
1:首先分析一下,系统中控件是怎么添加到容器里的呢?当然是addView这个方法,那么可以学习系统的做法在JumpView里重写这个addView方法。这时候有2种情况:添加了自定义属性的和没有添加自定义属性的,所以需要进行判断:
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
MyLayoutParams p = (MyLayoutParams) params;
if(!hasCustomAttrs(p)){
//没有自定义属性。直接添加
super.addView(child, index, params);
}else {
//有自定义属性,把TextView进行包装以后再进行添加
}
}
/**
* 判断是否有自定义属性,如果不大于0代表没有设置自定义属性
* @param p
* @return
*/
private boolean hasCustomAttrs(MyLayoutParams p) {
return p.jumpHeight>0;
}
/**
* 获取自定义属性
*/
public static class MyLayoutParams extends LinearLayout.LayoutParams{
private final float jumpHeight;
public MyLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.jumpWrap);
jumpHeight = typedArray.getFloat(R.styleable.jumpWrap_jump, 0);
typedArray.recycle();
}
}
那么下来对TextView进行包装
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
//把传进来的params强转成自己的params
MyLayoutParams p = (MyLayoutParams) params;
if(!hasCustomAttrs(p)){
//没有自定义属性。直接添加
super.addView(child, index, params);
}else {
//有自定义属性,new出包装类,传入获取的自定义属性值。然后把子View添加到包装类,再添加到父容器里
JumpWrap jumpWrap = new JumpWrap(getContext());
jumpWrap.setJump(p.jumpHeight);
jumpWrap.addView(child);
super.addView(jumpWrap,index,params);
}
}
上面的代码new出一个jumpWrap对象,然后把xml里设置的jump属性设置进去,然后添加child,其实就是设置的TextView。然后把包装好的jumpWrap通过super.addView()添加进去。
这样就把一个包装了TextView并且具有跳跃属性的jumpWrap添加进去了。
这里会有一个问题
jumpWrap.setJump(p.jumpHeight);
通过这句设置了自定义的属性的值,但是这时的p是强转来的,并没有获取自定义的值呢, super.addView()之后还是不会设置成功,那怎么办?
来看下super.addView的源码:
public void addView(View child, int index, LayoutParams params) {
//省略其他代码...
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
//省略其他代码...
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
}
通过查看源码发现,在addView里执行了一个addViewInner(),它里面有一个generateLayoutParams(params)对params 进行了赋值。那么就是说generateLayoutParams()之后params就有值了,那么再来看generateLayoutParams()
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p;
}
它直接返回了一个p,那么重写这个方法,返回自己的MyLayoutParams 也就是获取的自定义属性,就给params赋值了。
到这里基本上就完成了,只需要找到控件,执行它的jump方法就可以了。
补充
应用场景:可以封装成一个独立的库,加入一些常用的属性如:渐变,移动等。
类似于CoordinatorLayout里的behavior是不是就可以用这种方法实现?