一、概述
通过前几章我们对高德地图的操作有了基本的了解,后面将介绍如何获取高德地图的一些数据,例如:POI数据,公交数据,天气数据等等。这些都是高德地图为我们设计好了API,只需要调用就好了。不过本章要讲的是怎么自定义折叠列表并下载相应的城市地图,在此感谢 鸿洋 大神提供的折叠列表的思路。
二、本章内容
--- 下载离线地图
--- 折叠列表
1.首先,我们需要获取到城市数据,再高德地图API中OfflineMapManager类主要用于管理离线地图的下载,数据的获取等功能。
OfflineMapManager(Context ctx, OfflineMapManager.OfflineMapDownloadListener listener);
从OfflineMapManager的构造函数可以知道,第一个参数是一个上下文,第二个参数是监听下载地图的接口。我们实现这个接口必须实现3个方法
@Override
public void onDownload(int i, int i1, String s) {
//实现高德地图离线下载的接口
}
@Override
public void onCheckUpdate(boolean b, String s) {
//实现高德地图离线下载的接口
}
@Override
public void onRemove(boolean b, String s, String s1) {
//实现高德地图离线下载的接口
}
可以从方法名称中看出大致的作用,这里不过多描述。有了OfflineMapManager实例,下一步就是获取高德地图的城市数据
// //获取城市列表
// OfflineMapManager.getOfflineMapCityList()
// //获取省份列表
// OfflineMapManager.getOfflineMapProvinceList()
// //获取已下载的城市列表
// OfflineMapManager.getDownloadOfflineMapCityList()
// //获取正在下载的城市列表
// OfflineMapManager.getDownloadingCityList()
2.折叠列表实现,有了数据那么接下来开始将折叠列表,这里我使用RecyclerView来实现,至于还在用ListView的同学请使用RecyclerView,强烈推荐(RecyclerView的强大谁用谁知道)。
首先讲一下原理:
我们要实现的效果如下
/**
* 1-------根节点未展开
* 2-------根节点展开
* 2.1-------子节点未展开
* 2.2-------子节点未展开
* 2.3-------子节点展开
* 2.3.1---子节点未展开
* 2.3.2---子节点未展开
* 3-------根节点未展开
* */
假如我们只有这3级折叠,1,2,3为根节点;2.1,2.2,2.3为根节点2的子节点;同理2.3.1,2.3.2为二级节点中2.3节点的子节点。那么在Android中列表控件显示中,我们只会显示看的见的部分,所以我们在Adapter中需要有两个数据集合。
第一个是所有节点的全部数据集合,第二个就是我们需要展示的数据集合。当我们在点击可展开节点的时候,重新刷新一下展示数据集合中的内容并显示到列表中。这样就解决了数据显示的问题,那么还有一个难题就是数据存放的顺序。
我们知道Android中的列表都是自上而下的一次显示,那么折叠列表中我们怎样进行数据的排序呢?子节点中需要保存父节点,父节点中也必须要有所有子节点的集合。例如:
/**
* new Node(1, 0, "书籍")
* new Node(2, 1, "Java从入门到放弃")
* new Node(3, 1, "Mysql从入门到删库")
* new Node(4, 1, "Mysql从删库到跑路")
* new Node(5, 1, "Android从入门到改行")
* new Node(6, 1, "PHP")
* new Node(7, 0, "文档")
* new Node(8, 0, "游戏")
* new Node(9, 8, "魔兽世界")
* new Node(10, 8, "守望先锋")
* new Node(11, 8, "英雄联盟")
* new Node(12, 8, "炉石传说")
* new Node(13, 0, "程序")
* new Node(14, 13, "面向对象的语言")
* new Node(15, 14, "Java")
* new Node(16, 14, "C++")
* new Node(17, 14, "JavaScript")
* new Node(18, 14, "PHP")
* new Node(19, 13, "面向过程的语言")
* new Node(20, 19, "C")
*
* */
Node类构造方法第一个参数为序号,第二个参数为父节点序号,第三个参数为该节点的内容,将上面的这些实例按顺序加入到数据集合中,数据就搞定了。
注意上面一段只是为了简明原理举得例子,我们这里下载离线地图还是不一样的,但原理都是这样,理论上这个可以实现很多级折叠,但我建议3级折叠已经够多了,再往上你在处理数据的时候会变得更加复杂。
好了原理明白就直接上代码。
Activity
public class OfflineMapActivity extends BaseActivity implements OfflineMapManager.OfflineMapDownloadListener {
@BindView(R.id.m_recycler_view)
RecyclerView mRecyclerView;
private Unbinder binder;
//离线地图管理实例
private OfflineMapManager omm;
//省份list
private ArrayList<Province> provinces;
private TreeAdapter mAdapter;
@Override
public void setContentView(@Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_offline_map);
binder = ButterKnife.bind(this);
}
@Override
public void initData() {
provinces = new ArrayList<>();
omm = new OfflineMapManager(this, this);
mAdapter = new TreeAdapter(getApplicationContext());
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
mRecyclerView.addItemDecoration(new LinearLayoutDecoration(this, LinearLayoutManager.VERTICAL));
mRecyclerView.setAdapter(mAdapter);
ArrayList<OfflineMapProvince> data = omm.getOfflineMapProvinceList();
//这里我做了一个线程池,你可以把run()方法看成子线程中执行的
MyApplication.getInstance().getPoolExecutor().execute(new Runnable() {
@Override
public void run() {
String path = Environment.getExternalStorageDirectory() + "/MyMap/maps";
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
//自定义离线地图下载存放目录
MapsInitializer.sdcardDir = path;
// //获取城市列表
// OfflineMapManager.getOfflineMapCityList()
// //获取省份列表
// OfflineMapManager.getOfflineMapProvinceList()
// //获取已下载的城市列表
// OfflineMapManager.getDownloadOfflineMapCityList()
// //获取正在下载的城市列表
// OfflineMapManager.getDownloadingCityList()
// 获取高德地图的数据
ArrayList<OfflineMapProvince> data = omm.getOfflineMapProvinceList();
provinces = getProvinceData(data);
//将解析出来的数据组装成适配器的数据适配器中
//按顺序装载数据,根节点pid = 0,
final ArrayList<Node> allNodes = new ArrayList<>();
int index = 0;
for (int i = 0; i < provinces.size(); i++) {
Node n = new Node(index, 0);
n.setParentNode(null);
n.setData(provinces.get(i));
n.setExpand(false);
n.setLevel(1);
ArrayList<Node> subNodes = n.getSubNodeList();
n.setSubNodeList(subNodes);
ArrayList<City> cities = provinces.get(i).getCitys();
for (int j = 0; j < cities.size(); j++) {
index++;
Node one = new Node(index, n.getId());
one.setParentNode(n);
one.setData(cities.get(j));
one.setParentNode(n);
one.setLevel(2);
one.setExpand(false);
subNodes.add(one);
}
allNodes.add(n);
index++;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.setData(allNodes);
}
});
}
});
mAdapter.setListener(new TreeAdapter.OnClickListener() {
@Override
public void onClick(int position, String cityCode) {
try {
omm.downloadByCityCode(cityCode);
mAdapter.setStartDownLoading(position);
} catch (AMapException e) {
e.printStackTrace();
}
}
@Override
public void pause() {
if (omm != null) {
omm.pause();
}
}
});
}
/**
* 解析数据,也可以直接用高德地图类
*/
private ArrayList<Province> getProvinceData(ArrayList<OfflineMapProvince> data) {
ArrayList<Province> result = new ArrayList<>();
//将获取的省份,城市解析出来
for (int i = 0; i < data.size(); i++) {
ArrayList<City> cityList = new ArrayList<>();
Province p = new Province();
p.setName(data.get(i).getProvinceName())
.setSimplicity(data.get(i).getJianpin())
.setFullPinyin(data.get(i).getPinyin())
.setProvinceCode(data.get(i).getProvinceCode())
.setCitys(cityList);
ArrayList<OfflineMapCity> cities = data.get(i).getCityList();
//这里我就不判空操作了,在高德地图源码中已经做了这步
// if (cities != null && cities.size() > 0) {
for (int j = 0; j < cities.size(); j++) {
City c = new City();
c.setName(cities.get(j).getCity())
.setSimplicity(cities.get(j).getJianpin())
.setFullPinyin(cities.get(j).getPinyin())
.setCityCode(cities.get(j).getCode())
.setProvinceCityCode(cities.get(j).getAdcode());
cityList.add(c);
}
// }
result.add(p);
}
return result;
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
//注意binder紧跟在super.onDestroy()前一步,如果提前释放后,再释放UI控件会出空指针异常,注意位置
binder.unbind();
super.onDestroy();
}
@Override
public void onDownload(int i, int i1, String s) {
//实现高德地图离线下载的接口
// i ------- 状态
// i1 ------- 下载进度
// s ------- 下载城市
Log.e("CF", "onDownload: i=" + i + " i1=" + i1 + " s=" + s);
if (i == 0) {
//下载进度
mAdapter.setDownloading(i1, s);
} else if (i == -1) {
//下载完成
mAdapter.downloadComplete(s);
}
}
@Override
public void onCheckUpdate(boolean b, String s) {
//实现高德地图离线下载的接口
Log.e("CF", "onCheckUpdate: b=" + b + " s=" + s);
}
@Override
public void onRemove(boolean b, String s, String s1) {
//实现高德地图离线下载的接口
Log.e("CF", "onRemove: b=" + b + " s=" + s + " s1=" + s1);
}
}
Adapter
public class TreeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//item布局类型
private final static int NODE = 0;
private final static int LEAF = 1;
//所有数据
private ArrayList<Node> allNodes;
//可见数据
private ArrayList<Node> visibleNodes;
private LayoutInflater lf; //布局加载
private Context context;
//点击接口
private OnClickListener listener;
public TreeAdapter(Context context) {
this.context = context.getApplicationContext();
lf = LayoutInflater.from(this.context);
visibleNodes = new ArrayList<>();
}
/**
* 过滤可见数据
* */
private void filterVisibleNode() {
visibleNodes.clear();
for (int i = 0; i < allNodes.size(); i++) {
Node one = allNodes.get(i);
if (one.getPid() == 0 && !one.isSubNode()) {
visibleNodes.add(one);
expandNode(one);
} else if (one.getPid() == 0 && one.isSubNode()) {
visibleNodes.add(one);
}
}
}
/**
* 加入展开子节点的数据,如果子节点还有展开项,继续展开
* 列如: 1-------
* 2-------
* 2.1-------
* 2.2-------
* 2.3-------
* 2.3.1---
* 2.3.2---
* 3-------
*
* 按顺序将需要显示的展开数据添加到visibleNodes中
* */
private void expandNode(Node one) {
//如果节点不是叶节点,且节点展开
if (one.isExpand() && !one.isSubNode()) {
ArrayList<Node> subLists = one.getSubNodeList();
for (int j = 0; j < subLists.size(); j++) {
visibleNodes.add(subLists.get(j));
expandNode(subLists.get(j));
}
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//根据节点类型的不同加载不同的Item布局
if (viewType == NODE) {
NodeViewHolder holder = new NodeViewHolder(lf.inflate(R.layout.item_ndoe_layout, parent, false));
return holder;
} else if (viewType == LEAF) {
LeafViewHolder holder = new LeafViewHolder(lf.inflate(R.layout.item_leaf_layout, parent, false));
return holder;
}
return null;
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof NodeViewHolder) {
//带扩展数据的item设置
if (visibleNodes.get(position).isExpand()) {
((NodeViewHolder) holder).itemImag.setImageResource(R.drawable.triangle_bottom);
} else {
((NodeViewHolder) holder).itemImag.setImageResource(R.drawable.triangle_right);
}
((NodeViewHolder) holder).itemLayout.setPadding(visibleNodes.get(position).getLevel() * 30, 5, 5, 5);
final Province p = (Province) visibleNodes.get(position).getData();
((NodeViewHolder) holder).itemText.setText(p.getName());
((NodeViewHolder) holder).itemLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int id = visibleNodes.get(position).getId();
for (int i = 0; i < allNodes.size(); i++) {
if (allNodes.get(i).getId() == id) {
if (allNodes.get(i).isExpand()) {
allNodes.get(i).setExpand(false);
} else {
allNodes.get(i).setExpand(true);
}
break;
}
}
filterVisibleNode();
notifyDataSetChanged();
}
});
} else if (holder instanceof LeafViewHolder) {
//子叶节点,就是不可以再展开的节点item布局
final City c = (City) visibleNodes.get(position).getData();
((LeafViewHolder) holder).itemText.setText(c.getName());
((LeafViewHolder) holder).itemLayout.setPadding(20 + visibleNodes.get(position).getLevel() * 20, 5, 5, 0);
((LeafViewHolder) holder).itemLeafDownBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
if (!visibleNodes.get(position).isLoading()) {
listener.onClick(position, c.getCityCode());
visibleNodes.get(position).setLoading(true);
} else {
listener.pause();
visibleNodes.get(position).setLoading(false);
}
notifyDataSetChanged();
}
}
});
if (visibleNodes.get(position).isLoading()) {
((LeafViewHolder) holder).itemLeafDownBtn.setBackgroundResource(R.drawable.pause);
} else {
((LeafViewHolder) holder).itemLeafDownBtn.setBackgroundResource(R.drawable.dwonload);
}
if (visibleNodes.get(position).isVisible()) {
((LeafViewHolder) holder).itemLeafDownloadingLayout.setVisibility(View.VISIBLE);
((LeafViewHolder) holder).itemLeafPercentText.setText(visibleNodes.get(position).getPercent() + "%");
((LeafViewHolder) holder).itemLeafProgressbar.setProgress(visibleNodes.get(position).getPercent());
} else {
((LeafViewHolder) holder).itemLeafDownloadingLayout.setVisibility(View.GONE);
}
if (visibleNodes.get(position).isDowned()) {
((LeafViewHolder) holder).itemLeafDownBtn.setVisibility(View.INVISIBLE);
((LeafViewHolder) holder).itemLeafDownText.setVisibility(View.VISIBLE);
} else {
((LeafViewHolder) holder).itemLeafDownBtn.setVisibility(View.VISIBLE);
((LeafViewHolder) holder).itemLeafDownText.setVisibility(View.INVISIBLE);
}
}
}
@Override
public int getItemCount() {
return visibleNodes == null ? 0 : visibleNodes.size();
}
@Override
public int getItemViewType(int position) {
if (visibleNodes.get(position).isSubNode()) {
return LEAF;
} else {
return NODE;
}
}
/**
* 数据设置更新
* */
public void setData(ArrayList<Node> allNodes) {
this.allNodes = allNodes;
filterVisibleNode();
notifyDataSetChanged();
}
/**
* 显示下载进度
* */
public void setDownloading(int percent, String cityName) {
for (Node one : visibleNodes) {
if (one.isSubNode()) {
City c = (City) one.getData();
if (c.getName().equals(cityName)) {
one.setPercent(percent);
one.setVisible(true);
break;
}
}
}
notifyDataSetChanged();
}
/**
* 下载完成
* */
public void downloadComplete(String cityName) {
for (Node one : visibleNodes) {
if (one.isSubNode()) {
City c = (City) one.getData();
if (c.getName().equals(cityName)) {
one.setVisible(false);
one.setDowned(true);
break;
}
}
}
notifyDataSetChanged();
}
/**
* 设置显示下载
* */
public void setStartDownLoading(int position) {
visibleNodes.get(position).setVisible(true);
notifyDataSetChanged();
}
public class NodeViewHolder extends RecyclerView.ViewHolder {
private LinearLayout itemLayout;
private ImageView itemImag;
private TextView itemText;
public NodeViewHolder(View itemView) {
super(itemView);
itemLayout = itemView.findViewById(R.id.item_node_layout);
itemImag = itemView.findViewById(R.id.item_node_img);
itemText = itemView.findViewById(R.id.item_node_content);
}
}
public class LeafViewHolder extends RecyclerView.ViewHolder {
private ConstraintLayout itemLayout;
private TextView itemText;
private TextView itemLeafPercentText;
private ProgressBar itemLeafProgressbar;
private RelativeLayout itemLeafDownloadingLayout;
private ImageView itemLeafDownBtn;
private TextView itemLeafDownText;
public LeafViewHolder(View itemView) {
super(itemView);
itemLayout = itemView.findViewById(R.id.item_leaf_layout);
itemText = itemView.findViewById(R.id.item_leaf_content);
itemLeafPercentText = (TextView) itemView.findViewById(R.id.item_leaf_percent_text);
itemLeafProgressbar = (ProgressBar) itemView.findViewById(R.id.item_leaf_progressbar);
itemLeafDownloadingLayout = (RelativeLayout) itemView.findViewById(R.id.item_leaf_downloading_layout);
itemLeafDownBtn = (ImageView) itemView.findViewById(R.id.item_leaf_down_btn);
itemLeafDownText = (TextView) itemView.findViewById(R.id.item_leaf_down_text);
}
}
public void setListener(OnClickListener listener) {
this.listener = listener;
}
public interface OnClickListener {
void onClick(int position, String cityCode);
void pause();
}
}
数据封装类
public class City {
//城市名称
private String name;
//城市代码
private String cityCode;
//简拼
private String simplicity;
//全拼
private String fullPinyin;
//与省份的关系代码,注:跟省份代码不同
private String provinceCityCode;
public City() {
this.name = "";
this.cityCode = "";
this.simplicity = "";
this.fullPinyin = "";
this.provinceCityCode = "";
}
public String getName() {
return name;
}
public City setName(String name) {
this.name = name;
return this;
}
public String getCityCode() {
return cityCode;
}
public City setCityCode(String cityCode) {
this.cityCode = cityCode;
return this;
}
public String getSimplicity() {
return simplicity;
}
public City setSimplicity(String simplicity) {
this.simplicity = simplicity;
return this;
}
public String getFullPinyin() {
return fullPinyin;
}
public City setFullPinyin(String fullPinyin) {
this.fullPinyin = fullPinyin;
return this;
}
public String getProvinceCityCode() {
return provinceCityCode;
}
public City setProvinceCityCode(String provinceCityCode) {
this.provinceCityCode = provinceCityCode;
return this;
}
}
public class Province {
//省份名字
private String name;
//简拼
private String simplicity;
//全拼
private String fullPinyin;
//省份代码
private String provinceCode;
//省份下的城市
private ArrayList<City> citys;
public Province() {
this.name = "";
this.simplicity = "";
this.fullPinyin = "";
this.provinceCode = "";
this.citys = new ArrayList<>();
}
public String getName() {
return name;
}
public Province setName(String name) {
this.name = name;
return this;
}
public String getSimplicity() {
return simplicity;
}
public Province setSimplicity(String simplicity) {
this.simplicity = simplicity;
return this;
}
public String getFullPinyin() {
return fullPinyin;
}
public Province setFullPinyin(String fullPinyin) {
this.fullPinyin = fullPinyin;
return this;
}
public String getProvinceCode() {
return provinceCode;
}
public Province setProvinceCode(String provinceCode) {
this.provinceCode = provinceCode;
return this;
}
public ArrayList<City> getCitys() {
return citys;
}
public Province setCitys(ArrayList<City> citys) {
this.citys = citys;
return this;
}
}
public class Node {
//当前id
private int id;
//父id
private int pid;
//等级
private int level;
//数据,此处放置的对应item的数据,在使用时,一定要直到存放的什么数据,否则强转出错
private Object data;
//父节点实例
private Node parentNode;
//子节点集合
private ArrayList<Node> subNodeList;
//展开状态
private boolean isExpand;
//-------------------------------这下面成员变量是扩展用,根据需求变更,主要的是在上面那些成员变量必须要-----------------------------------------
//百分比
private int percent;
//是否可见进度条
private boolean isVisible;
//加载状态
private boolean isLoading;
//下载完成状态
private boolean isDowned;
public Node(int id, int pid) {
this.id = id;
this.pid = pid;
this.parentNode = null;
this.data = null;
this.subNodeList = new ArrayList<>();
this.isExpand = false;
this.percent = 0;
this.isVisible = false;
this.isLoading = false;
this.isDowned = false;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getParentNode() {
return parentNode;
}
public void setParentNode(Node parentNode) {
this.parentNode = parentNode;
}
public ArrayList<Node> getSubNodeList() {
return subNodeList;
}
public void setSubNodeList(ArrayList<Node> subNodeList) {
this.subNodeList = subNodeList;
}
public boolean isSubNode() {
return subNodeList.size() == 0;
}
public boolean isExpand() {
return isExpand;
}
public void setExpand(boolean expand) {
isExpand = expand;
}
public int getPercent() {
return percent;
}
public void setPercent(int percent) {
this.percent = percent;
}
public boolean isVisible() {
return isVisible;
}
public void setVisible(boolean visible) {
isVisible = visible;
}
public boolean isLoading() {
return isLoading;
}
public void setLoading(boolean loading) {
isLoading = loading;
}
public boolean isDowned() {
return isDowned;
}
public void setDowned(boolean downed) {
isDowned = downed;
}
}