这几天在做IM模块,设计图要求做一个类似下图所示的自定义控件。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec. getMode(heightMeasureSpec) ; int finalWidth = 0, finalHeight = 0; // 测量单个字符的宽度和高度 float charWidthAndHeight = paint.measureText( letter.get(0 )) + letterSpace; if (widthMode == MeasureSpec. EXACTLY) { finalWidth = MeasureSpec.getSize (widthMeasureSpec); } else if (widthMode == MeasureSpec.AT_MOST) { finalWidth = (int ) charWidthAndHeight + getPaddingLeft() + getPaddingRight(); } if (heightMode == MeasureSpec.EXACTLY) { finalHeight = MeasureSpec.getSize (heightMeasureSpec); } else if (heightMode == MeasureSpec.AT_MOST) { // 注意measureText的值与 paint.setTextSize的值有关 finalHeight = ( int) charWidthAndHeight * letter .size() + getPaddingBottom() + getPaddingTop(); } setMeasuredDimension(finalWidth, finalHeight); }
@Override protected void onDraw(Canvas canvas) { //27 个字符(包含 #)均分整个视图的高度,例如视图高度为 270,270/27 均分之后,每个字符的y坐标为10。 y = getHeight() / letter.size() ; for ( int i = 0 ; i < letter .size(); i++) { Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt(); // 计算绘制字符 X的坐标,整个视图宽度的一半减去字符宽度的一半 x = getWidth() / 2 - ( int) paint.measureText(letter .get(i)) / 2; // int correctY=y*i+y; // int correctY = y * i + y / 2; int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2; String tempString = isLetterUpper ? letter.get(i).toUpperCase() : letter .get(i).toLowerCase(); canvas.drawText(tempString , x, correctY, paint ); // canvas.drawLine(0, y * i + y, 100, y * i + y, paint); } }
第二种是int correctY=y*i+y/2。再运行程序,发现还是有点偏移。我是处女座有强迫症,果断不能忍啊。
刚看到这个式子可能不是很理解。我给大家解释一下,假设我们要绘制D这个字符。如图所示
/** * 回调接口。 */ public interface OnCurrentLetterListener { void showCurrentLetter(String currentLetter); void hideCurrentLetter (); }
@Override public boolean onTouchEvent(MotionEvent event) { float y = event.getY(); // 获取当前侧滑栏字母的下标 float currentLetterIndex = y / getHeight() * letter.size(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: if (onCurrentLetterListener != null) { //对上下边界对限制 if (currentLetterIndex >= letter.size()) { currentLetterIndex = letter.size() - 1 ; } else if (currentLetterIndex < 0) { currentLetterIndex = 0; } onCurrentLetterListener .showCurrentLetter(letter.get(( int) currentLetterIndex)); } return true; case MotionEvent. ACTION_UP: if (onCurrentLetterListener != null) { onCurrentLetterListener.hideCurrentLetter() ; } return true; default: return true; } }
package per.edward.ui; import android.content.Context ; import android.content.res.TypedArray ; import android.graphics.Canvas ; import android.graphics.Color ; import android.graphics.Paint ; import android.util.AttributeSet ; import android.view.MotionEvent ; import android.view.View ; import java.util.ArrayList ; import java.util.Arrays ; import java.util.List ; /** * 侧滑栏视图 * Created by Edward on 2016/4/27. */ public class SideBar extends View { private String[] letterStrings = { "#", "A" , "B", "C", "D" , "E", "F" , "G", "H", "I" , "J", "K", "L" , "M", "N", "O" , "P", "Q", "R", "S" , "T", "U", "V" , "W", "S", "Y" , "Z"} ; private Paint paint; // 字母列表 private List<String> letter; // 绘制字母的 x,y坐标 private int x, y ; // 字母的间距 private int letterSpace = 0 ; // 字母是否大写 private boolean isLetterUpper = true; private OnCurrentLetterListener onCurrentLetterListener; public void setLetter (List<String> letter) { this .letter = letter ; } // 设置回调接口 public void setOnCurrentLetterListener(OnCurrentLetterListener onCurrentLetterListener) { this .onCurrentLetterListener = onCurrentLetterListener ; } public SideBar(Context context) { this (context, null) ; } public SideBar(Context context, AttributeSet attrs) { super (context, attrs) ; if ( letter == null ) { letter = new ArrayList<>() ; letter = Arrays.asList(letterStrings ); } TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SideBar) ; letterSpace = typedArray.getInt(R.styleable.SideBar_SB_Letter_Space, 0); isLetterUpper = typedArray.getBoolean(R.styleable.SideBar_SB_Is_Letter_Upper, true); typedArray.recycle() ; paint = new Paint() ; paint .setColor(Color.BLACK) ; paint .setAntiAlias(true) ; paint .setTextSize(30) ;//3CAC48 paint .setColor(Color.parseColor("#ffffff" )); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec. getMode(heightMeasureSpec) ; int finalWidth = 0, finalHeight = 0; // 测量单个字符的宽度和高度 float charWidthAndHeight = paint.measureText( letter.get(0 )) + letterSpace; if (widthMode == MeasureSpec. EXACTLY) { finalWidth = MeasureSpec.getSize(widthMeasureSpec); } else if (widthMode == MeasureSpec.AT_MOST) { finalWidth = (int) charWidthAndHeight + getPaddingLeft() + getPaddingRight(); } if (heightMode == MeasureSpec.EXACTLY) { finalHeight = MeasureSpec.getSize(heightMeasureSpec) ; } else if (heightMode == MeasureSpec.AT_MOST) { // 注意measureText的值与 paint.setTextSize的值有关 finalHeight = ( int) charWidthAndHeight * letter .size() + getPaddingBottom() + getPaddingTop(); } // Log.e("--------------->", MeasureSpec.getSize(widthMeasureSpec) + " " + MeasureSpec.getSize(heightMeasureSpec)); setMeasuredDimension(finalWidth , finalHeight); } @Override protected void onDraw(Canvas canvas) { setBackgroundColor(Color.parseColor ("#31b2f7" )); //27 个字符(包含 #)均分整个视图的高度,例如视图高度为 270,270/27 均分之后,每个字符的 y坐标为10 y = getHeight() / letter.size() ; Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt(); for ( int i = 0 ; i < letter .size(); i++) { // 计算绘制字符 X的坐标,整个视图宽度的一半减去字符宽度的一半 x = getWidth() / 2 - ( int) paint.measureText(letter .get(i)) / 2; // int correctY=y*i+y; // int correctY = y * i + y / 2; int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2; String tempString = isLetterUpper ? letter.get(i).toUpperCase() : letter .get(i).toLowerCase(); canvas.drawText(tempString , x, ((y * i) + ( y * i + y)) / 2 , paint) ; canvas.drawLine( 0, y * i + y, 100, y * i + y, paint); } } @Override public boolean onTouchEvent(MotionEvent event) { float y = event.getY(); // 获取当前侧滑栏字母的下标 float currentLetterIndex = y / getHeight() * letter.size(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: if ( onCurrentLetterListener != null ) { //对上下边界对限制 if (currentLetterIndex >= letter.size()) { currentLetterIndex = letter.size() - 1 ; } else if (currentLetterIndex < 0) { currentLetterIndex = 0; } onCurrentLetterListener .showCurrentLetter(letter.get(( int) currentLetterIndex)); } return true; case MotionEvent. ACTION_UP: if ( onCurrentLetterListener != null ) { onCurrentLetterListener.hideCurrentLetter() ; } return true; default: return true; } } /** * 回调接口。 */ public interface OnCurrentLetterListener { void showCurrentLetter(String currentLetter); void hideCurrentLetter (); } }
<? xml version="1.0" encoding= "utf-8"?> <RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android" xmlns: app="http://schemas.android.com/apk/res-auto" android :layout_width="match_parent" android :layout_height="match_parent"> <TextView android :id="@+id/txt_show_current_letter" android :layout_width="100dp" android :layout_height="wrap_content" android :layout_centerInParent="true" android :background="#3CAC48" android :gravity="center_vertical|center_horizontal" android :text="A" android :textColor="#ffffff" android :textSize="50dp" android :visibility="gone" /> <per.edward.ui.SideBar android :id="@+id/side_bar" android :layout_width="30dp" android :layout_height="150dp" android :layout_alignParentRight="true" android :layout_centerVertical="true" /> </RelativeLayout>
package per.edward.ui; import android.app.Activity ; import android.os.Bundle ; import android.view.View ; import android.widget.TextView ; /** * author:Edward */ public class MainActivity extends Activity { private TextView txtShowCurrentLetter; private SideBar sideBar; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState) ; setContentView(R.layout. activity_main); txtShowCurrentLetter = (TextView) findViewById(R.id.txt_show_current_letter ); sideBar = (SideBar) findViewById(R.id. side_bar); setCallbackInterface() ; } /** * 设置回调接口 */ public void setCallbackInterface() { // 回调接口 sideBar .setOnCurrentLetterListener(new SideBar.OnCurrentLetterListener() { @Override public void showCurrentLetter(String currentLetter) { txtShowCurrentLetter .setVisibility(View.VISIBLE) ; txtShowCurrentLetter.setText(currentLetter) ; } @Override public void hideCurrentLetter() { txtShowCurrentLetter.setVisibility(View. GONE); } }); } }
package per.edward.ui; /** * 联系人列表实体类 * Created by Edward on 2016/4/26. */ public class ContactsModel { private String firstLetter; private String name; public String getFirstLetter() { return firstLetter; } public void setFirstLetter(String firstLetter) { this .firstLetter = firstLetter ; } public String getName() { return name; } public void setName(String name) { this .name = name ; } }
<? 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/txt_letter_category" android :layout_width="match_parent" android :layout_height="30dp" android :background="#d7d6d6" android :padding="5dp" android :text="A" android :textColor="#000000" android :textSize="16dp" /> <LinearLayout android :layout_width="match_parent" android :layout_height="wrap_content" android :background="#ffffff" android :orientation="horizontal"> <ImageView android :id="@+id/image" android :layout_width="50dp" android :layout_height="50dp" android :padding="5dp" android :src="@mipmap/ic_launcher" /> <TextView android :id="@+id/txt_name" android :layout_width="wrap_content" android :layout_height="wrap_content" android :layout_gravity="center_vertical" android :text="姓名 " android :textSize="14dp" /> </LinearLayout> </LinearLayout>
package per.edward.ui; import android.content.Context ; import android.view.LayoutInflater ; import android.view.View ; import android.view.ViewGroup ; import android.widget.BaseAdapter ; import android.widget.TextView ; import java.util.HashMap ; import java.util.List ; import java.util.Map ; /** * 侧栏适配器 * Created by Edward on 2016/4/27. */ public class SideBarAdapter extends BaseAdapter { // 是否第一个 Item的 private boolean isFirstItemLetter = true; // 记录是否显示字母标题,键为字母,值为下标 private Map<String, Integer> map; private List<ContactsModel> mDatas; private int mItemLayoutId ; private Context context; public SideBarAdapter(Context mContext , List<ContactsModel> mDatas, int mItemLayoutId) { this .mDatas = mDatas ; map = new HashMap<>() ; this. mItemLayoutId = mItemLayoutId; this. context = mContext; traverseList() ; } /** * 遍历列表 * 由于传进来的 mDatas是一个已排好序的列表,遍历整个列表,每遇到分类的第一个字母就把下标记录下来 */ public void traverseList() { // 获取初始值 String current = mDatas.get(0 ).getFirstLetter(); for ( int i = 0 ; i < mDatas .size(); i++) { char tempChar = mDatas.get(i).getFirstLetter().charAt(0) ; String tempFirstLetter = mDatas.get(i).getFirstLetter(); if (tempFirstLetter.equals(current) || (tempChar < 'A' || tempChar > 'Z' )) { if ( isFirstItemLetter) { map.put(current , i); } } else { //更新初始值 current = mDatas .get(i).getFirstLetter(); map.put(current , i); } isFirstItemLetter = false; } } /** * 获取当前字母的下标 * * @return */ public int getCurrentLetterPosition(String currentLetter) { if (map.get(currentLetter) != null) { return map.get(currentLetter); } else return - 1; } @Override public int getCount() { return mDatas.size(); } @Override public Object getItem( int position) { return mDatas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView( int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { viewHolder = new ViewHolder() ; convertView = LayoutInflater.from( context).inflate(mItemLayoutId , null); viewHolder. txtFirstLetter = (TextView) convertView.findViewById(R.id.txt_letter_category) ; viewHolder. txtName = (TextView) convertView.findViewById(R.id.txt_name) ; convertView.setTag(viewHolder) ; } else { viewHolder = (ViewHolder) convertView.getTag(); } // 判断是否显示字母标题 if (map.get( mDatas.get(position).getFirstLetter()) != null && map.get(mDatas .get(position).getFirstLetter()).equals(position)) { viewHolder.txtFirstLetter .setVisibility(View.VISIBLE) ; viewHolder.txtFirstLetter.setText( mDatas.get(position).getFirstLetter()); } else { viewHolder.txtFirstLetter .setVisibility(View.GONE) ; } viewHolder.txtName .setText(mDatas.get(position).getName()) ; return convertView ; } public class ViewHolder { TextView txtFirstLetter , txtName; } }
<? xml version="1.0" encoding= "utf-8"?> <RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android" xmlns: app="http://schemas.android.com/apk/res-auto" android :layout_width="match_parent" android :layout_height="match_parent" android :background="#ffffff"> <ListView android :id="@+id/list_view" android :layout_width="match_parent" android :layout_height="match_parent" /> <TextView android :id="@+id/txt_show_current_letter" android :layout_width="100dp" android :layout_height="wrap_content" android :layout_centerInParent="true" android :background="#3CAC48" android :gravity="center_vertical|center_horizontal" android :text="A" android :textColor="#ffffff" android :textSize="50dp" android :visibility="gone" /> <per.edward.ui.SideBar android :id="@+id/side_bar" android :layout_width="30dp" android :layout_height="match_parent" android :layout_alignParentRight="true" android :layout_centerVertical="true" /> </RelativeLayout>
package per.edward.ui; import android.os.Bundle ; import android.support.v7.app.AppCompatActivity ; import android.view.View ; import android.widget.ListView ; import android.widget.TextView ; import java.util.ArrayList ; import java.util.Collections ; import java.util.Comparator ; import java.util.HashSet ; import java.util.List ; import java.util.Set ; import opensource.jpinyin.PinyinHelper ; /** * author:Edward * 此 demo的博客地址:http://blog.csdn.net/u012814441 */ public class MainActivity extends AppCompatActivity { private ListView listView; private TextView txtShowCurrentLetter; private SideBar sideBar; private SideBarAdapter myAdapter; // private List<ContactsModel> list; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState) ; setContentView(R.layout. activity_main); txtShowCurrentLetter = (TextView) findViewById(R.id.txt_show_current_letter ); sideBar = (SideBar) findViewById(R.id. side_bar); listView = (ListView) findViewById(R.id. list_view); setCallbackInterface() ; List<ContactsModel> list = initData() ; chineseToPinyin(list) ; // 将联系人列表的标题字母排序 Collections. sort(list , new Comparator<ContactsModel>() { @Override public int compare(ContactsModel lhs, ContactsModel rhs) { return lhs.getFirstLetter().compareTo(rhs.getFirstLetter()) ; } }); // 将联系人列表的标题字母放到 List<String>列表中,准备数据去重 List<String> getLetter = new ArrayList<>(); for ( int i = 0 ; i < list.size(); i++) { getLetter.add(list.get(i).getFirstLetter()); } // 数据去重 getLetter = removeDuplicate(getLetter) ; // 将联系人列表的字母标题排序 Collections. sort(getLetter , new Comparator<String>() { @Override public int compare(String lhs, String rhs) { return lhs.compareTo(rhs) ; } }); // 设置已排序好的标题 sideBar .setLetter(getLetter); myAdapter = new SideBarAdapter( this, list, R.layout.adapter_side_bar) ; listView .setAdapter(myAdapter) ; } /** * 将中文转化为拼音 */ public void chineseToPinyin(List<ContactsModel> list) { for (int i = 0; i < list.size() ; i++) { ContactsModel contactsModel1 = list.get(i); // 将汉字转换为拼音 String pinyinString = PinyinHelper.getShortPinyin(list.get(i).getName()) ; // 将拼音字符串转换为大写拼音 String upperCasePinyinString = String.valueOf(pinyinString.charAt( 0)).toUpperCase(); // 获取大写拼音字符串的第一个字符 char tempChar = upperCasePinyinString.charAt( 0); if (tempChar < 'A' || tempChar > 'Z' ) { contactsModel1.setFirstLetter( "#"); } else { contactsModel1.setFirstLetter(String.valueOf(tempChar)) ; } } } /** * 设置回调接口 */ public void setCallbackInterface() { // 回调接口 sideBar .setOnCurrentLetterListener(new SideBar.OnCurrentLetterListener() { @Override public void showCurrentLetter(String currentLetter) { txtShowCurrentLetter .setVisibility(View.VISIBLE) ; txtShowCurrentLetter.setText(currentLetter) ; int position = myAdapter.getCurrentItemPosition(currentLetter); if (position != -1 ) listView.setSelection(position) ; } @Override public void hideCurrentLetter() { txtShowCurrentLetter.setVisibility(View. GONE); } }); } /** * 初始化数据 */ public List<ContactsModel> initData() { List<ContactsModel> list = new ArrayList<>(); ContactsModel contactsModel ; String[] nameStrings = { "覃" , "岑 ", "$ 来啊,来互相伤害啊 ", "疍姬" , "梵蒂冈 ", " 亳州", "佟" , "郄 ", " 张三", "Edward", " 李四", "萌萌哒" , "霾耷 ", " 离散", "赵信" , "啦啦 ", " 辣妹子", "嗷嗷" , "妹妹 ", "']asd" , "%Hello"} ; for ( int i = 0 ; i < nameStrings.length ; i++) { contactsModel = new ContactsModel() ; contactsModel.setName(nameStrings[i]) ; list.add(contactsModel) ; } return list; } /** * 去重数据 * * @param list * @param <T> * @return */ public <T> List< T> removeDuplicate (List<T> list) { Set<T > h = new HashSet<>(list) ; list.clear() ; list.addAll(h) ; return list ; } }
7、 最终效果图