image

核心类:

package com.example.test29api.slider_contact

import android.content.Context

import android.graphics.Canvas

import android.graphics.Color

import android.graphics.Paint

import android.graphics.Rect

import android.graphics.drawable.Drawable

import android.text.TextPaint

import android.text.TextUtils

import android.util.ArrayMap

import android.util.AttributeSet

import android.util.TypedValue

import android.view.MotionEvent

import android.view.View

import androidx.annotation.VisibleForTesting

import com.example.test29api.R

import java.util.*

import java.util.Collections.sort as sort1

class NewSideBar @JvmOverloads constructor(

context: Context,

attrs: AttributeSet? = null,

defStyleAttr: Int = 0

) : View(context, attrs, defStyleAttr) {

var indexes: Array = arrayOf(

"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 val normalTextColor //未选中字体颜色

: Int

private val selectedTextColor //选中字体颜色

: Int

private val selectedBackground //选中背景色

: Drawable?

private var selectedIndex = 0

private val indexPaint: TextPaint

private val indexBounds //索引大小

: Rect

private val backGroundBound //背景绘制区域

: Rect

private var listener: OnSelectIndexChangedListener? = null

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

val widthMode = MeasureSpec.getMode(widthMeasureSpec)

val heightMode = MeasureSpec.getMode(heightMeasureSpec)

val sizeWidth = MeasureSpec.getSize(widthMeasureSpec)

val sizeHeight = MeasureSpec.getSize(heightMeasureSpec)

val wrapHeight =

indexes.size * (indexBounds.height() + indexBounds.height() / 2) + indexBounds.height() / 2

val warpWidth = indexBounds.width() * 2

setMeasuredDimension(

if (widthMode == MeasureSpec.EXACTLY) sizeWidth else warpWidth

, if (heightMode == MeasureSpec.EXACTLY) sizeHeight else wrapHeight

)

}

override fun onDraw(canvas: Canvas) {

indexBounds.offsetTo(indexBounds.width() / 2, indexBounds.height() / 2)

backGroundBound.offsetTo(indexBounds.width() / 4, indexBounds.height() / 4)

for (i in indexes.indices) {

if (selectedIndex == i) { //绘制index背景

if (selectedBackground != null) {

selectedBackground.bounds = backGroundBound

selectedBackground.draw(canvas)

}

indexPaint.color = selectedTextColor

} else {

indexPaint.color = normalTextColor

}

indexes[i]?.let {

canvas.drawText(

it,

indexBounds.left + indexBounds.width() / 2.toFloat(),

indexBounds.top + baseLineOffset.toFloat(),

indexPaint

)

}

indexBounds.offset(0, 3 * indexBounds.height() / 2)

backGroundBound.offset(0, 3 * indexBounds.height() / 2)

}

}

override fun dispatchTouchEvent(event: MotionEvent): Boolean {

var selectedIndex = getSelectedIndex(event.y)

if (selectedIndex < 0) {

selectedIndex = 0

}

if (selectedIndex >= indexes.size) {

selectedIndex = indexes.size - 1

}

when (event.action) {

MotionEvent.ACTION_DOWN -> {

isHovered = true

if (listener != null && selectedIndex != -1 && selectedIndex != this.selectedIndex) {

this.selectedIndex = selectedIndex

listener!!.onIndexHovered(indexes[selectedIndex])

}

invalidate()

}

MotionEvent.ACTION_MOVE -> {

if (listener != null && selectedIndex != -1 && selectedIndex != this.selectedIndex) {

this.selectedIndex = selectedIndex

listener!!.onIndexHovered(indexes[selectedIndex])

}

invalidate()

}

MotionEvent.ACTION_UP -> {

isHovered = false

if (listener != null && selectedIndex >= 0 && selectedIndex < indexes.size) {

this.selectedIndex = selectedIndex

listener!!.onIndexSelected(indexes[selectedIndex])

}

}

}

return true

}

private fun getSelectedIndex(pointY: Float): Int {

return ((pointY - indexBounds.height() / 4) / (indexBounds.height() * 3 / 2)).toInt()

}

private val baseLineOffset: Int

get() {

val fontMetrics = indexPaint.fontMetricsInt

val dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom

return indexBounds.height() / 2 + dy

}

fun setListener(listener: OnSelectIndexChangedListener?) {

this.listener = listener

}

fun setSections(sections: Array) {

indexes = sections

requestLayout()

}

fun setSelectedIndex(selectedIndex: String?) {

if (isHovered) {

return

}

for (i in indexes.indices) {

val index = indexes[i]

if (TextUtils.equals(selectedIndex, index)) {

this.selectedIndex = i

invalidate()

break

}

}

}

@VisibleForTesting

fun getSelectedIndex(): String? {

return if (selectedIndex >= 0 && selectedIndex < indexes.size) indexes[selectedIndex] else null

}

interface OnSelectIndexChangedListener {

fun onIndexHovered(index: String?)

fun onIndexSelected(index: String?)

}

interface SectionIndexer {

val sections: Array

fun getPositionForSection(section: T): Int

fun getSectionForPosition(position: Int): T

}

class PinyinSectionIndexer :

SectionIndexer {

private var indexables: MutableList>? = null

private var mapSections: ArrayMap, String>? = null

private var skipCount = 0

fun > handleIndexableList(indexableList: MutableList): List {

return handleIndexableList(indexableList, 0)

}

/**

* @param indexableList 数据源

* @param skipCount 1-置顶第一个item不参与abc...排序

* 0-全部参与abc...排序

*/

fun > handleIndexableList(

indexableList: MutableList,

skipCount: Int

): List {

if (mapSections == null) {

mapSections = ArrayMap()

}

mapSections!!.clear()

if (indexables == null) {

indexables = ArrayList()

}

indexables!!.clear()

this.skipCount = skipCount

if (indexableList.size == 0 || indexableList.size < skipCount) {

return indexableList

}

val sortList: List =

ArrayList(indexableList.subList(skipCount, indexableList.size)).also {

//通过拼音首字母排序

it.sortWith(Comparator { o1, o2 ->

var index1 = getSection(o1)

if (TextUtils.equals(index1, "#")) {

index1 = "a"

}

var index2 = getSection(o2)

if (TextUtils.equals(index2, "#")) {

index2 = "a"

}

index1!!.compareTo(index2!!)

})

}

indexables!!.addAll(sortList)

val dest: MutableList = indexableList.subList(0, skipCount)

dest.addAll(sortList)

return dest

}

/**

* @return 返回数据源所有拼音首字母String[]集合

*/

override

val sections: Array

get() {

val sections =

ArrayList()

var i = 0

val len = indexables!!.size

while (i < len) {

val section = getSection(indexables!![i])

if (!sections.contains(section)) {

sections.add(section)

}

i++

}

return sections.toTypedArray()

}

/**

* @return 返回首字母对应数据的第一个position

*/

override fun getPositionForSection(section: String?): Int {

var i = 0

val len = indexables!!.size

while (i < len) {

if (TextUtils.equals(section, getSection(indexables!![i]))) {

return i + skipCount

}

i++

}

return -1

}

/**

* @return 通过数据的position返回首字母

*/

override fun getSectionForPosition(position: Int): String? {

return if (indexables != null && position >= 0 && position < indexables!!.size) {

getSection(indexables!![Math.max(position - skipCount, 0)])

} else {

""

}

}

/**

* @return 获取indexable.getIndexable()的拼音大写首字母(空则返回#),同时更新#sections

*/

fun getSection(indexable: Indexable): String? {

var section = mapSections!![indexable]

if (TextUtils.isEmpty(section)) {

val indexableString = indexable.indexable

val instance = CharacterParser.instance

if (TextUtils.isEmpty(indexableString)) {

section = "#"

} else { //获取首字的拼音

val c = instance.convert(indexableString.substring(0, 1))

//首字母转大写

section =

if (TextUtils.isEmpty(c)) "0" else c?.substring(0, 1)?.toUpperCase(

Locale.ROOT

)

if (!section.isNullOrEmpty() && (section[0] < 'A' || section[0] > 'Z')) {

section = "#"

}

}

mapSections!![indexable] = section

}

return section

}

}

interface Indexable {

val indexable: T

}

init {

val ta = context.obtainStyledAttributes(attrs, R.styleable.NewSideBar)

normalTextColor =

ta.getColor(R.styleable.NewSideBar_normalTextColor, Color.GRAY)

selectedTextColor =

ta.getColor(R.styleable.NewSideBar_selectedTextColor, Color.BLACK)

selectedBackground = ta.getDrawable(R.styleable.NewSideBar_selectedBackground)

val textSize = ta.getDimension(

R.styleable.NewSideBar_indexTextSize, TypedValue

.applyDimension(

TypedValue.COMPLEX_UNIT_SP, 12f,

resources.displayMetrics

)

)

ta.recycle()

indexBounds = Rect()

indexPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)

indexPaint.textSize = textSize

indexPaint.textAlign = Paint.Align.CENTER

indexPaint.getTextBounds("W", 0, 1, indexBounds)

val larger = Math.max(indexBounds.width(), indexBounds.height())

indexBounds[0, 0, larger] = larger

backGroundBound = Rect(

0,

0,

indexBounds.right + indexBounds.right / 2,

indexBounds.bottom + indexBounds.bottom / 2

)

}

}

