微信扫一扫

028-83195727 , 15928970361
business@forhy.com

仿京东收货地址管理

tablayout,地址管理,Android2016-08-02

一个仿京东收货地址的三级城市列表Demo,看似简单,其中坑却不少,一起看看怎么实现的吧:

1.需求分析:

1.使用Tablayout+Viewpager搭建主界面,通过动态管理Tablayout的tabs来控制城市列表的分级,Viewpager来控制城市列表的Fragment。

2.每一个Fragment里面展示的都是城市列表,只不过数据不一样,可以通过传递不同的标识符来创建Fragment,这样可以达到最大的复用效果。

3.城市列表Fragment中item的点击事件,需要Activity中的Tablayout做出对应的改变,这里使用EventBus来处理Fragment与Activity的通信,高效快捷,高度解耦。

2.界面布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tab"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        app:tabIndicatorColor="@color/orange_big"
        app:tabTextAppearance="@style/MyTabLayoutTextAppearanceInverse"
        app:tabSelectedTextColor="@color/orange_big"
        app:tabTextColor="@color/color_333">
    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white" />
</LinearLayout>

这里没有使用官方默认的样式,使用了自定义样式的TabLayout,详细介绍之前一片博客里面有:

仿CSDN客户端首页(一)—-TabLayout实现选项卡滑动效果

Fragment中的界面布局就是一个简单的ListView,item布局只有一个TextView,这里就不贴详细代码。

3.业务实现:

1.Fragment的初始化操作

    public static SupplierCityFragment newInstance(int type, int code) {
        SupplierCityFragment fragment = new SupplierCityFragment();
        Bundle bundle = new Bundle();
        bundle.putSerializable(FIRST_FRAGMENT, type);
        bundle.putSerializable(CODE, code);
        fragment.setArguments(bundle);
        return fragment;
    }
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (null != getArguments()) {
            type = (Integer) getArguments().getSerializable(FIRST_FRAGMENT);
            code = (Integer) getArguments().getSerializable(CODE);
        }
    }

使用arguments来创建Fragment的方法,给Fragment添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取,达到最大程度的复用。

2.Activity的初始化操作

    private void initViews() {
        titleLists.add("请选择");
        fragments.add(SupplierCityFragment.newInstance(1, 0));
        adapter = new TabAdapter(getSupportFragmentManager(), fragments);
        //设置标题
        adapter.setTabTitle(titleLists);
        //给ViewPager设置适配器
        viewpager.setAdapter(adapter);
        //将TabLayout和ViewPager关联起来
        tablayout.setupWithViewPager(viewpager);
        //设置可滑动
        tablayout.setTabMode(TabLayout.MODE_SCROLLABLE);
    }

Tablayout的基本使用,默认Tablayout的tabs是充满,将TabMode设置为TabLayout.MODE_SCROLLABLE表示不填充整个水平布局,并且可滑动。

当前Activity需要接受来自Fragment的消息,所以记得在onCreate()中进行注册:

EventBus.getDefault().register(this);

在ondestory()进行解除操作:

EventBus.getDefault().unregister(this);

3.获取数据

    private void initView(View view) {
        //清空城市名称集合
        nameLists.clear();
        //根据不同的code来获取数据的方法
        getCity(code);

        listview = (ListView) view.findViewById(R.id.list_four);
        listview.setAdapter(adapter);
        listview.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                City city = cityLists.get(position);
                code = city.getCode();
                String cityId = city.getId();
                Event item = new Event(nameLists.get(position), type, code, cityId, position);
                EventBus.getDefault().post(item);

            }
        });
    }

默认获取省级列表,获取省级城市列表的code为0,所以我们初始化Fragment的时候传递过来的code为0;点击省级城市列表,得到item的code,根据这个code获取市级城市列表;点击市级城市列表,得到item的code,根据这个code获取县级城市列表

同时需要传递的参数还有:城市的名称,标识符(Activity会根据标识符处理不同的点击事件),城市的ID(业务需要),城市的position(记录选择的城市,字体变红并标记对号)

那么我们的Event实体类构建如下,需要的参数添加进去即可

/**
 * Created by tangyangkai on 16/5/17.
 */
public class Event {
    private String title;
    private int code;
    private int type;
    private String id;
    private int position;

    public Event(String title, int type, int code, String id,int position) {
        this.title = title;
        this.code = code;
        this.type = type;
        this.id = id;
        this.position=position;
    }

    public String getTitle() {
        return title;
    }

    public int getCode() {
        return code;
    }

    public int getType() {
        return type;
    }

    public String getId() {
        return id;
    }

    public int getPosition() {
        return position;
    }
}

4.动态管理

