侧边索引的实现思路:
侧拉索引:音乐APP,即时通讯,电商选择城市,短信验证选择城市都有这个类型自定义控件 实现步骤: 1.绘制A-Z的字母列表(自绘式自定义控件) 2.响应触摸事件 3.提供监听回调 4.获取汉字的拼音,首字母(pinyin4J通过汉字得到她的拼音,只能一个字符一个字符去转换成拼音) 5.根据拼音排序 6.根据首字母分组 7.把监听回调和ListView结合起来 掌握解决问题的思路:把复杂的东西简单化,把复杂的东西分成尽可能小的模块把握住模块的关键点,一步一个脚印的去做,最终就可以实现复杂的效果
首先创建XML布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/mylist"></ListView> <com.example.customview.ui.QuickIndexBar android:id="@+id/action_bar" android:layout_width="30dp" android:layout_height="match_parent" android:layout_alignParentEnd="true" android:background="#ff0000" /> </RelativeLayout>新建一个item_person.xml
<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/tv_index" android:layout_width="match_parent" android:layout_height="40dp" android:background="#666666" android:gravity="center_vertical" android:paddingLeft="15dp" android:text="A" android:textColor="#FFFFFF" android:textSize="18sp" /> <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center_vertical" android:paddingLeft="15dp" android:text="宋江" android:textSize="22sp" /> </LinearLayout>创建工具类,根据汉字拿到拼音
public class HaoHan implements Comparable<HaoHan>{ private String name; private String pinyin; public HaoHan(String name) { this.name = name; //使用工具类,根据汉字拿到拼音 this.pinyin= PinyinUtil.getPingyin(name); } public String getName() { return name; } public String getPinyin() { return pinyin; } @Override public int compareTo(HaoHan haoHan) { return this.pinyin.compareTo(haoHan.pinyin); } }
快速索引栏实现思路 * 1.继承VIew,复写构造方法,初始化画笔 * 2.在onDrawer方法里绘制字符 * 3.在onMeasure方法里测量高度 * 4.在onTouchEvent事件知道用户具体按住了那个字母 * 5.定义抽象方法,实现监听回调新建一个自定义类,继承VIew,实现快速索引栏
public class QuickIndexBar extends View { private Paint paint; //A.要绘制的内容 private static final 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 cellWidth; private float cellHeight; private float y; private int currentIndex; public QuickIndexBar(Context context) { this(context, null); } public QuickIndexBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public QuickIndexBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //初始化画笔 initPaint(); } private void initPaint() { //创建一个抗锯齿的画笔 paint = new Paint(Paint.ANTI_ALIAS_FLAG); //画笔文本加粗 paint.setTypeface(Typeface.DEFAULT_BOLD); //颜色 paint.setColor(Color.WHITE); } //完成侧拉索引 @Override protected void onDraw(Canvas canvas) { //遍历了26个字母,进行坐标计算,进行绘制 for (int i = 0; i < LETTERS.length; i++) { //从数组,根据i取出字母 String letter = LETTERS[i]; //计算x坐标 float x = cellWidth * 0.5f - paint.measureText(letter) * 0.5f; //计算y坐标 float y = cellHeight * 0.5f + paint.measureText(letter) * 0.5f + i * cellHeight; canvas.drawText(letter, x, y, paint); } } //完成侧拉索引的测量,得到单元格的宽高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取控件的宽高 int mHeight = getMeasuredHeight(); cellWidth = getMeasuredWidth(); //获取单元格的高度,由自定义控件总高度,除以所有字母所占用的高度 cellHeight = mHeight * 1.0f / LETTERS.length;//为了精确,避免四舍五入,我们把数转换为小数 } //重写触摸事件,返回值为True,方起效果 //记录用户上一次按下的位置,以便进行判断这一次所按住的位置是否还是上一次的位置,如果是的话,不做任何处理 private int lastIndex=-1; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { //计算用户按到哪个字母的范围,主要是Y轴 case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: //获取被点击到的字母索引 y = event.getY(); currentIndex = (int) (y / cellHeight); //为了防止一个字母按下,不停地重复调用,将进行判断,判断是否还是按着上一个字母,是的话就不做任何处理提供程序的性能 if (currentIndex!=lastIndex){ //为了防止角标越界,我们只在用户按住的Y轴值大于0,小于数组长度方执行 if (currentIndex>=0&¤tIndex<LETTERS.length){ String letter = LETTERS[currentIndex]; //设置回调的监听 if (mOnLetterUpdateListener!=null){ mOnLetterUpdateListener.onLetterUpdate(letter); } lastIndex=currentIndex; } } break; case MotionEvent.ACTION_UP: break; } return true; } //c.定义接口 public interface OnLetterUpdateListener { void onLetterUpdate(String string); } //定义接口对象 private OnLetterUpdateListener mOnLetterUpdateListener; //暴露方法,让外界传过来一个实现接口的类对象 public void setmOnLetterUpdateListener(OnLetterUpdateListener onLetterUpdateListener) { mOnLetterUpdateListener = onLetterUpdateListener; } }工具类实现拼音
public class PinyinUtil { public static String getPingyin(String string){ HanyuPinyinOutputFormat format=new HanyuPinyinOutputFormat(); //不要音标 format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); //设置转换出大写字母 format.setCaseType(HanyuPinyinCaseType.UPPERCASE); //我把得到的字符串,改为了字符数组,pinying4j只能一个字符一个字符去传唤拼音 char[] chars = string.toCharArray(); //创建了一个装字符的容器,stringBuilder() StringBuffer sb = new StringBuffer(); for (int x=0;x<chars.length;x++){ char c = chars[x]; //如果时空格,跳过当前循环 if (Character.isWhitespace(c)){ continue; } //是不是汉字,如果不是汉字,直接拼写 if (c>-128&&c<127){ sb.append(c); }//是汉字,那么我们就获取拼音 else{ try { //获取某个字符对应的拼音,可以获取到多音字, String s = PinyinHelper.toHanyuPinyinStringArray(c, format)[0]; sb.append(s); } catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) { badHanyuPinyinOutputFormatCombination.printStackTrace(); } } } return sb.toString(); } }创建吐司的工具类,防止好久没有更新文字的问题
public class toastUtil { private static Toast toast; public static void showToast(Context context,String msg){ if (toast==null){ toast=Toast.makeText(context,"",Toast.LENGTH_SHORT); } toast.setText(msg); toast.show(); } }创建文本类
public class cheeses { public static final String[] NAMES = new String[]{"宋江", "卢俊义", "吴用", "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深", "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘", "雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍", " 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪", "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方", "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充", "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿", "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩", "周通", "李忠", "杜兴", "汤隆", "邹渊", "邹润", "朱富", "朱贵", "蔡福", "蔡庆", "李立", "李云", "焦挺", "石勇", "孙新", "顾大嫂", "张青", "孙二娘", " 王定六", "郁保四", "白胜", "时迁", "段景柱", "易宸锋"}; }判断当前首字母和上一个条目首字母是否一致,不一致时,就显示全部的界面,一致时,就隐藏第一个界面
public class HaoHanAdapter extends BaseAdapter { private ArrayList<HaoHan> persons = new ArrayList<>(); private final Context context; public HaoHanAdapter(ArrayList<HaoHan> persons, Context context) { this.persons = persons; this.context = context; } @Override public int getCount() { return persons.size(); } @Override public Object getItem(int i) { return null; } @Override public long getItemId(int i) { return 0; } @Override public View getView(int position, View convertView, ViewGroup viewGroup) { View view; if (convertView == null) { view = View.inflate(context, R.layout.item_person, null); } else { view = convertView; } TextView tv_index = (TextView) view.findViewById(R.id.tv_index); TextView tv_name = (TextView) view.findViewById(R.id.tv_name); HaoHan haoHan = persons.get(position); //当前的首字母 String currentStr = haoHan.getPinyin().charAt(0) + ""; String indexStr=null; //如果是第一个名字,直接显示 if (position == 0) { indexStr = currentStr; } else { //判断当前首字母和上一个条目的首字母是否一致,不一致显示完整的item界面 String lastStr = persons.get(position - 1).getPinyin().charAt(0) + ""; //判断两个参数是否一致,不一致就执行赋值的逻辑 if (!TextUtils.equals(lastStr,currentStr)){ //不一致时候赋值indexStr indexStr=currentStr; } } tv_index.setVisibility(indexStr!=null?View.VISIBLE:View.GONE); tv_index.setText(currentStr); tv_name.setText(haoHan.getName()); return view; } }最后再MainActivity中实现
public class MainActivity extends AppCompatActivity { private ListView lv; private ArrayList<HaoHan> persons; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); QuickIndexBar bar= (QuickIndexBar) findViewById(R.id.action_bar); bar.setmOnLetterUpdateListener(new QuickIndexBar.OnLetterUpdateListener() { @Override public void onLetterUpdate(String string) { toastUtil.showToast(MainActivity.this,string); } }); //view层 lv = (ListView) findViewById(R.id.mylist); //d.model层,创建集合 persons = new ArrayList<>(); //d.填充并排列数据 fillAndSortData(persons); //d.Controller层,设置适配器 lv.setAdapter(new HaoHanAdapter(persons,this)); //根据用户按住的字符,自动跳到对应的listveiw条目上 bar.setmOnLetterUpdateListener(new QuickIndexBar.OnLetterUpdateListener() { @Override public void onLetterUpdate(String letter) { for (int x=0;x<persons.size();x++){ String l = persons.get(x).getPinyin().charAt(0) + ""; if (TextUtils.equals(letter,l)){ //找到第一个首字母是letter条目 lv.setSelection(x); break; } } } }); } /** * 填充数据并进行排序 * @param persons */ private void fillAndSortData(ArrayList<HaoHan> persons) { //填充 for (int x=0;x<cheeses.NAMES.length;x++){ String name = cheeses.NAMES[x]; persons.add(new HaoHan(name)); } //排序 Collections.sort(persons); } }
好了,这就可以实现一个自定义VIew的侧边索引了!
大家可以做一下!