attrs:

以上类基本逻辑为:通过传入数据绘制字母及其背景,再通过接口和recycleView联动;

调用方式:

activity:

import android.os.Bundle

import android.util.Log

import android.view.View

import android.view.ViewGroup

import androidx.appcompat.app.AppCompatActivity

import androidx.recyclerview.widget.LinearLayoutManager

import androidx.recyclerview.widget.RecyclerView

import androidx.recyclerview.widget.RecyclerView.Adapter

import com.example.test29api.R

import kotlinx.android.extensions.LayoutContainer

import kotlinx.android.synthetic.main.activity_contact_slider.*

import kotlinx.android.synthetic.main.item_base.view.*

import kotlinx.coroutines.*

class ContactSliderActivity : AppCompatActivity() {

private val indexer by lazy {

NewSideBar.PinyinSectionIndexer()

}

private val testContactBeans by lazy {

mutableListOf(

TestContactBean("张三", "18888888888"),

TestContactBean("李四", "19999999999"),

TestContactBean("王五", "13333333333"),

TestContactBean("科比", "13444444444"),

TestContactBean("布鲁斯", "18213333333"),

TestContactBean("朱厚照", "18888888888"),

TestContactBean("朱祐樘", "17777777777"),

TestContactBean("朱高炽", "18888888888"),

TestContactBean("唐寅", "18888888855"),

TestContactBean("朱厚聪", "18888888866"),

TestContactBean("朱载厚", "18888888877"),

TestContactBean("刘瑾", "18215333333"),

TestContactBean("Alibaba", "15775757575"),

TestContactBean("Bruce", "15775757575"),

TestContactBean("富兰克林", "18215333333")

)

}

open class DefaultViewHolder(override val containerView: View) :

RecyclerView.ViewHolder(containerView), LayoutContainer

private val defaultAdapter by lazy {

object : Adapter() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DefaultViewHolder {

return DefaultViewHolder(

layoutInflater.inflate(

R.layout.item_base,

parent,

false

)

)

}

override fun getItemCount(): Int {

return testContactBeans.size

}

override fun onBindViewHolder(holder: DefaultViewHolder, position: Int) {

holder.itemView.run {

txtName.text = testContactBeans[position].name

txtPhone.text = testContactBeans[position].phone

}

}

}

}

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_contact_slider)