在接受消息的Activity中,重写onEventMainThread()方法:

    public void onEventMainThread(final Event item) {
        TabLayout.Tab tab = tablayout.newTab();
        tab.setText(item.getTitle());

        if (item.getType() == 1) {
            titleLists.clear();
            titleLists.add("请选择");

            TabLayout.Tab tab2 = tablayout.newTab();
            tablayout.removeAllTabs();
            tab2.setText("请选择");
            tablayout.addTab(tab2);
            tablayout.addTab(tab, 0);

            fragments.clear();
            fragments.add(SupplierCityFragment.newInstance(1, 0));
            fragments.add(SupplierCityFragment.newInstance(2, item.getCode()));

            cityLists.clear();
            cityLists.add(item.getTitle());
            positionLists.clear();
            position = item.getPosition();
            positionLists.add(position);
        } else if (item.getType() == 2) {
            if (titleLists.size() == 3) {
                fragments.remove(2);
                tablayout.removeTabAt(2);
            }
            titleLists.clear();
            titleLists.add(cityLists.get(0));
            titleLists.add("请选择");
            tablayout.addTab(tab, 1);
            fragments.add(SupplierCityFragment.newInstance(3, item.getCode()));

            positionLists.clear();
            positionLists.add(position);
            positionLists.add(item.getPosition());
        } else {
            cityLists.clear();
            cityLists.add(titleLists.get(0));
            cityLists.add(titleLists.get(1));
            cityLists.add(item.getTitle());
            return;
        }
        titleLists.add(titleLists.size() - 1, item.getTitle());
        adapter.notifyDataSetChanged();

        handler.post(new Runnable() {
            @Override
            public void run() {
                tablayout.getTabAt(titleLists.size() - 1).select();
                viewpager.setCurrentItem(titleLists.size() - 1);
            }
        });

    }

代码有点杂乱,这里挨个分析:

首先是得到传递过来的城市名称,将这个作为Tablayout的一个tabs;

当item.getType() == 1,也就是点击省级城市列表item的时候,清空标题集合,添加标题”请选择”,后插入城市名称;移除Tablayout的所有tabs,添加两个tabs;清空Fragment集合,并添加两个Fragment,也就是省级城市列表Fragment与市级城市列表Fragment;cityLists用来记录保存选择的城市名称用于最后返回结果;positionLists用于记录保存选择的城市position;

当item.getType() == 2,也就是点击市级城市列表item的时候,进行一下判断,如果城市tabs为三个,移除第三个Fragment,移除第三个tabs;添加一个最新的tabs;添加县级Fragment到Fragment集合中;清空记录position的集合,并添加两个最新的position进去;

当item.getType() == 3,也就是点击县级城市列表item的时候,获取到所有选择的城市列表名称,返回不做任何操作。

4.注意事项:

1.设置Tablayout的选中状态
刚开始我的写法是这样的:

        adapter.notifyDataSetChanged();
        tablayout.getTabAt(titleLists.size() - 1).select();
        viewpager.setCurrentItem(titleLists.size() - 1);

一直没有效果,能够滑动到指定的位置,但是指示器一直不会出现。后来主管分析源码和我说,Tablayout的tabs是通过计算坐标来实现的,每次初始化还未完成的时候,坐标无法传递过去,导致无法出现预期效果。这让我想起之前自定义QQ健康界面的时候,初始化没完成,就传递参数执行动画效果,一直没反应。其实原理都是一样,将这些操作放在子线程中处理,等初始化完成以后,再进行这些操作就OK

2.更新Fragment里的视图

public class TabAdapter extends FragmentStatePagerAdapter {

    private List<SupplierCityFragment> fragments;
    private List<String> tabTitle;


    public TabAdapter(FragmentManager fm, List<SupplierCityFragment> fragments) {
        super(fm);
        this.fragments = fragments;
    }

    public void setTabTitle(List<String> tabTitle) {
        this.tabTitle = tabTitle;
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }


    @Override
    public CharSequence getPageTitle(int position) {
        return tabTitle.get(position);

    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
}

第一次加载数据,视图显示一点问题没有,但当我滑动返回之前的界面,数据出现错乱,省级城市列表显示的是县级城市列表的数据,就算我初始化的时候,每次clear城市集合也不行。后来找到的解决办法是加上了:

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

直接上图:

3.设置选中的城市状态
观察示例图中,会发现滑动返回的时候,之前选中的城市状态会不一样,所以我们需要记住这个position。

    public ListViewAdapter(Context context, List<String> list, int position) {
        this.context = context;
        this.lists = list;
        this.selectedPosition = position;
        this.inflater = LayoutInflater.from(context);
    }

        if (position == selectedPosition && selectedPosition != 10000) {
            viewHolder.txt.setTextColor(context.getResources().getColor(R.color.orange_big));
            viewHolder.img.setVisibility(View.VISIBLE);
        }

加上第二个判断的原因是第一次加载的时候,没有选中任何城市,此时我将selectedPosition设置为10000

在Fragment初始化adapter的时候:

        switch (type) {
            case 1:
                if (SupplierCityActivity.positionLists.size() == 0) {
                    adapter = new ListViewAdapter(getActivity(), nameLists, 10000);
                } else {
                    adapter = new ListViewAdapter(getActivity(), nameLists, SupplierCityActivity.positionLists.get(0));
                }
                break;
            case 2:
                if (SupplierCityActivity.positionLists.size() == 1) {
                    adapter = new ListViewAdapter(getActivity(), nameLists, 10000);
                } else {
                    adapter = new ListViewAdapter(getActivity(), nameLists, SupplierCityActivity.positionLists.get(1));
                }
                break;
            case 3:
                adapter = new ListViewAdapter(getActivity(), nameLists, 10000);
                break;
        }

这样就能够达到预期的效果。

公司项目,所以源码就不方便贴出来,有问题随时向我留言,我尽力答复~