因为项目中使用到了通过滑动侧边栏上的字母bar来定位列表数据,我在此将业务代码剥离只留下了字母查找的功能,思路基本上是自个儿想的,也要一些是参照网上其他朋友写的,比如固定listview的头部,如果有疑问的朋友欢迎留言,一定及时回复!好,废话不多说,直接上代码和demo程序!!
1、首先,字母侧边栏我是自定义view来实现的,大体就是在屏幕右边画26个字母,然后根据字母的大小来计算他们的显示位置,比如需要居中显示。然后,定义了一个回调接口,是给调用者提供监听,监听我手指滑过字母bar并获得字母。最后,调用者传入他们自己的字母数组即可。
package com.example.querybyletter.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.example.querybyletter.util.DensityUtil;
/**
* Created by wangyangzi on 2016/12/2.
*/
public class LetterBar extends View {
private Paint mPaint;
private int textSize;
private int marginRight;
private int marginBottom;
public LetterBar(Context context) {
this(context,null);
}
public LetterBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public LetterBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//字母大小14sp
textSize = DensityUtil.sp2px(context,12);
//字母右边距4dp
marginRight = DensityUtil.dip2px(context,4);
//字母下边距4dp
marginBottom = DensityUtil.dip2px(context,4);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(textSize);
mPaint.setColor(Color.parseColor("#ff00ff"));
}
//默认是显示24个字母,如果调用者设置了setLetters(char[] letters)的话,会使用传入的字母数组
private String[] letters = new String[]{"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
private int[][] points = new int[letters.length][2];
@Override
protected void onDraw(Canvas canvas) {
//计算字母的x坐标
int textPosX = getWidth() - textSize - marginRight;
//计算letterbar的高度
int letterBarHeight = (textSize + marginBottom) * letters.length;
int textPosY = 0;
for(int i=0;i<letters.length;i++){
if(i == 0){
//使letterbar居中显示
textPosY = (getHeight() - letterBarHeight) / 2;
}else{
//计算每个字母的y坐标
textPosY += textSize + marginBottom;
}
//将字母画到界面中
canvas.drawText(letters[i],textPosX,textPosY,mPaint);
//保存每个字母的坐标位置
points[i][0] = textPosX;
points[i][1] = textPosY;
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_UP:
if(letterBarTouchListener != null){
letterBarTouchListener.offLetterBar();
}
break;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
//基于左上角位置+字体大小+边距去规划字母的可触摸区域
for(int i=0;i<points.length;i++){
int xStart = points[i][0] - marginRight;
int xEnd = points[i][0] + textSize + marginRight;
int yStart = points[i][1] - (marginBottom / 2);
int yEnd = points[i][1] + textSize + (marginBottom / 2);
if(x >= xStart && x<= xEnd && y>= yStart && y<= yEnd){
//获得手指触摸到的字母
if(letterBarTouchListener != null){
letterBarTouchListener.showCurrentTouchLetter(letters[i]);
}
break;
}
}
break;
}
return true;
}
public void setLetters(String[] letters){
if(letters != null && letters.length > 0){
this.letters = letters;
}
}
//定义回调接口
private OnLetterBarTouchListener letterBarTouchListener;
public void setOnLetterBarTouchListener(OnLetterBarTouchListener letterBarTouchListener){
this.letterBarTouchListener = letterBarTouchListener;
}
public interface OnLetterBarTouchListener{
void showCurrentTouchLetter(String letter);
void offLetterBar();
}
}
2、然后我自己写了一个demo数据,这里大家可以把他换成接口中 的数据。
package com.example.querybyletter;
/**
* Created by wangyangzi on 2016/12/4.
*/
public class LetterData {
public static String[] getLetters() {
String[] letters = new String[]{"A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","W","X","Y","Z"};
return letters;
}
public static String[][] getCities() {
String[][] cities = new String[][]{
{"鞍山市","安阳市","安康市","澳门"},{"承德市","成都市","长春市","常州市","潮州市","长沙市","赤峰市","崇左市"},{"大同市","大连市","大庆市","东营市","东莞市"},
{"鄂州市"},{"抚顺市","阜阳市","福州市","抚州市","佛山市"},{"赣州市","广州市","桂林市","广元市","贵阳市"},
{"衡水市","合肥市"},{"晋江市","九江市","吉安市","济宁市","荆州市"},{"开封市"},
{"乐山市","拉萨市"},{"牡丹江市","马鞍山市","绵阳市"},{"南京市","南通市","宁波市","南昌市","南宁市","怒江市"},
{"萍乡市"},{"秦皇岛市","泉州市","青岛市","清远市","曲靖市","庆阳市"},{"日照市"},
{"石家庄市","四平市","松原市","上海市","绍兴市","三明市","深圳市","沈阳市","上饶市","三亚市"},{"天津市","唐山市","太原市","通化市","台州市","天水市",},{"温州市","威海市","武汉市"},
{"徐州市","信阳市"},{"阳泉市","运城市","营口市","扬州市","宜春市","雅安市","银川市"},{"漳州市","株洲市"}
};
return cities;
}
}
3、letteradapter是通过继承BaseExpandableListAdapter来实现的,里面重点是方法getPositionForSection(int sectionIndex)、getSectionForPosition(int position),这两方法作用是给listview的每一项指定一个位置以及这个位置对应的字母分组。在监听listview的滚动时,需要通过首显项来确定下一个字母的位置,确定他还距离顶部有多少距离,会使用到这两个方法。
package com.example.querybyletter.adapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;
import com.example.querybyletter.R;
/**
* Created by wangyangzi on 2016/12/4.
*/
public class LetterAdapter extends BaseExpandableListAdapter {
private Context context;
private String[] letters;
private String[][] cities;
public LetterAdapter(Context context,String[] letters,String[][] cities){
this.context = context;
this.letters = letters;
this.cities = cities;
initGroupAndItemPosition();
}
//给每个字母及对应的城市指定一个position(group position- item position)
@SuppressLint("UseSparseArrays")
private static HashMap<Integer,List<Integer>> groupMap = new HashMap<Integer,List<Integer>>();
private void initGroupAndItemPosition(){
int itemPosition = 0;
for(int i=0;i<cities.length;i++){
List<Integer> itemPos = new ArrayList<Integer>();
//字母
groupMap.put(i,itemPos);
for(int j=0;j<cities[i].length;j++){
//城市(组名也算一个位置)
if(j == 0){
itemPos.add(itemPosition++);
}
itemPos.add(itemPosition++);
}
}
}
@Override
public int getGroupCount() {
return letters.length;
}
@Override
public int getChildrenCount(int groupPosition) {
return cities[groupPosition].length;
}
@Override
public Object getGroup(int groupPosition) {
return letters[groupPosition];
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return cities[groupPosition][childPosition];
}
@Override
public long getGroupId(int groupPosition) {
return 0;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public void onGroupExpanded(int groupPosition) {
}
@SuppressLint("InflateParams")
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
String letter = (String) getGroup(groupPosition);
ViewHolder viewHolder = null;
if(convertView == null){
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.area_letter_item,null);
viewHolder.letter = (TextView) convertView.findViewById(R.id.letter_tv);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.letter.setText(letter);
return convertView;
}
@SuppressLint("InflateParams")
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
String name = (String) getChild(groupPosition,childPosition);
ViewHolder viewHolder = null;
if(convertView == null){
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.area_name_item,null);
viewHolder.name = (TextView) convertView.findViewById(R.id.city_tv);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.name.setText(name);
return convertView;
}
//通过group的位置获得第一个子item的位置
public int getPositionForSection(int sectionIndex) {
List<Integer> itemList = groupMap.get(sectionIndex);
if(itemList != null && itemList.size() > 0){
return itemList.get(0);
}
return -1;
}
//通过item的位置获得该对应的group的位置
public int getSectionForPosition(int position) {
for(Map.Entry<Integer,List<Integer>> entry : groupMap.entrySet()){
List<Integer> itemList = entry.getValue();
//取出第一项和最后一项,如果在之间则属于这个分组
if(itemList.size() > 0){
int firstPos = itemList.get(0);
int endPos = itemList.get(itemList.size() - 1);
if(position >= firstPos && position <= endPos){
return entry.getKey();
}
}
}
return -1;
}
public static class ViewHolder{
public TextView letter;
public TextView name;
}
}
4、最后是MainActivity。主要实现了将手指触摸到的字母并把他显示出来和listview滚动时头部固定及字母查找。
package com.example.querybyletter;
import java.util.Arrays;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ExpandableListView;
import android.widget.TextView;
import com.example.querybyletter.adapter.LetterAdapter;
import com.example.querybyletter.widget.LetterBar;
public class MainActivity extends ActionBarActivity {
private static final String TAG = "MainActivity";
private TextView letterTv;
private String currentLetter;
private LetterBar letterBar;
private ExpandableListView expandableListView;
private LetterAdapter letterAdapter;
private TextView letterHeaderTv;
private String[] letters;
private String[][] cities;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获得列表显示数据
letters = LetterData.getLetters();
cities = LetterData.getCities();
//控件初始化
letterTv = (TextView) this.findViewById(R.id.current_letter);
letterHeaderTv = (TextView) this.findViewById(R.id.letter_header);
expandableListView = (ExpandableListView) this.findViewById(R.id.area_elv);
letterBar = (LetterBar) this.findViewById(R.id.letterBar);
letterBar.setLetters(letters);
//监听手指触摸latterbar事件
letterBar.setOnLetterBarTouchListener(new LetterBar.OnLetterBarTouchListener() {
@Override
public void showCurrentTouchLetter(String letter) {
if(!letter.equals(currentLetter)){
currentLetter = letter;
showLetterDialog(currentLetter);
//根据选中字母进行定位
locationForLetterbar(currentLetter);
}
}
@Override
public void offLetterBar() {
letterTv.setVisibility(View.GONE);
}
});
//绑定数据
letterAdapter = new LetterAdapter(this,letters,cities);
expandableListView.setAdapter(letterAdapter);
//去小箭头
expandableListView.setGroupIndicator(null);
//去分隔线
expandableListView.setDivider(null);
//默认全部展开
for (int i = 0; i < letters.length; i++) {
expandableListView.expandGroup(i);
}
//不允许收缩
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
return true;
}
});
//滚动时始终保持letterbar在页面顶部
expandableListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//获取第一个可见项的下一项的position
int section = letterAdapter.getSectionForPosition(firstVisibleItem);
int nextSectionPosition = letterAdapter.getPositionForSection(section+1);
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) letterHeaderTv.getLayoutParams();
//如果当前第一个可见项的下一项是nextSectionPosition,表示快滚动到了下一个字母位置,这个时候就要开始考虑移动了
if(nextSectionPosition == firstVisibleItem + 1){
View childView = view.getChildAt(0);
if(childView != null){
int distance = childView.getBottom() - letterHeaderTv.getHeight();
if(distance <= 0){//仅当childView的bottom距离小于或等于letterHeaderTv的高度时才开始移动
params.topMargin = distance;
letterHeaderTv.setLayoutParams(params);
letterHeaderTv.setText(letters[section]);
}
}
}
//其他情况保持letterbar在顶部不动
else{
params.topMargin = 0;
letterHeaderTv.setLayoutParams(params);
letterHeaderTv.setText(letters[section]);
}
}
});
}
//用侧边栏选中的字母去定位列表数据
private void locationForLetterbar(String letter){
//获得该字母对应的section
int section = Arrays.binarySearch(letters,letter);
//获得group的第一个子项,既group本身的position
int letterPos = letterAdapter.getPositionForSection(section);
//定位到该item
expandableListView.setSelection(letterPos);
//设置顶部letterbar的位置和显示字母
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) letterHeaderTv.getLayoutParams();
letterHeaderTv.setText(letter);
params.topMargin = 0;
letterHeaderTv.setLayoutParams(params);
}
//显示当前选中的字母
private void showLetterDialog(String letter){
letterTv.setText(letter);
letterTv.setVisibility(View.VISIBLE);
}
}
5、界面布局我就不啰嗦了,具体的大家可以去下载源码试着运行一遍,然后有疑问随时提,刚学会写博客回馈社会,写的不好的大家勿喷!谢谢啦!
demo地址:http://download.youkuaiyun.com/detail/fuyang7412/9702588