Android滑动列表索引波浪侧边栏与电话拨打功能

✍️作者简介:大鹏编码(专注于HarmonyOS、Java、Android、Web、TCP/IP等技术方向)
🐳博客主页: 开源中国、稀土掘金、51cto博客、知乎、简书、CSDN
🔔如果文章对您有一定的帮助请👉关注✨、点赞👍、收藏📂、评论💬。
🔥如需转载请参考【转载须知】

文章目录

      • 引言
      • 波浪侧边栏
        • 1. 什么是波浪侧边栏?
        • 2. 如何实现?
        • 3. 优势和冲击力
      • 代码实现
        • 波浪侧边栏代码
        • 适配器代码
        • 实例
      • 总结

老样子先看实例:
在这里插入图片描述

引言

Android应用中实现滑动列表索引波浪侧边栏,同时支持根据拼音分类的快速查找,并且具备拨打电话的功能。


波浪侧边栏

1. 什么是波浪侧边栏?

滑动列表索引波浪侧边栏是一种通过滑动屏幕侧边实现快速导航的交互设计。这种设计通常以字母或拼音为基础,让用户可以快速浏览和查找列表中的内容。

2. 如何实现?

在Android应用中,可以使用RecyclerView和定制的侧边栏组件来实现滑动列表索引波浪侧边栏。通过监听滑动事件和触摸事件,实时更新列表的位置,从而实现侧边栏与列表的同步操作。

3. 优势和冲击力
  • 提升用户导航效率:用户无需逐一滚动列表,通过直接点击侧边栏上的字母或拼音,即可快速跳转到相应位置,提升了用户查找内容的效率。

  • 引人注目的波浪动画:为了增强用户体验,可以添加波浪状的动画效果,使侧边栏更加生动有趣,吸引用户的注意力。

代码实现

需要的引用包build.gradle.kts

    implementation("androidx.recyclerview:recyclerview:1.2.1")implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.47")implementation("com.google.code.gson:gson:2.10.1")implementation("com.orhanobut:logger:2.2.0")implementation("com.belerweb:pinyin4j:2.5.1")

注意:这里BaseRecyclerViewAdapterHelper要在settings.gradle.kts
引用maven { url = uri("https://jitpack.io") }

