热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要介绍了Android基于RecyclerView实现的歌词滚动自定义控件,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

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

先来几张效果图:

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

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

接下来说一下实现原理:

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

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

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

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

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

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

List 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);
    }
    @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 文件。

<&#63;xml version="1.0" encoding="utf-8"&#63;>

  
  
  
  
  
  

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

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

https://www.jb51.net/article/103134.htm
https://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 控件,让我有好几点感触,我记录一下,一方面是希望告诫自己,一方面也算是分享给他人吧。

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

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

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


推荐阅读
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • 【MicroServices】【Arduino】装修甲醛检测,ArduinoDart甲醛、PM2.5、温湿度、光照传感器等,数据记录于SD卡,Python数据显示,UI5前台,微服务后台……
    这篇文章介绍了一个基于Arduino的装修甲醛检测项目,使用了ArduinoDart甲醛、PM2.5、温湿度、光照传感器等硬件,并将数据记录于SD卡,使用Python进行数据显示,使用UI5进行前台设计,使用微服务进行后台开发。该项目还在不断更新中,有兴趣的可以关注作者的博客和GitHub。 ... [详细]
author-avatar
曾wujcik_663
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有