Android自定义实现A-Z列表功能
项目中需要用到A-Z的列表,点击字母可以滚动到列表中对应字母位置。
先看下怎么使用:
xml中:
android:id="@+id/siderbar_letters"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:siderQuickItemTypebgColor="#3F88FF"
app:sliderLetterColor="#808080"
app:sliderLetterWidth="12sp"
app:siderQuickItemTypeTextWidth="14sp"
app:siderQuickItemTypeTextColor="#FFFFFF"/>
java中:
public class MainActivity extends AppCompatActivity {
private String[] str = new String[]{"一","二","三","死","一","一","一","一",
"商店","一","重新","我","as","第","是","把","留",
"阿萨德","as","都是","一","一","商店","商店","发","一",
"一","阿萨德","一","商店",};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List
list = new ArrayList<>(); for(int i=0;i
ItemBeans itemBeans = new ItemBeans();
itemBeans.setType(i+1);
itemBeans.setValues(str[i]);
list.add(itemBeans);
}
SiderQuickBarView siderQuickBarView = findViewById(R.id.siderbar_letters);
siderQuickBarView.setLetters(list)
.setOnItemClickListener(letterBean ->
Toast.makeText(this,"type==>"+letterBean.getType()+" value==>"+letterBean.getLetterName(),Toast.LENGTH_SHORT).show()
);
}
}
自定义属性:
只需要把任意文字转成list ,按规则传入,即可自动按字母排序,点击后可返回当前字母和字母对应的key值。
效果图:
下面说一下实现的思路:
1.控件由两部分构成:下面的RecyclerView和右侧的自定义字母,使用的是帧布局。
2.将输入的文字按字母分类。
3.将分类好的字母放入新的list中,字母和字母对应的文字用type进行区分。
4.为RecyclerView创建适配器,添加点击事件。
5.为字母添加点击事件,并使RecyclerView滑动。
6.封装自定义属性。
好了,放一下主要代码:
public class SiderQuickBarView extends FrameLayout {
private RecyclerView rvLetter;
private SliderLetterView sliderLetterView;
private GuideBbar guideBbar;
private Context context;
private List
letterBeans; private List
cityBeans;
//自定义属性
private int siderQuickItemTypebgColor;
private int siderQuickItemTypeTvColor;
private float siderQuickItemTypeTextWidth;
private int sliderLetterColor;
private int sliderLetterWidth;
private OnItemClickListener onItemClickListener;
public SiderQuickBarView(Context context) {
super(context);
initView(context);
}
public SiderQuickBarView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SiderQuickBarView);
siderQuickItemTypebgColor = array.getColor(R.styleable.SiderQuickBarView_siderQuickItemTypebgColor, ContextCompat.getColor(context,R.color.colorAccent));
siderQuickItemTypeTvColor = array.getColor(R.styleable.SiderQuickBarView_siderQuickItemTypeTextColor,ContextCompat.getColor(context,R.color.whilte));
siderQuickItemTypeTextWidth = array.getDimensionPixelSize(R.styleable.SiderQuickBarView_siderQuickItemTypeTextWidth,20);
sliderLetterColor = array.getColor(R.styleable.SiderQuickBarView_sliderLetterColor,ContextCompat.getColor(context,R.color.black));
sliderLetterWidth = array.getDimensionPixelSize(R.styleable.SiderQuickBarView_sliderLetterWidth,50);
initView(context);
array.recycle();
}
private void initView(Context context) {
this.context = context;
//左边列表
rvLetter = new RecyclerView(context);
rvLetter.setLayoutManager(new LinearLayoutManager(context));
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
rvLetter.setLayoutParams(layoutParams);
//右边字母列表
sliderLetterView = new SliderLetterView(context,sliderLetterColor,sliderLetterWidth);
LayoutParams layoutParams1 = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
layoutParams1.gravity = Gravity.END;
layoutParams1.bottomMargin = 10;
sliderLetterView.setLayoutParams(layoutParams1);
//GuiBar
guideBbar = new GuideBbar(context);
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams2.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
layoutParams2.bottomMargin = 100;
guideBbar.setLayoutParams(layoutParams2);
addView(rvLetter);
addView(sliderLetterView);
// addView(guideBbar);
//字母监听
setListener();
}
private void setListener() {
sliderLetterView.setOnLetterTouchListener(letter -> {
for(int i=0;i
if(letter.equals(letterBeans.get(i).getLetterName())){
moveToPosition(i);
break;
}
}
Toast.makeText(context,letter,Toast.LENGTH_SHORT).show();
});
rvLetter.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
double scrollY = recyclerView.computeVerticalScrollOffset();
double s = recyclerView.computeVerticalScrollExtent();
double sum = recyclerView.computeVerticalScrollRange()-s;
double f = scrollY/sum;
guideBbar.setGuideX(f);
}
});
}
private void moveToPosition(int position) {
if (position != -1) {
rvLetter.scrollToPosition(position);
LinearLayoutManager mLayoutManager =
(LinearLayoutManager) rvLetter.getLayoutManager();
assert mLayoutManager != null;
mLayoutManager.scrollToPositionWithOffset(position, 0);
}
}
public SiderQuickBarView setLetters(List
letters){ Map
> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues()))); Iterator
>> itLetter = letterMap.entrySet().iterator(); cityBeans = new ArrayList<>();
letterBeans = new ArrayList<>();
Iterator
keySet = letterMap.keySet().iterator(); while (keySet.hasNext()){
CityBean cityBean = new CityBean();
cityBean.setKey(keySet.next());
cityBeans.add(cityBean);
}
//对字母排序
SiderComparator siderComparator = new SiderComparator();
Collections.sort(cityBeans,siderComparator);
cityBeans.forEach(cityBean -> {
List
contentList = new ArrayList (); List
itemBeans = letterMap.get(cityBean.getKey()); LetterBean letterBean = new LetterBean();
letterBean.setLetterType(LetterType.title);
letterBean.setLetterName(cityBean.getKey());
contentList.add(letterBean);
itemBeans.forEach(item -> {
LetterBean letterBean1 = new LetterBean();
letterBean1.setLetterType(LetterType.contet);
letterBean1.setType(item.getType());
letterBean1.setLetterName(item.getValues());
contentList.add(letterBean1);
});
cityBean.setBeanList(contentList);
});
cityBeans.forEach(cityBean -> {
letterBeans.addAll(cityBean.getBeanList());
});
LetterListAdapter letterListAdapter = new LetterListAdapter(context, letterBeans, siderQuickItemTypeTvColor, siderQuickItemTypebgColor, siderQuickItemTypeTextWidth);
rvLetter.setAdapter(letterListAdapter);
letterListAdapter.setOnItemClickListener((adapter, position) -> {
LetterListAdapter la = (LetterListAdapter) adapter;
LetterBean item = la.getItem(position);
if(onItemClickListener != null){
onItemClickListener.onClick(item);
}
});
return this;
}
interface OnItemClickListener{
void onClick(LetterBean letterBean);
}
public SiderQuickBarView setOnItemClickListener(OnItemClickListener onItemClickListener){
this.onItemClickListener = onItemClickListener;
return this;
}
class SiderComparator implements Comparator
{
@Override
public int compare(CityBean cityBean1, CityBean cityBean2) {
return cityBean1.getKey().compareTo(cityBean2.getKey());
}
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({LetterType.title,LetterType.contet})
public @interface LetterType{
int title = 0;
int contet = 1;
}
}
这个就是整个控件了,继承自FrameLayout,包含一个RecyclerView和右边的自定义字母(忽略被注释的View)
这里最主要的就是将输入的字符串信息,按照字母分类,我这里用到了一个第三库:
implementation 'com.belerweb:pinyin4j:2.5.1'
外加这个库的一个工具类:
public class PinYinUtils {
/**
* 将字符串中的中文转化为拼音,其他字符不变
*
* @param inputString
* @return
*/
public static String getPingYin(String inputString) {
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
format.setVCharType(HanyuPinyinVCharType.WITH_V);
char[] input = inputString.trim().toCharArray();
String output = "";
try {
for (int i = 0; i < input.length; i++) {
if (java.lang.Character.toString(input[i]).matches("[\\u4E00-\\u9FA5]+")) {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(input[i], format);
output += temp[0];
} else {
output += java.lang.Character.toString(input[i]);
}
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
return output;
}
/**
* 获取汉字串拼音首字母,英文字符不变
* @param chinese 汉字串
* @return 汉语拼音首字母
*/
public static String getFirstSpell(String chinese) {
StringBuffer pybf = new StringBuffer();
char[] arr = chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 128) {
try {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat);
if (temp != null) {
pybf.append(temp[0].charAt(0));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pybf.append(arr[i]);
}
}
return pybf.toString().replaceAll("\\W", "").trim();
}
/**
* 获取汉字串拼音,英文字符不变
* @param chinese 汉字串
* @return 汉语拼音
*/
public static String getFullSpell(String chinese) {
StringBuffer pybf = new StringBuffer();
char[] arr = chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 128) {
try {
pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pybf.append(arr[i]);
}
}
return pybf.toString();
}
/** 获取首字母
* @param letter
* @return
*/
public static String getFirstLetter(String letter){
StringBuffer pybf = new StringBuffer();
char[] arr = letter.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 128) {
try {
pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pybf.append(arr[i]);
}
}
return pybf.substring(0,1);
}
}
这个工具类的作用,可以将文字的首字母给取出来, 那么取出来干嘛呢??
下面我正好借助lamda表达式,根据首字母将数据进行分组:
Map> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues())));
分完组之后的数据就是一个map集合 map的key就是 各数据对应的首字母,value就是各首字母对应的数据,再将map中的数据,放入一个新的list中,所以现在混乱数据就变成了一组组有序集合了,但现在还不够,因为是按照A-Z排序,目前不是按A-Z,可能是g,f,a,d 这样乱排序的,只是把数据都放在了它们各自的首字母下面而已,那么,下面就借助Comparator这个接口让字母按照A-Z的顺序排序:
class SiderComparator implements Comparator
{
@Override
public int compare(CityBean cityBean1, CityBean cityBean2) {
return cityBean1.getKey().compareTo(cityBean2.getKey());
}
}
//对字母排序
SiderComparator siderComparator = new SiderComparator();
Collections.sort(cityBeans,siderComparator);
排序完成之后,这个list就可以像普通的list一样放入RecyclerView的适配器中使用了。
下面看一下自定义的字母:
/**
* 右边字母自定义
* @author amggy
*/
public class SliderLetterView extends View {
private Paint paint;
private String[] letters;
private double itemHeight;
private int position;
private OnLetterTouchListener onLetterTouchListener;
private int paintColor;
private int paintStrokWith;
public SliderLetterView(Context context,int paintColor,int paintStrokWith) {
super(context);
this.paintColor = paintColor;
this.paintStrokWith = paintStrokWith;
initView(context);
}
public SliderLetterView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public SliderLetterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
letters = new String[]{"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"};
position = 0;
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(paintColor);
paint.setTextSize(paintStrokWith);
paint.setTextAlign(Paint.Align.CENTER);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
double screenHeight = getHeight();
itemHeight = screenHeight /letters.length;
float letterX = (float)getWidth()/2;
for(int i=0;i
canvas.drawText(letters[i],letterX,(float) ((i+1)*itemHeight),paint);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
float eventY = event.getY();
position = (int) (eventY/itemHeight);
if(onLetterTouchListener != null){
onLetterTouchListener.onTouch(letters[position]);
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int with = MeasureSpec.getSize(widthMeasureSpec);
int withMode = MeasureSpec.getMode(widthMeasureSpec);
int high = MeasureSpec.getSize(heightMeasureSpec);
int highMode = MeasureSpec.getMode(heightMeasureSpec);
int w = 0;
int h = 0;
switch(withMode){
case MeasureSpec.EXACTLY:
w = with;
break;
case MeasureSpec.AT_MOST:
w = 80;
break;
default:
break;
}
switch(highMode){
case MeasureSpec.EXACTLY:
h = high;
break;
case MeasureSpec.AT_MOST:
h = getMeasuredHeight();
break;
default:
break;
}
setMeasuredDimension(w,h);
}
public void setOnLetterTouchListener(OnLetterTouchListener onLetterTouchListener){
this.onLetterTouchListener = onLetterTouchListener;
}
public interface OnLetterTouchListener{
/** 返回选中字母
* @param letter 字母
*/
void onTouch(String letter);
}
}
自定义字母继承自View,按照控件的高度,等分的绘制字母,添加onTouch事件,将触碰到的字母返回出去,给RecyclerView滚动到指定位置做准备。