Android使用RecycleView实现魅族手机通讯录界面

简介: 本文主要是通过模仿魅族通讯录,学习一下RecycleView的基本用法

本文主要是通过模仿魅族通讯录,学习一下RecycleView的基本用法,水平有限,如有不当之处,欢迎批评指正,不胜感激!

先看通过RecycleView实现的一个效果图:
Contacts.gif

完整代码见github:仿魅族通讯录,如果喜欢给个Star吧!老铁,稳~

Recycleview继承关系
recycleview.png
RecycleView的三个主要参与者:
1、LayoutManager
2、ItemAnimator
3、ItemDecoration
本文主要用到的是ItemAnimator和ItemDecoration。

  • Item动画 ItemAnimator

ItemAnimator是个抽象类,ItemAnimator子类用来管理ViewHolder的动画,官方已经实现了一个DefaultItemAnimator,它继承自SimpleItemAnimator,而SimpleItemAnimator继承自ItemAnimator,SimpleItemAnimator是一个包装类,用来记录视图范围,决定当前ViewHolder是否执行移动、变化、添加和删除动画,如果想自定义动画可以通过继承SimpleItemAnimator来实现,github上已经有很多优秀的开源动画了,如:
https://github.com/gabrielemariotti/RecyclerViewItemAnimators
这里只展示一种从左边进入的动画效果,其余大家可以下载下源码查看:

ItemAnimator.gif
注:这里动画position=1是写死了的,主要是为了方便看效果~

  • ItemDecoration

ItemDecoration是RecycleView的一个静态内部类,通过对每一个ItemView的边界添加特殊绘制和布局,从而影响每一个ItemView的边界,如绘制分割线、绘制悬浮框等等,ItemDecoration中有三个方法:
1、getItemOffsets()
2、onDraw()
3、onDrawOver()
所有的ItemDecorations绘制都是顺序执行,即:
onDraw() < Item View < onDrawOver()
onDraw() 可以用来绘制divider,但在此之前必须在getItemOffsets设置了padding范围,否则onDraw()的绘制是在ItemView的下面导致不可见;onDrawOver()是绘制在最上层,所以可以用来绘制悬浮框等,下面来看各个方法:

1、getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
内部调用outRect.set(int left, int top, int right, int bottom)来改变ItemView的边界,类似于给ItemView设置Padding,默认getItemOffsets不会影响ItemView的边界,即默认内部调用的是outRect.set(0, 0, 0, 0),如果想得到当前正在修饰的ItemView的位置,可以通过parent.getChildAdapterPosition(view)来获取。

2、onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
在画布canvas上进行绘制,onDraw()方法是在ItemView被绘制之前执行的,因此onDraw()的绘制是在ItemView下方。

3、onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
在画布canvas上进行绘制,onDrawOver()方法是在ItemView被绘制之后执行的,因此onDrawOver()的绘制是在ItemView上方,可用来绘制本例中的悬浮框等。

下面介绍下实现本例魅族通讯录的主要思路:

1、自定义ItemDecoration来绘制悬浮框及ItemView之上的分类Tag:

//用来绘制每个ItemView的边距
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    //............省略部分代码............
    int position = parent.getChildAdapterPosition(view);
    if (position == 0) {
        //第一条数据有bar
        outRect.set(0, dividerHeight, 0, 0);
    } else if (position > 0) {
        if (TextUtils.isEmpty(mBeans.get(position).getIndexTag())) return;
        //与上一条数据中的tag不同时,该显示bar了
        if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) {
            outRect.set(0, dividerHeight, 0, 0);
        }
    }
}

//用来绘制最上面的悬浮框
@Override
 public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
     int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
     final int bottom = parent.getPaddingTop() + dividerHeight;
     mPaint.setColor(Color.WHITE);
     //绘制悬浮框的范围
     canvas.drawRect(parent.getLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + dividerHeight, mPaint);
     //............省略部分代码............
     mPaint.setTextSize(40);
     canvas.drawCircle(DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 2, 35, mPaint);
     mPaint.setColor(Color.WHITE);
     canvas.drawText(mBeans.get(position).getIndexTag(), DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 3, mPaint);
    }

