1. Java

自制滚动歌词效果

最近一直在完善jayPlayer(自制杰伦播放器)当然不仅仅可以播放杰伦的歌曲,其他歌手也有哈。这次添加的是歌词滚动显示效果。废话不多说先上效果图:

这个界面看上去还是阔以的,特别赞一下咪咕音乐的曲库,这首革命人永远是年轻 是我听过的最好的一版!远看这个布局也不怎么看出是如何实现的大概猜一个TextView?是的,就是TextView,每行歌词都是一个TextView,所有TextView都塞在一个ListView中,下面是我的布局文件:activity_play_lrc.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/play_lrc_frame"
    android:layout_width="match_parent"
    android:layout_height="450dp"
    android:padding="10dp"
    app:layout_constraintBottom_toTopOf="@id/seek_bar_grid"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toBottomOf="@id/nav_bar_grid">
    <ScrollView
        android:id="@+id/play_lrc_scroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        android:padding="5dp"
        android:scrollbars="none">
        <ListView
            android:scrollbars="none"
            android:id="@+id/play_lrc_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="#00000000"
            />


    </ScrollView>
</FrameLayout>

和自定义适配器是一个思路,就是重新一个适配器LrcAdapter继承自BaseAdapter,并实现几个方法即可。再看一个布局文件就是ListView中每个人Item的布局,这里就简单点,是个TextView。

show_lrc_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/show_lrc_item"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/play_lrc_view"
        android:textColor="#333333"
        android:textSize="20sp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello this lrc yo~"
        android:textAlignment="center" />
</LinearLayout>

然后再写适配器LrcAdapter

package top.sencom.jayplayer.adapter;

import android.content.Context;
import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

import top.sencom.jayplayer.R;
import top.sencom.jayplayer.lrcparser.parser.Sentence;

public class LrcAdapter extends BaseAdapter {
    private List<Sentence> data;
    private LayoutInflater layoutInflater;
    private Context context;
    private SparseBooleanArray selected;
    int old = 0;

    public LrcAdapter( Context context, List<Sentence> data) {
        this.data = data;
        this.layoutInflater = LayoutInflater.from(context);
        this.context = context;
        this.selected = new SparseBooleanArray();
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView = layoutInflater.inflate(R.layout.show_lrc_item, null);
        TextView lrcLine = convertView.findViewById(R.id.play_lrc_view);
        lrcLine.setText(data.get(position).getContent());

        if(selected.get(position)){
            lrcLine.setTextColor(context.getResources().getColor(R.color.colorPrimary));
            lrcLine.setTextSize(TypedValue.COMPLEX_UNIT_SP,25);
        }else {
            lrcLine.setTextColor(context.getResources().getColor(R.color.lrcDefault));

        }

        return convertView;
    }
    public void setSelectedItem(int position){
        if(old!=-1){
            this.selected.put(old,false);
        }
        this.selected.put(position,true);
        old = position;
    }
}

这里我们设置了一个SparseBooleanArray 用来存放当前选中的Item的标志;用于实现当前播放行的字体加大和上色,相关方法是getView()和setSelectedItem(),old变量用来记忆上一个被选中的Item用于恢复。其实也可以不用因为都是顺序的完全可以通过-1得到。注意这里的数据data变量的类型是List,这里我的歌词解析用的是github上的https://github.com/authorfu/LrcParser但是他的代码有个小bug,早在我写基于java swing GUI开发的音乐播放器时候就发现并解决了,所以这次直接改了就用就行了。

接下来就是activity中的核心代码了,当然之前应该已经将歌词lrc文件获取到,并将其存放在reader变量中。下面给出lrc显示线程的代码:

lrcT =  new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    lyric= LrcParser.create(reader);
                }catch(IOException e){
                    e.printStackTrace();
                }
                while (true){
                    try {
                        Thread.sleep(256);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Sentence sentence=lyric.findSentence(mediaPlayer.getCurrentPosition());
                   try {
                       if(sentence.getIndex() == lyric.getSize() || lrcExit){
                           break;
                       }else {
                           //System.out.println(sentence);
                           runOnUiThread(new Runnable() {
                               @Override
                               public void run() {
                                   lrcListView.post(new Runnable() {
                                       @Override
                                       public void run() {
                                           lrcAdapter.setSelectedItem(sentence.getIndex());
                                           lrcAdapter.notifyDataSetChanged();
                                           lrcListView.setSelectionFromTop(sentence.getIndex(),550);
                                       }
                                   });
                               }
                           });
                       }
                   }catch (Exception e){
                       break;
                   }


                }
            }
        });

在歌词准备好,listview添加好适配器后就可以启动这个线程,其中核心代码如下:

runOnUiThread(new Runnable() {
                               @Override
                               public void run() {
                                   lrcListView.post(new Runnable() {
                                       @Override
                                       public void run() {
                                           lrcAdapter.setSelectedItem(sentence.getIndex());
                                           lrcAdapter.notifyDataSetChanged();
                                           lrcListView.setSelectionFromTop(sentence.getIndex(),550);
                                       }
                                   });
                               }
                           });

每次执行此方法时首先将选中的Item调整为当前播放的歌曲所匹配的歌词,然后通知listview数据集有改动这样listview就会重新绘制布局执行getView方法将旧的歌词恢复,新的歌词变大上色,最后执行listview选中方法将当前位置的歌词滚动到屏幕相对中间的地方,至此完成一行歌词显示。