最近一直在研究高德地图提供的电子眼、导航、定位相关,利用周末时间写个博客梳理一下这阶段对于高德地图的使用,主要参考高德api和参考手册。

在高德导航页面中,有一个路径选项卡,需要切换不同的路径方案,这个功能简单的看来就是一个 RadioGroup 包含一些 RadioButton 实现单选框效果。如下图:

amap

而 RadioButton 并不能实现所需要的样式,每个子 Item 中包含该 3 个 TextView ,获得数据然后赋到控件上。

实现方法

  • 写一个子 item 布局,然后在代码中根据规划的路径数动态添加,并实现单选的逻辑。这种自己写单选的逻辑代码量相对较多,也不变日后需求变化的维护。
  • 利用 GridView ,然后根据路径数实现添加,数据为空不显示,实现起来相对简单,日后修改布局也易修改。
  • 参考 RadioGroup 和 RadioButton 重写一个子 item 布局,用于数据展示,一个 Group 实现单选功能。

采用第三种方法来实现,相当于利用两个控件就能实现,在子 item 不多的情况下,既可以在 xml 布局直接添加,又可以在代码中动态添加。

首先写一个子 item 的xml 布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="100dp"
android:layout_height="64dp"
android:orientation="vertical"
android:id="@+id/ll_route_info_frame"
android:background="@drawable/bg_gray_frame">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/ll_route_info_title"
android:background="@drawable/bg_gray_title">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:maxEms="7"
android:id="@+id/tv_route_info_title"
android:textColor="@color/gray"
android:gravity="center"
android:textSize="12sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/ll_route_info_content"
android:background="@drawable/bg_gray_content"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_route_info_time"
android:textColor="@color/black"
android:textStyle="bold"
android:gravity="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_route_info_distance"
android:textColor="@color/gray"
android:textSize="10sp"
android:gravity="center"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

之后将其整体封装为一个组合控件,并添加展示数据的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class RouteInfoLayout extends FrameLayout {
private View view;
private TextView tvTitle;
private TextView tvTime;
private TextView tvDistance;
private LinearLayout llFrame;
private LinearLayout llTitle;
private LinearLayout llContent;
private Boolean isChecked;
public RouteInfoLayout(@NonNull Context context) {
super(context);
}
public RouteInfoLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
view = View.inflate(context, R.layout.view_route_info_item, this);
tvTitle = (TextView) view.findViewById(R.id.tv_route_info_title);
tvDistance = (TextView) view.findViewById(R.id.tv_route_info_distance);
tvTime = (TextView) view.findViewById(R.id.tv_route_info_time);
llFrame = (LinearLayout) view.findViewById(R.id.ll_route_info_frame);
llContent = (LinearLayout) view.findViewById(R.id.ll_route_info_content);
llTitle = (LinearLayout) view.findViewById(R.id.ll_route_info_title);
}
public RouteInfoLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(Boolean isChecked) {
if (isChecked == this.isChecked) {
return;
}
if (isChecked) {
llTitle.setBackgroundResource(R.drawable.bg_blue_title);
llFrame.setBackgroundResource(R.drawable.bg_blue_frame);
llContent.setBackgroundResource(R.drawable.bg_blue_content);
tvTitle.setTextColor(getContext().getResources().getColor(R.color.white));
tvTime.setTextColor(getContext().getResources().getColor(R.color.blue_light));
tvDistance.setTextColor(getContext().getResources().getColor(R.color.blue_light));
} else {
llContent.setBackgroundResource(R.drawable.bg_gray_content);
llFrame.setBackgroundResource(R.drawable.bg_gray_frame);
llTitle.setBackgroundResource(R.drawable.bg_gray_title);
tvTitle.setTextColor(getContext().getResources().getColor(R.color.gray));
tvTime.setTextColor(getContext().getResources().getColor(R.color.black));
tvDistance.setTextColor(getContext().getResources().getColor(R.color.gray));
}
this.isChecked = isChecked;
}
public boolean isChecked() {
return this.isChecked;
}
public void setRouteInfoTitle(String s) {
if (!StringUtils.isNullOrEmpty(s)) {
tvTitle.setText(s);
}
}
public void setRouteInfoTime(int value) {
int hour;
int minute;
minute = value / 60;
hour = minute / 60;
if (hour == 0 && minute != 0) {
tvTime.setText(minute + "分钟");
} else if (hour != 0 && minute == 0) {
tvTime.setText(hour + "小时");
} else if (hour != 0 && minute != 0) {
tvTime.setText(hour + "小时" + (minute - 60 * hour) + "分钟");
}
}
public void setRouteInfoDistance(int value) {
if (value>1000) {
float kilometre;
String result;
kilometre = (float) value / 1000;
DecimalFormat df = new DecimalFormat("0.0");
result = df.format(kilometre);
tvDistance.setText(result + "公里");
} else {
tvDistance.setText(value + "米");
}
}
}

