最近看到有代码在实现类似通讯录字母索引的功能的时候,使用了ListView来做处理,个人觉得用ListView甚至现在很流行的RecycleView来做并不好,可以来自定义一个索引的控件,达到更好的效果,话不多说,先上效果图:
首先:先来说说需求
- 把字母[A - Z - #]在控件中画出来(这里以字母来举例)
- 当触摸到某个字母时,高亮显示
- 给控件设置监听,显示当前触摸到的字母
下面,就开始撸码(其实自定义View基本上就是那几个套路);
1:自定义需要的属性:
<declare-styleable name="CustomSideBar">
<!-- 字体大小 高亮的颜色 普通颜色(未选中时候) -->
<attr name="slideBarTextSize" format="dimension"/>
<attr name="highLightColor" format="color"/>
<attr name="normalColor" format="color"/>
</declare-styleable>
2:在布局文件中使用
<com.justh.dell.customsidebar.CustomSideBar
android:id="@+id/side_bar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:slideBarTextSize="20dp"
app:highLightColor="@color/colorAccent"
app:normalColor="@color/colorPrimary"/>
3:在自定义View中获取属性,并使用
public CustomSideBar(Context context) {
this(context,null);
}
public CustomSideBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public CustomSideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.CustomSideBar);
mSideBarTextSize = array.getDimensionPixelSize(R.styleable.CustomSideBar_slideBarTextSize,dp2px(mSideBarTextSize));
mHighLightColor = array.getColor(R.styleable.CustomSideBar_highLightColor,mHighLightColor);
mNormalColor = array.getColor(R.styleable.CustomSideBar_normalColor,mNormalColor);
array.recycle();
//初始化画笔
mHightPaint = getPaint(mHighLightColor);
mNormalPaint = getPaint(mNormalColor);
}
//初始化画笔
private Paint getPaint(int color){
Paint paint = new Paint();
paint.setColor(color);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setTextSize(mSideBarTextSize);
return paint;
}
在onMeasure中去测量空间的宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//计算宽度大小
//宽度大小等于一个字母的宽度加上左右的padding值 治理取A来做例子
int width = (int) mNormalPaint.measureText("A") + getPaddingLeft() + getPaddingRight();
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width,height);
}
然后在onDraw方法中间字母滑到对应的位置(这里直接将触摸到的时候的高亮显示字母的代码一并贴上了,懒死算了 哈哈哈)
@Override
protected void onDraw(Canvas canvas) {
//首先 由于是竖直排列的 所以先计算出一个字母所占据的高度
//一个字母所占据的高度 = (高度总和 - paddingTop - paddingBottom) / 元素总和
mLetterHeight = (getHeight() - getPaddingTop() - getPaddingBottom()) / letters.length;
for(int i = 0;i<letters.length;i++){
//计算每一个字母所在的位置的位置的中间值centerY
int centerY = getPaddingTop() + i * mLetterHeight + mLetterHeight /2;
Paint.FontMetricsInt fontMetricsInt = mNormalPaint.getFontMetricsInt();
int dy = (fontMetricsInt.bottom - fontMetricsInt.top)/2 - fontMetricsInt.bottom;
int baseLine = centerY + dy;
//计算每一个字母所在的位置的X的起点 (居中情况下)
int startX = (int) (getWidth()/2 - mNormalPaint.measureText(letters[i])/2);
if(letters[i].equals(mCurrentLetter)){
canvas.drawText(letters[i],startX,baseLine,mHightPaint);
}else{
canvas.drawText(letters[i],startX,baseLine,mNormalPaint);
}
}
}
最后重写onTouch方法,用来获取当前触摸到的是哪一个字母:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//根据当前的坐标 计算当前触摸到的是哪个条目
int dy = (int) event.getY();
int currentItem = (dy - getPaddingTop())/mLetterHeight;
if(currentItem < 0){
currentItem = 0;
}
if(currentItem >= letters.length){
currentItem = letters.length - 1;
}
//获取到惦记的是哪个字母
mCurrentLetter = letters[currentItem];
if(mSideBarTouchLetterListener != null){
mSideBarTouchLetterListener.onTouchLetter(mCurrentLetter,true);
}
invalidate();
break;
case MotionEvent.ACTION_UP:
//设置延迟消失
mHandler.sendEmptyMessageDelayed(1,1000);
break;
}
return true;
}
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1){
if(mSideBarTouchLetterListener != null){
mSideBarTouchLetterListener.onTouchLetter(mCurrentLetter,false);
}
}
}
};
这里,使用了一个Handler 的延迟发送消息来处理延迟1秒消失TextView的效果
最后,给其设置字母的触摸监听回调
private SideBarTouchLetterListener mSideBarTouchLetterListener;
public void setSideBarTouchLetterListener(SideBarTouchLetterListener listener){
this.mSideBarTouchLetterListener = listener;
}
//设置回调监听
public interface SideBarTouchLetterListener{
//flag控制显示与不显示在Activity中的圆形TextView
void onTouchLetter(String letter,boolean flag);
}
一直习惯了把注释写到代码里,这样阅读起来也会更方便,相信大家都能理解我的良苦用心,这里就不过多解释了。
最后 只需要在actvity中监听CustomSideBar的回调,并将对应点击的字母设置给TextView显示出来就可以了。
public class MainActivity extends AppCompatActivity implements CustomSideBar.SideBarTouchLetterListener{
private TextView mTextView;
private CustomSideBar mSideBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView(){
mTextView = (TextView) findViewById(R.id.textView);
mSideBar = (CustomSideBar) findViewById(R.id.side_bar);
}
private void initListener(){
mSideBar.setSideBarTouchLetterListener(this);
}
@Override
public void onTouchLetter(String letter,boolean flag) {
if(flag){
mTextView.setVisibility(View.VISIBLE);
mTextView.setText(letter);
}else{
mTextView.setVisibility(View.GONE);
}
}
}
好了,到这里就已经结束了,再来一波最后的效果图,如果大家发现有什么问题,可以回复交流下,谢谢!