这两天有个任务,说是要写一个QQ音乐播放器歌词的那种效果,毕竟刚学自定义View,没有什么思路,然后就Google.写了一个歌词效果,效果图在后面,下面是我整理的代码。

首先实现这种效果有两种方式

1.自定义View里重载onDraw方法,自己绘制歌词

2.用ScrollView实现

第一种方式比较精确,但要支持滑动之后跳转播放的话难度很大,所以我选择第二种,自定义ScrollView

我也不多说,直接上代码,代码中有注释

一.自定义LycicView extends ScrollView

里面包括一个空白布局,高度是LycicView的一半,再是一个布局存放歌词的,最后是一个空白布局高度是LycicView的一半

这里动态的向第二个布局里面添加了显示歌词的TextView,并利用ViewTreeObserver得到每个textview的高度,方便知道每个textview歌词所要滑动到的高度

public class LycicView extends ScrollView {LinearLayout rootView;//父布局LinearLayout lycicList;//垂直布局ArrayList<TextView> lyricItems = new ArrayList<TextView>();//每项的歌词集合ArrayList<String> lyricTextList = new ArrayList<String>();//每行歌词文本集合,建议先去看看手机音乐里的歌词格式和内容ArrayList<Long> lyricTimeList = new ArrayList<Long>();//每行歌词所对应的时间集合ArrayList<Integer> lyricItemHeights;//每行歌词TextView所要显示的高度int height;//控件高度int width;//控件宽度int prevSelected = 0;//前一个选择的歌词所在的itempublic LycicView(Context context) {super(context);init();}public LycicView(Context context, AttributeSet attrs) {super(context, attrs);init();}public LycicView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init(){rootView = new LinearLayout(getContext());rootView.setOrientation(LinearLayout.VERTICAL);//创建视图树,会在onLayout执行后立即得到正确的高度等参数ViewTreeObserver vto = rootView.getViewTreeObserver();vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {height = LycicView.this.getHeight();width = LycicView.this.getWidth();refreshRootView();}});addView(rootView);//把布局加进去}/****/void refreshRootView(){rootView.removeAllViews();//刷新,先把之前包含的所有的view清除//创建两个空白viewLinearLayout blank1 = new LinearLayout(getContext());LinearLayout blank2 = new LinearLayout(getContext());//高度平分LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width,height/2);rootView.addView(blank1,params);if(lycicList !=null){rootView.addView(lycicList);//加入一个歌词显示布局rootView.addView(blank2,params);}}/***设置歌词,*/void refreshLyicList(){if(lycicList == null){lycicList = new LinearLayout(getContext());lycicList.setOrientation(LinearLayout.VERTICAL);//刷新,重新添加lycicList.removeAllViews();lyricItems.clear();lyricItemHeights = new ArrayList<Integer>();prevSelected = 0;//为每行歌词创建一个TextViewfor(int i = 0;i<lyricTextList.size();i++){final TextView textView = new TextView(getContext());textView.setText(lyricTextList.get(i));//居中显示LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);params.gravity = Gravity.CENTER_HORIZONTAL;textView.setLayoutParams(params);//对高度进行测量ViewTreeObserver vto = textView.getViewTreeObserver();final int index = i;vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);//api 要在16以上 >=16lyricItemHeights.add(index,textView.getHeight());//将高度添加到对应的item位置}});lycicList.addView(textView);lyricItems.add(index,textView);}}}/***     滚动到index位置*/public void scrollToIndex(int index){if(index < 0){scrollTo(0,0);}//计算index对应的textview的高度if(index < lyricTextList.size()){int sum = 0;for(int i = 0;i<=index-1;i++){sum+=lyricItemHeights.get(i);}//加上index这行高度的一半sum+=lyricItemHeights.get(index)/2;scrollTo(0,sum);}}/*** 歌词一直滑动,小于歌词总长度* @param length* @return*/int getIndex(int length){int index = 0;int sum = 0;while(sum <= length){sum+=lyricItemHeights.get(index);index++;}//从1开始,所以得到的是总item,脚标就得减一return index - 1;}/*** 设置选择的index,选中的颜色* @param index*/void setSelected(int index){//如果和之前选的一样就不变if(index == prevSelected){return;}for(int i = 0;i<lyricItems.size();i++){//设置选中和没选中的的颜色if(i == index){lyricItems.get(i).setTextColor(Color.BLUE);}else{lyricItems.get(i).setTextColor(Color.WHITE);}prevSelected = index;}}/*** 设置歌词,并调用之前写的refreshLyicList()方法设置view* @param textList* @param timeList*/public void setLyricText(ArrayList<String> textList,ArrayList<Long> timeList){//因为你从歌词lrc里面可以看出,每行歌词前面都对应有时间,所以两者必须相等if(textList.size() != timeList.size()){throw  new IllegalArgumentException();}this.lyricTextList = textList;this.lyricTimeList = timeList;refreshLyicList();}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);//滑动时,不往回弹,滑到哪就定位到哪setSelected(getIndex(t));if(listener != null){listener.onLyricScrollChange(getIndex(t),getIndex(oldt));}}OnLyricScrollChangeListener listener;public void setOnLyricScrollChangeListener(OnLyricScrollChangeListener l){this.listener = l;}/*** 向外部提供接口*/public interface  OnLyricScrollChangeListener{void onLyricScrollChange(int index,int oldindex);}
}

