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

这两天有个任务,说是要写一个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 lyricItems = new ArrayList();//每项的歌词集合

    ArrayList lyricTextList = new ArrayList();//每行歌词文本集合,建议先去看看手机音乐里的歌词格式和内容
    ArrayList lyricTimeList = new ArrayList();//每行歌词所对应的时间集合
    ArrayList 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();
            prevSelected = 0;
            //为每行歌词创建一个TextView
            for(int i = 0;i=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 textList,ArrayList 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中的布局

 




    

    

   具体实现代码如下:

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 lrcs;
    private ArrayList list;
    private ArrayList 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();
        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 redLrc(InputStream in) {
        ArrayList alist = new ArrayList();
        //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;
    }
}

效果图:

实现音乐播放器歌词显示效果_第1张图片

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


你可能感兴趣的:(自定义View)