Android 基于RecyclerView实现的歌词滚动自定义控件

本文介绍了Android 基于RecyclerView实现的歌词滚动自定义控件,分享给大家,具体如下:

先来几张效果图:

这几天打算做一个控件,来让自己复习一下自定义 view 的知识以及事件分发机制的原理与应用。对于这个控件,我已经封装好了,只要调用就可以了。

本来是想放上 gitHub 和 添加依赖的。但是提交 github 出了问题一直不会弄,所以就只能先等等了。((;′⌒`))

接下来说一下实现原理:

该控件分为以下几个部分:

  1. 歌词自动滚动
  2. 歌词颜色字体变化
  3. 触碰屏幕歌词不滚动,高亮显示,离开时自动移动到当前歌词位置
  4. 触碰屏幕中间线条出现以及显示该歌词的时间
  5. 点击歌词跳转到当前位置并输出当时时间
  6. 可设置跳转时间跳到相应歌词位置

接下来我一个一个大概讲述一下思路。

1.对于滚动,我们可以调用 RecyclerView.smoothScrollBy() 方法,

相对于 ScrollBy() 方法,该方法能够实现平滑滑动。

我设置了总共显示九句歌词。而且因为我想在歌词前面和后面留一些空白,这些看起来会好看些。所以,在歌词列表里面我加多了一些空白。

List<String> wordList = new ArrayList<>();
    wordList.add("");
    wordList.add("");
    wordList.add("");
    wordList.add("");
    wordList.addAll(mWordList);
    wordList.add("");
    wordList.add("");
    wordList.add("");
    wordList.add("");

由于歌词的滚自动滚动是根据歌词时间来进行移动的。所以我们需要需要使用 Runable 来执行滚动操作。而且为了避免内存泄漏。将 Runable 实现类修饰为 static 。所以歌词列表索引位置有所变化。

private static class AutoPullWork implements Runnable {
    public AutoPullWork(AutoPullRecyclerView autoPullRecyclerView) {
      weakReference = new WeakReference<AutoPullRecyclerView>(autoPullRecyclerView);
    }
    @Override
    public void run() {
    autoPullRecyclerView.smoothScrollBy(0, autoPullRecyclerView.getMeasuredHeight() / 9);
    autoPullRecyclerView.postDelayed(autoPullRecyclerView.autoPullWork, autoPullRecyclerView.timeList.get(autoPullRecyclerView.currentWord - 4) - autoPullRecyclerView.timeList.get(autoPullRecyclerView.currentWord - 5));
    ......

2.对于歌词的高亮显示,我们可以调用 notifyItemChange(int position) 方法,这个方法调用会重新去绘制特定 position 上的 viewHolder 。hightLightItem() 在这个方法中设置我们想要改变 viewHolder 的位置,并调用 notifyItemChange(int position) 。然后在 onBindViewHolder() 中的设置可以判断当前是否需要高亮显示。

public void hightLightItem(int position){
     mHighLightPosition = position;
     notifyItemChanged(position-1);
     notifyItemChanged(position);
  }
private boolean isHighLight(int position){
    return mHighLightPosition == position;
  }
@Override
  public void onBindViewHolder(ViewHolder holder, int position) {
    String word = mWordList.get(position);
    holder.textView.setText(word);

    try {
      if (!isHighLight(position)) {
        holder.textView.setTextSize(mOrdinarySize);
        holder.textView.setTextColor(Color.parseColor(mOrdinaryColor));

      } else if (isHighLight(position)) {
        holder.textView.setTextSize(mHighLightSize);
        holder.textView.setTextColor(Color.parseColor(mHighLightColor));
      }
    }catch ( Exception e){
      e.printStackTrace();
    }
  }

3.对于歌词自动移动到当前语句:

本身我的想法就是多设置一个变量还是在这个 Runable() 里面进行操作。但是一个很严重的问题,导致我连续几天一直想不到对策方法。由于手指离开屏幕的时候我使用 postDelayed() 方法有可能跟里面 Runable 里面使用的 postDelayed() 时间上可能会相互冲突,事件的执行情况就很有可能变得跟你想不一样。所以我们应该重新写一个 Runable() 来控制它的自动移动到当前位置。这样子的话各做各的事情,在写逻辑的时候会比较容易理顺。(当时没想好害我调了好久,一直都不对,哈哈).

/**
   * 歌词自动滑动到特定位置任务
   */
  private static class AutoBackWork implements Runnable{

    @Override
    public void run() {
    }
  }

对于点击屏幕时就重写 onTouchEvent() 方法,

在 down 事件中 ,设置变量让 Runable () 事件中不滚动。

而对于歌词在离开屏幕后的一段时间后自动回到该位置。同样的,还是需要使用 smoothScrollBy() 方法移动。而移动多少呢?这是个问题。这个要分为四种情况:

第一种:

当前歌词在屏幕之外:由于我是打算将歌词移动到屏幕中的第四个位置。

那么我就需要找到屏幕中的第一个位置,还有当前显示的是哪一句歌词。

由于我是想要让他显示在屏幕的第四行,所以是相差 currentWord + 5 - firstPosition 个位置 。

第二种:

当歌词在第四行之前但是在第一行之后。

第三种:

当歌词在第四行之后但是在最后一行之前。

第四种:

当歌词在最后一行之后。

其实我们就根据自己想要在显示在第几行来判断需要移动多少个位置。

我就不详说啦,具体看代码:

AutoPullRecyclerView autoPullRecyclerView = weakReference.get();
      LinearLayoutManager linearLayoutManager = (LinearLayoutManager) autoPullRecyclerView.getLayoutManager();
      int firtPosition = linearLayoutManager.findFirstVisibleItemPosition();
      int lastPosition = linearLayoutManager.findLastVisibleItemPosition();

      if (firtPosition>autoPullRecyclerView.currentWord){ // 第一种
        autoPullRecyclerView.smoothScrollBy(0, -(firtPosition - autoPullRecyclerView.currentWord + 5) * height);
      }else if(firtPosition+9>autoPullRecyclerView.currentWord){
        if (firtPosition+3>autoPullRecyclerView.currentWord){ // 第二种
          int top = autoPullRecyclerView.getChildAt(autoPullRecyclerView.currentWord-firtPosition).getTop();
          autoPullRecyclerView.smoothScrollBy(0, -(4*height-top)); //--
        }else{  // 第三种
          int top = autoPullRecyclerView.getChildAt(autoPullRecyclerView.currentWord-firtPosition).getTop();
          autoPullRecyclerView.smoothScrollBy(0,top-(4*height)); //++
        }
      }else { // 第四种
        autoPullRecyclerView.smoothScrollBy(0, (autoPullRecyclerView.currentWord - lastPosition + 5) * height);
      }
     }
 }

4.显示中间线条以及显示该歌词时间

中间的 view 不可能镶嵌在 RecyclerView 中。所以我们要自定义一个布局来放自定义 RecyclerView 和中间的 view。

这个是整个的 xml 文件。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:clickable="true"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <com.example.administrator.animationview.AutoPullRecyclerView
    android:id="@+id/auto_word"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  <RelativeLayout
    android:layout_centerVertical="true"
    android:id="@+id/divide_line"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
  <ImageView
    android:id="@+id/item_play_here"
    android:layout_marginStart="8dp"
    android:layout_centerVertical="true"
    android:src="@drawable/play"
    android:layout_width="20dp"
    android:layout_height="20dp" />
  <View
    android:id="@+id/divide_line1"
    android:layout_marginEnd="48dp"
    android:layout_marginStart="4dp"
    android:layout_toEndOf="@+id/item_play_here"
    android:layout_centerVertical="true"
    android:background="#E6E6FA"
    android:layout_width="match_parent"
    android:layout_height="1px"/>
  <TextView
    android:id="@+id/time1"
    android:layout_marginEnd="4dp"
    android:layout_alignParentEnd="true"
    android:layout_centerVertical="true"
    android:textSize="12sp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  </RelativeLayout>

</RelativeLayout>

中间线的逻辑是当点击屏幕的时候显示出中间的线,离开屏幕的时候过一小段时间消失。也就是需要处理 down 事件和 up 事件 。但是我们在 RecyclerView 中是处理了点击事件的,而且本身 RecyclerView 就已经重写了拦截了该事件的。而且一般是父 View 是不拦截事件的。那我们要怎么在里面设置 down 时间和 up 事件呢?我们怎么能让父 View 接收到事件处理了一下同时最后又是子 view 处理事件呢?

在此,我推荐一篇博客,里面很详细地介绍了事件分发处理机制的流程。

//www.jb51.net/article/103134.htm
//www.jb51.net/article/103141.htm

我先说一下结论吧。就是重写 dispatchTouchEvent() 。因为假如我们重写 onTouchEvent 的话,由于 RecyclerView 处理了事件。是不会处理这个方法的。

而对于 dispatchTouchEvent() 方法 ,如果你是在子 view 中处理事件。那么每次事件都会从 dispatchTouchEvent() 往下传递。具体原理可以看一下源码。

@Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()){
      case MotionEvent.ACTION_DOWN:
        performClick();
        view.setVisibility(VISIBLE);
        show = true;
        view.setOnClickListener(new OnClickListener() {
          @Override
          public void onClick(View view) {
            autoPullRecyclerView.setComeToPlay();
            onClickListener.onClickListener(mCurrentTime);
          }
        });
        break;
      case MotionEvent.ACTION_UP:
        view.removeCallbacks(runnable);
        view.postDelayed(runnable,4000);
        break;
      default:
        break;
    }
    return super.dispatchTouchEvent(ev);
  }

对于显示歌词的时间,由于线条是在最中间的部分,我想要的是中间的线在哪一个 item 里面显示该 item 对应时间。对于最原先的做法,我是通过 firstPosition 第一个看到的 item 变化时便变化时间。但是如果只是靠第一个可视化位置的话,由于中间线的位置,这样会导致恰好在中间的位置往上移动一点和往下移动一点是两个不同的时间变化。但是此时都是在同一 item 中 。所以我做的是去第二个可视化位置,判断该位置离 top 与 item/2 的距离的比较。从而解决问题。

最开始只是根据第一个可视化位置而显示的时间,但是显示时间变化的位置不对。

改了思路根据第二个可视化位置之后根据位移来判断。

private void showTime(){
    int height = autoPullRecyclerView.getMeasuredHeight() / 9;
    int top = autoPullRecyclerView.getChildAt(1).getTop();
    int currentPosition = linearLayoutManager.findFirstVisibleItemPosition();
    int position;
    if (top > height / 2) {
      position = currentPosition;
    } else {
      position = currentPosition + 1;
    }

点击歌词跳转并且返回时间

点击歌词的时候改变高亮的位置和恢复原先的高亮的位置,并且通过回调返回时间。

case MotionEvent.ACTION_DOWN:
        performClick();
        view.setVisibility(VISIBLE);
        show = true;
        view.setOnClickListener(new OnClickListener() {
          @Override
          public void onClick(View view) {
            autoPullRecyclerView.setComeToPlay();
            onClickListener.onClickListener(mCurrentTime);
          }
        });
        break;
/**
   * 点击歌词滑动
   */
  public void setComeToPlay(){
    type =3;
    comeToPlay = true;
    lastWord = currentWord-1;
    removeCallbacks(autoPullWork);
    post(autoPullWork);
  }

5.点击进度条跳转到相应位置

先调用 seekBar 的 onSeekBarChangeListener() 中监听方法,获取当前时间,根据时间获得当前应该所处的索引。然后调用自动移动滚动方法和高亮方法。

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar seekBar, int i, boolean b) {

      }

      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {

      }

      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
        int progress = seekBar.getProgress();    // 获取当前进度
        worldRelativeLayout.setChangeTime(progress);
      }
    });

这次做一个自定义 View 控件,让我有好几点感触,我记录一下,一方面是希望告诫自己,一方面也算是分享给他人吧。

当你要做某个控件或项目的时候,不要着急着动笔。要先想好整个流程和框架。这方面先考虑清楚在动笔写。你的逻辑一定要现在白纸上实现一遍后才开始敲代码。就像我之前做的项目还有这次这个控件,我都比较着急写。等到开始运行的时候,出现了跟我想的不太一样。那我又根据结果去改代码,但是这可能只是代表着某一个方面而已,下次有可能其他方面出问题了。这样你就会被问题牵着走,而不能从整体上去看问题。

事情总是一点一点一点地解决。在写代码的过程中,总有我们当时不知道的,不会的,不知道怎么做的。但是也正是因为这些东西我们才会扩展了更多,丰富了许多,从另一个方面讲,这也是在跳出舒适区吧,所以不要慌张,作为工程师,或者说作为生活的人,我们都需要有耐心和热情。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • RecyclerView实现抖音纵向滚动ViewPager效果

    使用RecyclerView实现抖音纵向滚动ViewPager效果,供大家参考,具体内容如下 重写LinearLayoutManager,在onAttachedToWindow方法中使用 PagerSnapHelper设置RecyclerView条目加载方式为每次滚动加载一页 class MyLinearLayoutManager : LinearLayoutManager { private lateinit var mPagerSnapHelper: PagerSnapHelper priv

  • Android代码实现AdapterViews和RecyclerView无限滚动

    应用的一个共同的特点就是当用户欢动时自动加载更多的内容,这是通过用户滑动触发一定的阈值时发送数据请求实现的. 相同的是:信息实现滑动的效果需要定义在列表中最后一个可见项,和某些类型的阈值以便于开始在最后一项到达之前开始抓取数据,实现无限的滚动. 实现无限滚动的现象的重要之处就在于在用户滑动到最低端之前就行数据的获取,所以需要加上一个阈值来帮助实现获取数据的预期. 使用ListView和GridView实现 每个AdapterView 例如ListView 和GridView 当用户开始进行滚动操

  • Android RecyclerView滚动定位

    概述 RecyclerView在安卓开发中非常实用,而且简单易用,但是在实际开发中一直有一个问题困扰着我,就是定位问题,实际的项目中总是会遇到这样的需求:检索RecyclerView的某一项(各个项的高度不确定),然后定位这一项,将它显示在顶部.用RecyclerView的默认移动的方法并不能实现这一点(个人感觉官方可能出于性能考虑才不实现这一点).这篇博客就讲解下我个人是如何实现这个需求的. Demo演示 敲代码前的思考 RecyclerView提供的用于控制移动的方法有2个 - scroll

  • Android RecyclerView 实现快速滚动的示例代码

    简评:Android Support Library 26 中终于实现了一个等待已久的功能: RecyclerView 的快速滚动 . Android 官方早就在建议开发者使用 RecyclerView 替代 ListView,RecyclerView 也确实表现要好于 ListView,除了没有快速滚动,就像下面这样: 因此,之前要想在 RecyclerView 上实现快速滚动,往往是依赖第三方库,比如:FutureMind/recycler-fast-scroll或 timusus/Recy

  • 浅谈Android RecyclerView UI的滚动控件示例

    ListView 由于其强大的功能,在过去的 Andorid 开发中使用非常广泛.不过 ListView 需要优化来提升运行效率,就像我们之前所优化的那样,否则性能将很差.还有就是只能够纵向滚动,如果要想实现横向移动,用 ListView 是做不到的. RecyclerView 可以说是一个增强版的 ListView .它不仅实现了和 ListView 同样的效果,而且还优化了 ListView 存在的各种不足. RecyclerView 现在可是官方推荐使用的滚动控件哦O(∩_∩)O~ 1 基

  • Android使用RecyclerView实现水平滚动控件

    前言 相信大家都知道Android滚动控件的实现方式有很多, 使用RecyclerView也比较简单. 做了一个简单的年龄滚动控件, 让我们来看看RecyclerView的使用方式, 主要有以下几点: (1) 对齐控件中心位置. (2) 计算滚动距离. (3) 高亮中心视图. (4) 实时显示中心数据. (5) 停止时自动对齐. (6) 滚动时, 设置按钮状态开关. 效果 1. 框架 主要关注RecyclerView部分逻辑. /** * 初始化年龄滑动条 */ private void ini

  • Android中RecyclerView实现分页滚动的方法详解

    一.需求分析 最近公司项目要实现一个需求要满足以下功能: 1)显示一个 list 列表, item 数量不固定. 2)实现翻页功能,一次翻一页. 3)实现翻至某一页功能. 下面介绍通过 RecyclerView 实现该需求的实现过程(效果图如下). 二.功能实现 2.1 OnTouchListener 记录当前开始滑动位置 要实现翻页滑动首先我们要确定是向前翻页还是向后翻页,这里通过记录开始翻页前当前的位置和滑动后的位置比较即可得知,下面选择手指触摸按下时滑动的位置为当前开始滑动位置: //当前

  • 功能强大的Android滚动控件RecyclerView

    RecyclerView的使用比ListView的使用是比较复杂的,ListView的使用是五个步骤,而我们的RecyclerView的使用有7个步骤,分别为: 1.在当前项目的build.gradle中的dependencies闭包中加入compile 'com.android.support.recyclerview-v7:xx.x.x'(x是当前最新版本) 2.布局加入RecyclerView控件以及创建子项布局和适配器类. 3.创建适配器 4.定义数据源 5.通过findViewById

  • Android使用Recyclerview实现图片水平自动循环滚动效果

    简介: 本篇博客主要介绍的是如何使用RecyclerView实现图片水平方向自动循环(跑马灯效果) 效果图:  思路: 1.准备m张图片 1.使用Recyclerview实现,返回无数个(实际Interge.MAXVALUE)item,第n个item显示第n%m张图片 3.使用recyclerview.scrollBy  每个一段时间水平滚动一段距离 4.通过layoutManager.findFirstVisibleItemPosition()获取当前显示的第一个View是第几个item,上面

  • Android RecyclerView 滚动到中间位置的方法示例

    最近看到QQ音乐的歌词每次滑动后都可以滚回到中间位置.觉得甚是神奇,打开开发者模式显示布局,发现歌词部分不是采用 android 控件的写的,应该是前端写的.于是,我想,能不能用 recyclerView 实现这个自动回滚到中间位置呢. 功夫不负有心人,查找了一些资料之后,终于搞定了. 下面由我细细讲来. 目标 点击某个条目,在经过4s无任何操作之后,该条目滚动到中间位置显示.点击后,用户在滑动,等用户不操作后再开始延时.用户多次点击,记最后一次点击位置. 分析 首先先考虑,滚动到指定位置是如何

  • Android_RecyclerView实现上下滚动广告条实例(带图片)

    前言 公司新项目首页有个类似京东/淘宝滚动广告条,查了一下大概都是两种实现方式,一是textview,如果只有文字的话是可行的,但我们这个上面还有个小图片,所以pass:二是两个viewGroup,使用动画交替滚动,可以实现,就是显得很麻烦,于是偷懒的我就想着用recyclerView来解决这个小问题! 思路 这个滚动广告条高度通常是固定的,用一个固定高度的viewGroup来包裹一个recyclerView,recylerView的item布局设置一个minHeight为viewGroup的高

  • XRecyclerView实现下拉刷新、滚动到底部加载更多等功能

    介绍: 一个实现了下拉刷新,滚动到底部加载更多以及添加header功能的的RecyclerView.使用方式和RecyclerView完全一致,不需要额外的layout,不需要写特殊的adater. 加载效果内置了AVLoadingIndicatorView上的所有效果,可以根据需要指定. 项目地址:https://github.com/jianghejie/XRecyclerView 效果: 使用: xml <RelativeLayout xmlns:android="http://sc

  • RecyclerView实现纵向和横向滚动

    为方便自己以后学习,自己记录学习,大家也可以参考,有什么问题一起探讨. 今天学习RecyclerView,下边来说一下实现数据垂直滚动和数据横向滚动.先上图为敬: 所用工具:Android Studio 纵向滚动 1.添加依赖库: 打开app/build.gradle文件,在dependencies闭包中添加如下内容(compile 'com.android.support:recyclerview-v7:24.2.1'为添加的内容) dependencies { compile fileTre

随机推荐