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

这两天有个任务,说是要写一个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实现音乐播放器歌词显示效果 的全部内容, 来源链接: utcz.com/p/241939.html

回到顶部