//按需绘制ItemView上面的分类Tag
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    //............省略部分代码............
    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
        int position = params.getViewLayoutPosition();
        if (position == 0) {
            //第一条数据有bar
            drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag()));
        } else if (position > 0) {
            //与上一条数据中的tag不同时,该显示bar了
            if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) {
                drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag()));
            } } 
     }  
   }

2、绘制右侧导航栏:

首先自定义SideBar(SideBar extends View ) 绘制最右侧字母:

@Override
 protected void onDraw(Canvas canvas) {
    //for循环绘制出所有的导航栏字母
     for (int i = 0; i < indexStr.length(); i++) {
         String textTag = indexStr.substring(i, i + 1);
         float xPos = (mWidth - mPaint.measureText(textTag)) / 2;
         canvas.drawText(textTag, xPos, singleHeight * (i + 1) + DpUtil.dp2px(mContext, TOP_MARGIN), mPaint);
     }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
    //处理按下滑动事件
     switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
             //按下
             mPaint.setColor(Color.BLACK);
             invalidate();
        case MotionEvent.ACTION_MOVE:
             //滑动 event.getY()得到在父View中的Y坐标,通过和总高度的比例再乘以字符个数总长度得到按下的位置
             int position = (int) ((event.getY() - getTop() - DpUtil.dp2px(mContext, 80)) / mHeight * indexStr.toCharArray().length);
             if (position >= 0 && position < indexStr.length()) {
                 ((IndexBar) getParent()).setDrawData(event.getY(), String.valueOf(indexStr.toCharArray()[position]), position);
                 if (listener != null) {
                     listener.indexChanged(indexStr.substring(position, position + 1));
                 }
             }
             break;
         case MotionEvent.ACTION_UP:
             //抬起
             ((IndexBar) getParent()).setTagStatus(false);
             mPaint.setColor(Color.GRAY);
             invalidate();
             break;
     }
     return true;
 }

SideBar中绘制了导航字母并在onTouchEvent处理了滑动事件,当手指上下滑动时左侧有个圆跟着滑动,这里用的自定义IndexBar( IndexBar extends ViewGroup,IndexBar包含SideBar )来处理的,当SideBar滑动处于MOVE状态时通过((IndexBar) getParent()).setDrawData()把一系列位置参数传到IndexBar中去,:

@Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
     int childNum = getChildCount();
     if (childNum <= 0) return;
     //得到SideBar
     View childView = getChildAt(0);
     childWidth = childView.getMeasuredWidth();
     //把SideBar排列到最右侧
     childView.layout((mWidth - childWidth), 0, mWidth, mHeight);
    }

@Override
 protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     if (isShowTag) {
         //根据位置来不断变换Paint的颜色
         ColorUtil.setPaintColor(mPaint, position);
         //绘制圆和文字
         canvas.drawCircle((mWidth - childWidth) / 2, centerY, circleRadius, mPaint);
         mPaint.setColor(Color.WHITE);
         mPaint.setTextSize(80);
         canvas.drawText(tag, (mWidth - childWidth - mPaint.measureText(tag)) / 2, centerY - (mPaint.ascent() + mPaint.descent()) / 2, mPaint);
     }
 }

主要是在onLayout中把SideBar排列到最右侧,并在onDraw中根据SideBar传过来的一系列位置参数来不断改变圆的位置,这里要注意一下,自定义ViewGroup的onDraw()方法默认是不会调用的,如果想执行onDraw方法,可以通过下面两种方法:
1.设置透明背景:
在构造函数中:setBackgroundColor(Color.TRANSPARENT);
或者在xml中:android:background="@color/transparent"
2.或者可以在构造函数中添加setWillNotDraw(false);

本文例子中用到的三方库:
1、https://github.com/amulyakhare/TextDrawable
2、https://github.com/promeG/TinyPinyin
3、https://github.com/gabrielemariotti/RecyclerViewItemAnimators

