仿京东收货地址管理
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;
}
这样就能够达到预期的效果。
公司项目,所以源码就不方便贴出来,有问题随时向我留言,我尽力答复~