自定義控件:快速索引

快速索引在應(yīng)用中很常見瑞信,在聯(lián)系人,微信穴豫,省市列表凡简,應(yīng)用管理逼友,文件管理等應(yīng)用場景都可以看到快速索引的身影,本篇博客將講解快速索引的自定義秤涩,從中你可以學(xué)到獲取漢字首字母的方法帜乞,繪制字母時(shí),縱坐標(biāo)的計(jì)算方法

這里寫圖片描述

一筐眷、靜態(tài)繪制

初始化數(shù)據(jù)

創(chuàng)建自定義控件QuickIndexBar 繼承View

public class QuickIndexBar extends View {
        private Paint paint;
        // 字母數(shù)組
        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" };
        public QuickIndexBar(Context context) {
            this(context, null);
        }
        public QuickIndexBar(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            paint = new Paint();
            // 設(shè)置抗鋸齒黎烈,設(shè)置后畫出來的邊緣更加平滑
            paint.setAntiAlias(true);
            //設(shè)置字體為粗體
            paint.setTypeface(Typeface.DEFAULT_BOLD);
            // 設(shè)置字體顏色
            paint.setColor(Color.WHITE);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //畫文字的方法
            canvas.drawText("A", 10f, 10f, paint);
        }
    }
  • 第3-6 行初始化字母數(shù)組
  • 第7-12 行串連構(gòu)造方法
  • 第15-21 行初始化畫筆
  • 第27 行畫文字的方法

將QuickIndexBar 布局到activity_main.xml 中

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.quickindexer.widget.QuickIndexBar
        android:id="@+id/quick_bar"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:background="#ff0000"/>
</RelativeLayout>

計(jì)算字母坐標(biāo)

這里寫圖片描述

畫文字的x,y 坐標(biāo)是文字左下角的位置

  1. 第一個(gè)字母的x 坐標(biāo)則等于單元格寬度的一半減去文字寬度的一半匀谣,由于所有字母離左邊的距離
    一樣照棋,所以x 不變int x = cellWidth/2 - textWidth/2
  2. 第一個(gè)字母的y 坐標(biāo)則等于單元格高度的一半加上文字,第二個(gè)字母需要加上一個(gè)單元格的寬度
    由此類推int y = cellHeight/2 +textHeight/2 +i*cellHeight
  3. 單元格cellWidth 為QuickIndexBar 寬度武翎,cellHeight 為QuickIndexBar 高度/字母數(shù)組的長度
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //遍歷字母數(shù)組烈炭,計(jì)算坐標(biāo),進(jìn)行繪制
        for (int i = 0; i < LETTERS.length; i++) {
            String letter = LETTERS[i];
            //計(jì)算x 坐標(biāo)
            float x = cellWidth*0.5f - paint.measureText(letter)*0.5f;
            //計(jì)算y 坐標(biāo)
            Rect bounds = new Rect();
            //獲取文本的矩形區(qū)域
            paint.getTextBounds(letter, 0, letter.length(), bounds);
            float y = cellHeight*0.5f + bounds.height()+ i*cellHeight;
            //繪制文本
            canvas.drawText(letter, x, y, paint);
        }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //控件高度
        int height = getMeasuredHeight();
        //控件寬度后频,也為單元格寬度
        cellWidth = getMeasuredWidth();
        //單元格寬度梳庆,控件高度除以字母數(shù)組長度,此處需要用float 類型
        //10/3 = 3.333 如果用int 接收則為3卑惜,此時(shí)高度比實(shí)際分配的高度小膏执,所以用float 接收
        cellHeight = height*1.0f/LETTERS.length;
    }
  • 第19-26 行獲取單元的寬高,注意單元格高度需要用float 類型
  • 第8-12 行通過paint 測(cè)量文字的寬高露久,并計(jì)算出每個(gè)字母的坐標(biāo)

Android drawText獲取text寬度的三種方式

原文鏈接:http://blog.csdn.net/chuekup/article/details/7518239

String str = "Hello";  
canvas.drawText( str , x , y , paint);  
  
//1. 粗略計(jì)算文字寬度  
Log.d(TAG, "measureText=" + paint.measureText(str));  
  
//2. 計(jì)算文字所在矩形更米,可以得到寬高  
Rect rect = new Rect();  
paint.getTextBounds(str, 0, str.length(), rect);  
int w = rect.width();  
int h = rect.height();  
Log.d(TAG, "w=" +w+"  h="+h);  
  
//3. 精確計(jì)算文字寬度  
int textWidth = getTextWidth(paint, str);  
Log.d(TAG, "textWidth=" + textWidth);  
  
    public static int getTextWidth(Paint paint, String str) {  
        int iRet = 0;  
        if (str != null && str.length() > 0) {  
            int len = str.length();  
            float[] widths = new float[len];  
            paint.getTextWidths(str, widths);  
            for (int j = 0; j < len; j++) {  
                iRet += (int) Math.ceil(widths[j]);  
            }  
        }  
        return iRet;  
    }  

//4. mPaint.getTextSize();

二、響應(yīng)觸摸事件

重寫onTouchEvent()方法毫痕,解析觸摸事件

//初始值需要設(shè)置為-1征峦,不能為0,因?yàn)榘聪碌谝粋€(gè)字母的索引是0
    private int lastIndex = -1;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y ;
        int currentIndex;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                y = event.getY();
                //根據(jù)y 計(jì)算當(dāng)前按下的字母索引
                //例如:y = 21,cellHeight = 10 =>(int) (y/cellHeight)= 2
                currentIndex = (int) (y/cellHeight);
                if(lastIndex != currentIndex){
                    //判斷計(jì)算出來的索引值消请,避免數(shù)組越界
                    if(0 <= currentIndex && currentIndex < LETTERS.length){
                        String letter = LETTERS[currentIndex];
                        Utils.showToast(getContext(), letter);
                        //記錄上次按下的索引
                        lastIndex = currentIndex;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                y = event.getY();
                //根據(jù)y 計(jì)算當(dāng)前按下的字母索引
                //例如:y = 21,cellHeight = 10 =>(int) (y/cellHeight)= 2
                currentIndex = (int) (y/cellHeight);
                //判斷計(jì)算出來的索引值栏笆,避免數(shù)組越界
                if(0 <= currentIndex && currentIndex < LETTERS.length){
                    String letter = LETTERS[currentIndex];
                    Utils.showToast(getContext(), letter);
                    //記錄上次按下的索引
                    lastIndex = currentIndex;
                }
                break;
            case MotionEvent.ACTION_UP:
                //手指抬起時(shí)需要將記錄的值設(shè)為-1,否則再次按下該字母不會(huì)彈出toast
                lastIndex = -1;
                break;

            default:
                break;
        }
        //事件已被處理臊泰,返回true
        return true;
    }
  • 第12 行通過觸摸的y 值計(jì)算按下字母的索引值
  • 第19 行記錄上次按下字母的索引值蛉加,通過判斷上次按下字母的索引與本次按下字母的索引是否相同,如果不同才彈出toast缸逃,避免在同一個(gè)字母上來回移動(dòng)也一直彈出toast
  • 第38 行手指抬起需要將lastIndex 還原為初始值
  • 第45 行一定要返回true针饥,代表事件已被消費(fèi)
  • 第17 行是單例Toast
