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;
  }
}

效果图:

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

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

(0)

相关推荐

  • android播放器实现歌词显示功能

    网上android播放器虽然挺多,感觉提供的歌词显示功能比较死板,要么搜索给的条件死死的,要么放置sdcard内部的歌词格式需要统一,应该提供类似文件夹浏览的功能.^_^,不过在这之前先搞定歌词的现实界面: 播放器的歌词界面实现以下几个功能 根据歌曲的播放进度自下而上滚动: 提供上下拖动调整歌曲进度的功能: 突出显示当前进度的歌词段,并保证该歌词段处于布局中心 不多说了直接贴代码,首先开启一个线程每隔一段时间往view中送入一串字符 Java代码 import android.os.Bundle

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

    这两天有个任务,说是要写一个QQ音乐播放器歌词的那种效果,毕竟刚学自定义View,没有什么思路,然后就Google.写了一个歌词效果,效果图在后面,下面是我整理的代码. 首先实现这种效果有两种方式 1.自定义View里重载onDraw方法,自己绘制歌词 2.用ScrollView实现 第一种方式比较精确,但要支持滑动之后跳转播放的话难度很大,所以我选择第二种,自定义ScrollView 我也不多说,直接上代码,代码中有注释 一.自定义LycicView extends ScrollView 里面

  • Android仿音乐播放器功能

    本文实例为大家分享了Android仿音乐播放器功能的具体代码,供大家参考,具体内容如下 读取本地音乐文件 源代码: import android.media.MediaPlayer; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.ImageButton; import android.widget.

  • 原生JS实现网页手机音乐播放器 歌词同步播放的示例

    整了一下  之前写了好几次每一次都丢三落四的 今天花了半天理了下思路 整理了下头绪 //获取歌词文本 var txt = document.getElementById("lrc"); var lrc = txt.value;//获取文本域里的值 /*console.log(lrc);*/ var lrcArr = lrc.split("[");//去除[ /*console.log(lrcArr);*/ var html = "";//定义一个

  • android实现音乐播放器进度条效果

    本文实例为大家分享了android实现音乐播放器进度条效果的具体代码,供大家参考,具体内容如下 效果图 依赖3个对象 MediaPlayer:实现音乐播放,暂停,缓冲. SeekBar:滑动的进度条. java.util.Timer:定时器,时时更新进度条. main.xml样式文件 <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android

  • Android版音乐播放器

    音乐播放器是一个非常常见的应用,这篇博客就是介绍如何制作一个简单的音乐播放器,这款音乐播放器具有以下的功能:播放歌曲.暂停播放歌曲..显示歌曲的总时长.显示歌曲的当前播放时长.调节滑块可以将歌曲调节到任何时间播放.退出音乐播放器. 实现效果如下 实现方式: 第一步:使用Android Studio创建一个Android工程,并且修改activity_main.xml文件 <?xml version="1.0" encoding="utf-8"?> <

  • Android简易音乐播放器实现代码

    本文实例为大家分享了Android音乐播放器的具体代码,供大家参考,具体内容如下 1.播放项目内的音乐 package com.thm.g150820_android26_playmusic; import Android.media.MediaPlayer; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.wid

  • Android绘制音乐播放器示波器

    示波器是在大学的时候老师教的,但是出来工作一直没有用到过,渐渐的也就忘记了,现在重新学习一下.来看看效果图: 这里是一个自定义的柱状图,然后有一个按钮,点击按钮的时候,这里柱子会不停的运动,类似于音乐播放器里示波器的跳动. 跟前面几个自定义view的方式类似,重写了onSizeChange()方法和onDraw()方法 先列一下我们要用到的变量: /**画笔*/ private Paint mPaint; /**控件的宽度*/ private float mWidth; /**单个柱子的宽度*/

  • Android实现音乐播放器锁屏页

    本文实例为大家分享了Android音乐播放器锁屏页的具体代码,供大家参考,具体内容如下 首页我们先看一下效果图 下边来说一下实现逻辑,其主要思路就是新建一个activity使其覆盖在锁屏页上边. 一.我们新建一个LockActivty,既然是四大组件之一,必不可少的在AndroidManifest.xml中注册: <activity android:name=".LockActivity" android:excludeFromRecents="true" a

  • 简单实现Android本地音乐播放器

    音乐播放需要调用service,在此,只是简单梳理播放流程. public class PlayMusicService extends Service { //绑定服务 调用服务的方法. @Override public IBinder onBind(Intent intent) { return null; } } <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:to

  • android实现音乐播放器

    需求描述: 拥有播放,暂停,重新播放和停止等功能. 并且随着音乐的进度,进图条会自动更新.手动拖动进度条也会更新音乐的进度. 效果展示 示例代码 MainActivity package com.example.www.musicdemo; import android.Manifest; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConne

随机推荐