網站首頁 編程語言 正文
今天分享一個以前實現的通訊錄字母導航控件,下面自定義一個類似通訊錄的字母導航 View,可以知道需要自定義的幾個要素,如繪制字母指示器、繪制文字、觸摸監聽、坐標計算等,自定義完成之后能夠達到的功能如下:
- 完成列表數據與字母之間的相互聯動;
- 支持布局文件屬性配置;
- 在布局文件中能夠配置相關屬性,如字母顏色、字母字體大小、字母指示器顏色等屬性。
主要內容如下:
- 自定義屬性
- Measure測量
- 坐標計算
- 繪制
- 顯示效果
自定義屬性
在 value 下面創建 attr.xml ,在里面配置需要自定義的屬性,具體如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LetterView"> <!--字母顏色--> <attr name="letterTextColor" format="color" /> <!--字母字體大小--> <attr name="letterTextSize" format="dimension" /> <!--整體背景--> <attr name="letterTextBackgroundColor" format="color" /> <!--是否啟用指示器--> <attr name="letterEnableIndicator" format="boolean" /> <!--指示器顏色--> <attr name="letterIndicatorColor" format="color" /> </declare-styleable> </resources>
然后在相應的構造方法中獲取這些屬性并進行相關屬性的設置,具體如下:
public LetterView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); //獲取屬性 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LetterView); int letterTextColor = array.getColor(R.styleable.LetterView_letterTextColor, Color.RED); int letterTextBackgroundColor = array.getColor(R.styleable.LetterView_letterTextBackgroundColor, Color.WHITE); int letterIndicatorColor = array.getColor(R.styleable.LetterView_letterIndicatorColor, Color.parseColor("#333333")); float letterTextSize = array.getDimension(R.styleable.LetterView_letterTextSize, 12); enableIndicator = array.getBoolean(R.styleable.LetterView_letterEnableIndicator, true); //默認設置 mContext = context; mLetterPaint = new Paint(); mLetterPaint.setTextSize(letterTextSize); mLetterPaint.setColor(letterTextColor); mLetterPaint.setAntiAlias(true); mLetterIndicatorPaint = new Paint(); mLetterIndicatorPaint.setStyle(Paint.Style.FILL); mLetterIndicatorPaint.setColor(letterIndicatorColor); mLetterIndicatorPaint.setAntiAlias(true); setBackgroundColor(letterTextBackgroundColor); array.recycle(); }
Measure測量
要想精確的控制自定義的尺寸以及坐標,必須要測量出當前自定義 View 的寬高,然后才可以通過測量到的尺寸計算相關坐標,具體測量過程就是繼承 View 重寫 omMeasure() 方法完成測量,關鍵代碼如下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //獲取寬高的尺寸大小 int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //wrap_content默認寬高 @SuppressLint("DrawAllocation") Rect mRect = new Rect(); mLetterPaint.getTextBounds("A", 0, 1, mRect); mWidth = mRect.width() + dpToPx(mContext, 12); int mHeight = (mRect.height() + dpToPx(mContext, 5)) * letters.length; if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(mWidth, mHeight); } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(mWidth, heightSize); } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(widthSize, mHeight); } mWidth = getMeasuredWidth(); int averageItemHeight = getMeasuredHeight() / 28; int mOffset = averageItemHeight / 30; //界面調整 mItemHeight = averageItemHeight + mOffset; }
坐標計算
自定義 View 實際上就是在 View 上找到合適的位置,將自定義的元素有序的繪制出來即可,繪制過程最困難的就是如何根據具體需求計算合適的左邊,至于繪制都是 API 的調用,只要坐標位置計算好了,自定義 View 繪制這一塊應該就沒有問題了,下面的圖示主要是標注了字母指示器繪制的中心位置坐標的計算以及文字繪制的起點位置計算,繪制過程中要保證文字在指示器中心位置,參考如下:
繪制
自定義 View 的繪制操作都是在 onDraw() 方法中進行的,這里主要使用到圓的繪制以及文字的繪制,具體就是 drawCircle() 和 drawText() 方法的使用,為避免文字被遮擋,需繪制字母指示器,然后再繪制字母,代碼參考如下:
@Override protected void onDraw(Canvas canvas) { //獲取字母寬高 @SuppressLint("DrawAllocation") Rect rect = new Rect(); mLetterPaint.getTextBounds("A", 0, 1, rect); int letterWidth = rect.width(); int letterHeight = rect.height(); //繪制指示器 if (enableIndicator){ for (int i = 1; i < letters.length + 1; i++) { if (mTouchIndex == i) { canvas.drawCircle(0.5f * mWidth, i * mItemHeight - 0.5f * mItemHeight, 0.5f * mItemHeight, mLetterIndicatorPaint); } } } //繪制字母 for (int i = 1; i < letters.length + 1; i++) { canvas.drawText(letters[i - 1], (mWidth - letterWidth) / 2, mItemHeight * i - 0.5f * mItemHeight + letterHeight / 2, mLetterPaint); } }
到此為止,可以說 View 的基本繪制結束了,現在使用自定義的 View 界面能夠顯示出來了,只是還沒有添加相關的事件操作,下面將在 View 的觸摸事件里實現相關邏輯。
Touch事件處理
為了判斷手指當前所在位置對應的是哪一個字母,需要獲取當前觸摸的坐標位置來計算字母索引,重新 onTouchEvent() 方法,監聽 MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE 來計算索引位置,監聽 MotionEvent.ACTION_UP 將獲得結果回調出去,具體參考如下:
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: isTouch = true; int y = (int) event.getY(); Log.i("onTouchEvent","--y->" + y + "-y-dp-->" + DensityUtil.px2dp(getContext(), y)); int index = y / mItemHeight; if (index != mTouchIndex && index < 28 && index > 0) { mTouchIndex = index; Log.i("onTouchEvent","--mTouchIndex->" + mTouchIndex + "--position->" + mTouchIndex); } if (mOnLetterChangeListener != null && mTouchIndex > 0) { mOnLetterChangeListener.onLetterListener(letters[mTouchIndex - 1]); } invalidate(); break; case MotionEvent.ACTION_UP: isTouch = false; if (mOnLetterChangeListener != null && mTouchIndex > 0) { mOnLetterChangeListener.onLetterDismissListener(); } break; } return true; }
到此為止,View 的自定義關鍵部分基本完成。
數據組裝
字母導航的基本思路是將某個需要與字母匹配的字段轉換為對應的字母,然后按照該字段對數據進行排序,最終使得通過某個數據字段的首字母就可以批匹配到相同首字母的數據了,這里將漢字轉化為拼音使用的是 pinyin4j-2.5.0.jar ,然后對數據項按照首字母進行排序將數據展示到出來即可,漢字裝換為拼音如下:
//漢字轉換為拼音 public static String getChineseToPinyin(String chinese) { StringBuilder builder = new StringBuilder(); HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); format.setCaseType(HanyuPinyinCaseType.UPPERCASE); format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); char[] charArray = chinese.toCharArray(); for (char aCharArray : charArray) { if (Character.isSpaceChar(aCharArray)) { continue; } try { String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(aCharArray, format); if (pinyinArr != null) { builder.append(pinyinArr[0]); } else { builder.append(aCharArray); } } catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) { badHanyuPinyinOutputFormatCombination.printStackTrace(); builder.append(aCharArray); } } return builder.toString(); }
至于數據排序使用 Comparator 接口即可
顯示效果
顯示效果如下:
原文鏈接:https://segmentfault.com/a/1190000041289549
相關推薦
- 2022-04-01 Fatal Python error: Py_Initialize: unable to load
- 2022-08-31 React?SSR?中的限流案例詳解_React
- 2022-10-29 vite 配置 @ 路徑別名
- 2023-05-23 numpy數組之讀寫文件的實現_python
- 2022-06-17 C語言詳細講解多維數組與多維指針_C 語言
- 2024-01-29 Spring 的存儲和獲取Bean
- 2023-07-28 nrm的安裝與配置及問題修復
- 2023-04-19 Git 推送報錯:error: GH007: Your push would publish a p
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支