波浪侧边栏代码
public class WaveSideBar extends View {private static final String TAG = "WaveSideBar";// 计算波浪贝塞尔曲线的角弧长值private static final double ANGLE = Math.PI * 45 / 180;private static final double ANGLE_R = Math.PI * 90 / 180;private OnTouchLetterChangeListener mListener;// 渲染字母表private List<String> mLetters;// 当前选中的位置private int mChoosePosition = -1;private int mOldPosition;private int mNewPosition;// 字母列表画笔private Paint mLettersPaint = new Paint();// 提示字母画笔private Paint mTextPaint = new Paint();// 波浪画笔private Paint mWavePaint = new Paint();private int mTextSize;private int mHintTextSize;private int mTextColor;private int mWaveColor;private int mTextColorChoose;private int mWidth;private int mHeight;private int mItemHeight;private int mPadding;// 波浪路径private Path mWavePath = new Path();// 圆形路径private Path mCirclePath = new Path();// 手指滑动的Y点作为中心点private int mCenterY; //中心点Y// 贝塞尔曲线的分布半径private int mRadius;// 圆形半径private int mCircleRadius;// 用于过渡效果计算private ValueAnimator mRatioAnimator;// 用于绘制贝塞尔曲线的比率private float mRatio;// 选中字体的坐标private float mPointX, mPointY;// 圆形中心点Xprivate float mCircleCenterX;public WaveSideBar(Context context) {this(context, null);}public WaveSideBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public WaveSideBar(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(context, attrs);}private void init(Context context, AttributeSet attrs) {mLetters = Arrays.asList(context.getResources().getStringArray(R.array.waveSideBarLetters));mTextColor = Color.parseColor("#969696");mWaveColor = Color.parseColor("#bef9b81b");mTextColorChoose = context.getResources().getColor(android.R.color.white);mTextSize = context.getResources().getDimensionPixelSize(R.dimen.textSize);mHintTextSize = context.getResources().getDimensionPixelSize(R.dimen.hintTextSize);mPadding = context.getResources().getDimensionPixelSize(R.dimen.padding);if (attrs != null) {TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.waveSideBar);mTextColor = a.getColor(R.styleable.waveSideBar_textColor, mTextColor);mTextColorChoose = a.getColor(R.styleable.waveSideBar_chooseTextColor, mTextColorChoose);mTextSize = a.getDimensionPixelSize(R.styleable.waveSideBar_textSize, mTextSize);mHintTextSize = a.getDimensionPixelSize(R.styleable.waveSideBar_hintTextSize, mHintTextSize);mWaveColor = a.getColor(R.styleable.waveSideBar_backgroundColor, mWaveColor);mRadius = a.getDimensionPixelSize(R.styleable.waveSideBar_radius, context.getResources().getDimensionPixelSize(R.dimen.radius));mCircleRadius = a.getDimensionPixelSize(R.styleable.waveSideBar_circledRadius, context.getResources().getDimensionPixelSize(R.dimen.circleRadius));a.recycle();}mWavePaint = new Paint();mWavePaint.setAntiAlias(true);mWavePaint.setStyle(Paint.Style.FILL);mWavePaint.setColor(mWaveColor);mTextPaint.setAntiAlias(true);mTextPaint.setColor(mTextColorChoose);mTextPaint.setStyle(Paint.Style.FILL);mTextPaint.setTextSize(mHintTextSize);mTextPaint.setTextAlign(Paint.Align.CENTER);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {final float y = event.getY();final float x = event.getX();mOldPosition = mChoosePosition;mNewPosition = (int) (y / mHeight * mLetters.size());switch (event.getAction()) {case MotionEvent.ACTION_DOWN://限定触摸范围if (x < mWidth - 1.5 * mRadius) {return false;}mCenterY = (int) y;startAnimator(1.0f);break;case MotionEvent.ACTION_MOVE:mCenterY = (int) y;if (mOldPosition != mNewPosition) {if (mNewPosition >= 0 && mNewPosition < mLetters.size()) {mChoosePosition = mNewPosition;if (mListener != null) {mListener.onLetterChange(mLetters.get(mNewPosition));}}}invalidate();break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:startAnimator(0f);mChoosePosition = -1;break;default:break;}return true;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mHeight = MeasureSpec.getSize(heightMeasureSpec);mWidth = getMeasuredWidth();mItemHeight = (mHeight - mPadding) / mLetters.size();mPointX = mWidth - 1.6f * mTextSize;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//绘制字母列表drawLetters(canvas);//绘制波浪drawWavePath(canvas);//绘制圆drawCirclePath(canvas);//绘制选中的字体drawChooseText(canvas);}/*** 绘制字母列表** @param canvas*/private void drawLetters(Canvas canvas) {RectF rectF = new RectF();rectF.left = mPointX - mTextSize;rectF.right = mPointX + mTextSize;rectF.top = mTextSize / 2;rectF.bottom = mHeight - mTextSize / 2;mLettersPaint.reset();mLettersPaint.setStyle(Paint.Style.FILL);mLettersPaint.setColor(Color.parseColor("#F9F9F9"));mLettersPaint.setAntiAlias(true);canvas.drawRoundRect(rectF, mTextSize, mTextSize, mLettersPaint);mLettersPaint.reset();mLettersPaint.setStyle(Paint.Style.STROKE);mLettersPaint.setColor(mTextColor);mLettersPaint.setAntiAlias(true);canvas.drawRoundRect(rectF, mTextSize, mTextSize, mLettersPaint);for (int i = 0; i < mLetters.size(); i++) {mLettersPaint.reset();mLettersPaint.setColor(mTextColor);mLettersPaint.setAntiAlias(true);mLettersPaint.setTextSize(mTextSize);mLettersPaint.setTextAlign(Paint.Align.CENTER);Paint.FontMetrics fontMetrics = mLettersPaint.getFontMetrics();float baseline = Math.abs(-fontMetrics.bottom - fontMetrics.top);float pointY = mItemHeight * i + baseline / 2 + mPadding;if (i == mChoosePosition) {mPointY = pointY;} else {canvas.drawText(mLetters.get(i), mPointX, pointY, mLettersPaint);}}}/*** 绘制选中的字母** @param canvas*/private void drawChooseText(Canvas canvas) {if (mChoosePosition != -1) {// 绘制右侧选中字符mLettersPaint.reset();mLettersPaint.setColor(mTextColorChoose);mLettersPaint.setTextSize(mTextSize);mLettersPaint.setTextAlign(Paint.Align.CENTER);canvas.drawText(mLetters.get(mChoosePosition), mPointX, mPointY, mLettersPaint);// 绘制提示字符if (mRatio >= 0.9f) {String target = mLetters.get(mChoosePosition);Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();float baseline = Math.abs(-fontMetrics.bottom - fontMetrics.top);float x = mCircleCenterX;float y = mCenterY + baseline / 2;canvas.drawText(target, x, y, mTextPaint);}}}/*** 绘制波浪** @param canvas*/private void drawWavePath(Canvas canvas) {mWavePath.reset();// 移动到起始点mWavePath.moveTo(mWidth, mCenterY - 3 * mRadius);//计算上部控制点的Y轴位置int controlTopY = mCenterY - 2 * mRadius;//计算上部结束点的坐标int endTopX = (int) (mWidth - mRadius * Math.cos(ANGLE) * mRatio);int endTopY = (int) (controlTopY + mRadius * Math.sin(ANGLE));mWavePath.quadTo(mWidth, controlTopY, endTopX, endTopY);//计算中心控制点的坐标int controlCenterX = (int) (mWidth - 1.8f * mRadius * Math.sin(ANGLE_R) * mRatio);int controlCenterY = mCenterY;//计算下部结束点的坐标int controlBottomY = mCenterY + 2 * mRadius;int endBottomX = endTopX;int endBottomY = (int) (controlBottomY - mRadius * Math.cos(ANGLE));mWavePath.quadTo(controlCenterX, controlCenterY, endBottomX, endBottomY);mWavePath.quadTo(mWidth, controlBottomY, mWidth, controlBottomY + mRadius);mWavePath.close();canvas.drawPath(mWavePath, mWavePaint);}/*** 绘制左边提示的圆** @param canvas*/private void drawCirclePath(Canvas canvas) {//x轴的移动路径mCircleCenterX = (mWidth + mCircleRadius) - (2.0f * mRadius + 2.0f * mCircleRadius) * mRatio;mCirclePath.reset();mCirclePath.addCircle(mCircleCenterX, mCenterY, mCircleRadius, Path.Direction.CW);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {mCirclePath.op(mWavePath, Path.Op.DIFFERENCE);}mCirclePath.close();canvas.drawPath(mCirclePath, mWavePaint);}private void startAnimator(float value) {if (mRatioAnimator == null) {mRatioAnimator = new ValueAnimator();}mRatioAnimator.cancel();mRatioAnimator.setFloatValues(value);mRatioAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator value) {mRatio = (float) value.getAnimatedValue();//球弹到位的时候,并且点击的位置变了,即点击的时候显示当前选择位置if (mRatio == 1f && mOldPosition != mNewPosition) {if (mNewPosition >= 0 && mNewPosition < mLetters.size()) {mChoosePosition = mNewPosition;if (mListener != null) {mListener.onLetterChange(mLetters.get(mNewPosition));}}}invalidate();}});mRatioAnimator.start();}public void setOnTouchLetterChangeListener(OnTouchLetterChangeListener listener) {this.mListener = listener;}public List<String> getLetters() {return mLetters;}public void setLetters(List<String> letters) {this.mLetters = letters;invalidate();}public interface OnTouchLetterChangeListener {void onLetterChange(String letter);}
}
适配器代码
public class UserAdapter extends BaseQuickAdapter<UserBean, BaseViewHolder> {private List<UserBean> userList;public UserAdapter(@Nullable List<UserBean> data) {super(R.layout.item_user, data);this.userList = data;}@Overrideprotected void convert(@NonNull BaseViewHolder helper, UserBean item) {helper.setText(R.id.tv_contacts_name, item.getRealName());helper.setText(R.id.tv_department, item.getOrganizationName());helper.setText(R.id.tv_phone, item.getUserPhone());helper.addOnClickListener(R.id.llUserName);helper.addOnClickListener(R.id.ivPhone);}/*** 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置*/public int getPositionForSection(int section) {for (int i = 0; i < getItemCount(); i++) {String sortStr = userList.get(i).getLetters();if (sortStr != null && !sortStr.isEmpty()) {char firstChar = sortStr.toUpperCase().charAt(0);if (firstChar == section) {return i;}}}return -1;}
}
实例
public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;private UserAdapter userAdapter;/*** 根据拼音来排列RecyclerView里面的数据类*/private PinyinComparator mComparator;private LinearLayoutManager manager;private TitleItemDecoration mDecoration;private List<UserBean> contactsList = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());String rqsSrc = "UserList.json";String rqsJson = AssetsUtil.getAssetsJson(this, rqsSrc);BaseBean baseBean = MyJson.fromJson(rqsJson, BaseBean.class);List<UserBean> userList = MyJson.fromJson(MyJson.toJson(baseBean.getData()),new TypeToken<ArrayList<UserBean>>() {}.getType());mComparator = new PinyinComparator();Collections.sort(userList, mComparator);manager = new LinearLayoutManager(this);manager.setOrientation(LinearLayoutManager.VERTICAL);binding.recyclerView.setLayoutManager(manager);userAdapter = new UserAdapter(new ArrayList<>());binding.recyclerView.setAdapter(userAdapter);contactsList = filledData(userList);refreshAdapter(contactsList);userAdapter.setOnItemChildClickListener((adapter, view, position) -> {UserBean item = (UserBean) adapter.getItem(position);int id = view.getId();if (id == R.id.llUserName) {Toast.makeText(this, "点击条目测试:Name " + item.getRealName(), Toast.LENGTH_SHORT).show();} else if (id == R.id.ivPhone) {String realName    = item.getRealName();String phoneNumber = item.getUserPhone();if (TextUtils.isEmpty(phoneNumber)) {Toast.makeText(this, "联系人没有保留电话!", Toast.LENGTH_SHORT).show();} else {new AlertDialog.Builder(this).setTitle("拨号").setMessage(realName + "\n" + phoneNumber + "\n" + "请确认要拨打的电话?").setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {Intent intent = new Intent(Intent.ACTION_DIAL);Uri ril = Uri.parse("tel:" + phoneNumber);intent.setData(ril);startActivity(intent);}}).show();}}});//设置右侧SideBar触摸监听binding.sideBar.setOnTouchLetterChangeListener(new WaveSideBar.OnTouchLetterChangeListener() {@Overridepublic void onLetterChange(String letter) {//该字母首次出现的位置int position = userAdapter.getPositionForSection(letter.charAt(0));if (position != -1) {manager.scrollToPositionWithOffset(position, 0);}}});}/*** 为RecyclerView填充数据** @param date* @return*/private List<UserBean> filledData(List<UserBean> date) {for (int i = 0; i < date.size(); i++) {//汉字转换成拼音String pinyin = PinyinUtils.getPingYin(date.get(i).getRealName());String sortString = pinyin.substring(0, 1).toUpperCase();// 正则表达式,判断首字母是否是英文字母if (sortString.matches("[A-Z]")) {date.get(i).setLetters(sortString.toUpperCase());} else {date.get(i).setLetters("#");}}return date;}private void refreshAdapter(List<UserBean> contactsListBean) {// 根据a-z进行排序源数据Collections.sort(contactsListBean, mComparator);userAdapter.getData().clear();userAdapter.replaceData(contactsListBean);if (mDecoration == null) {mDecoration = new TitleItemDecoration(this, contactsList);//如果add两个,那么按照先后顺序,依次渲染。binding.recyclerView.addItemDecoration(mDecoration);binding.recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));}}
}