public class Utils {

        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();
        }
    }

三、監(jiān)聽回調(diào)

定義監(jiān)聽回調(diào)接口

 private OnLetterUpdateListener onLetterUpdateListener;
    public OnLetterUpdateListener getOnLetterUpdateListener() {
        return onLetterUpdateListener;
    }
    public void setOnLetterUpdateListener(
            OnLetterUpdateListener onLetterUpdateListener) {
        this.onLetterUpdateListener = onLetterUpdateListener;
    }
    public interface OnLetterUpdateListener{
        public void onLetterUpdate(String letter);
    }

在彈出Toast 的地方替換成調(diào)用接口方法

public boolean onTouchEvent(MotionEvent event) {
        float y ;
        int currentIndex;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                y = event.getY();
                //根據(jù)y 計(jì)算當(dāng)前按下的字母索引
                //例如:y = 21,cellHeight = 10 =>(int) (y/cellHeight)= 2
                currentIndex = (int) (y/cellHeight);
                if(lastIndex != currentIndex){
                    //判斷計(jì)算出來的索引值需频,避免數(shù)組越界
                    if(0 <= currentIndex && currentIndex < LETTERS.length){
                        String letter = LETTERS[currentIndex];
                        if(onLetterUpdateListener != null){
                            onLetterUpdateListener.onLetterUpdate(letter);
                        }

                        //記錄上次按下的索引
                        lastIndex = currentIndex;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                y = event.getY();
                //根據(jù)y 計(jì)算當(dāng)前按下的字母索引
                //例如:y = 21,cellHeight = 10 =>(int) (y/cellHeight)= 2
                currentIndex = (int) (y/cellHeight);
                //判斷計(jì)算出來的索引值丁眼,避免數(shù)組越界
                if(0 <= currentIndex && currentIndex < LETTERS.length){
                    String letter = LETTERS[currentIndex];
                    if(onLetterUpdateListener != null){
                        onLetterUpdateListener.onLetterUpdate(letter);
                    }
                    //記錄上次按下的索引
                    lastIndex = currentIndex;
                }
                break;
            case MotionEvent.ACTION_UP:
                //手指抬起時(shí)需要將記錄的值設(shè)為-1,否則再次按下該字母不會(huì)彈出toast
                lastIndex = -1;
                break;
            default:
                break;
        }
        //事件已被處理昭殉,返回true
        return true;
    }
  • 第14-16 調(diào)用回調(diào)接口方法苞七,通知外面當(dāng)前觸摸的字母
  • 第30-32 調(diào)用回調(diào)接口方法藐守,通知外面當(dāng)前觸摸的字母

主Activity 中給QuickIndexBar 設(shè)置回調(diào)監(jiān)聽

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        QuickIndexBar quickBar = (QuickIndexBar) findViewById(R.id.quick_bar);
        quickBar.setOnLetterUpdateListener(new OnLetterUpdateListener() {
            @Override
            public void onLetterUpdate(String letter) {
                Utils.showToast(getApplicationContext(), letter);
            }
        });
    }

四、根據(jù)拼音排序

1莽鸭、創(chuàng)建PinyinUtil.java

GitHub上有可以將漢字轉(zhuǎn)拼音的開源項(xiàng)目吗伤,TinyPinyinpinyin4j

漢字轉(zhuǎn)拼音需要導(dǎo)入pinyin4j-2.5.0.jar

public class PinyinUtil {
        /**
         * 根據(jù)指定的漢字字符串, 返回其對(duì)應(yīng)的拼音
         * @param string
         * @return
         */
        public static String getPinyin(String string) {
            // 黑-> HEI 馬-> MA
            // 黑馬*&^*
            // 黑123dfasdf 馬
            HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
            // 不需要音標(biāo)
            format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
            // 設(shè)置轉(zhuǎn)換出大寫字母
            format.setCaseType(HanyuPinyinCaseType.UPPERCASE);

            char[] charArray = string.toCharArray();

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < charArray.length; i++) {
                char c = charArray[i];

                // 如果是空格, 跳過當(dāng)前循環(huán)
                if(Character.isWhitespace(c)){
                    continue;
                }

                if(c >= -128 && c < 127){
                    // 不可能是漢字, 直接拼接
                    sb.append(c);
                }else {
                    try {
                        // 獲取某個(gè)字符對(duì)應(yīng)的拼音. 可以獲取到多音字. 單->DAN, SHAN
                        String s = PinyinHelper.toHanyuPinyinStringArray(c, format)[0];
                        sb.append(s);
                    } catch (BadHanyuPinyinOutputFormatCombination e) {

                        e.printStackTrace();
                    }
                }
            }
            return sb.toString();
        }
    }

2硫眨、填充ListView

用于填充ListView 的姓名數(shù)組

