仿VPGAME客户端跟RecyclerView联动指针控件
来源:互联网 发布:java 如何encode 编辑:程序博客网 时间:2024/06/03 22:41
先看VPGAME客户端的这个效果:
接着是我实现的效果:
转成gif图质量不太好,实际效果比这个好很多,可以去运行demo看看实际效果。链接:https://github.com/DarkSherlock/DateViewWithRvDemo
我们可以看到这个效果,当recyclerview滑动的时候,这个控件里的那个时钟指针
会跟着转动,后面的文字也会跟着item的值 有一个滑进滑出动画。
我本以为这是一个自定义View,然而当我用打开DDMS用HierachyView查看它的布局的时候。
我们可以看到他这个不是用一个自定义View来完成的,而是多个自定义View
来组合在RelativieLayout里来实现的。那么我们就可以借鉴他的这个思路。
Studio打开HierachyView的步骤:
那么接下来就来分析下实现的思路:
1.首先要和RecyclerView完成交互,那么就需要添加OnScrollListener来监听
RV的滑动,根据滑动距离来算出滑动了几个Item,根据Item的某字段(它这里是时间月份)来传给自定义控件,让其完成UI更新。
2.那个滑进滑出的控件,觉得不需要再去自定义,只需要用TextView加位移动画就能实现。
3.自定义指针转动控件,根据OnScrollListener监听到的dy滑动距离,来设置转动的角度。
具体实现:
//为了和dateview 完成联动,添加滑动监听 rv.addOnScrollListener(new MyScrollListener());
在onScrollListener()里着重关注onScrolled();
@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if ( mRvItemHeight != 0 ) { y += dy; //将累计的滑动距离 跟一个item的高度 比较,判断滑动了相当于几个item的距离。 float position = y / mRvItemHeight; //将每次滑动了相当于多少个Item高度的值传给指针控件, //滑动一个item高度指针就转动一圈,按比例转动角度。 dateview.setProcess(position); mBean = mList.get((int) position);//拿到对应的item的javabean //只要有轻微的滑动onScrolled就会调用,但是我们不需要这么频繁的去更新滑进滑出的UI //所以我们这里判断只有当2个item的月份字段不一样的时候,这时候需要执行滑进滑出的 //动画,并且将月份更新显示。 if (mBean.getMonth()!= Integer.parseInt(mTvMonth.getText().toString())) { mCurrentMonth = mBean.getMonth(); if (dy > 0) { //判断执行向上还是向下滑动动画 startUpAnim( ); } else { startDownAnim(); } } } }
接着看看动画:
由于位移动画我们需要拿到执行动画的textview的Y轴起始坐标和高度,所以我们post一个runnable(直接在activity的oncreat()中去拿的话因为控件可能还未layout完毕,所以可能取到的值为0);
**动画分为:**1.向上滑出动画2.向上滑进动画3.向下滑出动画4.向下滑进动画。
textview向上滑出顶部不可见后再从底部向上滑进(1执行完毕后执行2)
textview向下滑出底部不可见后再从顶部向下滑进(3执行完毕后执行4)
//post 一个runnable 待 view layout 完毕后测量 rcyclerview item的高度 并且初始化动画 rv.post(new Runnable() { @Override public void run() { View childAt = rv.getLayoutManager().findViewByPosition(0); if (childAt != null) { mRvItemHeight = (float) childAt.getHeight(); initAnimation(); } } });
private void initAnimation() { // Y轴方向上的坐标 float translationY = mTvMonth.getTranslationY(); float tvMonthHeight = mTvMonth.getHeight(); //向上弹出动画 //第一个参数是要执行动画的控件,第二个参数是更改的属性字段(需带有setter方法), //第三个参数是 动画开始时 要更改的属性字段的起始值,第四个是结束时的值(translationY - tvMonthHeight 相当于滑出边界不可见了。) //这里指mTvMonth执行Y轴上的坐标 更改(Y轴位移动画) mUpAnimOut = ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY, translationY - tvMonthHeight); //向上弹进动画 mUpAnimIn =ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY + tvMonthHeight, translationY); mUpAnimOut.setDuration(ANIMATION_DURATION); mUpAnimIn.setDuration(ANIMATION_DURATION); //添加动画执行监听 addUpAnimListener(mUpAnimIn); //向下弹出动画 mDownAnimOut =ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY, translationY + tvMonthHeight); //向下弹进动画 ObjectAnimator downAnimIn =ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY - tvMonthHeight, translationY); mDownAnimOut.setDuration(ANIMATION_DURATION); downAnimIn.setDuration(ANIMATION_DURATION); //添加动画执行监听 addDownAnimListener(downAnimIn); }
private void addUpAnimListener(final ObjectAnimator upAnimIn) { mUpAnimOut.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (!upAnimIn.isStarted()) { upAnimIn.start(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); upAnimIn.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mTvMonth.setText(String.valueOf(mCurrentMonth)); } @Override public void onAnimationEnd(Animator animation) { //当recycler滑动速度非常快的时候,当前的动画还未执行,已经滑动到下条数据要执行下一个动画时, //因为我们判断了!upAnimIn.isStarted() ,所以下个动画不会执行,这时候就需要以下判断当RecyclerView //滑动停止,当前动画结束时将正确的(下一条的数据)设置给mTvMonth,避免数据错乱. if (scrollState == RecyclerView.SCROLL_STATE_IDLE && mCurrentMonth != Integer.parseInt(mTvMonth.getText().toString())) { mTvMonth.setText(String.valueOf(mCurrentMonth)); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); }
动画设置执行时间为50ms,但是由于recyclerview可能会非常快速地滑动,所以如果动画还在执行就跳过,在 RecyclerView滑动停止时即状态等于SCROLL_STATE_IDLE时将要更新的值保存下来,在动画执行完毕的时候去判断 如果数据显示不正确再重新赋值正确的数据给textview
/** * 开始向上滑出的动画 */ private void startUpAnim( ) { if (!mUpAnimOut.isStarted()) { mUpAnimOut.start(); } }
@Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (mBean != null) { scrollState = newState; //当非常快速滑动的时候 在滑动的最后判断数据是否准确,将正确的数据返回。 if (scrollState == RecyclerView.SCROLL_STATE_IDLE && mCurrentMonth != Integer.parseInt(mTvMonth.getText().toString())) { mCurrentMonth = mBean.getMonth(); } } }
这样动画的部分就实现完了,接着看转动指针的部分
**转动指针自定义View分为2部分:**1.不动的圆形背景类似于时钟背景
2.转动的指针,类似于时钟指针。
背景直接canvas.drawCircle就行,没什么可说的。
指针转动的角度就需要根据传onScrollListener传进来的值进行一定的计算来算出需要转动多少角度,直接看代码就懂了。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int height = getHeight(); int width = getWidth(); int radius = width / 2;//圆形背景半径 canvas.translate(width / 2, height / 2); canvas.save(); //画灰色圆形背景 canvas.drawCircle(0, 0, width / 2, mCirclePaint); //画12 3 6 9 四个刻度 长度为半径(width/2)的0.25 mCursor.setColor(Color.parseColor("#FFAAAAAA")); canvas.drawLine(0, -height / 2, 0, ((radius * R_QUARTER) - height / 2), mCursor);//12 canvas.drawLine(width / 2, 0, (width / 2 - (radius * R_QUARTER)), 0, mCursor);//3 canvas.drawLine(0, height / 2, 0, (height / 2 - (radius * R_QUARTER)), mCursor);//6 canvas.drawLine(-width / 2, 0, (-width / 2 + (radius * R_QUARTER)), 0, mCursor);//9 //画根据传进来的process 转动的指针 int stopX = (int) (0.6 * (width / 2) * Math.sin(mProcess * 2 * Math.PI)); int stopY = (int) (0.6 * (width / 2) * Math.cos(mProcess * 2 * Math.PI)); mCursor.setColor(Color.WHITE); canvas.drawLine(0, 0, stopX, -stopY, mCursor); }
/** * 设置指针转动角度比率 * @param process */ public void setProcess(float process) { this.mProcess = process; invalidate(); }
这样就完成了,挺简单的代码,完整的代码可以去githup上的demo中看。
- 仿VPGAME客户端跟RecyclerView联动指针控件
- RecyclerView---高仿网易新闻客户端
- RecyclerView联动
- 仿网易客户端的viewpager和头部的联动效果
- 仿ios时间,日期选择和三级联动控件
- Head联动RecyclerView
- ViewPager+RecyclerView联动
- Android仿QQ,网易邮箱客户端收件人控件
- 仿各种客户端都具备的评分控件
- RecyclerView(控件)
- Android自定义控件——仿饿了么联动ListView
- recyclerview联动滑动 关键代码
- RecyclerView 实现省市区三级联动
- RecyclerView实现双列表联动
- ToolBar与RecyclerView联动效果
- RecyclerView实现双列表联动
- Android基础控件——RecyclerView实现混排效果,仿网易云音乐
- 客户端练习 联动
- 如何手动将jar文件加入到maven的local repository中
- C++动态规划 求换取指定数额钱币的最少货币数 普通动态规划和空间压缩方法
- 小白算法学习 二分图 匈牙利算法
- LeetCode
- APP跨进程数据通信-访问手机联系人
- 仿VPGAME客户端跟RecyclerView联动指针控件
- 学习笔记之浅谈面向对象编程2
- ArrayList和LinkedList都是实现了List接口的类,他们都是元素的容器,用于存放对象的引用; 他们都可以对存放的元素进行增删改查的操作,还可以进行排序。 但是,他们还是有区别的。
- javascript正则表达式demo
- -ObjC 的故事
- 分布式、集群和微服务概念整理
- Android初级开发(九)——网络交互—OkHttp
- vim 文本编辑器
- 在unity中实现截图功能