以上是大部分主要代码,由于代码比较多,只能贴出主要代码,如果有不明白的的也可以下载demo来参考,如果有积分的可以直接点击下载!

下载地址: CSDN 点击下载

如果没有积分,也想要代码的可以留言单独发,请有积分的勿骚扰!
文章写作不易请大家点赞👍、收藏📁!

总结

通过引入滑动列表索引波浪侧边栏和电话拨打功能,我们不仅提高了应用的用户体验,还为用户提供了更便捷的操作方式。这一创新设计不仅让应用更加吸引人,同时也符合现代用户对于高效、直观操作的期望。在移动应用开发中,不断尝试新的交互设计和功能整合,将是取得成功的关键之一。

无论是哪个阶段,坚持努力都是成功的关键。不要停下脚步,继续前行,即使前路崎岖,也请保持乐观和勇气。相信自己的能力,你所追求的目标定会在不久的将来实现。加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/245332.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NIFI源码编译部署在服务器CentOS环境中

一、下载Apache NiFi源码&#xff1a; Apache NiFi官网地址&#xff0c;文档 Apache NiFi源码GitHub地址 二、部署nifi 2.1进入opt目录&#xff0c;并创建software、module [rootlocalhost /]# cd /opt/ [rootlocalhost opt]# ls containerd [rootlocalhost opt]# mkdir s…