public class Cheeses {
        public static final String[] NAMES = new String[] { "宋江", "盧俊義", "吳用",
                "公孫勝", "關(guān)勝", "林沖", "秦明", "呼延灼", "花榮", "柴進(jìn)", "李應(yīng)", "朱仝", "魯智
                深",
                "武松", "董平", "張清", "楊志", "徐寧", "索超", "戴宗", "劉唐", "李逵", "史進(jìn)", "
                穆弘",
                "雷橫", "李俊", "阮小二", "張橫", "阮小五", " 張順", "阮小七", "楊雄", "石秀", "
                解珍",
                " 解寶", "燕青", "朱武", "黃信", "孫立", "宣贊", "郝思文", "韓滔", "彭玘", "單廷珪
                ",
                "魏定國", "蕭讓", "裴宣", "歐鵬", "鄧飛", " 燕順", "楊林", "凌振", "蔣敬", "呂方
                ",
                "郭盛", "安道全", "皇甫端", "王英", "扈三娘", "鮑旭", "樊瑞", "孔明", "孔亮", "
                項(xiàng)充",
                "李袞", "金大堅(jiān)", "馬麟", "童威", "童猛", "孟康", "侯健", "陳達(dá)", "楊春", "鄭天壽
                ",
                "陶宗旺", "宋清", "樂和", "龔?fù)?, "丁得孫", "穆春", "曹正", "宋萬", "杜遷", "薛永
                ", "施恩",
                "周通", "李忠", "杜興", "湯隆", "鄒淵", "鄒潤", "朱富", "朱貴", "蔡福", "蔡慶", "
                李立",
                "李云", "焦挺", "石勇", "孫新", "顧大嫂", "張青", "孫二娘", " 王定六", "郁保四", "
                白勝",
                "時(shí)遷", "段景柱" };
    }

將姓名轉(zhuǎn)化為HaoHan 對(duì)象

public class HaoHan implements Comparable<HaoHan>{
        private String name;
        private String pinyin;
        public HaoHan(String name) {
            super();
            this.name = name;
            this.pinyin = PinyinUtil.getPinyin(name);
        }


        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getPinyin() {
            return pinyin;
        }
        public void setPinyin(String pinyin) {
            this.pinyin = pinyin;
        }

        @Override
        public int compareTo(HaoHan another) {
            return this.pinyin.compareTo(another.pinyin);
        }
    }

第24-27 行實(shí)現(xiàn)Comparable 接口,用于排序操作
activity_main.xml 中添加ListView

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

    <com.example.quickindexer.widget.QuickIndexBar
        android:id="@+id/quick_bar"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:background="#ff0000"/>
</RelativeLayout>

為ListView 創(chuàng)建數(shù)據(jù)適配器
ListView 條目的布局item_person.xml,每一個(gè)條目上都加上顯示首字母的TextView

<?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/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>

數(shù)據(jù)適配器代碼

public class HaoHanAdapter extends BaseAdapter {
        private ArrayList<HaoHan> persons = new ArrayList<HaoHan>();
        private final Context context;

        public HaoHanAdapter(ArrayList<HaoHan> persons, Context context) {
            super();
            this.persons = persons;
            this.context = context;
        }
        @Override
        public int getItemViewType(int position) {
            // TODO Auto-generated method stub
            return super.getItemViewType(position);
        }
        @Override
        public int getViewTypeCount() {
            // TODO Auto-generated method stub
            return super.getViewTypeCount();

        }
        @Override
        public int getCount() {
            return persons.size();
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            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);

            // 當(dāng)前首字母
            String currentStr = haoHan.getPinyin().charAt(0) + "";
            tv_index.setText(currentStr);
            tv_name.setText(haoHan.getName());

            return view;
        }
        @Override
        public Object getItem(int position) {
            return null;
        }
        @Override
        public long getItemId(int position) {
            return 0;
        }
    }

五巢块、根據(jù)首字母分組

修改數(shù)據(jù)適配器的getView()方法

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        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);

        // 當(dāng)前首字母
        String currentStr = haoHan.getPinyin().charAt(0) + "";

        String indexStr = null;
        // 如果是第一個(gè), 直接顯示
        if(position == 0){
            indexStr = currentStr;
        }else {
            // 判斷當(dāng)前首字母和上一個(gè)條目的首字母是否一致, 不一致時(shí)候顯示.
            String lastStr = persons.get(position - 1).getPinyin().charAt(0) + "";
            if(!TextUtils.equals(lastStr, currentStr)){
                // 不一致時(shí)候賦值indexStr
                indexStr = currentStr;
            }

        }

        tv_index.setVisibility(indexStr != null ? View.VISIBLE : View.GONE);
        tv_index.setText(currentStr);
        tv_name.setText(haoHan.getName());

        return view;
    }

第16-32 行如果是第一行直接顯示首字母條目礁阁,如果不是第一行判斷當(dāng)前首字母和上一個(gè)條目的首字母是否一致, 不一致時(shí)候顯示,一致則隱藏

六族奢、ListView 和自定義控件結(jié)合

修改回調(diào)接口代碼姥闭,for 循環(huán)persons 集合找到與傳回來的letter 值相同的索引值,ListView 直接滾動(dòng)到對(duì)應(yīng)位置即可

QuickIndexBar quickBar = (QuickIndexBar) findViewById(R.id.quick_bar);
    quickBar.setOnLetterUpdateListener(new OnLetterUpdateListener() {
        @Override
        public void onLetterUpdate(String letter) {
            Utils.showToast(MainActivity.this, letter);
            for (int i = 0; i < persons.size(); i++) {


                String l = persons.get(i).getPinyin().charAt(0) + "";
                if(TextUtils.equals(letter, l)){
                    // 找到第一個(gè)首字母是letter 條目.
                    lv.setSelection(i);
                    break;
                }
            }
        }
    });

七越走、細(xì)節(jié)優(yōu)化置

置為當(dāng)前選中的字母

public boolean onTouchEvent(MotionEvent event) {
        float y ;
        int currentIndex;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                y = event.getY();
                //根據(jù)y 計(jì)算當(dāng)前按下的字母索引
                //例如:y = 21,cellHeight = 10 =>(int) (y/cellHeight)= 2
                currentIndex = (int) (y/cellHeight);
                if(lastIndex != currentIndex){
                    //判斷計(jì)算出來的索引值棚品,避免數(shù)組越界
                    if(0 <= currentIndex && currentIndex < LETTERS.length){
                        String letter = LETTERS[currentIndex];
                        if(onLetterUpdateListener != null){
                            onLetterUpdateListener.onLetterUpdate(letter);
                        }
                        //記錄上次按下的索引
                        lastIndex = currentIndex;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                y = event.getY();
                //根據(jù)y 計(jì)算當(dāng)前按下的字母索引
                //例如:y = 21,cellHeight = 10 =>(int) (y/cellHeight)= 2
                currentIndex = (int) (y/cellHeight);
                //判斷計(jì)算出來的索引值,避免數(shù)組越界
                if(0 <= currentIndex && currentIndex < LETTERS.length){
                    String letter = LETTERS[currentIndex];
                    if(onLetterUpdateListener != null){
                        onLetterUpdateListener.onLetterUpdate(letter);
                    }
                    //記錄上次按下的索引
                    lastIndex = currentIndex;
                }
                break;
            case MotionEvent.ACTION_UP:
                //手指抬起時(shí)需要將記錄的值設(shè)為-1廊敌,否則再次按下該字母不會(huì)彈出toast
                lastIndex = -1;
                break;

            default:
                break;
        }
        //重繪一次界面铜跑,會(huì)再次調(diào)用onDraw()方法,通過判斷l(xiāng)astIndex 的值骡澈,把按下的字母置為灰色
        invalidate();
        //事件已被處理锅纺,返回true
        return true;
    }

第46 行為新增代碼,此時(shí)lastIndex 為當(dāng)前按下的字母索引肋殴,調(diào)用一次invalidate()方法囤锉,會(huì)再次調(diào)用onDraw()方法,在onDraw()方法中通過判斷l(xiāng)astIndex 的值护锤,把當(dāng)前按下的字母置為灰色

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //遍歷字母數(shù)組官地,計(jì)算坐標(biāo),進(jìn)行繪制
        for (int i = 0; i < LETTERS.length; i++) {
            String letter = LETTERS[i];
            //計(jì)算x 坐標(biāo)
            float x = cellWidth*0.5f - paint.measureText(letter)*0.5f;
            //計(jì)算y 坐標(biāo)
            Rect bounds = new Rect();
            //獲取文本的矩形區(qū)域
            paint.getTextBounds(letter, 0, letter.length(), bounds);
            float y = cellHeight*0.5f + bounds.height()+ i*cellHeight;
            //把當(dāng)前選中的字母置為灰色
            if(lastIndex == i){
                paint.setColor(Color.GRAY);
            }else{
                paint.setColor(Color.WHITE);
            }
            //繪制文本
            canvas.drawText(letter, x, y, paint);
        }
    }

第13-18 行如果當(dāng)前字母的索引是選中的烙懦,則將畫筆顏色改為灰色驱入,沒有選中的將畫筆改為白色

把回調(diào)方法中Toast 的顯示方式改為TextView 顯示

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

    <TextView
        android:id="@+id/tv_center"
        android:layout_width="160dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:background="@drawable/shape_tv_center"
        android:gravity="center"
        android:text="A"
        android:textColor="#ffffff"
        android:textSize="32sp"
        android:visibility="gone"/>

    <com.example.quickindexer.widget.QuickIndexBar
        android:id="@+id/quick_bar"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:background="#ff0000"/>
</RelativeLayout>
  • 第11-21 行在屏幕中間添加一個(gè)提示框,默認(rèn)情況為不顯示
  • 第16 行提示框的背景文件修陡,需要在res 下新建一個(gè)drawable 文件夾沧侥,將shape_tv_center.xml 放在此文件夾下
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle" >
    <!-- android:shape="rectangle" 該形狀為矩形-->
    <solid android:color="#66000000" /><!--填充顏色-->
    <corners android:radius="20dp" /><!--圓角半徑-->
</shape>

修改回調(diào)接口中字母提示方式

//屏幕中間的提示框
    tvCenter = (TextView) findViewById(R.id.tv_center);
    quickBar.setOnLetterUpdateListener(new OnLetterUpdateListener() {
        @Override
        public void onLetterUpdate(String letter) {
            //Utils.showToast(MainActivity.this, letter);
            //將Toast 改成文本提示框
            showLetter(letter);
            for (int i = 0; i < persons.size(); i++) {
                String l = persons.get(i).getPinyin().charAt(0) + "";
                if(TextUtils.equals(letter, l)){
                    // 找到第一個(gè)首字母是letter 條目.
                    lv.setSelection(i);
                    break;
                }
            }
        }
    });
    private Handler mHandler = new Handler();
    /**
     * 在屏幕中間顯示一個(gè)字母提示
     * @param letter
     */
    private void showLetter(String letter) {
        tvCenter.setText(letter);
        tvCenter.setVisibility(View.VISIBLE);
        //移除所有的消息及任務(wù)
        mHandler.removeCallbacksAndMessages(null);
        //用消息機(jī)制延遲隱藏提示框
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //2 秒后隱藏提示框
                tvCenter.setVisibility(View.GONE);
            }
        }, 2000);
    }
  • 第6-8 行將Toast 提示改為文本框提示
  • 第28-36 行用消息機(jī)制延遲隱藏文本提示框,如果快速滑動(dòng)showLetter()方法會(huì)頻繁調(diào)用魄鸦,會(huì)有多個(gè)延遲任務(wù)在消息隊(duì)列中宴杀,其實(shí)當(dāng)前延遲任務(wù)之前的任務(wù)都是沒有必要執(zhí)行的,所以可以先移除隊(duì)列中所有的任務(wù)再將本次任務(wù)添加到隊(duì)列中

QuickIndexBar

/**
 * 快速索引
 * 
 * 用于根據(jù)字母快速定位聯(lián)系人
 * @author AllenIverson
 *
 */
public class QuickIndexBar extends View {
    
    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 static final String TAG = "TAG";
    
    private Paint mPaint;

    private int cellWidth;

    private float cellHeight;
    
    /**
     * 暴露一個(gè)字母的監(jiān)聽
     */
    public interface OnLetterUpdateListener{
        void onLetterUpdate(String letter);
    }
    private OnLetterUpdateListener listener;
    
    public OnLetterUpdateListener getListener() {
        return listener;
    }
    /**
     * 設(shè)置字母更新監(jiān)聽
     * @param listener
     */
    public void setListener(OnLetterUpdateListener listener) {
        this.listener = listener;
    }

    public QuickIndexBar(Context context) {
        this(context, null);
    }

    public QuickIndexBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        
        for (int i = 0; i < LETTERS.length; i++) {
            String text = LETTERS[i];
            // 計(jì)算坐標(biāo)
            int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f);
            // 獲取文本的高度
            Rect bounds = new Rect();// 矩形
            mPaint.getTextBounds(text, 0, text.length(), bounds);
            int textHeight = bounds.height();
            int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + i * cellHeight);
            
            // 根據(jù)按下的字母, 設(shè)置畫筆顏色
            mPaint.setColor(touchIndex == i ? Color.GRAY : Color.WHITE);
            
            // 繪制文本A-Z
            canvas.drawText(text, x, y, mPaint);
        }
    }
    
    int touchIndex = -1;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = -1;
        switch (MotionEventCompat.getActionMasked(event)) {
            case MotionEvent.ACTION_DOWN:
                // 獲取當(dāng)前觸摸到的字母索引
                index = (int) (event.getY() / cellHeight);
                if(index >= 0 && index < LETTERS.length){
                    // 判斷是否跟上一次觸摸到的一樣
                    if(index != touchIndex) {
                        if(listener != null){
                            listener.onLetterUpdate(LETTERS[index]);
                        }
                        Log.d(TAG, "onTouchEvent: " + LETTERS[index]);
                        
                        touchIndex = index;
                    }
                }
                
                break;
            case MotionEvent.ACTION_MOVE:
                index = (int) (event.getY() / cellHeight);
                if(index >= 0 && index < LETTERS.length){
                    // 判斷是否跟上一次觸摸到的一樣
                    if(index != touchIndex){
                        
                        if(listener != null){
                            listener.onLetterUpdate(LETTERS[index]);
                        }
                        Log.d(TAG, "onTouchEvent: " + LETTERS[index]);
                        
                        touchIndex = index;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                touchIndex = -1;
                break;
    
            default:
                break;
        }
        invalidate();
        
        return true;
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 獲取單元格的寬和高
        
        cellWidth = getMeasuredWidth();
        
        int mHeight = getMeasuredHeight();
        cellHeight = mHeight * 1.0f / LETTERS.length;
        
    }
}

MainActivity

public class MainActivity extends Activity {

    private ListView mMainList;
    private ArrayList<Person> persons;
    private TextView tv_center;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        QuickIndexBar bar = (QuickIndexBar) findViewById(R.id.bar);
        // 設(shè)置監(jiān)聽
        bar.setListener(new OnLetterUpdateListener() {
            @Override
            public void onLetterUpdate(String letter) {
//              Utils.showToast(getApplicationContext(), letter);
                
                showLetter(letter);
                // 根據(jù)字母定位ListView, 找到集合中第一個(gè)以letter為拼音首字母的對(duì)象,得到索引
                for (int i = 0; i < persons.size(); i++) {
                    Person person = persons.get(i);
                    String l = person.getPinyin().charAt(0) + "";
                    if(TextUtils.equals(letter, l)){
                        // 匹配成功
                        mMainList.setSelection(i);
                        break;
                    }
                }
            }
        });
        
        mMainList = (ListView) findViewById(R.id.lv_main);
        
        persons = new ArrayList<Person>();
        
        // 填充數(shù)據(jù) , 排序
        fillAndSortData(persons);
        
        mMainList.setAdapter(new HaoHanAdapter(MainActivity.this , persons));
        
        tv_center = (TextView) findViewById(R.id.tv_center);
        
        
    }

    private Handler mHandler = new Handler();
    
    /**
     * 顯示字母
     * @param letter
     */
    protected void showLetter(String letter) {
        tv_center.setVisibility(View.VISIBLE);
        tv_center.setText(letter);
        
        mHandler.removeCallbacksAndMessages(null);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                tv_center.setVisibility(View.GONE);             
            }
        }, 2000);
        
    }

    private void fillAndSortData(ArrayList<Person> persons) {
        // 填充數(shù)據(jù)
        for (int i = 0; i < Cheeses.NAMES.length; i++) {
            String name = Cheeses.NAMES[i];
            persons.add(new Person(name));
        }
        
        // 進(jìn)行排序
        Collections.sort(persons);
    }
}

HaoHanAdapter

public class HaoHanAdapter extends BaseAdapter {

    private Context mContext;
    private ArrayList<Person> persons;

    public HaoHanAdapter(Context mContext, ArrayList<Person> persons) {
        this.mContext = mContext;
        this.persons = persons;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return persons.size();
    }

    @Override
    public Object getItem(int position) {
        return persons.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        
        View view = convertView;
        if(convertView == null){
            view = view.inflate(mContext, R.layout.item_list, null);
        }
        ViewHolder mViewHolder = ViewHolder.getHolder(view);
        
        Person p = persons.get(position);
        
        String str = null;
        String currentLetter = p.getPinyin().charAt(0) + "";
        // 根據(jù)上一個(gè)首字母,決定當(dāng)前是否顯示字母
        if(position == 0){
            str = currentLetter;
        }else {
            // 上一個(gè)人的拼音的首字母
            String preLetter = persons.get(position - 1).getPinyin().charAt(0) + "";
            if(!TextUtils.equals(preLetter, currentLetter)){
                str = currentLetter;
            }
        }
        
        // 根據(jù)str是否為空,決定是否顯示索引欄
        mViewHolder.mIndex.setVisibility(str == null ? View.GONE : View.VISIBLE);
        mViewHolder.mIndex.setText(currentLetter);
        mViewHolder.mName.setText(p.getName());
        
        return view;
    }
    
    static class ViewHolder {
        TextView mIndex;
        TextView mName;

        public static ViewHolder getHolder(View view) {
            Object tag = view.getTag();
            if(tag != null){
                return (ViewHolder)tag;
            }else {
                ViewHolder viewHolder = new ViewHolder();
                viewHolder.mIndex = (TextView) view.findViewById(R.id.tv_index);
                viewHolder.mName = (TextView) view.findViewById(R.id.tv_name);
                view.setTag(viewHolder);
                return viewHolder;
            }
        }
        
    }
}

FancyListIndexer

public class FancyIndexer extends View {
    
    
    public interface OnTouchLetterChangedListener {
        public void onTouchLetterChanged(String s);
    }
    
    private static final String TAG = "FancyIndexer";
    
    /////////////////////////////////////////////////////////////////////////
    
    //Properties
    // 向右偏移多少畫字符拾因, default 30
    float mWidthOffset = 30.0f;
    
    // 最小字體大小
    int mMinFontSize = 24;
    
    // 最大字體大小
    int mMaxFontSize = 48;
    
    // 提示字體大小
    int mTipFontSize = 52;
    
    // 提示字符的額外偏移
    float mAdditionalTipOffset = 20.0f;
    
    // 貝塞爾曲線控制的高度
    float mMaxBezierHeight = 150.0f;
    
    // 貝塞爾曲線單側(cè)寬度
    float mMaxBezierWidth = 240.0f;
    
    // 貝塞爾曲線單側(cè)模擬線量
    int  mMaxBezierLines = 32;
    
    // 列表字符顏色
    int  mFontColor = 0xffffffff;
    
    // 提示字符顏色
//  int  mTipFontColor = 0xff3399ff;
    int  mTipFontColor = 0xffd33e48;
    
    /////////////////////////////////////////////////////////////////////////
    
    private OnTouchLetterChangedListener mListener;
    
    private final String[] ConstChar = {"#","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"};
    
    int mChooseIndex = -1;
    Paint mPaint = new Paint();
    PointF mTouch = new PointF();
    
    PointF[] mBezier1;
    PointF[] mBezier2;
    
    float mLastOffset[] = new float[ConstChar.length]; // 記錄每一個(gè)字母的x方向偏移量, 數(shù)字<=0
    PointF mLastFucusPostion = new PointF();
    
    Scroller mScroller;
    boolean mAnimating = false;
    float mAnimationOffset;
    
    boolean mHideAnimation = false;
    int mAlpha = 255; 
    
    Handler mHideWaitingHandler = new Handler() {
        
        @Override
        public void handleMessage(Message msg) {
            if( msg.what == 1 )
            {
//              mScroller.startScroll(0, 0, 255, 0, 1000);
                mHideAnimation = true;
                mAnimating = false;
                FancyIndexer.this.invalidate();
                return;
            }
            super.handleMessage(msg);
        }
    };
    
    public FancyIndexer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initData(context, attrs);
    }

    public FancyIndexer(Context context, AttributeSet attrs) {
        super(context, attrs);
        initData(context, attrs);
    }

    public FancyIndexer(Context context) {
        super(context);
        initData(null, null);
    }
    
    private void initData(Context context, AttributeSet attrs) {
        
        if( context != null && attrs != null ) {
            
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FancyIndexer, 0, 0);
            
            mWidthOffset    = a.getDimension(R.styleable.FancyIndexer_widthOffset, mWidthOffset);
            mMinFontSize    = a.getInteger(R.styleable.FancyIndexer_minFontSize, mMinFontSize);
            mMaxFontSize    = a.getInteger(R.styleable.FancyIndexer_maxFontSize, mMaxFontSize);
            mTipFontSize    = a.getInteger(R.styleable.FancyIndexer_tipFontSize, mTipFontSize);
            mMaxBezierHeight = a.getDimension(R.styleable.FancyIndexer_maxBezierHeight, mMaxBezierHeight);
            mMaxBezierWidth = a.getDimension(R.styleable.FancyIndexer_maxBezierWidth, mMaxBezierWidth);         
            mMaxBezierLines =  a.getInteger(R.styleable.FancyIndexer_maxBezierLines, mMaxBezierLines);
            mAdditionalTipOffset = a.getDimension(R.styleable.FancyIndexer_additionalTipOffset, mAdditionalTipOffset);
            mFontColor = a.getColor(R.styleable.FancyIndexer_fontColor, mFontColor);
            mTipFontColor = a.getColor(R.styleable.FancyIndexer_tipFontColor, mTipFontColor);
            a.recycle();
        }
        mScroller = new Scroller( getContext() );
        mTouch.x = 0;
        mTouch.y = -10*mMaxBezierWidth;
        
        mBezier1 = new PointF[mMaxBezierLines];
        mBezier2 = new PointF[mMaxBezierLines];
        
        calculateBezierPoints();
        
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        
        // 控件寬高
        int height = getHeight();
        int width = getWidth();

        // 單個(gè)字母高度
        float singleHeight = height / (float)ConstChar.length;
        
        int workHeight = 0;
        
        if( mAlpha == 0 )
            return;
        
        mPaint.reset();
        
        int saveCount = 0;
        
        if( mHideAnimation )
        {
            saveCount = canvas.save();
            canvas.saveLayerAlpha( 0, 0, width, height, mAlpha, Canvas.ALL_SAVE_FLAG );
        }
        
        for(int i=0;i<ConstChar.length;i++) {
            
           mPaint.setColor(mFontColor);
           mPaint.setAntiAlias(true);
           
           float xPos = width - mWidthOffset;
           float yPos = workHeight + singleHeight/2;
           
           //float adjustX = adjustXPos( yPos, i == mChooseIndex );
           // 根據(jù)當(dāng)前字母y的位置計(jì)算得到字體大小
           int fontSize = adjustFontSize(i, yPos ); 
           mPaint.setTextSize(fontSize);
           
           // 添加一個(gè)字母的高度
           workHeight += singleHeight;
           
           // 繪制字母
           drawTextInCenter(canvas, ConstChar[i], xPos + ajustXPosAnimation(i,  yPos )  , yPos );
           
           // 繪制的字母和當(dāng)前觸摸到的一致, 繪制紅色被選中字母
           if(i == mChooseIndex) {
               mPaint.setColor( mTipFontColor );
               mPaint.setFakeBoldText(true);
               mPaint.setTextSize( mTipFontSize );
               yPos = mTouch.y;
               
               float pos = 0;
               
               if( mAnimating || mHideAnimation ) {
                   pos = mLastFucusPostion.x;
                   yPos = mLastFucusPostion.y;
               } else {
                   pos = xPos + ajustXPosAnimation(i, yPos ) - mAdditionalTipOffset;
                   mLastFucusPostion.x = pos;
                   mLastFucusPostion.y = yPos;
               }
                   
               drawTextInCenter(canvas, ConstChar[i], pos, yPos );
//             mPaint.setStrokeWidth(5);
//             canvas.drawLine(0, yPos, width, yPos, mPaint);
           }
           mPaint.reset();
        }
        
        if( mHideAnimation ) 
        {
            canvas.restoreToCount(saveCount);
        }
       
    }
    
    /**
     * @param canvas 畫板
     * @param string 被繪制的字母
     * @param xCenter 字母的中心x方向位置
     * @param yCenter 字母的中心y方向位置
     */
    private void drawTextInCenter(Canvas canvas, String string, float xCenter, float yCenter) {

        FontMetrics fm = mPaint.getFontMetrics();
        //float fontWidth = paint.measureText(string);
        float fontHeight = mPaint.getFontSpacing();
        
        float drawY = yCenter + fontHeight/2 - fm.descent;
        
        if( drawY < -fm.ascent -fm.descent )
            drawY = -fm.ascent -fm.descent;
        
        if( drawY > getHeight() )
            drawY = getHeight() ;
        
        mPaint.setTextAlign(Align.CENTER);
        
        canvas.drawText(string, xCenter, drawY, mPaint);
    }
    
    private int adjustFontSize(int i, float yPos ) {
        
        // 根據(jù)水平方向偏移量計(jì)算出一個(gè)放大的字號(hào)
        float adjustX = Math.abs(ajustXPosAnimation(i,  yPos ));
        
        int adjustSize =(int)( (mMaxFontSize - mMinFontSize ) * adjustX / (float)mMaxBezierHeight) + mMinFontSize;
        
        return adjustSize;
    }
    
    /**
     * x 方向的向左偏移量
     * @param i 當(dāng)前字母的索引
     * @param yPos y方向的初始位置
     * @return
     */
    private float ajustXPosAnimation (int i, float yPos ) {

        float offset ;
        if( this.mAnimating || this.mHideAnimation ) {
            // 正在動(dòng)畫中或在做隱藏動(dòng)畫
            offset = mLastOffset[i];
            if( offset !=0.0f ) {
                offset += this.mAnimationOffset;
                if( offset > 0)
                    offset = 0;
            }
        } else {
            
            // 根據(jù)當(dāng)前字母y方向位置, 計(jì)算水平方向偏移量
            offset = adjustXPos( yPos );
            
            // 當(dāng)前觸摸的x方向位置
            float xPos = mTouch.x  ;
            
            float width = getWidth() - mWidthOffset;
            width = width - 60;
            
            // 字母繪制時(shí)向左偏移量 進(jìn)行修正, offset需要是<=0的值
            if( offset != 0.0f  && xPos > width )
                offset +=  ( xPos - width );
            if( offset > 0)
                offset = 0;
            
            mLastOffset[i] = offset;
        }
        return offset;
    }
    
    private float adjustXPos(float yPos ) {
    
        float dis = yPos - mTouch.y; // 字母y方向位置和觸摸時(shí)y值坐標(biāo)的差值, 距離越小, 得到的水平方向偏差越大 
        if( dis > -mMaxBezierWidth  && dis < mMaxBezierWidth ) {
            // 在2個(gè)貝賽爾曲線寬度范圍以內(nèi) (一個(gè)貝賽爾曲線寬度是指一個(gè)山峰的一邊)

            // 第一段 曲線
            if( dis > mMaxBezierWidth/4 ) {
                for( int i = mMaxBezierLines-1; i>0 ; i-- ) {
                    // 從下到上, 逐個(gè)計(jì)算
                    
                    if( dis == -mBezier1[i].y ) // 落在點(diǎn)上
                        return mBezier1[i].x;
                    
                    // 如果距離dis落在兩個(gè)貝塞爾曲線模擬點(diǎn)之間, 通過三角函數(shù)計(jì)算得到當(dāng)前dis對(duì)應(yīng)的x方向偏移量
                    if( dis > -mBezier1[i].y && dis < -mBezier1[i-1].y ) {
                        return (dis + mBezier1[i].y) * ( mBezier1[i-1].x - mBezier1[i].x ) / ( -mBezier1[i-1].y + mBezier1[i].y ) + mBezier1[i].x;
                    }
                }
                return mBezier1[0].x;
            }
            
            // 第三段 曲線, 和第一段曲線對(duì)稱
            if( dis < -mMaxBezierWidth/4 ) {
                for( int i = 0; i< mMaxBezierLines-1; i++ ) {
                    // 從上到下
                    
                    if( dis == mBezier1[i].y ) // 落在點(diǎn)上
                        return mBezier1[i].x;

                    // 如果距離dis落在兩個(gè)貝塞爾曲線模擬點(diǎn)之間, 通過三角函數(shù)計(jì)算得到當(dāng)前dis對(duì)應(yīng)的x方向偏移量
                    if( dis > mBezier1[i].y && dis < mBezier1[i+1].y ) {
                        return (dis - mBezier1[i].y )* (mBezier1[i+1].x - mBezier1[i].x ) / ( mBezier1[i+1].y - mBezier1[i].y ) + mBezier1[i].x;
                    }
                }
                return mBezier1[mMaxBezierLines-1].x;
            }
            
            // 第二段 峰頂曲線
            for( int i = 0; i< mMaxBezierLines-1; i++ ) {
                
                if( dis == mBezier2[i].y )
                    return mBezier2[i].x;

                // 如果距離dis落在兩個(gè)貝塞爾曲線模擬點(diǎn)之間, 通過三角函數(shù)計(jì)算得到當(dāng)前dis對(duì)應(yīng)的x方向偏移量
                if( dis > mBezier2[i].y && dis < mBezier2[i+1].y ) {
                    return ( dis - mBezier2[i].y) * ( mBezier2[i+1].x - mBezier2[i].x ) / (mBezier2[i+1].y - mBezier2[i].y ) + mBezier2[i].x;
                }
            }   
            return mBezier2[mMaxBezierLines-1].x;       

        }
        
        return 0.0f;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final float y = event.getY();
        final int oldmChooseIndex = mChooseIndex;
        final OnTouchLetterChangedListener listener = mListener;
        final int c = (int) (y/getHeight()*ConstChar.length);
        
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                
                if( this.getWidth() > mWidthOffset ) {
                    if ( event.getX() < this.getWidth() - mWidthOffset )
                        return false;
                }
                
                mHideWaitingHandler.removeMessages(1);
                
                mScroller.abortAnimation();
                mAnimating = false;
                mHideAnimation = false;
                mAlpha = 255;
                
                mTouch.x = event.getX();
                mTouch.y = event.getY();
                
                if(oldmChooseIndex != c && listener != null){
                    if(c > 0 && c< ConstChar.length){
                        listener.onTouchLetterChanged(ConstChar[c]);
                        mChooseIndex = c;
                    }
                }
                invalidate();               
                break;
            case MotionEvent.ACTION_MOVE:
                mTouch.x = event.getX();
                mTouch.y = event.getY();
                invalidate();
                if(oldmChooseIndex != c && listener != null){

                    if(c >= 0 && c< ConstChar.length){
                        listener.onTouchLetterChanged(ConstChar[c]);
                        mChooseIndex = c;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:

                mTouch.x = event.getX();
                mTouch.y = event.getY();

                //this.mChooseIndex = -1;
                
                mScroller.startScroll(0, 0, (int)mMaxBezierHeight, 0, 2000);
                mAnimating = true;
                postInvalidate();
                break;
        }
        return true;
    }
    
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            if( mAnimating ) {
                float x = mScroller.getCurrX();
                mAnimationOffset = x;               
            } else if( mHideAnimation ) {
                mAlpha = 255 - (int) mScroller.getCurrX();
            }
            invalidate();
        } else if( mScroller.isFinished() ) {
            if( mAnimating ) {
                mHideWaitingHandler.sendEmptyMessage(1);
            } else if( mHideAnimation ) {
                mHideAnimation = false;
                this.mChooseIndex = -1;
                mTouch.x = -10000;
                mTouch.y = -10000;
            }

        }
    }
    public void setOnTouchLetterChangedListener( OnTouchLetterChangedListener listener) {
        this.mListener = listener;
    }

    /**
     * 計(jì)算出所有貝塞爾曲線上的點(diǎn) 
     * 個(gè)數(shù)為 mMaxBezierLines * 2 = 64
     */
    private void calculateBezierPoints() {
        
        PointF mStart = new PointF();   // 開始點(diǎn)
        PointF mEnd = new PointF();     // 結(jié)束點(diǎn)
        PointF mControl = new PointF(); // 控制點(diǎn)
        
        
        // 計(jì)算第一段紅色部分 貝賽爾曲線的點(diǎn)
        // 開始點(diǎn)
        mStart.x = 0.0f;
        mStart.y = -mMaxBezierWidth;

        // 控制點(diǎn)
        mControl.x = 0.0f;
        mControl.y = -mMaxBezierWidth/2;
        
        // 結(jié)束點(diǎn)
        mEnd.x = - mMaxBezierHeight / 2;
        mEnd.y = - mMaxBezierWidth / 4;
        
        mBezier1[0] = new PointF();
        mBezier1[mMaxBezierLines-1] = new PointF();
        
        mBezier1[0].set(mStart);
        mBezier1[mMaxBezierLines-1].set(mEnd);
        
        for( int i = 1; i< mMaxBezierLines -1; i++ ) {
            
            mBezier1[i] = new PointF();
            
            mBezier1[i].x = calculateBezier( mStart.x, mEnd.x, mControl.x, i / (float) mMaxBezierLines );
            mBezier1[i].y = calculateBezier( mStart.y, mEnd.y, mControl.y, i / (float) mMaxBezierLines );
            
        }

        // 計(jì)算第二段藍(lán)色部分 貝賽爾曲線的點(diǎn)
        mStart.y = -mMaxBezierWidth / 4;
        mStart.x = -mMaxBezierHeight / 2;
        
        mControl.y = 0.0f;
        mControl.x = -mMaxBezierHeight;

        mEnd.y = mMaxBezierWidth / 4;
        mEnd.x = -mMaxBezierHeight / 2;
        
        mBezier2[0] = new PointF();
        mBezier2[mMaxBezierLines-1] = new PointF();

        mBezier2[0].set(mStart);
        mBezier2[mMaxBezierLines-1].set(mEnd);
        
        for( int i = 1; i< mMaxBezierLines -1 ; i++ ) {
            
            mBezier2[i]= new PointF();
            mBezier2[i].x = calculateBezier( mStart.x, mEnd.x, mControl.x,  i / (float) mMaxBezierLines );
            mBezier2[i].y = calculateBezier( mStart.y, mEnd.y, mControl.y,  i / (float) mMaxBezierLines );
        }
    }

    /**
     * 貝塞爾曲線核心算法
     * @param start
     * @param end
     * @param control
     * @param val
     * @return
     * 公式及動(dòng)圖, 維基百科: https://en.wikipedia.org/wiki/B%C3%A9zier_curve
     * 中文可參考此網(wǎng)站: http://blog.csdn.net/likendsl/article/details/7852658
     * 
     */
    private float calculateBezier(float start, float end, float control, float val) {
        
        float t = val;
        float s = 1-t;
        
        float ret = start * s * s + 2 * control * s * t + end * t * t;
        
        return ret;
    }   
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旺罢,一起剝皮案震驚了整個(gè)濱河市旷余,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扁达,老刑警劉巖正卧,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跪解,居然都是意外死亡炉旷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門叉讥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窘行,“玉大人,你說我怎么就攤上這事图仓」蘅” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵救崔,是天一觀的道長惶看。 經(jīng)常有香客問我,道長六孵,這世上最難降的妖魔是什么纬黎? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮狸臣,結(jié)果婚禮上莹桅,老公的妹妹穿的比我還像新娘。我一直安慰自己烛亦,他們只是感情好诈泼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煤禽,像睡著了一般铐达。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檬果,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天瓮孙,我揣著相機(jī)與錄音,去河邊找鬼选脊。 笑死杭抠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恳啥。 我是一名探鬼主播偏灿,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼钝的!你這毒婦竟也來了翁垂?” 一聲冷哼從身側(cè)響起莱褒,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤螺句,失蹤者是張志新(化名)和其女友劉穎成福,沒想到半個(gè)月后困食,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啼肩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年橄妆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祈坠。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呼畸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颁虐,到底是詐尸還是另有隱情,我是刑警寧澤卧须,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布另绩,位于F島的核電站,受9級(jí)特大地震影響花嘶,放射性物質(zhì)發(fā)生泄漏笋籽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一椭员、第九天 我趴在偏房一處隱蔽的房頂上張望车海。 院中可真熱鬧,春花似錦隘击、人聲如沸侍芝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽州叠。三九已至,卻和暖如春凶赁,著一層夾襖步出監(jiān)牢的瞬間咧栗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工虱肄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留致板,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓咏窿,卻偏偏與公主長得像斟或,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翰灾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評(píng)論 25 707
  • 一缕粹、Android開發(fā)初體驗(yàn) 二稚茅、Android與MVC設(shè)計(jì)模式模型對(duì)象存儲(chǔ)著應(yīng)用的數(shù)據(jù)和業(yè)務(wù)邏輯。模型類通常用來...
    為夢(mèng)想戰(zhàn)斗閱讀 886評(píng)論 0 3
  • “篾絡(luò)迎霜野柿紅"平斩,艷而不俗的柿子又在秋風(fēng)瑟瑟中掛滿條條疏枝亚享。 據(jù)說柿子樹能經(jīng)受約零下18度的嚴(yán)寒,可謂堅(jiān)韌不拔绘面。...
    然依紫閱讀 645評(píng)論 10 14
  • 2017年11月18號(hào) 星期天 晴 今天是星期六欺税,終于不用早早起床了,大寶睡到快八點(diǎn)...
    星曦寶貝閱讀 100評(píng)論 0 0
  • 1揭璃、如果你能控制住你的注意力晚凿,你就能更好地掌控自己的生產(chǎn)力。在信息溝通方式多種多樣的今天瘦馍,能有片刻自己的時(shí)光真的是...
    文硯閱讀 165評(píng)論 0 0