使用Databinding輕鬆打造仿攜程app篩選控件(一)

10.gif

使用Databinding輕鬆打造仿攜程app篩選控件(一)

序言

如今主流app例如攜程、美團淘寶等都支持對篩選結果進行復雜而精細的篩選,已達到更高的轉化率。由於業務的複雜,這些控件實現起來也很是具備挑戰性。今天就來帶你們嘗試打造一款仿攜程的篩選控件,不只是在ui層面模仿,同時也要實現業務邏輯,保持開箱可用。php

需求分析

首先看圖 java

dump_2315378105131195430.png

使用uiautomator分析發現:左右兩邊分別是兩個列表,兩個列表聯動,點擊左邊的一項右邊的列表會自動滾動到對應的項的開始,右邊滾動時左邊的列表對應項會變動選中狀態。左邊的列表簡單,右邊的列表較爲複雜,須要支持展開隱藏多餘項。android

實現

  1. 整體思路 反編譯發現攜程是使用ListView實現的, 我不打算使用ListView,由於ListView的限制太多,這裏咱們選擇跟優秀的RecyclerView 咱們不使用傳統的組合自定義view方式,而是使用Databinding去實現。git

  2. 實現展開/隱藏功能github

要在recycerView中實現對一個item view實現展開隱藏功能,難點在於要保持每一個item的展開關閉狀態,這若是沒有控制好會產生bug,實現方式考慮了兩種:docker

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

第一種方式,須要考慮不一樣的數據和對應的佈局,這樣若是要處理展開/隱藏還須要本身去管理狀態,實現起來太麻煩。 第二種方式,一個group是一個item view, 裏面嵌套裏一個recyclerView,這樣展開/隱藏就是直接刷新內部的recyclerview的adapter的數據就好了,狀態保持的問題也迎刃而解了,連動畫效果都能很好的支持,缺點是沒有使用到RecyclerView的複用機制,反編譯發現攜程也是使用的第二種方式,綜合考慮使用這種方式。app

核心代碼:maven

  • 定義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的選中狀態:ide

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

more

Github 簡書 掘金 JCenter dockerHub
Github 簡書 掘金 JCenter dockerHub
相關文章
相關標籤/搜索