效果图:
功能描述:
主要就是实现右侧的滑动,然后根据首字母来将列表滑动到对应的首字母处。
布局说明:
整个布局为一个ExpandableListView(二级列表),一个RecyclerView,一个由RelativeLayout包裹的TextView。其实如果不做首字母的话没必要用二级列表的,虽然二级列表和ListView用法上没什么区别。
具体思路:
将json解析后,利用TinyPinyin来获得每一项的首字母,然后首字母作为二级列表的Group,对应首字母的地区作为Child,这样子来存放。在加载布局的时候,将二级列表的Group点击事件屏蔽掉,并且默认展开全部子项。右边的滑动条,利用RecyclerView的addOnItemTouchListener来处理。
代码实现:
首先是主布局文件的XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ExpandableListView
android:id="@+id/country"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:childDivider="@android:color/transparent"
android:divider="@android:color/transparent"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_slide"
android:layout_alignParentEnd="true"
android:layout_width="30dp"
android:layout_height="match_parent"
/>
<RelativeLayout
android:id="@+id/rl_center_letter"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/tv_center_letter"
android:layout_centerInParent="true"
android:visibility="gone">
<TextView
android:id="@+id/tv_center_letter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A"
android:textSize="40sp"
android:textColor="#ffffff"
android:layout_centerInParent="true"/>
</RelativeLayout>
</RelativeLayout>
然后是GroupItem的布局XML:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/ll_letter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="10dp"
android:paddingTop="23dp"
android:paddingEnd="10dp"
>
<TextView
android:id="@+id/tv_letter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="A"
android:textSize="14sp" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#000000"
android:layout_marginTop="3dp"/>
</LinearLayout>
</FrameLayout>
其次是ChildItem的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingTop="13dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_country_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="中国"
android:textSize="16sp"
android:textColor="#000000"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:background="@drawable/tv_country_code"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:layout_gravity="center">
<TextView
android:id="@+id/tv_country_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="666"
android:textSize="10sp"
/>
</FrameLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.7dp"
android:background="#dedede"
android:layout_marginTop="13dp"/>
</LinearLayout>
最后的是滑动条的Item布局XML,这个就比较简单了:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="3dp"
android:paddingEnd="3dp"
android:orientation="vertical"
>
<TextView
android:id="@+id/tv_letter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#000000"
android:text="@string/slide_icon_0"
android:layout_gravity="center"
android:gravity="center"
/>
</LinearLayout>
代码实现:
二级列表的Adapter,由于和ListView类似,所以就不贴全部代码了,主要贴一下我觉得容易被绕晕的子View的处理:
/**
* 在这里处理ChildView
* @param parentPosition 该项对应的GroupItem的位置
* @param childPosition 该项的位置
* @param b 是否在展开状态
* @param view ChildView
* @param viewGroup 父容器
* @return
*/
@Override
public View getChildView(int parentPosition, int childPosition, boolean b, View view, ViewGroup viewGroup) {
//和ListView一样的处理方法
ChildViewHolder viewHolder;
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.item_country_child, viewGroup, false);
viewHolder = new ChildViewHolder();
viewHolder.tv_country_name = view.findViewById(R.id.tv_country_name);
viewHolder.tv_country_code = view.findViewById(R.id.tv_country_code);
view.setTag(viewHolder);
} else {
viewHolder = (ChildViewHolder) view.getTag();
}
viewHolder.tv_country_name.setText(childList.get(parentPosition).get(childPosition).getZh());
//这里可能容易被绕晕,由于子集合是其实就像一个二维数组,第一个下标索引对应着父集合,
//第二个下标索引才是对应父集合下的子集合。。emmm,自己体会吧,大概的格式是这样子的:
//[[0,1,2],[3,4,5],[6,7]]
//假如此时的parentPosition = 1,childPosition = 2;那对应的值就是5
viewHolder.tv_country_code.setText(String.valueOf(childList.get(parentPosition).get(childPosition).getCode()));
return view;
}
滑动栏的Adapter,一样,主要贴一下如何让每一个Item的高度均分,做到刚好填充整个父容器:
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
ViewHolder viewHolder = new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_slide, viewGroup, false));
int parentHeight = viewGroup.getMeasuredHeight();//父容器的高度
ViewGroup.LayoutParams layoutParams = viewHolder.tv_letter.getLayoutParams();
layoutParams.height = parentHeight / itemList.length;//子项的高度=父容器高度/子项数量
return viewHolder;
}
解析Json:
//从assets中读取json
try {
//从assets中获得输入流,读取到内存中
InputStream inputStream = getContext().getAssets().open("area_phone_code.json");
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader);
String line = "";
StringBuilder builder = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
bufferedReader.close();
//将读取的字符串转成json数组
JsonArray array = new Gson().fromJson(builder.toString(), JsonArray.class);
Log.d("dong", array.get(0).toString());
//遍历数组,将所有的首字母给拿到
for (int i = 0; i < array.size(); i++) {
Country country = new Gson().fromJson(array.get(i).getAsJsonObject(), Country.class);
String letter = Pinyin.toPinyin(country.getZh().charAt(0)).substring(0, 1);//首字母
letterList.add(letter);
}
//利用set的特性去重
Set<String> set = new LinkedHashSet<>(letterList);
letterList.clear();
letterList.addAll(set);
Collections.sort(letterList);//按照字母排序
// Log.d("dong", "排序后:" + letterList.toString());
//根据首字母的数量,新增集合
for (int i = 0; i < letterList.size(); i++) {
List<Country> list = new ArrayList<>();
countryList.add(list);
}
//将国家按照字母顺序给加入到集合中,到此json就解析完成了
for (int i = 0; i < array.size(); i++) {
Country country = new Gson().fromJson(array.get(i).getAsJsonObject(), Country.class);
String letter = Pinyin.toPinyin(country.getZh().charAt(0)).substring(0, 1);//首字母
//遍历找到对应的开头字母的集合,然后加入进去
for (int j = 0; j < letterList.size(); j++) {
if (letter.equals(letterList.get(j))) {
countryList.get(j).add(country);
}
}
}
json文件中大概长这样子的:
[
{
"code": 86,
"tw": "中國",
"en": "China",
"locale": "CN",
"zh": "中国"
},
{
"code": 244,
"tw": "安哥拉",
"en": "Angola",
"locale": "AO",
"zh": "安哥拉"
},
...
]
侧滑栏的实现:
//拦截触摸事件,两个地方都要写一样的处理,不然会出现点击无法响应的情况
slideView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
//根据触摸的位置,找到对应的子项
View child = recyclerView.findChildViewUnder(event.getX(), event.getY());
if (child != null) {
TextView textView = child.findViewById(R.id.tv_letter);
tv_center_letter.setText(textView.getText());
rl_center_letter.setVisibility(View.VISIBLE);
scrool(textView.getText().toString());//根据字母匹配列表中的位置,并且将其滚动到屏幕范围内
Log.d("dong", "当前触摸:" + textView.getText().toString());
} else {
Log.d("dong", "没找到对应的child");
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
rl_center_letter.setVisibility(View.GONE);
}
return true;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
//根据触摸的位置,找到对应的子项
View child = recyclerView.findChildViewUnder(event.getX(), event.getY());
if (child != null) {
TextView textView = child.findViewById(R.id.tv_letter);
tv_center_letter.setText(textView.getText());
rl_center_letter.setVisibility(View.VISIBLE);
scrool(textView.getText().toString());//根据字母匹配列表中的位置,并且将其滚动到屏幕范围内
Log.d("dong", "当前触摸:" + textView.getText().toString());
} else {
Log.d("dong", "没找到对应的child");
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
rl_center_letter.setVisibility(View.GONE);
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
});
scrool这个方法的具体实现:
/**
* 找到GroupList中和Tag相等的值的位置
* @param tag
*/
private void scrool(String tag){
for (int i = 0; i < letterList.size(); i++){
if (letterList.get(i).equals(tag)){
listView.setSelectedGroup(i);
}
}
}
所有源码已经上传到GayHub了,由于是临时起意写的,所以源码中还有些杂七杂八的练手代码,主要看wechat这个文件下的东西就好了。
结束语:
由于本人技术比较菜,如果有不足的地方希望大家可以提出,帮助我进步!