使用Databinding轻松打造仿携程app筛选控件
序言
现在主流app例如携程、美团淘宝等都支持对筛选结果进行复杂而精细的筛选,已达到更高的转化率。因为业务的复杂,这些控件实现起来也非常具有挑战性。今天就来带大家尝试打造一款仿携程的筛选控件,不仅是在ui层面模仿,同时也要实现业务逻辑,保持开箱可用。
需求分析
如图所示,使用uiautomator分析发现:左右两边分别是两个列表,两个列表联动,点击左边的一项右边的列表会自动滚动到对应的项的开始,右边滚动时左边的列表对应项会变更选中状态。左边的列表简单,右边的列表较为复杂,需要支持展开隐藏多余项。
实现
总体思路
反编译发现携程是使用ListView实现的, 我不打算使用ListView,因为ListView的限制太多,这里我们选择跟优秀的RecyclerView
我们不使用传统的组合自定义view方式,而是使用Databinding去实现。实现展开/隐藏功能
要在recycerView中实现对一个item view实现展开隐藏功能,难点在于要保持每个item的展开关闭状态,这如果没有控制好会产生bug,实现方式考虑了两种:
- 使用多item type 的方式
- 使用嵌套RecyclerView的方式 ,
第一种方式实现起来太麻烦,需要考虑不同的数据和对应的布局。
第二种方式,一个group是一个item view, 里面嵌套里一个recyclerView,这样展开/隐藏就是直接刷新内部的recyclerview的adapter的数据就行了,状态保持的问题也迎刃而解了,连动画效果都能很好的支持,缺点是没有使用到RecyclerView的复用机制,反编译发现携程也是使用的第二种方式,综合考虑使用这种方式。
核心代码:
- 定义item
- 使用bindingAdapter绑定数据
@BindingAdapter("items")
public static void setChild(RecyclerView rv, List- items) {
if (rv.getAdapter() == null) {
InnerRecyclerAdapter adapter = new InnerRecyclerAdapter();
MOTypedRecyclerAdapter.AdapterDelegate childDelegate = new MOTypedRecyclerAdapter.AdapterDelegate() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(MOTypedRecyclerAdapter adapter, ViewGroup parent) {
return new BindingViewHolder<>(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
R.layout.list_item_selectable, parent, false));
}
@Override
public void onBindViewHolder(MOTypedRecyclerAdapter moTypedRecyclerAdapter, RecyclerView.ViewHolder viewHolder, Object o) {
BindingViewHolder vh = (BindingViewHolder) viewHolder;
vh.getBinding().setVariable(BR.item, o);
vh.getBinding().executePendingBindings();
}
@Override
public boolean isDelegateOf(Class> clazz, Object item, int position) {
return Item.class.isAssignableFrom(clazz);
}
};
adapter.addDelegate(childDelegate);
rv.setLayoutManager(new GridLayoutManager(rv.getContext(), 3));
rv.setAdapter(adapter);
}
InnerRecyclerAdapter adapter = (InnerRecyclerAdapter) rv.getAdapter();
adapter.setDataSet(items);
}
- 实现左右列表的联动
实现联动主要通过添加滚动监听,这点RecyclerView远优于ListView,在滚动的时候判断当前在顶部的item,更新item的选中状态:
rvRight.addOnScrollListener(new RecyclerView.OnScrollListener() {
boolean isDragging = false;
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
isDragging = (newState != SCROLL_STATE_IDLE);
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (!isDragging) {
return;
}
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager == null) {
return;
}
int position = layoutManager.findFirstVisibleItemPosition();
if (position == RecyclerView.NO_POSITION) {
return;
}
RecyclerView.ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(position);
if (vh == null) {
return;
}
BindingViewHolder holder = (BindingViewHolder) vh;
Group group = (Group) holder.getItem();
if (group == null) {
return;
}
mPresenter.swapScollActiveGroup(group);
}
});
实现点击一套联动滚动到对应项, 当点击一个item的时候,滚动右侧recyclerview到对应位置,核心方法:layoutManager.scrollToPositionWithOffset
:
public class Presenter {
final RecyclerView rv;
private Group mScrollTopGroup;
private Group mClickCheckGroup;
public Presenter(RecyclerView rv) {
this.rv = rv;
}
public void onGroupClick(Group item, int position) {
Group prevChecked = mClickCheckGroup;
if (prevChecked != null && !prevChecked.equals(item)) {
prevChecked.setChecked(false);
}
Group prevChecked2 = mScrollTopGroup;
if (prevChecked2 != null && !prevChecked2.equals(item)) {
prevChecked2.setChecked(false);
//scroll to position
scrollToGroup(item, position);
}
item.setChecked(true);
mClickCheckGroup = item;
mScrollTopGroup = item;
}
public void swapScollActiveGroup(Group group) {
if (mScrollTopGroup != null && !mScrollTopGroup.equals(group)) {
mScrollTopGroup.setChecked(false);
}
mScrollTopGroup = group;
mScrollTopGroup.setChecked(true);
}
public void scrollToGroup(Group group, int position) {
if (rv == null || group == null) {
return;
}
LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager();
if (layoutManager == null) {
return;
}
layoutManager.scrollToPositionWithOffset(position, 0);
}
}
总结
使用Databing大大的简化了我们的代码,原本需要自定义view的限制只需要BindingAdapter就可以实现了。
项目地址/代码/github
其他
使用Databinding轻松快速打造仿携程app筛选控件(一)
使用Databinding轻松快速打造仿携程app筛选控件(二)
使用Databinding轻松快速打造仿携程app筛选控件(三)
more
Github | 掘金 | JCenter | dockerHub | |
---|---|---|---|---|
Github | 掘金 | JCenter | dockerHub |