/** * Created by admin on 2015/9/22. */ public class PhoneBookActivity extends Activity { private static final String TAG = "PhoneBookActivity"; private IndexableListView phoneListView; // 电话本数据List ArrayList<PhoneInfo> phoneInfosList; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.test_phone_main); phoneListView = (IndexableListView) findViewById(R.id.phoneListView); phoneInfosList = new ArrayList<>(); // 初始化数据 getPhoneContacts(); // 按字典序进行排序 Collections.sort(phoneInfosList, new Comparator<PhoneInfo>() { @Override public int compare(PhoneInfo lhs, PhoneInfo rhs) { return lhs.getFirstLetter() - rhs.getFirstLetter(); } }); // 判断ListView中的每个字母索引的头部 PhoneInfo.tagIsHead(phoneInfosList); PhoneAdapter adapter = new PhoneAdapter(this); phoneListView.setAdapter(adapter); phoneListView.setFastScrollEnabled(true); // 设置快速滑动,这一步不可少 } private class PhoneAdapter extends BaseAdapter implements SectionIndexer { // 定义索引列表 private String mSections = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; LayoutInflater layoutInflater; public PhoneAdapter (Context context) { layoutInflater = LayoutInflater.from(context); } @Override public int getCount() { return phoneInfosList != null ? phoneInfosList.size() : 0; } @Override public PhoneInfo getItem(int position) { return phoneInfosList != null ? phoneInfosList.get(position) : null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = layoutInflater.inflate(R.layout.phone_item, parent, false); viewHolder = new ViewHolder(); viewHolder.img_phonePhoto = (ImageView) convertView.findViewById(R.id.img_phonePhoto); viewHolder.txt_contactName= (TextView) convertView.findViewById(R.id.txt_contactName); viewHolder.txt_phoneNumber= (TextView) convertView.findViewById(R.id.txt_phoneNumber); viewHolder.txtHeadLetter = (TextView) convertView.findViewById(R.id.txtHeadLetter); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } try { PhoneInfo info = getItem(position); viewHolder.img_phonePhoto.setImageBitmap(info.getContactPhoto()); viewHolder.txt_contactName.setText(info.getContactName()); viewHolder.txt_phoneNumber.setText(info.getPhoneNumber()); if (info.isHead()) { viewHolder.txtHeadLetter.setText(String.valueOf(info.getFirstLetter())); viewHolder.txtHeadLetter.setVisibility(View.VISIBLE); } else { viewHolder.txtHeadLetter.setVisibility(View.GONE); } } catch (Exception e) { Log.e(TAG, e.toString()); } return convertView; } @Override public int getPositionForSection(int section) { // 如果当前部分没有item,则之前的部分将被选择 for (int i = section; i >= 0; i--) { for (int j = 0; j < getCount(); j++) { if (i == 0) { // # // For numeric section 数字 for (int k = 0; k <= 9; k++) {// 1...9 // 字符串第一个字符与1~9之间的数字进行匹配 if (StringMatcher.match(String.valueOf(getItem(j).getFirstLetter()), String.valueOf(k))) return j; } } else { // A~Z // getItem即获取ListView中的数据,并与索引进行匹配,找到Item的位置 if (StringMatcher.match(String.valueOf(getItem(j).getFirstLetter()), String.valueOf(mSections.charAt(i)))) return j; } } } return 0; } @Override public int getSectionForPosition(int position) { return 0; } @Override public Object[] getSections() { String[] sections = new String[mSections.length()]; for (int i = 0; i < mSections.length(); i++) sections[i] = String.valueOf(mSections.charAt(i)); return sections; } class ViewHolder { ImageView img_phonePhoto; TextView txt_contactName; TextView txt_phoneNumber; TextView txtHeadLetter; } } // 获取通讯录: /**得到手机通讯录联系人信息**/ private void getPhoneContacts() { ContentResolver resolver = this.getContentResolver(); // 获取手机联系人 Cursor phoneCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PhoneInfo.PHONES_PROJECTION, null, null, null); if (phoneCursor != null) { while (phoneCursor.moveToNext()) { //得到手机号码 String phoneNumber = phoneCursor.getString(PhoneInfo.PHONES_NUMBER_INDEX); //当手机号码为空的或者为空字段 跳过当前循环 if (TextUtils.isEmpty(phoneNumber)) continue; //得到联系人名称 String contactName = phoneCursor.getString(PhoneInfo.PHONES_DISPLAY_NAME_INDEX); //得到联系人ID Long contactid = phoneCursor.getLong(PhoneInfo.PHONES_CONTACT_ID_INDEX); //得到联系人头像ID Long photoid = phoneCursor.getLong(PhoneInfo.PHONES_PHOTO_ID_INDEX); phoneInfosList.add(new PhoneInfo(phoneNumber, contactName, contactid, photoid, resolver, this)); } phoneCursor.close(); } } }
<?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"> <com.woozzu.android.widget.IndexableListView android:id="@+id/phoneListView" android:layout_width="match_parent" android:layout_height="match_parent"></com.woozzu.android.widget.IndexableListView> </LinearLayout>
/** * 自定义索引列表 */ public class IndexableListView extends ListView { // 是否允许快速滑动 private boolean mIsFastScrollEnabled = false; // 索引条 private IndexScroller mScroller = null; private GestureDetector mGestureDetector = null; public IndexableListView(Context context) { super(context); } public IndexableListView(Context context, AttributeSet attrs) { super(context, attrs); } public IndexableListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean isFastScrollEnabled() { return mIsFastScrollEnabled; } // 初始化Scroller @Override public void setFastScrollEnabled(boolean enabled) { mIsFastScrollEnabled = enabled; if (mIsFastScrollEnabled) { if (mScroller == null) mScroller = new IndexScroller(getContext(), this); } else { if (mScroller != null) { mScroller.hide(); mScroller = null; } } } @Override public void draw(Canvas canvas) { super.draw(canvas); // 调用Scroller的绘制函数 // Overlay index bar if (mScroller != null) mScroller.draw(canvas); } // 处理触摸事件 @Override public boolean onTouchEvent(MotionEvent ev) { // Intercept ListView's touch event // 如果触摸事件发生在Scroller上,则屏蔽掉ListView上的触摸事件 if (mScroller != null && mScroller.onTouchEvent(ev)) return true; // 当ListView发生滑动时,显示Scroller if (mGestureDetector == null) { // 创建一个GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 显示索引条 mScroller.show(); return super.onFling(e1, e2, velocityX, velocityY); } }); } mGestureDetector.onTouchEvent(ev); return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } // 设置Adapter @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mScroller != null) mScroller.setAdapter(adapter); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mScroller != null) mScroller.onSizeChanged(w, h, oldw, oldh); } }
/** * 右侧的索引条 */ public class IndexScroller { private float mIndexbarWidth; // 索引条宽度 private float mIndexbarMargin; // 索引条外边距 private float mPreviewPadding; // private float mDensity; // 密度 private float mScaledDensity; // 缩放密度 private float mAlphaRate; // 透明度 private int mState = STATE_HIDDEN; // 状态 private int mListViewWidth; // ListView宽度 private int mListViewHeight; // ListView高度 private int mCurrentSection = -1; // 当前部分 private boolean mIsIndexing = false; // 是否正在索引 private ListView mListView = null; private SectionIndexer mIndexer = null; private String[] mSections = null; private RectF mIndexbarRect; // 4种显示状态(已隐藏、正在显示、已显示、正在隐藏) private static final int STATE_HIDDEN = 0; private static final int STATE_SHOWING = 1; private static final int STATE_SHOWN = 2; private static final int STATE_HIDING = 3; public IndexScroller(Context context, ListView lv) { mDensity = context.getResources().getDisplayMetrics().density; mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity; mListView = lv; setAdapter(mListView.getAdapter()); // 设置索引条宽度 mIndexbarWidth = 20 * mDensity; // 索引条宽度 mIndexbarMargin = 10 * mDensity;// 索引条间距 mPreviewPadding = 5 * mDensity; // 内边距 } // Scroller控件绘制函数 public void draw(Canvas canvas) { if (mState == STATE_HIDDEN) return; // mAlphaRate determines the rate of opacity(透明度) Paint indexbarPaint = new Paint(); indexbarPaint.setColor(Color.BLACK); indexbarPaint.setAlpha((int) (64 * mAlphaRate)); // 设置抗锯齿效果 indexbarPaint.setSubpixelText(true); indexbarPaint.setAntiAlias(true); // 画右侧字母索引的圆角矩形 canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint); if (mSections != null && mSections.length > 0) { // Preview is shown when mCurrentSection is set // 绘制选中时中间突出显示的TextView if (mCurrentSection >= 0) { Paint previewPaint = new Paint(); // 用来绘画所以条背景的画笔 previewPaint.setColor(Color.BLACK);// 设置画笔颜色为黑色 previewPaint.setAlpha(96); // 设置透明度 previewPaint.setAntiAlias(true);// 设置抗锯齿 previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0)); // 设置阴影层 Paint previewTextPaint = new Paint(); // 用来绘画索引字母的画笔 previewTextPaint.setColor(Color.WHITE); // 设置画笔为白色 previewTextPaint.setAntiAlias(true); // 设置抗锯齿 previewTextPaint.setTextSize(50 * mScaledDensity); // 设置字体大小 // 文本的宽度 float previewTextWidth = previewTextPaint.measureText(mSections[mCurrentSection]); // 计算字体的大小 float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent(); RectF previewRect = new RectF( (mListViewWidth - previewSize) / 2, (mListViewHeight - previewSize) / 2, (mListViewWidth - previewSize) / 2 + previewSize, (mListViewHeight - previewSize) / 2 + previewSize); // 中间索引的那个框 canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint); // 绘画索引字母 canvas.drawText( mSections[mCurrentSection], previewRect.left + (previewSize - previewTextWidth) / 2 - 1, previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint); } // 绘画右侧索引条的字母 Paint indexPaint = new Paint(); indexPaint.setColor(Color.WHITE); indexPaint.setAlpha((int) (255 * mAlphaRate)); indexPaint.setAntiAlias(true); indexPaint.setTextSize(12 * mScaledDensity); float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length; float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint.ascent())) / 2; for (int i = 0; i < mSections.length; i++) { float paddingLeft = (mIndexbarWidth - indexPaint.measureText(mSections[i])) / 2; canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft, mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint); } } } // 响应触摸事件 public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 按下,开始索引 // If down event occurs inside index bar region, start indexing if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) { setState(STATE_SHOWN); // It demonstrates that the motion event started from index bar mIsIndexing = true; // Determine which section the point is in, and move the list to // that section mCurrentSection = getSectionByPoint(ev.getY()); mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection)); return true; } break; case MotionEvent.ACTION_MOVE: // 移动 if (mIsIndexing) { // If this event moves inside index bar if (contains(ev.getX(), ev.getY())) { // Determine which section the point is in, and move the // list to that section mCurrentSection = getSectionByPoint(ev.getY()); mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection)); } return true; } break; case MotionEvent.ACTION_UP: // 抬起 if (mIsIndexing) { mIsIndexing = false; mCurrentSection = -1; } if (mState == STATE_SHOWN) setState(STATE_HIDING); break; } return false; } public void onSizeChanged(int w, int h, int oldw, int oldh) { mListViewWidth = w; mListViewHeight = h; // left, top, right , bottom mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth, mIndexbarMargin, w - mIndexbarMargin, h - mIndexbarMargin); } // 显示 public void show() { if (mState == STATE_HIDDEN) setState(STATE_SHOWING); else if (mState == STATE_HIDING) setState(STATE_HIDING); } // 隐藏 public void hide() { if (mState == STATE_SHOWN) setState(STATE_HIDING); } // 注意此限制,所以自定义Adapter一定注意implement SectionIndexer接口 public void setAdapter(Adapter adapter) { if (adapter instanceof SectionIndexer) { mIndexer = (SectionIndexer) adapter; mSections = (String[]) mIndexer.getSections(); } } // 设置显示状态 private void setState(int state) { if (state < STATE_HIDDEN || state > STATE_HIDING) return; // 最终都是通过Handler进行更新 mState = state; switch (mState) { case STATE_HIDDEN: // Cancel any fade effect // 取消渐退的效果 mHandler.removeMessages(0); break; case STATE_SHOWING: // Start to fade in // 开始渐进效果 mAlphaRate = 0; fade(0); break; case STATE_SHOWN: // Cancel any fade effect // 取消渐退的效果 mHandler.removeMessages(0); break; case STATE_HIDING: // Start to fade out after three seconds // 隐藏3秒钟 mAlphaRate = 1; fade(3000); break; } } // 判断触摸点是否在Scroller控件上 private boolean contains(float x, float y) { // Determine if the point is in index bar region, which includes the // right margin of the bar return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height()); } private int getSectionByPoint(float y) { if (mSections == null || mSections.length == 0) return 0; if (y < mIndexbarRect.top + mIndexbarMargin) return 0; if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin) return mSections.length - 1; return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect .height() - 2 * mIndexbarMargin) / mSections.length)); } // 延时显示fade效果 private void fade(long delay) { mHandler.removeMessages(0); mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay); } // 处理进度条View的更新操作 private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (mState) { case STATE_SHOWING: // Fade in effect // 淡进效果 mAlphaRate += (1 - mAlphaRate) * 0.2; if (mAlphaRate > 0.9) { mAlphaRate = 1; setState(STATE_SHOWN); } // 请求重绘ListView mListView.invalidate(); fade(10); break; case STATE_SHOWN: // If no action, hide automatically setState(STATE_HIDING); break; case STATE_HIDING: // Fade out effect // 淡出效果 mAlphaRate -= mAlphaRate * 0.2; if (mAlphaRate < 0.1) { mAlphaRate = 0; setState(STATE_HIDDEN); } mListView.invalidate(); fade(10); break; } } }; }
4、ListView的工具类,用来匹配字符:
public class StringMatcher { // 这些变量是韩文,小巫也不知道是什么意思,有谁懂的马上联系我啊 private final static char KOREAN_UNICODE_START = '가'; // 韩文字符编码开始? private final static char KOREAN_UNICODE_END = '힣'; // 韩文字符编码结束? private final static char KOREAN_UNIT = '까' - '가'; // 不知道是啥? // 韩文的一些字符初始化 private final static char[] KOREAN_INITIAL = { 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' }; /** * 字符匹配 * @param value 需要keyword匹配的字符串 * @param keyword #ABCDEFGHIJKLMNOPQRSTUVWXYZ中的一个 * @return */ public static boolean match(String value, String keyword) { if (value == null || keyword == null) return false; if (keyword.length() > value.length()) return false; int i = 0, j = 0; do { // 如果是韩文字符并且在韩文初始数组里面 if (isKorean(value.charAt(i)) && isInitialSound(keyword.charAt(j))) { if (keyword.charAt(j) == getInitialSound(value.charAt(i))) { i++; j++; } else if (j > 0) break; else i++; } else { // 逐个字符匹配 if (keyword.charAt(j) == value.charAt(i)) { i++; j++; } else if (j > 0) break; else i++; } } while (i < value.length() && j < keyword.length()); // 如果最后j等于keyword的长度说明匹配成功 return (j == keyword.length()) ? true : false; } // 判断字符是否在韩文字符编码范围内 private static boolean isKorean(char c) { if (c >= KOREAN_UNICODE_START && c <= KOREAN_UNICODE_END) return true; return false; } // 判断是否在韩文字符里面 private static boolean isInitialSound(char c) { for (char i : KOREAN_INITIAL) { if (c == i) return true; } return false; } // 获得韩文初始化字符数组里面的一个字符 private static char getInitialSound(char c) { return KOREAN_INITIAL[(c - KOREAN_UNICODE_START) / KOREAN_UNIT]; } }
/** * Created by admin on 2015/9/21. */ public class PhoneInfo { /** 查询通讯录相关参数 **/ public static final String[] PHONES_PROJECTION = new String[] { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.PHOTO_ID, ContactsContract.CommonDataKinds.Phone.CONTACT_ID }; public final static int PHONES_DISPLAY_NAME_INDEX = 0; public final static int PHONES_NUMBER_INDEX = 1; public final static int PHONES_PHOTO_ID_INDEX = 2; public final static int PHONES_CONTACT_ID_INDEX = 3; private String contactName; private String phoneNumber; private Long contactid; private Long photoid; private Bitmap contactPhoto; private char firstLetter; // contactName的首字母(大写) private boolean isHead; // 判断是否是一个索引的首元素 // 获取contactName首字母的相关参数 private final static int[] li_SecPosValue = { 1601, 1637, 1833, 2078, 2274, 2302, 2433, 2594, 2787, 3106, 3212, 3472, 3635, 3722, 3730, 3858, 4027, 4086, 4390, 4558, 4684, 4925, 5249, 5590 }; private final static String[] lc_FirstLetter = { "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z" }; public PhoneInfo() { } public PhoneInfo(String phoneNumber, String contactName, Long contactid, Long photoid, ContentResolver resolver, Context context) { this.phoneNumber = phoneNumber; this.contactName = contactName; this.contactid = contactid; this.photoid = photoid; getContactPhoto(photoid, resolver, context); firstLetter = getFirstLetter(contactName).charAt(0); isHead = false; } // 根据photoid获取图片Bitmap public Bitmap getContactPhoto(Long photoid, ContentResolver resolver, Context context) { //photoid大于0 表示联系人有头像 如果没有给此人设置头像则给他一个默认的 if(photoid > 0 ) { Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactid); InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(resolver, uri); contactPhoto = BitmapFactory.decodeStream(input); }else { contactPhoto = BitmapFactory.decodeResource(context.getResources(), R.mipmap.error_photo); } return contactPhoto; } // 判断每个string的首字母 /** * 取得给定汉字的首字母,即声母 * @param chinese 给定的汉字 * @return 给定汉字的声母 */ private String getFirstLetter(String chinese) { if (chinese == null || chinese.trim().length() == 0) { return ""; } chinese = this.conversionStr(chinese, "GB2312", "ISO8859-1"); if (chinese.length() > 1) { // 判断是不是汉字 int li_SectorCode = (int) chinese.charAt(0); // 汉字区码 int li_PositionCode = (int) chinese.charAt(1); // 汉字位码 li_SectorCode = li_SectorCode - 160; li_PositionCode = li_PositionCode - 160; int li_SecPosCode = li_SectorCode * 100 + li_PositionCode; // 汉字区位码 if (li_SecPosCode > 1600 && li_SecPosCode < 5590) { for (int i = 0; i < 23; i++) { if (li_SecPosCode >= li_SecPosValue[i] && li_SecPosCode < li_SecPosValue[i + 1]) { chinese = lc_FirstLetter[i]; break; } } } else { // 非汉字字符,如图形符号或ASCII码 chinese = this.conversionStr(chinese, "ISO8859-1", "GB2312"); chinese = chinese.substring(0, 1); } } return chinese; } /** * 字符串编码转换 * @param str 要转换编码的字符串 * @param charsetName 原来的编码 * @param toCharsetName 转换后的编码 * @return 经过编码转换后的字符串 */ private String conversionStr(String str, String charsetName,String toCharsetName) { try { str = new String(str.getBytes(charsetName), toCharsetName); } catch (UnsupportedEncodingException ex) { System.out.println("字符串编码转换异常:" + ex.getMessage()); } return str; } /** 为一个List<PhoneInfo> 提供一个函数判断该元素是否一个字母索引的首字母 **/ public static void tagIsHead (List<PhoneInfo> phoneInfos) { boolean[] isHead = new boolean[27]; int index; // 先清除所有标志 for (PhoneInfo info : phoneInfos) { info.isHead = false; index = info.firstLetter >= 'A' && info.firstLetter <= 'Z' ? info.firstLetter - 'A' : 26; if (!isHead[index]) { isHead[index] = true; info.isHead = true; } } } /** 一些列Setter函数 **/ public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } public void setContactName(String contactName) { this.contactName = contactName; } public void setContactid(Long contactid) { this.contactid = contactid; } public void setPhotoid(Long photoid, ContentResolver resolver, Context context) { this.photoid = photoid; getContactPhoto(photoid, resolver, context); } public void setContactPhoto(Bitmap contactPhoto) { this.contactPhoto = contactPhoto; } public void setFirstLetter(char firstLetter) { this.firstLetter = firstLetter; } public void setIsHead(boolean isHead) { this.isHead = isHead; } /** 一些getter函数 **/ public String getPhoneNumber() { return phoneNumber; } public String getContactName() { return contactName; } public Long getContactid() { return contactid; } public Long getPhotoid() { return photoid; } public Bitmap getContactPhoto() { return contactPhoto; } public char getFirstLetter() { return firstLetter; } public boolean isHead() { return isHead; } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="15dp" > <TextView android:id="@+id/txtHeadLetter" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/grey21" android:textColor="@color/white" android:gravity="left|center_vertical" android:paddingLeft="10dp" android:visibility="gone" /> <ImageView android:id="@+id/img_phonePhoto" android:layout_width="40dp" android:layout_height="40dp" android:layout_below="@id/txtHeadLetter" /> <TextView android:id="@+id/txt_contactName" android:layout_width="match_parent" android:layout_height="20dp" android:layout_toRightOf="@id/img_phonePhoto" android:layout_marginLeft="10dp" android:gravity="center_vertical" android:layout_below="@id/txtHeadLetter" /> <TextView android:id="@+id/txt_phoneNumber" android:layout_width="match_parent" android:layout_height="20dp" android:layout_toRightOf="@id/img_phonePhoto" android:layout_marginLeft="10dp" android:layout_below="@id/txt_contactName" android:gravity="center_vertical" /> </RelativeLayout>