服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android实现音乐播放器歌词显示效果

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

2022-10-21 14:52Tenderness4 Android

这篇文章主要为大家详细介绍了Android实现音乐播放器歌词显示效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

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

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

2.用ScrollView实现

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

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

一.自定义LycicView extends ScrollView

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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中的布局

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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>

具体实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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实体类  

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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实现音乐播放器歌词显示效果

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

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

原文链接:https://blog.csdn.net/Tenderness4/article/details/52267024

延伸 · 阅读

精彩推荐