Android实现音乐播放器歌词显示效果
程序员文章站
2022-10-13 09:29:08
这两天有个任务,说是要写一个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; } }
效果图:
大体就这样,如有无情纠正,附上源码地址:点击打开链接
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。