自定义View,实现侧边索引

侧边索引的实现思路:

侧拉索引:音乐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&&currentIndex<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的侧边索引了!

大家可以做一下!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值