欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现音乐播放器歌词显示效果

程序员文章站 2023-11-22 15:24:10
这两天有个任务,说是要写一个qq音乐播放器歌词的那种效果,毕竟刚学自定义view,没有什么思路,然后就google.写了一个歌词效果,效果图在后面,下面是我整理的代码。...

这两天有个任务,说是要写一个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;//前一个选择的歌词所在的item
 
 
 public 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() {
 @override
 public void ongloballayout() {
 height = lycicview.this.getheight();
 width = lycicview.this.getwidth();
 
 refreshrootview();
 
 }
 });
 addview(rootview);//把布局加进去
 }
 
 /**
 *
 */
 void refreshrootview(){
 rootview.removeallviews();//刷新,先把之前包含的所有的view清除
 //创建两个空白view
 linearlayout 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;
 //为每行歌词创建一个textview
 for(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() {
 @override
 public void ongloballayout() {
 textview.getviewtreeobserver().removeongloballayoutlistener(this);//api 要在16以上 >=16
 lyricitemheights.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();
 }
 
 @override
 protected 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">
 
 <edittext
 android: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" />
 
 <button
 android: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" />
 
 <relativelayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_alignparenttop="true"
 android:layout_centerhorizontal="true"
 android:layout_above="@+id/edittext">
 
 <custom.lycicview
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:id="@+id/view"
 android:layout_centervertical="true"
 android:layout_centerhorizontal="true" />
 
 <view
 android:layout_width="match_parent"
 android:layout_height="2dp"
 android:background="@null"
 android:id="@+id/imageview"
 android:layout_centervertical="true"
 android:layout_centerhorizontal="true" />
 <view
 android: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() {
 @override
 public 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;
 
 @override
 protected 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() {
 @override
 public void run() {
 view.scrolltoindex(0);
 }
 },1000);
 
 btn.setonclicklistener(new view.onclicklistener() {
 @override
 public void onclick(view v) {
 string text = edittext.gettext().tostring();
 int index = 0;
 index = integer.parseint(text);
 view.scrolltoindex(index);
 }
 });
 view.setonlyricscrollchangelistener(new lycicview.onlyricscrollchangelistener() {
 @override
 public 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() {
 @override
 public 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;
 }
}

效果图:

Android实现音乐播放器歌词显示效果

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

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