您的位置:首页 > 其它

自定义View,实现侧边索引

2017-09-06 09:11 218 查看
侧边索引的实现思路:

侧拉索引:音乐APP,即时通讯,电商选择城市,短信验证选择城市都有这个类型自定义控件
实现步骤:
1.绘制A-Z的字母列表(自绘式自定义控件)
2.响应触摸事件
3.提供监听回调
4.获取汉字的拼音,首字母(pinyin4J通过汉字得到她的拼音,只能一个字符一个字符去转换成拼音)
5.根据拼音排序
6.根据首字母分组
7.把监听回调和
4000
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", <
17a6c
span style="color:#008000;font-weight:bold;">"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的侧边索引了!

大家可以做一下!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: