快速索引在應(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)是文字左下角的位置
- 第一個(gè)字母的x 坐標(biāo)則等于單元格寬度的一半減去文字寬度的一半匀谣,由于所有字母離左邊的距離
一樣照棋,所以x 不變int x = cellWidth/2 - textWidth/2 - 第一個(gè)字母的y 坐標(biāo)則等于單元格高度的一半加上文字,第二個(gè)字母需要加上一個(gè)單元格的寬度
由此類推int y = cellHeight/2 +textHeight/2 +i*cellHeight - 單元格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)目吗伤,TinyPinyin,pinyin4j
- TinyPinyin:https://github.com/promeG/TinyPinyin
- pinyin4j:https://github.com/belerweb/pinyin4j
漢字轉(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;
}
}