initView()

}

private fun initView() {

recyclerView.apply {

layoutManager = LinearLayoutManager(this@ContactSliderActivity)

adapter = defaultAdapter

addOnScrollListener(object : RecyclerView.OnScrollListener() {

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {

val position =

(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()

if (defaultAdapter.itemCount != 0 && position in 0 until testContactBeans.size) {

sideBar.setSelectedIndex(indexer.getSectionForPosition(position))

}

}

})

}

sideBar.apply {

visibility = View.GONE

setListener(object : NewSideBar.OnSelectIndexChangedListener {

override fun onIndexHovered(index: String?) {

(recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(

indexer.getPositionForSection(index),

0

)

}

override fun onIndexSelected(index: String?) {

val manager = recyclerView.layoutManager as LinearLayoutManager

if (manager.findLastVisibleItemPosition() == (recyclerView.adapter?.itemCount

?: 0) - 1

) {

sideBar.setSelectedIndex(indexer.getSectionForPosition(manager.findFirstVisibleItemPosition()))

}

}

})

}

//设置排序后的数据源

val mutableListOf = mutableListOf()

mutableListOf.addAll(testContactBeans)

indexer.handleIndexableList(mutableListOf, 0).let { list ->

defaultAdapter.notifyItemRangeRemoved(0, testContactBeans.size - 1)

testContactBeans.clear()

testContactBeans.addAll(list)

defaultAdapter.notifyItemRangeChanged(0, testContactBeans.size - 1)

}

sideBar.setSections(indexer.sections)

sideBar.visibility = View.VISIBLE

}

}

activity_contact_slider:

android:layout_width="match_parent"

android:layout_height="match_parent"

xmlns:app="http://schemas.android.com/apk/res-auto">

android:id="@+id/recyclerView"

android:layout_width="0dp"

android:layout_height="0dp"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent"

/>

android:id="@+id/sideBar"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginEnd="8dp"

app:indexTextSize="12sp"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintTop_toTopOf="parent"

app:normalTextColor="@android:color/darker_gray"

app:selectedBackground="@drawable/side_bar_choose_background"

app:selectedTextColor="@android:color/white" />

side_bar_choose_background:

android:shape="oval">

item_base:

android:layout_width="match_parent"

android:layout_height="70dp"

android:background="@android:color/white"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools">

android:id="@+id/txtName"

android:layout_width="0dp"

android:layout_height="48dp"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent"

app:layout_constraintEnd_toStartOf="@id/txtPhone"

app:layout_constraintVertical_chainStyle="packed"

android:layout_marginStart="16dp"

android:layout_marginEnd="14dp"

android:textSize="16sp"

tools:text="项目成员"

android:gravity="center_vertical"

android:singleLine="true"/>

android:id="@+id/txtPhone"

android:layout_width="0dp"

android:layout_height="wrap_content"

app:layout_constraintStart_toEndOf="@id/txtName"

app:layout_constraintBottom_toBottomOf="@id/txtName"

app:layout_constraintTop_toTopOf="@id/txtName"

app:layout_constraintEnd_toEndOf="parent"

android:layout_marginStart="16dp"

android:layout_marginTop="8dp"

android:layout_marginEnd="14dp"

tools:text="1888888888"

android:singleLine="true"/>

CharacterParser

package com.example.test29api.slider_contact

/**

* Created by PengCangXu on 2017/5/7.

*/

class CharacterParser {

private var buffer: StringBuilder? = null

var resource: String? = null

/**

* 汉字转成ASCII码 * * @param chs * @return

*/

private fun getChsAscii(chs: String): Int {

var asc = 0

try {

val bytes = chs.toByteArray(charset("gb2312"))

if (bytes.size > 2 || bytes.size <= 0) {

throw RuntimeException("illegal resource string")

}

if (bytes.size == 1) {

asc = bytes[0].toInt()

}

if (bytes.size == 2) {

val hightByte = 256 + bytes[0]

val lowByte = 256 + bytes[1]

asc = 256 * hightByte + lowByte - 256 * 256

}

} catch (e: Exception) {

println("ERROR:ChineseSpelling.class-getChsAscii(String chs)$e")

}

return asc

}

/**

* 单字解析 * * @param str * @return

*/

fun convert(str: String): String? {

var result: String? = null

val ascii = getChsAscii(str)

if (ascii in 1..159) {

result = ascii.toString()

} else {

for (i in pyvalue.size - 1 downTo 0) {

if (pyvalue[i] <= ascii) {

result = pystr[i]

break

}

}

}

return result

}

/***词组解析 * * @param chs * @return */

fun parse(chs: String?): String {

var key: String

var value: String?

buffer = StringBuilder()

for (i in chs!!.indices) {

key = chs.substring(i, i + 1)

if (key.toByteArray().size >= 2) {

value = convert(key)

if (value == null) {

value = "unknown"

}

} else {

value = key

}

buffer!!.append(value)

}

return buffer.toString()

}

val spelling: String

get() = parse(resource)

companion object {

private val pyvalue = intArrayOf(

-20319,

-20317,

-20304,

-20295,

-20292,

-20283,

-20265,

-20257,

-20242,

-20230,

-20051,

-20036,

-20032,

-20026,

-20002,

-19990,

-19986,

-19982,

-19976,

-19805,

-19784,

-19775,

-19774,

-19763,

-19756,

-19751,

-19746,

-19741,

-19739,

-19728,

-19725,

-19715,

-19540,

-19531,

-19525,

-19515,

-19500,

-19484,

-19479,

-19467,

-19289,

-19288,

-19281,

-19275,

-19270,

-19263,

-19261,

-19249,

-19243,

-19242,

-19238,

-19235,

-19227,

-19224,

-19218,

-19212,

-19038,

-19023,

-19018,

-19006,

-19003,

-18996,

-18977,

-18961,

-18952,

-18783,

-18774,

-18773,

-18763,

-18756,

-18741,

-18735,

-18731,

-18722,

-18710,

-18697,

-18696,

-18526,

-18518,

-18501,

-18490,

-18478,

-18463,

-18448,

-18447,

-18446,

-18239,

-18237,

-18231,

-18220,

-18211,

-18201,

-18184,

-18183,

-18181,

-18012,

-17997,

-17988,

-17970,

-17964,

-17961,

-17950,

-17947,

-17931,

-17928,

-17922,

-17759,

-17752,

-17733,

-17730,

-17721,

-17703,

-17701,

-17697,

-17692,

-17683,

-17676,

-17496,

-17487,

-17482,

-17468,

-17454,

-17433,

-17427,

-17417,

-17202,

-17185,

-16983,

-16970,

-16942,

-16915,

-16733,

-16708,

-16706,

-16689,

-16664,

-16657,

-16647,

-16474,

-16470,

-16465,

-16459,

-16452,

-16448,

-16433,

-16429,

-16427,

-16423,

-16419,

-16412,

-16407,

-16403,

-16401,

-16393,

-16220,

-16216,

-16212,

-16205,

-16202,

-16187,

-16180,

-16171,

-16169,

-16158,

-16155,

-15959,

-15958,

-15944,

-15933,

-15920,

-15915,

-15903,

-15889,

-15878,

-15707,

-15701,

-15681,

-15667,

-15661,

-15659,

-15652,

-15640,

-15631,

-15625,

-15454,

-15448,

-15436,

-15435,

-15419,

-15416,

-15408,

-15394,

-15385,

-15377,

-15375,

-15369,

-15363,

-15362,

-15183,

-15180,

-15165,

-15158,

-15153,

-15150,

-15149,

-15144,

-15143,

-15141,

-15140,

-15139,

-15128,

-15121,

-15119,

-15117,

-15110,

-15109,

-14941,

-14937,

-14933,

-14930,

-14929,

-14928,

-14926,

-14922,

-14921,

-14914,

-14908,

-14902,

-14894,

-14889,

-14882,

-14873,

-14871,

-14857,

-14678,

-14674,

-14670,

-14668,

-14663,

-14654,

-14645,

-14630,

-14594,

-14429,

-14407,

-14399,

-14384,

-14379,

-14368,

-14355,

-14353,

-14345,

-14170,

-14159,

-14151,

-14149,

-14145,

-14140,

-14137,

-14135,

-14125,

-14123,

-14122,

-14112,

-14109,

-14099,

-14097,

-14094,

-14092,

-14090,

-14087,

-14083,

-13917,

-13914,

-13910,

-13907,

-13906,

-13905,

-13896,

-13894,

-13878,

-13870,

-13859,

-13847,

-13831,

-13658,

-13611,

-13601,

-13406,

-13404,

-13400,

-13398,

-13395,

-13391,

-13387,

-13383,

-13367,

-13359,

-13356,

-13343,

-13340,

-13329,

-13326,

-13318,

-13147,

-13138,

-13120,

-13107,

-13096,

-13095,

-13091,

-13076,

-13068,

-13063,

-13060,

-12888,

-12875,

-12871,

-12860,

-12858,

-12852,

-12849,

-12838,

-12831,

-12829,

-12812,

-12802,

-12607,

-12597,

-12594,

-12585,

-12556,

-12359,

-12346,

-12320,

-12300,

-12120,

-12099,

-12089,

-12074,

-12067,

-12058,

-12039,

-11867,

-11861,

-11847,

-11831,

-11798,

-11781,

-11604,

-11589,

-11536,

-11358,

-11340,

-11339,

-11324,

-11303,

-11097,

-11077,

-11067,

-11055,

-11052,

-11045,

-11041,

-11038,

-11024,

-11020,

-11019,

-11018,

-11014,

-10838,

-10832,

-10815,

-10800,

-10790,

-10780,

-10764,

-10587,

-10544,

-10533,

-10519,

-10331,

-10329,

-10328,

-10322,

-10315,

-10309,

-10307,

-10296,

-10281,

-10274,

-10270,

-10262,

-10260,

-10256,

-10254

)

var pystr = arrayOf(

"a",

"ai",

"an",

"ang",

"ao",

"ba",

"bai",

"ban",

"bang",

"bao",

"bei",

"ben",

"beng",

"bi",

"bian",

"biao",

"bie",

"bin",

"bing",

"bo",

"bu",

"ca",

"cai",

"can",

"cang",

"cao",

"ce",

"ceng",

"cha",

"chai",

"chan",

"chang",

"chao",

"che",

"chen",

"cheng",

"chi",

"chong",

"chou",

"chu",

"chuai",

"chuan",

"chuang",

"chui",

"chun",

"chuo",

"ci",

"cong",

"cou",

"cu",

"cuan",

"cui",

"cun",

"cuo",

"da",

"dai",

"dan",

"dang",

"dao",

"de",

"deng",

"di",

"dian",

"diao",

"die",

"ding",

"diu",

"dong",

"dou",

"du",

"duan",

"dui",

"dun",

"duo",

"e",

"en",

"er",

"fa",

"fan",

"fang",

"fei",

"fen",

"feng",

"fo",

"fou",

"fu",

"ga",

"gai",

"gan",

"gang",

"gao",

"ge",

"gei",

"gen",

"geng",

"gong",

"gou",

"gu",

"gua",

"guai",

"guan",

"guang",

"gui",

"gun",

"guo",

"ha",

"hai",

"han",

"hang",

"hao",

"he",

"hei",

"hen",

"heng",

"hong",

"hou",

"hu",

"hua",

"huai",

"huan",

"huang",

"hui",

"hun",

"huo",

"ji",

"jia",

"jian",

"jiang",

"jiao",

"jie",

"jin",

"jing",

"jiong",

"jiu",

"ju",

"juan",

"jue",

"jun",

"ka",

"kai",

"kan",

"kang",

"kao",

"ke",

"ken",

"keng",

"kong",

"kou",

"ku",

"kua",

"kuai",

"kuan",

"kuang",

"kui",

"kun",

"kuo",

"la",

"lai",

"lan",

"lang",

"lao",

"le",

"lei",

"leng",

"li",

"lia",

"lian",

"liang",

"liao",

"lie",

"lin",

"ling",

"liu",

"long",

"lou",

"lu",

"lv",

"luan",

"lue",

"lun",

"luo",

"ma",

"mai",

"man",

"mang",

"mao",

"me",

"mei",

"men",

"meng",

"mi",

"mian",

"miao",

"mie",

"min",

"ming",

"miu",

"mo",

"mou",

"mu",

"na",

"nai",

"nan",

"nang",

"nao",

"ne",

"nei",

"nen",

"neng",

"ni",

"nian",

"niang",

"niao",

"nie",

"nin",

"ning",

"niu",

"nong",

"nu",

"nv",

"nuan",

"nue",

"nuo",

"o",

"ou",

"pa",

"pai",

"pan",

"pang",

"pao",

"pei",

"pen",

"peng",

"pi",

"pian",

"piao",

"pie",

"pin",

"ping",

"po",

"pu",

"qi",

"qia",

"qian",

"qiang",

"qiao",

"qie",

"qin",

"qing",

"qiong",

"qiu",

"qu",

"quan",

"que",

"qun",

"ran",

"rang",

"rao",

"re",

"ren",

"reng",

"ri",

"rong",

"rou",

"ru",

"ruan",

"rui",

"run",

"ruo",

"sa",

"sai",

"san",

"sang",

"sao",

"se",

"sen",

"seng",

"sha",

"shai",

"shan",

"shang",

"shao",

"she",

"shen",

"sheng",

"shi",

"shou",

"shu",

"shua",

"shuai",

"shuan",

"shuang",

"shui",

"shun",

"shuo",

"si",

"song",

"sou",

"su",

"suan",

"sui",

"sun",

"suo",

"ta",

"tai",

"tan",

"tang",

"tao",

"te",

"teng",

"ti",

"tian",

"tiao",

"tie",

"ting",

"tong",

"tou",

"tu",

"tuan",

"tui",

"tun",

"tuo",

"wa",

"wai",

"wan",

"wang",

"wei",

"wen",

"weng",

"wo",

"wu",

"xi",

"xia",

"xian",

"xiang",

"xiao",

"xie",

"xin",

"xing",

"xiong",

"xiu",

"xu",

"xuan",

"xue",

"xun",

"ya",

"yan",

"yang",

"yao",

"ye",

"yi",

"yin",

"ying",

"yo",

"yong",

"you",

"yu",

"yuan",

"yue",

"yun",

"za",

"zai",

"zan",

"zang",

"zao",

"ze",

"zei",

"zen",

"zeng",

"zha",

"zhai",

"zhan",

"zhang",

"zhao",

"zhe",

"zhen",

"zheng",

"zhi",

"zhong",

"zhou",

"zhu",

"zhua",

"zhuai",

"zhuan",

"zhuang",

"zhui",

"zhun",

"zhuo",

"zi",

"zong",

"zou",

"zu",

"zuan",

"zui",

"zun",

"zuo"

)

val instance = CharacterParser()

}

}

以上的类主要做一些汉字ascii转拼音的操作;

TestContactBean:

package com.example.test29api.slider_contact

class TestContactBean(val name: String = "", val phone: String = "") : NewSideBar.Indexable {

override val indexable: String

get() {

return name

}

}

这里不要忘了继承NewSideBar的Indexable接口,便于获取需要排序的词组;

android 汉字字母排序,android recycleView自定义字母检索A-Z排序滑动通讯录汉字英文相互转换...相关推荐

  1. android列表字母排序,Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音...

    [实例简介]Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音 [实例截图] [核心代码] package com.example.sortlistview; imp ...

  2. Android好友联系人按字母排序分组,自定义通讯录导航栏View

    Android中仿微信实现联系人列表 按字母排序分组 自定义通讯录导航栏View,下边是效果图: 1. 自定义View public class SideBar extends View {// 触摸 ...

  3. [Android精品源码] Android 仿美团网,探索ListView的A-Z字母排序功能实现选择城市

    Material Design中文版Code4APPPHP100UI4APP 开启辅助访问设为首页收藏本站快捷导航切换到宽版切换风格 石刚 | |我的 |签到打卡 |设置 |消息 |提醒(2) |退出 ...

  4. Android获取手机联系人匹配用户表并按字母A Z排序展示

    1.前言 最近在做公司项目的时候遇到一个添加手机联系人的需求,主要有以下几个功能点: 读取联系人:读取用户手机上的通讯录里的联系人列表 好友排序:按照拼音顺序对好友进行排序,兼容英文数字符号等 字母索 ...

  5. android 通讯录字母排序,Android仿微信联系人字母排序效果

    本文实例为大家分享了Android联系人字母排序的具体代码,供大家参考,具体内容如下 实现思路:首先说下布局,整个是一个相对布局,最下面是一个listview,listview上面是一个自定义的vie ...

  6. Android自定义字母索引

    Android字母索引这个功能还是很常见的,例如:电话联系人,城市选择等一些功能都会用到.轮子已造好直接使用, Github源码 . 先看效果图: 自定义分析: 1.自定义View用 Paint 画笔 ...

  7. android自定义数字键盘和字母键盘,Android自定义键盘的实现(数字键盘和字母键盘)...

    Android自定义键盘的实现(数字键盘和字母键盘) 发布时间:2020-09-04 03:18:48 来源:脚本之家 阅读:100 作者:浪淘沙xud 在项目中,产品对于输入方式会有特殊的要求,需要 ...

  8. Android仿微信自定义字母导航栏

    自定义侧边字母导航栏,根据实际字母高度进行显示 先上效果图 导航栏                                                                    ...

  9. android 联系人 字母索引,Android ListView字母索引(仿微信通訊錄列表)

    布局代碼 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_paren ...

  10. 城市列表取汉字的第一个字的首字母并排序功能

    先上图效果: 数据结构原来是这样: Array ([0] => Array([sid] => 2885842[recetcstoredpay] => 24000[recetclpri ...

最新文章

  1. 对线程与进程的区别以及对多线程并发的理解
  2. 关于常用meta的总结
  3. 主程序分析法MATLAB编程,专题五  概率统计问题的Matlab求解
  4. 前端开发必须要了解的CSS原理
  5. 湖北职称计算机考试报名时间2016,湖北2016年下半年职称计算机考试报名时间延长通知...
  6. 起到的C++是中间层的作用
  7. 索尼发布工业设备用SWIR图像传感器 采用5微米像素尺寸
  8. java 存储过程_Java 调用存储过程
  9. 华为云NP考试题库_阿里云ACP大数据及云计算经验感悟
  10. windows下,linux下elasticsearch安装插件head插件的步骤
  11. 自己的Linux用户
  12. Matlab中TCP通讯-实现外部程序提供优化目标函数解
  13. Git从远程主分支切换出一个开发分支
  14. php lumen timestamp,Lumen 5.4 时区设置
  15. C++字符读入函数(getchgetchar)
  16. 204. 电子编程入门到工程师--混沌与秩序--天书信号
  17. Android 12 WiFi 架构
  18. word嵌入对象依损坏_出错提示“Word 未能写某些嵌入对象,因为内容或磁盘空间不足”...
  19. 再来!使用frida框架hook来获取APP的加密算法的参数
  20. 憨猴科技“分布式运营系统“荣登世界互联网大会

热门文章

  1. 电子计算机的算数,电子计算机的算术/逻辑单元,控制单元合称为____
  2. 1582年日历怎么了_1582年从10月5日到15日到底发生了什么?为何所有日历全是空白?...
  3. 最小采样频率计算公式_速度采样频率
  4. 用c语言用*组成C字母,C语言字符集由字母,数字,空格,标点符号和特殊字符组成...
  5. android手机访问网站时 出现您未被授权查看该页 您试图访问的 Web 服务器上有一个不被允许访问该网站的 IP 地
  6. 【Houdini MAYA】从MAYA到Houdini入门学习笔记(三)
  7. springboot接口慢_Springboot tomcat 启动慢 响应时间超长 问题解决
  8. 七种常见的电子邮件安全协议简析
  9. 读书札记:30个因素预示美国将再现“大萧条”...
  10. Pacman基本使用