Android自定義View實(shí)現(xiàn)通訊錄字母索引(仿微信通訊錄)
一、效果:我們看到很多軟件的通訊錄在右側(cè)都有一個(gè)字母索引功能,像微信,小米通訊錄,QQ,還有美團(tuán)選擇地區(qū)等等。這里我截了一張美團(tuán)選擇城市的圖片來(lái)看看;
我們今天就來(lái)實(shí)現(xiàn)圖片中右側(cè)模塊的索引功能,包括觸摸顯示以選中的索引字母。這里我的UI界面主要是參照微信的界面來(lái)實(shí)現(xiàn),所以各位也可以對(duì)照微信來(lái)看看效果,什么都不說(shuō)了,只有效果圖最具有說(shuō)服力!
二、分析:
我們看到這樣的效果我們心理都回去琢磨,他是如何實(shí)現(xiàn)的;
首先,它肯定是通過(guò)自定義 View 來(lái)實(shí)現(xiàn)的,因?yàn)?Android 沒(méi)有提供類似這樣的控件、那么接下來(lái)就是如何自定義我們的 View ,我們知道自定義 View 最最主要的兩個(gè)方法就是 onDraw(Canvas canvas)和
onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,當(dāng)然,如果是自定義 ViewGroup 的話就必須實(shí)現(xiàn)
onLayout(boolean changed, int left, int top, int right, int bottom) 方法,這里我們顯然用自定義 View 就能夠?qū)崿F(xiàn)此功能,通過(guò)效果圖可以看帶,當(dāng)觸摸這塊區(qū)域的時(shí)候,會(huì)彈出一個(gè)懸浮類似 Toast 的框來(lái)顯示已經(jīng)選中的索引內(nèi)容,所以這里還需要重寫View 的onTouchEvent(MotionEvent event)事件,最后就是懸浮框的實(shí)現(xiàn)。那么接下來(lái)就開(kāi)始我們編碼。
三、編碼實(shí)現(xiàn):
我們就按照 View 的執(zhí)行順序來(lái)實(shí)現(xiàn)
1、實(shí)現(xiàn)onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,這個(gè)方法的功能是測(cè)量出我們的寬和高,具體實(shí)現(xiàn)看代碼
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec)); }
這里定義了兩個(gè)方法measureWidth( int) 和 measureHeight(int) ,通過(guò)方法名可以很清楚的知道,其功能分別是測(cè)量寬和高,進(jìn)去看看是如何測(cè)量的。
/** * 測(cè)量本身的大小,這里只是測(cè)量寬度 * @param widthMeaSpec 傳入父View的測(cè)量標(biāo)準(zhǔn) * @return 測(cè)量的寬度 */ private int measureWidth(int widthMeaSpec){ /*定義view的寬度*/ int width ; /*獲取當(dāng)前 View的測(cè)量模式*/ int mode = MeasureSpec.getMode(widthMeaSpec) ; /* * 獲取當(dāng)前View的測(cè)量值,這里得到的只是初步的值, * 我們還需根據(jù)測(cè)量模式來(lái)確定我們期望的大小 * */ int size = MeasureSpec.getSize(widthMeaSpec) ; /* * 如果,模式為精確模式 * 當(dāng)前View的寬度,就是我們 * 的size ; * */ if(mode == MeasureSpec.EXACTLY){ width = size ; }else { /*否則的話我們就需要結(jié)合padding的值來(lái)確定*/ int desire = size + getPaddingLeft() + getPaddingRight() ; if(mode == MeasureSpec.AT_MOST){ width = Math.min(desire,size) ; }else { width = desire ; } } mViewWidth = width ; return width ; }
以上是測(cè)量寬度的代碼,其測(cè)量高度的代碼,跟測(cè)量寬度的代碼大致雷同,就不貼出來(lái)了,我會(huì)在最后附上源碼。
2、實(shí)現(xiàn)onDraw(Canvas c)方法,這個(gè)方法相信大家都非常熟悉,就是把這些索引的內(nèi)容繪制到 View 上顯示出來(lái),包括選中的時(shí)候背景顏色的變化;
@Override protected void onDraw(Canvas canvas) { if(mTouched){ canvas.drawColor(0x30000000); } for (int i = 0 ; i < mIndex.length ; i ++){ mPaint.setColor(0xff000000); mPaint.setTextSize(mTextSize * 3.0f / 4.0f); mPaint.setTypeface(Typeface.DEFAULT) ; mPaint.getTextBounds(mIndex[i],0,mIndex[i].length(),mTextBound); float formX = mViewWidth/2.0f - mTextBound.width()/2.0f ; float formY = mTextSize*i + mTextSize/2.0f + mTextBound.height()/2.0f ; canvas.drawText(mIndex[i],formX,formY,mPaint); mPaint.reset(); } }
我來(lái)講一下 onDraw 方法中大致做了什么事,第一,繪制背景顏色,注意不是一上來(lái)就繪制,而是等到有手指觸摸的時(shí)候就繪制背景顏色,第二,就是繪制索引的內(nèi)容,這里需要根據(jù)當(dāng)前 View 的寬和高來(lái)決定繪制內(nèi)容的大小,和位置。
3、onTouchEvent(MotionEvent event)方法的實(shí)現(xiàn)
@Override public boolean onTouchEvent(MotionEvent event) { float y = event.getY() ; int index = (int) (y / mTextSize); if(index >= 0 && index < mIndex.length){ Log.v("zgy","======index======="+index) ; selectItem(index); } if(event.getAction() == MotionEvent.ACTION_MOVE){ mTouched = true ; }else if (event.getAction() == MotionEvent.ACTION_MOVE){ }else { mFloatView.setVisibility(INVISIBLE); mTouched = false ; } invalidate(); /*過(guò)濾點(diǎn)其他觸摸事件*/ return true; }
代碼也相對(duì)比較簡(jiǎn)單,首先獲取當(dāng)前觸摸的點(diǎn),根據(jù)點(diǎn)的坐標(biāo)來(lái)獲取索引的位置,從而拿到索引的位置。
4、到這里其實(shí)就已經(jīng)實(shí)現(xiàn)了我們想要的效果,但是這樣我們還是無(wú)法運(yùn)用它,這里就需要定義一個(gè)回調(diào)接口
/*定義一個(gè)回調(diào)接口*/ public interface OnIndexSelectListener{ /*返回選中的位置,和對(duì)應(yīng)的索引名*/ void onItemSelect(int position, String value) ; }
回調(diào)接口我們放在哪里調(diào)用呢,當(dāng)我們手指按下的時(shí)候,這時(shí)候其實(shí)我們需要確定我們按下的是哪個(gè)索引,滑動(dòng)的時(shí)候也是一樣,所以,這個(gè)沒(méi)什么好商量的,直接放在onTouchEvent(MotionEvent event)中就可以,
float y = event.getY() ; int index = (int) (y / mTextSize); if(index >= 0 && index < mIndex.length){ Log.v("zgy","======index======="+index) ; selectItem(index); }
selectItem(int)方法中就是執(zhí)行的回調(diào)方法。
5、實(shí)現(xiàn)懸浮框顯示已經(jīng)選中的索引內(nèi)容
這里需要用到 WindowManager 容器,然需要現(xiàn)實(shí)的 View 附在這上面的就行,當(dāng)手指按下的時(shí)候,讓 View 顯示出來(lái),松開(kāi)不顯示就行了
/*設(shè)置浮動(dòng)選中的索引*/ /*獲取windowManager*/ mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); /*overly 視圖,通過(guò)LayoutInflater 獲取*/ mFloatView = LayoutInflater.from(getContext()).inflate(R.layout.overlay_indexview,null) ; /*開(kāi)始讓其不可見(jiàn)*/ mFloatView.setVisibility(INVISIBLE); /*轉(zhuǎn)換 高度 和寬度為Sp*/ mOverlyWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,70,getResources().getDisplayMetrics()) ; mOverlyHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,70,getResources().getDisplayMetrics()) ; post(new Runnable() { @Override public void run() { WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(mOverlyWidth,mOverlyHeight, WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT) ; mWindowManager.addView(mFloatView,layoutParams); } }) ;
同樣的道理,如果需要改變顯示的內(nèi)容,就需要在調(diào)用回調(diào)的位置,為 View 中的 TextView 設(shè)置當(dāng)前的索引內(nèi)容。
好了此 View 的代碼就這么多,
接下來(lái)就把引用他的 Xml 和浮動(dòng) View 的 Xml 也貼出來(lái),
引用的布局文件
<moon.wechat.view.IndexView android:layout_width="25dp" android:layout_height="match_parent" android:layout_alignParentRight="true"/>
浮動(dòng) View 的布局文件
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/overly_text" android:layout_width="70dp" android:layout_height="70dp" android:text="A" android:gravity="center" android:background="@drawable/bg_overly_text" android:textSize="40sp" android:textColor="#ffffffff" android:layout_gravity="center"> </TextView>
浮動(dòng) View 的背景
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape> <solid android:color="#88000000"/> <corners android:radius="5dp"/> </shape> </item> </layer-list>
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
- 使用adb命令向Android模擬器中導(dǎo)入通訊錄聯(lián)系人的方法
- Android獲取手機(jī)通訊錄、sim卡聯(lián)系人及調(diào)用撥號(hào)界面方法
- Android通訊錄開(kāi)發(fā)之刪除功能的實(shí)現(xiàn)方法
- Android個(gè)人手機(jī)通訊錄開(kāi)發(fā)詳解
- Android實(shí)現(xiàn)通訊錄效果——獲取手機(jī)號(hào)碼和姓名
- Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目
- Android破解微信獲取聊天記錄和通訊錄信息(靜態(tài)方式)
- android仿微信通訊錄搜索示例(匹配拼音,字母,索引位置)
- Android實(shí)現(xiàn)仿通訊錄側(cè)邊欄滑動(dòng)SiderBar效果代碼
- Android Studio實(shí)現(xiàn)簡(jiǎn)單的通訊錄
相關(guān)文章
Android通過(guò)原生APi獲取所在位置的經(jīng)緯度
本篇文章主要介紹了Android通過(guò)原生APi獲取所在位置的經(jīng)緯度,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07Android錄音功能的實(shí)現(xiàn)以及踩坑實(shí)戰(zhàn)記錄
在Android 開(kāi)發(fā)過(guò)程中,有些功能是通用的,或者是多個(gè)業(yè)務(wù)方都需要使用的,下面這篇文章主要給大家介紹了關(guān)于Android錄音功能的實(shí)現(xiàn)以及踩坑的相關(guān)資料,需要的朋友可以參考下2022-06-06Android Studio快捷鍵生成TAG、Log.x日志輸出介紹
這篇文章主要介紹了Android Studio快捷鍵生成TAG、Log.x日志輸出介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04Android開(kāi)發(fā)實(shí)例之登錄界面的實(shí)現(xiàn)
本文主要介紹Android 登錄界面實(shí)現(xiàn),這里主要講解類似Twitter的登錄界面的實(shí)現(xiàn),有興趣的小伙伴可以參考下2016-08-08Android Studio打包APK文件具體實(shí)現(xiàn)步驟解析
這篇文章主要介紹了Android Studio打包APK文件具體實(shí)現(xiàn)步驟解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11