最近自己编写下拉刷新的时候,发现了一个问题,就是有一个需求是这样的:要求页面中是一个Tab切换界面,一个界面有底部操作栏,不可下拉刷新,另一个界面没有底部操作栏,但可以下拉刷新。
按照平常的做法,我在xml文件中使用了RelativeLayout,声明下拉刷新组件的layout_above为底部操作栏,然后在测试的时候发现一个奇怪的现象:如果一开始设置底部操作栏可见,在另一个运行下拉刷新的界面在下拉的时候就会出现和底部操作栏同样位置,同样大小,但颜色采用系统默认的布局,如果设置为不可见,则不会出现这个问题。
<BottomView android:id="@+id/bottom" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentBottom="true"/> <RefreshListView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/bottom" />
经过调试,底部操作栏的确是在下拉刷新的时候被设为不可见,所以这个迷之物体并不是底部操作栏。
查了一些资料,虽然并没有找到和我一样的问题,但最大的可能就是在RelativeLayout中,如果layout_above对应的组件被gone掉,有可能产生布局错乱的问题。
既然这样,就换一种写法:声明底部操作栏的layout_bellow为下拉刷新组件。可惜,同样的问题还是会产生。
好吧,既然有可能是因为组件的相对关系产生问题,那么思考是否能够动态的添加对应关系。
Android依然可以提供这种方案,可以通过view.getLayoutParams来获取组件的LayoutParams,然后通过addRule和removeRule方法来动态的添加和删除相对关系。但可惜的是,removeRule和addRule这两个方法并不是成套出售的,removeRule需要更高的API才能支持。
想想,为了考虑兼容性问题,还是放弃这种做法。
结合现象,可能的问题就是下拉刷新需要一个下拉区域,而这个区域在View刚被绘制的时候就已经确定好,如果后面的对应关系被打乱了,可能系统也会产生默认的布局来明确这个区域。
更好的解决方法就出来了:将底部操作栏也作为下拉刷新的区域,也就是和下拉刷新组件一起作为一个布局。
问题是解决了,但我觉得不是很舒服:我的 下拉刷新组件是通用的,只要不启动下拉刷新这个功能,它完全就是一个ListView,并且还可以自由的配置没有数据时候的界面。现在因为一个需求,就加入一个特化的底部操作栏,即使可以控制它的可见性一定程度上还是可以保证它是可用的,但如果我要替换底部操作栏呢?
所以我开始把底部操作栏也可以封装进来。
一开始想到的解决方法就是利用面向对象的继承关系,声明一个基类组件,但实际上,Android的xml文件并不能识别组件的继承关系,不能让子类组件用在基类组件声明的地方。
既然继承行不通,就利用组合。
声明一个类,持有自定义布局底部操作栏的引用,就可以对它进行控制。
public class ContactBottomOperation { private BaseBottomOperation bottomOperation; ... }
但要想做到自由替换布局,就必须传入layout的id,然后由该类对布局的渲染进行控制。
public BaseBottomOperation initBottom(int id) { bottomOperation.setVisibility(View.VISIBLE); bottomOperation.init(id); return bottomOperation; } public class BaseBottomOperation{ ... public void init(int id) { LayoutInflater.from(context).inflate(id, this); } }
问题是解决了,但还是发现一个不舒服的地方:点击事件呢?
因为我只是持有布局的引用,所以我不能让该类实现onItemClickListener,无法监听点击事件,而自定义的底部操作栏也只有在渲染的时候才知道自己的控件到底是啥。
其实,这个问题根本就不是问题,我完全可以在控制类中获取到底部操作栏的控件,然后设置相应的事件,但无法使用ButterKnife这样的工具来简化代码。
rlSave = (RelativeLayout)bottom.findViewById(R.id.rl_save); rlSave.setOnItemClickListener(new OnItemClickListener(){ @Override public void onClick(...){ save(); } } }
想到ButterKnife原本就是利用反射的原理,于是我也开始思考反射能否解决我的困扰。
这样就可以解决问题了,我只要传入需要监听的View和调用的方法,就可以完成View和事件的绑定。
bottomOperation.setItemClick(rlFav, this, "favorite"); public void setItemClick(View view, final Object object, final String methodName) { view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Class classObj = object.getClass(); try { Method method = classObj.getMethod(methodName, null); method.invoke(object, null); } catch (NoSuchMethodException e) { LogUtil.e(e.toString()); } catch (InvocationTargetException e) { LogUtil.e(e.toString()); } catch (IllegalAccessException e) { LogUtil.e(e.toString()); } } });
实际编码中总会遇到各种奇葩的问题,但问题并不是解决了就完事了,往往问题的解决才是真正的开始。