二..MainActivity中的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"android:layout_height="match_parent"android:background="@mipmap/img01"tools:context=".MainActivity"><EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:inputType="number"android:ems="10"android:id="@+id/editText"android:layout_alignParentBottom="true"android:layout_alignParentLeft="true"android:layout_alignParentStart="true" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="scroll to"android:id="@+id/button"android:layout_alignTop="@+id/editText"android:layout_alignParentRight="true"android:layout_alignParentEnd="true" /><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentTop="true"android:layout_centerHorizontal="true"android:layout_above="@+id/editText"><custom.LycicViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/view"android:layout_centerVertical="true"android:layout_centerHorizontal="true" /><Viewandroid:layout_width="match_parent"android:layout_height="2dp"android:background="@null"android:id="@+id/imageView"android:layout_centerVertical="true"android:layout_centerHorizontal="true" /><Viewandroid:layout_below="@id/imageView"android:layout_width="match_parent"android:layout_height="1dp"android:layout_marginTop="6dp"android:background="#999999"android:id="@+id/imageView2"android:layout_centerVertical="true"android:layout_centerHorizontal="true" /></RelativeLayout>
</RelativeLayout>

具体实现代码如下:

public class MainActivity extends AppCompatActivity {LycicView view;EditText editText;Button btn;Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {if(msg.what == 1){if(lrc_index == list.size()){handler.removeMessages(1);}lrc_index++;System.out.println("******"+lrc_index+"*******");view.scrollToIndex(lrc_index);handler.sendEmptyMessageDelayed(1,4000);}return false;}});private ArrayList<LrcMusic> lrcs;private ArrayList<String> list;private ArrayList<Long> list1;private int lrc_index;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initViews();initEvents();}private void initViews(){view = (LycicView) findViewById(R.id.view);editText = (EditText) findViewById(R.id.editText);btn = (Button) findViewById(R.id.button);}private void initEvents(){InputStream is = getResources().openRawResource(R.raw.eason_tenyears);// BufferedReader br = new BufferedReader(new InputStreamReader(is));list = new ArrayList<String>();list1 = new ArrayList<>();lrcs = Utils.redLrc(is);for(int i = 0; i< lrcs.size(); i++){list.add(lrcs.get(i).getLrc());System.out.println(lrcs.get(i).getLrc()+"=====");list1.add(0l);//lrcs.get(i).getTime()}view.setLyricText(list, list1);view.postDelayed(new Runnable() {@Overridepublic void run() {view.scrollToIndex(0);}},1000);btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String text = editText.getText().toString();int index = 0;index = Integer.parseInt(text);view.scrollToIndex(index);}});view.setOnLyricScrollChangeListener(new LycicView.OnLyricScrollChangeListener() {@Overridepublic void onLyricScrollChange(final int index, int oldindex) {editText.setText(""+index);lrc_index = index;System.out.println("===="+index+"======");//滚动handle不能放在这,因为,这是滚动监听事件,滚动到下一次,handle又会发送一次消息,出现意想不到的效果}});handler.sendEmptyMessageDelayed(1,4000);view.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:handler.removeCallbacksAndMessages(null);System.out.println("取消了");break;case MotionEvent.ACTION_UP:System.out.println("开始了");handler.sendEmptyMessageDelayed(1,2000);break;case MotionEvent.ACTION_CANCEL://时间别消耗了break;}return false;}});getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);}}

其中utils类和LycicMusic是一个工具类和存放Music信息实体类
   Utils类

public class Utils {public static ArrayList<LrcMusic> redLrc(InputStream in) {ArrayList<LrcMusic> alist = new ArrayList<LrcMusic>();//File f = new File(path.replace(".mp3", ".lrc"));try {//FileInputStream fs = new FileInputStream(f);InputStreamReader input = new InputStreamReader(in, "utf-8");BufferedReader br = new BufferedReader(input);String s = "";while ((s = br.readLine()) != null) {if (!TextUtils.isEmpty(s)) {String lyLrc = s.replace("[", "");String[] data_ly = lyLrc.split("]");if (data_ly.length > 1) {String time = data_ly[0];String lrc = data_ly[1];LrcMusic lrcMusic = new LrcMusic(lrcData(time), lrc);alist.add(lrcMusic);}}}} catch (FileNotFoundException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}return alist;}public static int lrcData(String time) {time = time.replace(":", "#");time = time.replace(".", "#");String[] mTime = time.split("#");//[03:31.42]int mtime = Integer.parseInt(mTime[0]);int stime = Integer.parseInt(mTime[1]);int mitime = Integer.parseInt(mTime[2]);int ctime = (mtime*60+stime)*1000+mitime*10;return ctime;}
}

LrcMusic实体类

public class LrcMusic {private int time;private String lrc;public LrcMusic() {}public LrcMusic(int time, String lrc) {this.time = time;this.lrc = lrc;}public int getTime() {return time;}public void setTime(int time) {this.time = time;}public String getLrc() {return lrc;}public void setLrc(String lrc) {this.lrc = lrc;}
}

效果图:

大体就这样,如有无情纠正,附上源码地址: 点击打开链接

实现音乐播放器歌词显示效果相关推荐