相关文章
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
279 0
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
530 65
Android自定义view之网易云推荐歌单界面
|
Android开发 数据安全/隐私保护
安卓手机修改设备id, 安卓硬改一键新机,手机机型修改(伪装)
提供了完整的设备信息修改功能,包含设备模板配置、基础信息修改、网络信息修改、模拟器检测绕
|
存储 人工智能 文字识别
三款安卓手机word编辑器下载,Microsoft Word,wps office,Word手机版,手机word编辑查看阅读器,PDF转换器apk下载
WPS Office是一款功能强大的办公软件,支持文档编辑、表格处理和演示文稿制作,兼容多种格式并提供丰富的云服务。它具备低内存占用、快速运行的特点,支持跨设备同步与多人协作,内置海量模板及AI辅助功能,如智能写作和PPT自动生成。此外,还可扫描文件、编辑PDF并转换为其他格式,极大提升办公效率,适合手机用户便捷操作。
1269 1
|
Android开发 开发者
Android企业级实战-界面篇-3
本文是《Android企业级实战-界面篇》系列的第三篇,主要介绍分割线和条形跳转框的实现方法,二者常用于设置和个人中心界面。文章通过具体代码示例展示了如何实现这两种UI组件,并提供了效果图。实现前需准备`dimens.xml`、`ids.xml`、`colors.xml`等文件,部分资源可参考系列第一、二篇文章。代码中详细说明了布局文件的配置,如分割线的样式定义和条形跳转框的组件组合,帮助开发者快速上手并应用于实际项目中。
168 1
|
XML Android开发 数据格式
Android企业级实战-界面篇-2
本文为《Android企业级实战-界面篇》系列第二篇,主要介绍三个UI模块的实现:用户资料模块、关注与粉丝统计模块以及喜欢和收藏功能模块。通过详细的XML代码展示布局设计,包括dimens、ids、colors配置文件的使用,帮助开发者快速构建美观且功能齐全的界面。文章结合实际效果图,便于理解和应用。建议配合第一篇文章内容学习,以获取完整工具类支持。
198 0
|
算法 Java Android开发
Android企业级实战-界面篇-1
本文详细介绍了Android企业级开发中界面实现的过程,涵盖效果展示、实现前准备及代码实现。作者通过自身经历分享了Android开发经验,并提供了`dimens.xml`、`ids.xml`、`colors.xml`和`strings.xml`等配置文件内容,帮助开发者快速构建规范化的UI布局。文章以一个具体的用户消息界面为例,展示了如何使用线性布局(LinearLayout)和相对布局(RelativeLayout)实现功能模块排列,并附带注意事项及使用方法,适合初学者和进阶开发者参考学习。
291 0
|
Android开发 数据安全/隐私保护 虚拟化
安卓手机远程连接登录Windows服务器教程
安卓手机远程连接登录Windows服务器教程
3991 5
|
安全 搜索推荐 Android开发
Android vs. iOS:解锁智能手机操作系统的奥秘####
【10月更文挑战第21天】 在当今这个数字化时代,智能手机已成为我们生活中不可或缺的伙伴。本文旨在深入浅出地探讨两大主流操作系统——Android与iOS的核心差异、优势及未来趋势,帮助读者更好地理解这两个平台背后的技术哲学和用户体验设计。通过对比分析,揭示它们如何塑造了我们的数字生活方式,并展望未来可能的发展路径。无论您是技术爱好者还是普通用户,这篇文章都将带您走进一个充满创新与可能性的移动世界。 ####
543 3
|
Android开发 iOS开发 UED
安卓与iOS的较量:谁才是智能手机市场的王者?
本文将深入探讨安卓和iOS两大智能手机操作系统之间的竞争关系,分析它们各自的优势和劣势。通过对比两者在市场份额、用户体验、应用生态等方面的表现,我们将揭示出谁才是真正的市场领导者。无论你是安卓粉丝还是iOS忠实用户,这篇文章都将为你提供一些有趣的观点和见解。

热门文章

最新文章