外包干了4年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

抓取Chrome所有版本密码

谷歌浏览器存储密码的方式 在使用谷歌浏览器时,如果我们输入某个网站的账号密码,他会自动问我们是否要保存密码,以便下次登录的时候自动填写账号和密码 在设置中可以找到登录账户和密码 也可以直接看密码,不过需要凭证 这其实是windows的DPAPI机制 DPAPI Data Protection Ap…

NFTScan | 11.27~12.03 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2023.11.20~ 2023.11.26 NFT Hot News 01/ Web3 教育平台 Open Campus 获 Binance Labs 315 万美元投资 11 月 27 日&#xff0c;Binance Labs 已向社区主导的 Web3 教育平台 Open Campu…

FFmpeg之将视频转为16:9(横屏)或9:16(竖屏)(三十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

Python快速配置爬虫代码示例

在当今数字化时代&#xff0c;信息爆炸已成为常态。数据是现代商业的核心&#xff0c;而爬虫程序是获取数据的重要工具。Python作为一门通用编程语言&#xff0c;提供了许多方便快捷的库来配置爬虫代码。下面是一个简单的Python爬虫代码示例&#xff0c;帮助你快速上手。 首先…

C++ : 友元

不能从外部访问类的私有数据成员和方法&#xff0c;但这条规则不适用于友元类和友元函数。要声明友元 类或友元函数&#xff0c;可使用关键字 friend&#xff0c;通过让函数成为类的友元&#xff0c;可以赋予该函数与类的成员函数 同的访问权限。 生活中你的家有客厅 (Public)…

蓝桥杯每日一题2023.12.4

题目描述 竞赛中心 - 蓝桥云课 (lanqiao.cn) 题目分析 本题使用树型DP&#xff0c;蓝桥杯官网出现了一个点的错误&#xff0c;但实际答案是正确的 状态表示&#xff1a;f[u]&#xff1a;在以u为根的子树中包含u的所有联通块的权值的最大值 假设s1&#xff0c;s2,…sk 是u的…

如何打印社保参保凭证

西安市&#xff1a; 陕西政务服务网&#xff1a; 个人服务 珠海市&#xff1a; 广东政务服务网&#xff1a; 用户登录 | 珠海市人力资源和社会保障网上服务平台 武汉市&#xff1a; 湖北政务服务网&#xff1a; 湖北政务服务网

涵盖多种功能,龙讯旷腾Module第三期:光、磁、力学和极化性质

Module是什么 在PWmat的基础功能上&#xff0c;我们针对用户的使用需求开发了一些顶层模块&#xff08;Module&#xff09;。这些Module中的一部分是与已有的优秀工具的接口&#xff0c;一部分是以PWmat的计算结果为基础得到实际需要的物理量&#xff0c;一部分则是为特定的计…

2024品牌营销为何需要提供“情绪价值”和“感官滋养”?徐礼昭

什么是情绪价值&#xff1f; 品牌营销在当今市场中&#xff0c;已经超越了单纯的产品推广和销售&#xff0c;更多地涉及到提供“情绪价值”和“感官滋养”。 情绪价值是指产品或服务能够引发的消费者情感反应和共鸣&#xff0c;从而满足消费者情感需求的一种价值。它与产品的…

计算机间的通信艺术解析

1*NwzOU-ne2vvobtubtEmBhw.png 网络基础知识 首先&#xff0c;让我们谈谈网络基础知识&#xff0c;即计算机如何彼此通信。 在这种通信的核心是IP地址&#xff0c;它是网络上每个设备的唯一标识符。IPv4地址是32位的&#xff0c;允许大约40亿个唯一地址。然而&#xff0c;随着设…