使用Databinding轻松快速打造仿携程app筛选控件(一)

10.gif

使用Databinding轻松打造仿携程app筛选控件

序言

现在主流app例如携程、美团淘宝等都支持对筛选结果进行复杂而精细的筛选,已达到更高的转化率。因为业务的复杂,这些控件实现起来也非常具有挑战性。今天就来带大家尝试打造一款仿携程的筛选控件,不仅是在ui层面模仿,同时也要实现业务逻辑,保持开箱可用。

需求分析

dump_2315378105131195430.png

如图所示,使用uiautomator分析发现:左右两边分别是两个列表,两个列表联动,点击左边的一项右边的列表会自动滚动到对应的项的开始,右边滚动时左边的列表对应项会变更选中状态。左边的列表简单,右边的列表较为复杂,需要支持展开隐藏多余项。

实现

  1. 总体思路
    反编译发现携程是使用ListView实现的, 我不打算使用ListView,因为ListView的限制太多,这里我们选择跟优秀的RecyclerView
    我们不使用传统的组合自定义view方式,而是使用Databinding去实现。

  2. 实现展开/隐藏功能

要在recycerView中实现对一个item view实现展开隐藏功能,难点在于要保持每个item的展开关闭状态,这如果没有控制好会产生bug,实现方式考虑了两种:

  • 使用多item type 的方式
  • 使用嵌套RecyclerView的方式 ,

第一种方式实现起来太麻烦,需要考虑不同的数据和对应的布局。
第二种方式,一个group是一个item view, 里面嵌套里一个recyclerView,这样展开/隐藏就是直接刷新内部的recyclerview的adapter的数据就行了,状态保持的问题也迎刃而解了,连动画效果都能很好的支持,缺点是没有使用到RecyclerView的复用机制,反编译发现携程也是使用的第二种方式,综合考虑使用这种方式。

核心代码:

  • 定义item
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="item"
            type="github.hotstu.demo.hof.Group" />

        <import type="android.view.View" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="5dp"
                android:layout_weight="1"
                android:text="@{item.toString()}"
                android:textSize="22sp"
                tools:text="title" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginRight="5dp"
                android:onClick="@{()->item.toggle()}"
                android:text="@{item.expanded? @string/collapse : @string/expand}"
                android:textColor="@color/blue"
                android:textSize="16sp"
                android:visibility="@{item.fullItems.size() &lt;=6? View.GONE: View.VISIBLE}"
                android:drawableEnd="@{item.expanded? @drawable/common_icon_flight_arrow_up: @drawable/common_icon_flight_arrow_down}"
                tools:text="展开" />
        </LinearLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:items="@{item.expanded?item.fullItems:item.internalItems}">

        </androidx.recyclerview.widget.RecyclerView>

    </LinearLayout>
</layout>

  • 使用bindingAdapter绑定数据
    @BindingAdapter("items")
    public static void setChild(RecyclerView rv, List<Item> 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);

    }
  1. 实现左右列表的联动

实现联动主要通过添加滚动监听,这点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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 内容 抽屉菜单 ListView WebView SwitchButton 按钮 点赞按钮 进度条 TabLayo...
    小狼W阅读 5,480评论 0 10
  • Android UI相关开源项目库汇总OpenDigg 抽屉菜单MaterialDrawer ★7337 - 安卓...
    黄海佳阅读 12,789评论 3 77
  • 今天带宝贝逛超市打了宝贝,已经买好了菜出来她要在地砖上溜冰,做电梯,提了很重的菜,我觉得她不听话,我忘了自己带她出...
    有事多舒缓没事多感赏阅读 1,150评论 0 0
  • 虽自幼便在外婆家长大的,但是除了外公外婆和某些玩的比较好的朋友,还真对那里的大人无感。 越长大越孤单,随...
    书演阅读 1,735评论 0 0
  • ‘遇见老年的自己’,今天玩了一次岁月的穿越。触动到我的,开始真正理解陪伴的意义。前两天问起女儿,小时候记...
    心灵的笑声阅读 1,209评论 1 5