1. 程式人生 > >實現音樂播放器歌詞顯示效果

實現音樂播放器歌詞顯示效果

這兩天有個任務,說是要寫一個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;
    }
}

效果圖:


大體就這樣,如有無情糾正,附上原始碼地址:點選開啟連結