这样就可以在xml中使用子 item 控件,相当于 RadioButton,这是还需要一个如 RadioGroup 的控件。参考 RadioGroup 控件的源码,找到这一个监听:

1
2
mPassThroughListener = new PassThroughHierarchyChangeListener();
super.setOnHierarchyChangeListener(mPassThroughListener);

其对应的监听内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private class PassThroughHierarchyChangeListener implements
ViewGroup.OnHierarchyChangeListener {
private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
/**
* {@inheritDoc}
*/
public void onChildViewAdded(View parent, View child) {
if (parent == RadioGroup.this && child instanceof RadioButton) {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
id = View.generateViewId();
child.setId(id);
}
((RadioButton) child).setOnCheckedChangeWidgetListener(
mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
/**
* {@inheritDoc}
*/
public void onChildViewRemoved(View parent, View child) {
if (parent == RadioGroup.this && child instanceof RadioButton) {
((RadioButton) child).setOnCheckedChangeWidgetListener(null);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}

这个监听接口是对其 层次结构的监听,不像onMeasure或onLayout,他只有在层次结构发生变化时,也就是说在添加view和移除view的时候才会调用,所以他有两个回调方法。onChildViewAdded和onChildViewRemoved。在初始化的时候,子布局是一个一个的被添加到RadioGroup,所以调用onChildViewAdded,可以得带其对应的孩子,就可以对其设置监听了。

接着改变子控件的状态,RouteInfoGroup 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
public class RouteInfoGroup extends LinearLayout {
private ArrayList<Integer> tabChildsId = new ArrayList<Integer>();
/**
* 记录当前id,默认id为-1
*/
private int mCheckedId = -1;
private int defaultid;
private CheckedStateTracker mChildOnCheckedChangeListener;
private PassThroughHierarchyChangeListener mPassThroughListener;
private OnItemTabClickListener listener;
public RouteInfoGroup(Context context) {
super(context);
}
public RouteInfoGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public RouteInfoGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
// radiobutton的监听
mChildOnCheckedChangeListener = new CheckedStateTracker();
// 层次结构的监听
mPassThroughListener = new PassThroughHierarchyChangeListener();
// 设置监听
super.setOnHierarchyChangeListener(mPassThroughListener);
}
/**
* 设置tab数据
*
* @param datas
*/
public void setDataOfTabs(List<AMapNaviPath> datas) {
L.i("TAG2","getChildCount()===>>>"+getChildCount());
L.i("TAG2","datas()===>>>"+datas.size());
int count = 0;
if (datas.size() >= getChildCount()) {
count = getChildCount();
// return;
}else{
count = datas.size();
}
for (int i = 0; i < count; i++) {
L.i("TAG2","data=1111==>>>");
RouteInfoLayout view = (RouteInfoLayout) findViewById(tabChildsId.get(i));
AMapNaviPath data = datas.get(i);
L.i("TAG2","data===>>>"+data);
if (i == 0) {
check(view.getId());
defaultid = view.getId();
}
view.setRouteInfoDistance(data.getAllLength());
view.setRouteInfoTime(data.getAllTime());
view.setRouteInfoTitle(data.getLabels());
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
L.i("TAG2", "onFinishInflate=111==>>>>");
check(mCheckedId);
}
/**
* 改变item的选择状态
*
* @param viewId
* @param checked
* true 为选择,false 为不选择
*/
private void setCheckedStateForView(int viewId, boolean checked) {
L.i("TAG2", "setCheckedStateForView====>>>");
View checkedView = findViewById(viewId);
L.i("TAG2", "setCheckedStateForView===checkedView=>>>" + checkedView);
if (checkedView != null && checkedView instanceof RouteInfoLayout) {
((RouteInfoLayout) checkedView).setChecked(checked);
L.i("TAG2", "setCheckedStateForView===checkedView=111>>>"
+ checkedView);
}
}
public void check(int id) {
// don't even bother
L.i("TAG2", "check=000======>>>" + id);
L.i("TAG2", "check=000=11=====>>>" + mCheckedId);
if (id != -1 && (id == mCheckedId)) {
L.i("TAG2", "check=111======>>>" + id);
return;
}
if (mCheckedId != -1) {
L.i("TAG2", "check=222======>>>" + id);
setCheckedStateForView(mCheckedId, false);
}
if (id != -1) {
L.i("TAG2", "check=333======>>>" + id);
setCheckedStateForView(id, true);
}
setCheckedId(id);
}
/**
* 选择Item后要执行的操作
*
* @param id
*/
private void setCheckedId(int id) {
// 更新当前选择的Item的id
mCheckedId = id;
if (listener != null) {
RouteInfoLayout view = (RouteInfoLayout) findViewById(id);
listener.onItemClick(view, defaultid);
}
}
/**
* 设置点击item的监听
*
* @param l
*/
public void setOnItemTabClickListener(OnItemTabClickListener l) {
this.listener = l;
}
/**
* item选择监听 Title: RechargeValueGroup.java Description:
*
* @author Liusong
* @date 2015-6-26
* @version V1.0
*/
public interface OnItemTabClickListener {
public void onItemClick(RouteInfoLayout view, int defaultid);
}
/**
* 图层变换监听 Title: RechargeValueGroup.java Description:
*
* @author Liusong
* @date 2015-6-26
* @version V1.0
*/
private class PassThroughHierarchyChangeListener implements
ViewGroup.OnHierarchyChangeListener {
private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
/**
* {@inheritDoc}
*/
public void onChildViewAdded(View parent, View child) {
if (parent == RouteInfoGroup.this
&& child instanceof RouteInfoLayout) {
int id = child.getId();
// Log.i("TAG2", "onChildViewAdded=111==>>>>" + id);
// generates an id if it's missing
if (id == View.NO_ID) {
id = View.generateViewId();
child.setId(id);
}
tabChildsId.add(id);
((RouteInfoLayout) child)
.setOnClickListener(mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
/**
* {@inheritDoc}
*/
public void onChildViewRemoved(View parent, View child) {
if (parent == RouteInfoGroup.this
&& child instanceof RouteInfoLayout) {
((RouteInfoLayout) child).setOnClickListener(null);
tabChildsId.remove(0);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
/**
* 每一项Item的点击事件 Title: RechargeValueGroup.java Description:
*
* @author Liusong
* @date 2015-6-26
* @version V1.0
*/
class CheckedStateTracker implements OnClickListener {
@Override
public void onClick(View v) {
check(v.getId());
}
};
}

自此两个控件写好 RouteInfoGroup 与 RouteInfoLayout,现在只需要在 xml 或在代码中 addView 既可。由于路径数并不是固定的,将采用在xml 中布局 RouteInfoGroup,然后代码中动态 addView RouteInfoLayout。(使用方法如 RadioGroup 和 RadioButton)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mRouteInfoGroup = (RouteInfoGroup) findViewById(R.id.routeInfoGroup);
if (mRouteInfoGroup.getChildCount() != 0) {
mRouteInfoGroup.removeAllViews();//清空上次子 item
}
....
RouteInfoLayout routeInfoLayout = new RouteInfoLayout(this,null);
routeInfoLayout.setId(id+i);
if (i == 0 ) {
routeInfoLayout.setChecked(true);
}
LinearLayout.LayoutParams llparams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
llparams.weight = 1.0f;//权重
if (ints.length == 3 && i == 1) {
llparams.leftMargin = 32;//边距
llparams.rightMargin = 32;
}else if (ints.length == 2) {
if (i == 0) {
llparams.rightMargin = 100;//边距
}else {
llparams.leftMargin = 100;
}
}
mRouteInfoGroup.addView(routeInfoLayout,llparams);

效果图如下:

参考:http://blog.csdn.net/ls703/article/details/46694967