  1. javafx音乐播放器----歌词同步实时显示(包含获取酷我歌词方式,歌词同步方法)

    首先我是爬虫获取的酷我的音源,因此歌词也是通过爬虫获取的,下面这个方法可以获取到歌曲对应的歌词信息.简单说下,在搜索歌曲之后会返回一个歌曲列表,查看源代码是包含在li标签里面的,这个li标签里面就有请 ...

  2. 音乐播放器歌词的逐字渲染效果

    受到Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器的启发,决定用这个来仿制大多数音乐播放器的逐字染色效果,效果图如下: 关键点剖析一:逐字染色效果 关键代码如下: publi ...

  3. 原生js html音乐播放器(歌词滚动)

    本周学习了js的,用老师教的敲了一个音乐播放器 准备工作 首先随便找首歌 然后用一个小工具扒它的歌词 前期准备完成 代码部分 现在就开始写 html 和 css 了 很简单 就不解释了 直接上代码 h ...

  4. android 音乐播放器-------歌词同步 lrc

    lrc格式 : [al:这首歌所在的唱片集 ] [ar:歌词作者 ] [by:本LRC文件的创建者 ] [offset:+/- 以毫秒为单位整体时间戳调整,+增加,-减小 ] [re:创建此LRC文件 ...

  5. android 本地lrc 歌词同步,android 音乐播放器-------歌词同步 lrc

    lrc格式 : [al:这首歌所在的唱片集 ] [ar:歌词作者 ] [by:本LRC文件的创建者 ] [offset:+/- 以毫秒为单位整体时间戳调整,+增加,-减小 ] [re:创建此LRC文件 ...

  6. android 音乐播放器----歌词在线下载

    本文来自CSDN丹丹博客,转载请必须注明出处: http://blog.csdn.net/dany1202/archive/2011/06/09/6533513.aspx 使用百度的歌词API,如: ...

  7. Xamarin.Android开发音乐播放器

    最近.Net开源着实让C#火了一把,好久就听说Mono for Android一直没静下心来看,上周末找来看看,确实不错,前台界面axml编写跟Java安卓开发毫无区别,后台用C#其实很多window ...

  8. html5多媒体播放器,走进HTML5-学习多媒体,带你实现视频播放器、音乐播放器功能(*^▽^*)...

    欢迎关注个人我的博客分享一些前端技术.面试题.面试技巧等html Web 中的音频和视频 自 21 世纪初以来,咱们的带宽开始可以支持任意类型的视频在早些时候,传统的 web 技术(如 HTML )不 ...

  9. 音乐播放器类的Android项目源码

    收集了很多音乐播放器类的Android项目源码,非常不错的开源项目,会让你事半功倍,希望大家补充...谢谢! Android基于经纬度切歌的冲绳音乐播放器源码 http://neast.cn/foru ...

  10. 最全的Android开源音乐播放器源码汇总

    收集了很多音乐播放器类的Android项目源码,非常不错的开源项目,会让你事半功倍,希望大家补充...谢谢! Android基于经纬度切歌的冲绳音乐播放器源码 http://neast.cn/foru ...

最新文章

  1. 狎昵关系和依恋情结辨诠
  2. 【转载】优酷网首席执行官兼创始人古永锵演讲
  3. 我设计的简单事务控制
  4. 专科学历事业单位工资计算机,事业单位人来告诉你:学历跟入编之后待遇关系有多大!很直接...
  5. LeetCode-95-Unique Binary Search Trees II
  6. axios 设置拦截器 全局设置带默认参数(发送 token 等)
  7. 1+X web中级 Laravel学习笔记——视图和模型
  8. git安装-centos版
  9. 版本化SQL Server数据库
  10. mybatis 存储过程 tmp_count_MyBatis框架介绍及实战操作
  11. Java 核心 API 必须掌握的程度
  12. 微信小程序实战篇:小程序之页面数据传递
  13. OpenCV源码解析之动态内存管理CvMemStorage与CvSeq
  14. oj系统 的c语言答案,宁波大学OJ系统前105道C语言题目及答案精讲
  15. 独立游戏人:像素风格游戏制作分享(转)
  16. python 爬取淘宝第二弹(淘宝数据爬取)
  17. linux_5.10 iptables踩坑
  18. phpcms 下载数统计
  19. 【跨境电商】5个最佳WordPress插件推荐
  20. Cg学习记录002 之Uniform参数

热门文章

  1. Android自定义View:带你了解神秘的MeasureSpec类
  2. 软件测试跟踪需求矩阵,测试管理之从需求到跟踪操作实务
  3. 指数型组织:打造独角兽公司的11个最强属性
  4. MATLAB怎么加0x,matlab 0xc0150002的解决办法
  5. Head First 深入浅出系列 电子书
  6. 内存卡卡速测试软件,手机绝配 这款128G存储卡实测速度惊人
  7. Objective-C JSON字符串解析
  8. PostgreSQL shapefile 导入导出
  9. 如何将国际音标插入到Word中? | 怎么打48个国际音标?
  10. 网易相册:停止新用户注册 5月8日停止运营