Android自定义View实现通讯录字母索引(仿微信通讯录)
作者:钟光燕 发布时间:2023-05-14 04:34:22
一、效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等。这里我截了一张美团选择城市的图片来看看;
我们今天就来实现图片中右侧模块的索引功能,包括触摸显示以选中的索引字母。这里我的UI界面主要是参照微信的界面来实现,所以各位也可以对照微信来看看效果,什么都不说了,只有效果图最具有说服力!
二、分析:
我们看到这样的效果我们心理都回去琢磨,他是如何实现的;
首先,它肯定是通过自定义 View 来实现的,因为 Android 没有提供类似这样的控件、那么接下来就是如何自定义我们的 View ,我们知道自定义 View 最最主要的两个方法就是 onDraw(Canvas canvas)和
onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,当然,如果是自定义 ViewGroup 的话就必须实现
onLayout(boolean changed, int left, int top, int right, int bottom) 方法,这里我们显然用自定义 View 就能够实现此功能,通过效果图可以看带,当触摸这块区域的时候,会弹出一个悬浮类似 Toast 的框来显示已经选中的索引内容,所以这里还需要重写View 的onTouchEvent(MotionEvent event)事件,最后就是悬浮框的实现。那么接下来就开始我们编码。
三、编码实现:
我们就按照 View 的执行顺序来实现
1、实现onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,这个方法的功能是测量出我们的宽和高,具体实现看代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
这里定义了两个方法measureWidth( int) 和 measureHeight(int) ,通过方法名可以很清楚的知道,其功能分别是测量宽和高,进去看看是如何测量的。
/**
* 测量本身的大小,这里只是测量宽度
* @param widthMeaSpec 传入父View的测量标准
* @return 测量的宽度
*/
private int measureWidth(int widthMeaSpec){
/*定义view的宽度*/
int width ;
/*获取当前 View的测量模式*/
int mode = MeasureSpec.getMode(widthMeaSpec) ;
/*
* 获取当前View的测量值,这里得到的只是初步的值,
* 我们还需根据测量模式来确定我们期望的大小
* */
int size = MeasureSpec.getSize(widthMeaSpec) ;
/*
* 如果,模式为精确模式
* 当前View的宽度,就是我们
* 的size ;
* */
if(mode == MeasureSpec.EXACTLY){
width = size ;
}else {
/*否则的话我们就需要结合padding的值来确定*/
int desire = size + getPaddingLeft() + getPaddingRight() ;
if(mode == MeasureSpec.AT_MOST){
width = Math.min(desire,size) ;
}else {
width = desire ;
}
}
mViewWidth = width ;
return width ;
}
以上是测量宽度的代码,其测量高度的代码,跟测量宽度的代码大致雷同,就不贴出来了,我会在最后附上源码。
2、实现onDraw(Canvas c)方法,这个方法相信大家都非常熟悉,就是把这些索引的内容绘制到 View 上显示出来,包括选中的时候背景颜色的变化;
@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();
}
}
我来讲一下 onDraw 方法中大致做了什么事,第一,绘制背景颜色,注意不是一上来就绘制,而是等到有手指触摸的时候就绘制背景颜色,第二,就是绘制索引的内容,这里需要根据当前 View 的宽和高来决定绘制内容的大小,和位置。
3、onTouchEvent(MotionEvent event)方法的实现
@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();
/*过滤点其他触摸事件*/
return true;
}
代码也相对比较简单,首先获取当前触摸的点,根据点的坐标来获取索引的位置,从而拿到索引的位置。
4、到这里其实就已经实现了我们想要的效果,但是这样我们还是无法运用它,这里就需要定义一个回调接口
/*定义一个回调接口*/
public interface OnIndexSelectListener{
/*返回选中的位置,和对应的索引名*/
void onItemSelect(int position, String value) ;
}
回调接口我们放在哪里调用呢,当我们手指按下的时候,这时候其实我们需要确定我们按下的是哪个索引,滑动的时候也是一样,所以,这个没什么好商量的,直接放在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)方法中就是执行的回调方法。
5、实现悬浮框显示已经选中的索引内容
这里需要用到 WindowManager 容器,然需要现实的 View 附在这上面的就行,当手指按下的时候,让 View 显示出来,松开不显示就行了
/*设置浮动选中的索引*/
/*获取windowManager*/
mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
/*overly 视图,通过LayoutInflater 获取*/
mFloatView = LayoutInflater.from(getContext()).inflate(R.layout.overlay_indexview,null) ;
/*开始让其不可见*/
mFloatView.setVisibility(INVISIBLE);
/*转换 高度 和宽度为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);
}
}) ;
同样的道理,如果需要改变显示的内容,就需要在调用回调的位置,为 View 中的 TextView 设置当前的索引内容。
好了此 View 的代码就这么多,
接下来就把引用他的 Xml 和浮动 View 的 Xml 也贴出来,
引用的布局文件
<moon.wechat.view.IndexView
android:layout_width="25dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"/>
浮动 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>
浮动 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>
来源:http://blog.csdn.net/jxxfzgy/article/details/43771983


猜你喜欢
- 本文为大家分享了如何使用eclipse创建java项目,供大家参考,具体内容如下首先,打开Eclipse,在工具栏依次点击【File】>
- 公司的研发管理平台实现了Gitlab+Kubernetes的Devops,在ToB和ToC场景中,由于用户量大,且预发布环境和生产环境或多或
- 开始以前,先认识一下WinForm控件数据绑定的两种形式,简单数据绑定和复杂数据绑定。1. 简单的数据绑定例1using (SqlConne
- Java音频播放,因为必须依赖到本地环境,所以JAVA在音频处理方面优势不大,或者说打从Java体系开发时就没太多的考虑音频播放
- list.remove最近做项目的过程中,需要用到list.remove()方法,结果发现两个有趣的坑,经过分析后找到原因,记录一下跟大家分
- 一、JDK * Java 在 java.lang.reflect 包中有自己的代理支持,该类(Proxy.java)用于动态生成代理类,只
- 子类重新实现父类的方法称重写;重写时可以修改访问权限修饰符和返回值,方法名和参数类型及个数都不可以修改;仅当返回值为类类型时,重写的方法才可
- 参数设置java程序启动参数 -D是用来做什么的呢? 官方解释如下:Set a system property value. If valu
- @ConfigurationProperties注入创建一个新的模板此过程就不在这介绍了,在我SpringBoot专栏里有详细过程。⭐⭐⭐注
- 时间格式化在项目中使用频率是非常高的,当我们的 API 接口返回结果,需要对其中某一个 date 字段属性进行特殊的格式化处理,通常会用到
- 在学习monkeyrunner之前,让我们先搭建好eclipse安卓开发环境。对于程序开发人员而言,eclipse并不陌生,它提供了一个非常
- cookie机制和session机制的区别具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持
- 这篇文章主要介绍了SpringBoot跨域Access-Control-Allow-Origin实现解析,文中通过示例代码介绍的非常详细,对
- 事件(event),这个词儿对于初学者来说,往往总是显得有些神秘,不易弄懂。而这些东西却往往又是编程中常用且非常重要的东西。大家都知道win
- 一、Spring Boot任务线程池线程池的作用防止资源占用无限的扩张调用过程省去资源的创建和销毁所占用的时间在高并发环境下,不断的分配新资
- 本文实例讲述了Java String类简单用法。分享给大家供大家参考,具体如下:一 String类的实例化方式1 代码public clas
- 本文实例讲述了Java面向对象程序设计:类的定义,静态变量,成员变量,构造函数,封装与私有,this概念与用法。分享给大家供大家参考,具体如
- JNI中的java接口使用项目需求,需要在c++函数中监听相应的状态,并在java端进行一些列的处理。这个需要在JNI中写一个subscri
- Android 中SwipeRefreshLayout与ViewPager滑动事件冲突解决方法问题描述:开发中发现,SwipeRefresh
- RecyclerView 已经出来很久了,但是在项目中之前都使用的是ListView,最近新的项目上了都大量的使用了RecycleView.