标签:
我们的手机通讯录一般都有这样的效果,如下图:
OK,这种效果大家都见得多了,基本上所有的android手机通讯录都有这样的效果。那我们今天就来看看这个效果该怎么实现。
整体上来说,左边是一个ListView,右边是一个自定义View,但是左边的ListView和我们平常使用的ListView还有一点点不同,就是在ListView中我对所有的联系人进行了分组,那么这种效果的实现最常见的就是两种思路:
1.使用ExpandableListView来实现这种分组效果
2.使用普通ListView,在构造Adapter时实现SectionIndexer接口,然后在Adapter中做相应的处理
这两种方式都不难,都属于普通控件的使用,那么这里我们使用第二种方式来实现,第一种方式的实现方法大家可以自行研究,如果你还不熟悉ExpandableListView的使用,可以参考我的另外两篇博客:
2.android开发之ExpandableListView的使用,实现类似QQ好友列表
OK,这是我们左边ListView的实现思路,右边这个东东就是我们今天的主角,这里我通过自定义一个View来实现,View中的A、B......#这些字符我都通过canvas的drawText方法绘制上去。然后重写onTouchEvent方法来实现事件监听。
要实现的效果如上图所示,但是大家看图片有些地方可能还不太清楚,所以这里我再强调一下:
1.左边的ListView对数据进行分组显示
2.当左边ListView滑动的时候,右边滑动控件中的文字颜色能够跟随左边ListView的滑动自动变化
3.当手指在右边的滑动控件上滑动时,手指滑动到的地方的文字颜色应当发生变化,同时在整个页面的正中央有一个TextView显示手指目前按下的文字
4.当手指按下右边的滑动控件时,右边的滑动控件背景变为灰色,手指松开后,右边的滑动控件又变为透明色
无论多大的工程,我们都要将之分解为一个个细小的功能块分步来实现,那么这里我们就先来看看左边的ListView的分组的实现,这个效果实现之后,我们再来看看右边的滑动控件该怎么实现。
首先我需要在布局文件中添加一个ListView,这个很简单,和普通的ListView一模一样,我就不贴代码了,另外,针对ListView中的数据集,我需要自建一个实体类,该实体类如下:
/** * Created by wangsong on 2016/4/24. */ public class User { private int img; private String username; private String pinyin; private String firstLetter; public User() { } public String getFirstLetter() { return firstLetter; } public void setFirstLetter(String firstLetter) { this.firstLetter = firstLetter; } public int getImg() { return img; } public void setImg(int img) { this.img = img; } public String getPinyin() { return pinyin; } public void setPinyin(String pinyin) { this.pinyin = pinyin; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public User(String firstLetter, int img, String pinyin, String username) { this.firstLetter = firstLetter; this.img = img; this.pinyin = pinyin; this.username = username; } }
private void initData() { list = new ArrayList<>(); String[] allUserNames = getResources().getStringArray(R.array.arrUsernames); for (String allUserName : allUserNames) { User user = new User(); user.setUsername(allUserName); String convert = ChineseToPinyinHelper.getInstance().getPinyin(allUserName).toUpperCase(); user.setPinyin(convert); String substring = convert.substring(0, 1); if (substring.matches("[A-Z]")) { user.setFirstLetter(substring); }else{ user.setFirstLetter("#"); } list.add(user); } Collections.sort(list, new Comparator<User>() { @Override public int compare(User lhs, User rhs) { if (lhs.getFirstLetter().contains("#")) { return 1; } else if (rhs.getFirstLetter().contains("#")) { return -1; }else{ return lhs.getFirstLetter().compareTo(rhs.getFirstLetter()); } } }); }
OK,数据源构造好之后,我还需要对List集合进行一个简单的排序,那么这个排序是Java中的操作,我这里就不再赘述。
构造完数据源之后,接着就该是构造ListView的Adapter了,我们来看看这个怎么做,先来看看源码:
/** * Created by wangsong on 2016/4/24. */ public class MyAdapter extends BaseAdapter implements SectionIndexer { private List<User> list; private Context context; private LayoutInflater inflater; public MyAdapter(Context context, List<User> list) { this.context = context; this.list = list; inflater = LayoutInflater.from(context); } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.listview_item, null); holder = new ViewHolder(); holder.showLetter = (TextView) convertView.findViewById(R.id.show_letter); holder.username = (TextView) convertView.findViewById(R.id.username); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } User user = list.get(position); holder.username.setText(user.getUsername()); //获得当前position是属于哪个分组 int sectionForPosition = getSectionForPosition(position); //获得该分组第一项的position int positionForSection = getPositionForSection(sectionForPosition); //查看当前position是不是当前item所在分组的第一个item //如果是,则显示showLetter,否则隐藏 if (position == positionForSection) { holder.showLetter.setVisibility(View.VISIBLE); holder.showLetter.setText(user.getFirstLetter()); } else { holder.showLetter.setVisibility(View.GONE); } return convertView; } @Override public Object[] getSections() { return new Object[0]; } //传入一个分组值[A....Z],获得该分组的第一项的position @Override public int getPositionForSection(int sectionIndex) { for (int i = 0; i < list.size(); i++) { if (list.get(i).getFirstLetter().charAt(0) == sectionIndex) { return i; } } return -1; } //传入一个position,获得该position所在的分组 @Override public int getSectionForPosition(int position) { return list.get(position).getFirstLetter().charAt(0); } class ViewHolder { TextView username, showLetter; } }
1.getPositionForSection(int sectionIndex)
这个方法接收一个int类型的参数,该参数实际上就是指我们的分组,我们在这里传入分组的值【A.....Z】,然后我们在方法中通过自己的计算,返回该分组中第一个item的position。
2.getSectionForPosition(int position)
这个方法接收一个int类型的参数,该参数实际上就是我们的ListView即将要显示的item的position,我们通过传入这个position,可以获得该position的item所属的分组,然后再将这个分组的值返回。
说了这么多,大家可能有疑问了,我为什么要实现这个接口呢?大家来看看我的item的布局文件:
<?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="match_parent" android:orientation="vertical"> <TextView android:id="@+id/show_letter" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="3dp"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/userface" android:layout_width="72dp" android:layout_height="72dp" android:padding="12dp" android:src="@mipmap/ic_launcher"/> <TextView android:id="@+id/username" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerVertical="true" android:layout_marginLeft="36dp" android:layout_toRightOf="@id/userface" android:gravity="center" android:text="username"/> </RelativeLayout> </LinearLayout>
//获得当前position是属于哪个分组 int sectionForPosition = getSectionForPosition(position); //获得该分组第一项的position int positionForSection = getPositionForSection(sectionForPosition); //查看当前position是不是当前item所在分组的第一个item //如果是,则显示showLetter,否则隐藏 if (position == positionForSection) { holder.showLetter.setVisibility(View.VISIBLE); holder.showLetter.setText(user.getFirstLetter()); } else { holder.showLetter.setVisibility(View.GONE); }
ListView listView = (ListView) findViewById(R.id.lv); MyAdapter adapter = new MyAdapter(this, list); listView.setAdapter(adapter);
右边这个东东很明显是一个自定义View,那我们就一起来看看这个自定义View吧。
首先这个自定义控件继承自View,继承自View,需要实现它里边的构造方法,关于这三个构造方法的解释大家可以查看我的另一篇博客android自定义View之钟表诞生记,这里对于构造方法我不再赘述。在这个自定义View中,我需要首先声明5个变量,如下:
//当前手指滑动到的位置 private int choosedPosition = -1; //画文字的画笔 private Paint paint; //右边的所有文字 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", "#"}; //页面正中央的TextView,用来显示手指当前滑动到的位置的文本 private TextView textViewDialog; //接口变量,该接口主要用来实现当手指在右边的滑动控件上滑动时ListView能够跟着滚动 private UpdateListView updateListView;
public LetterIndexView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); paint = new Paint(); paint.setAntiAlias(true); paint.setTextSize(24); }
准备工作做完之后,接下来就是onDraw了,代码如下:
@Override protected void onDraw(Canvas canvas) { int perTextHeight = getHeight() / letters.length; for (int i = 0; i < letters.length; i++) { if (i == choosedPosition) { paint.setColor(Color.RED); } else { paint.setColor(Color.BLACK); } canvas.drawText(letters[i], (getWidth() - paint.measureText(letters[i])) / 2, (i + 1) * perTextHeight, paint); } }
绘制完成之后,就是重写onTouchEvent了,如下:
@Override public boolean onTouchEvent(MotionEvent event) { int perTextHeight = getHeight() / letters.length; float y = event.getY(); int currentPosition = (int) (y / perTextHeight); String letter = letters[currentPosition]; switch (event.getAction()) { case MotionEvent.ACTION_UP: setBackgroundColor(Color.TRANSPARENT); if (textViewDialog != null) { textViewDialog.setVisibility(View.GONE); } break; default: setBackgroundColor(Color.parseColor("#cccccc")); if (currentPosition > -1 && currentPosition < letters.length) { if (textViewDialog != null) { textViewDialog.setVisibility(View.VISIBLE); textViewDialog.setText(letter); } if (updateListView != null) { updateListView.updateListView(letter); } choosedPosition = currentPosition; } break; } invalidate(); return true; }
public void updateLetterIndexView(int currentChar) { for (int i = 0; i < letters.length; i++) { if (currentChar == letters[i].charAt(0)) { choosedPosition = i; invalidate(); break; } } }
TextView textView = (TextView) findViewById(R.id.show_letter_in_center); final LetterIndexView letterIndexView = (LetterIndexView) findViewById(R.id.letter_index_view); letterIndexView.setTextViewDialog(textView); letterIndexView.setUpdateListView(new LetterIndexView.UpdateListView() { @Override public void updateListView(String currentChar) { int positionForSection = adapter.getPositionForSection(currentChar.charAt(0)); listView.setSelection(positionForSection); } }); listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int sectionForPosition = adapter.getSectionForPosition(firstVisibleItem); letterIndexView.updateLetterIndexView(sectionForPosition); } });
源码下载http://download.csdn.net/detail/u012702547/9501208
以上。
android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
标签:
原文地址:http://blog.csdn.net/u012702547/article/details/51234227