桌面歌词的同步显示

要实现桌面歌词,第一步必须做到将view置于屏幕顶端,实现这个效果可以用window系统自带的winmanager来实现,具体实现代码:
WindowManager wm;//创建浮动窗口盛放歌词
    public void createFloatView(int paddingBottom) {

        wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        view = LayoutInflater.from(this).inflate(R.layout.floatlrc,null,false);//拿到桌面歌词的布局
        desk =(LrcView2)view.findViewById(R.id.lrc);//自定义的盛放桌面歌词的textview
        final LinearLayout gone = (LinearLayout)view.findViewById(R.id.lrcgone);
        final ImageView iv = (ImageView)view.findViewById(R.id.iv);

//设置监听点击关闭按钮关闭桌面歌词
        iv.setOnClickListener(new View.OnClickListener() {
            @Override
            publicvoidonClick(View v) {
                removeFloatView();
                lrcIsShow=true;
            }
        });

//定义布局参数
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;// 所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL//设置浮动窗口下面的组件可以被点击
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.format = PixelFormat.TRANSLUCENT;// 不设置这个弹出框的透明遮罩显示为黑色params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;;
        params.gravity = Gravity.TOP | Gravity.LEFT;
        int screenWidth = getResources().getDisplayMetrics().widthPixels;
        int screenHeight = getResources().getDisplayMetrics().heightPixels;
        params.x = screenWidth;
        params.y = screenHeight - paddingBottom;
        view.setBackgroundColor(Color.TRANSPARENT);
        view.setVisibility(View.VISIBLE);

//重写onTouchListener,达到可拖动的目的,只可以上下拖动
        view.setOnTouchListener(new View.OnTouchListener() {
            // 触屏监听float lastX, lastY;
            int oldOffsetX, oldOffsetY;
            int tag = 0;// 悬浮球 所需成员变量

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                final int action = event.getAction();
                float x = event.getX();
                float y = event.getY();
                if (tag == 0) {
                    oldOffsetX = params.x; // 偏移量
                    oldOffsetY = params.y; // 偏移量
                }
                if (action == MotionEvent.ACTION_DOWN) {
                    lastX = x;
                    lastY = y;
                } elseif (action == MotionEvent.ACTION_MOVE) {
                    params.x += (int) (x - lastX) / 3; // 减小偏移量,防止过度抖动params.y += (int) (y - lastY) / 3; // 减小偏移量,防止过度抖动
                    tag = 1;
                    wm.updateViewLayout(v, params);//更新位置
                } elseif (action == MotionEvent.ACTION_UP) {

//判断为点击后做的事
                    int newOffsetX = params.x;
                    int newOffsetY = params.y;
                    // 只要按钮一动位置不是很大,就认为是点击事件if (Math.abs(oldOffsetX - newOffsetX) <= 20
                            && Math.abs(oldOffsetY - newOffsetY) <= 20) {
                        if (gone.getVisibility() == View.GONE) {
                            gone.setVisibility(View.VISIBLE);
                        } else {
                            gone.setVisibility(View.GONE);
                        }
                        v.setFocusable(false);
                    } else {
                        tag = 0;
                    }
                }
                return  true;
            }
        });
        initLrc();
        wm.addView(view, params);
    }

第二步就是对布局中自定义的textview(desk)做的事了,initLrc()就是为desk绑定监听事件,首先呢需要自定义接口初始化歌词以及监听歌词的变化
publicinterfaceOnLrcChangedListener {
    publicvoidonLrcLoaded(List<LrcModel> lrcModels);
    publicvoidonLyricSentenceChanged(int index);
}

第三步:从网络或者本地数据库中拿到lrc歌词,将歌词处理成我们需要的,一行一行的格式,定义LrcDownloadManager类做这些事情,
package com.example.user.myapplication;

/**
 * Created by user on 2016/9/13.
 */public class LrcDownLoadManager {

    private LrcDao lrcDao;
    private List<LrcModel> lrcModels;
//可能不止一个地方需要歌词信息,因此定义一个集合存放监听器,需要的时候遍历就好
    private List<OnLrcChangedListener> onLrcChangedListeners = new ArrayList<>();
    private OnLrcChangedListener lrcChangedListener;
    publicvoid setOnLrcChangedListener(OnLrcChangedListener lrcChangedListener){
        onLrcChangedListeners.add(lrcChangedListener);
    }
    public LrcDownLoadManager(Context context) {
        lrcModels = new ArrayList<LrcModel>();
        lrcDao = new LrcDao(context);
    }
//从本地数据库加载格式为lrc的歌词,如果没有就从网络中加载
    publicvoid getSongInfo(final BenDiMusic music){
        List<Lrc> lrcs = lrcDao.queryForName(music.getName());
        if(lrcs.size()!=0){
                String[] lines = lrcs.get(0).getContent().split("\\n");
                lrcModels.clear();
                for(String l:lines) {
                    parseLrc(l);
                }
for(OnLrcChangedListener l:onLrcChangedListeners){
                if(l!=null){
                    l.onLrcLoaded(lrcModels);
                }
            }

        }else{
            if(1==1){

                VolleyUtil.get().setCallBack(new CallBack() {
                    @Override
                    publicvoid onSuccess(String response) {
                        lrcModels.clear();
                        try {
                            Log.i("response", music.getName());
                            JSONObject jsonObject = newJSONObject(response);
                            JSONArray array = jsonObject.getJSONArray("song");
                            for(int i = 0;i<array.length();i++){
                                JSONObject object = array.getJSONObject(i);
                                if(object.getString("artistname").equals(music.getCuthor())){
                                    music.setId(object.getString("songid"));
                                    break;
                                }
                            }
                            getLrc(music.getId(),music.getName());
                        } catch (JSONException e) {
                            lrcModels.clear();
                            e.printStackTrace();
                            return;
                        }
                    }

                    @Override
                    publicvoid onError(VolleyError error) {
                        lrcModels.clear();
                    }
                }).setUrl("http://tingapi.ting.baidu.com/v1/restserver/ting?format=json&calback=&" +
                        "from=webapp_music&method=baidu.ting.search.catalogSug&query="+music.getName()).builder()
                        .setPriority(Request.Priority.HIGH)
                        .start();

            }


        }

    }
    publicvoid getLrc(String id, finalString name){
        VolleyUtil.get().setCallBack(new CallBack() {
            @Override
            publicvoid onSuccess(String response) {
                lrcModels.clear();
                try {
                    JSONObject jsonObject = newJSONObject(response);
                  if(jsonObject.getString("lrcContent")==null){
                      return;
                  }
                        Lrc lrc = new Lrc();
                        lrc.setContent(jsonObject.getString("lrcContent"));
                        lrc.setName(name);
                        lrcDao.addBook(lrc);

                    String[] lines = jsonObject.getString("lrcContent").split("\\n");
                    lrcModels.clear();
                    for(String l:lines) {
                        parseLrc(l);
                    }
                    for(OnLrcChangedListener l:onLrcChangedListeners){
                        if(lrcModels.size()==0&&l!=null){
                            lrcModels.clear();
                            l.onLrcLoaded(lrcModels);
                        }
                        if(l!=null){
                            l.onLrcLoaded(lrcModels);
                        }
                    }

                } catch (JSONException e) {
                    e.printStackTrace();
                    return;
                }
            }

            @Override
            publicvoid onError(VolleyError error) {
            }
        }).setUrl("http://tingapi.ting.baidu.com/v1/restserver/ting?" +
                "method=baidu.ting.song.lry&songid="+id)
                .builder().setPriority(Request.Priority.HIGH).start();
    }
//将歌词每行的文字提取出来放入集合
publicvoid parseLrc(Stringline){
        String reg="\\[(\\d{2}:\\d{2}\\.\\d{2})\\]";
        Pattern pattern=Pattern.compile(reg);
        Matcher matcher=pattern.matcher(line);
        while(matcher.find()){
            String time=matcher.group();
            LrcModel lModel=new LrcModel();
            lModel.setCurrentTime(parseTime(time));
            lModel.setCurrentContent(line.substring(time.length()));
            lrcModels.add(lModel);
        }
    }
//每行歌词的播放时间提取出来
public Integer parseTime(String time){
        String temp=time.substring(1,time.length()-1);
        String[] s = temp.split(":");
        intmin = Integer.parseInt(s[0]);
        String[] ss = s[1].split("\\.");
        int sec = Integer.parseInt(ss[0]);
        int mill = Integer.parseInt(ss[1]);
        returnmin * 60 * 1000 + sec * 1000 + mill * 10;
    }
//通过当前播放进度判断播放位置,返回当前行在集合中的索引位置
publicint lrcIndex(int duration,int currentTime) {
        int index = 0;
        if (currentTime < duration) {
            for (int i = 0; i < lrcModels.size(); i++) {
                if (i < lrcModels.size() - 1) {
                    if (currentTime < lrcModels.get(i).getCurrentTime() && i == 0) {
                        index = i;
                    }
                    if ((currentTime > lrcModels.get(i).getCurrentTime())&& currentTime < lrcModels.get(i+1).getCurrentTime()) {
                        index = i;
                    }
                }
                if ((i == lrcModels.size() - 1)&& currentTime > lrcModels.get(i).getCurrentTime()) {
                    index = i;
                }
            }
        }
        return index;
    }

}


第四步当然是开启桌面歌词的同时为desk添加监听器了,我是在服务中为desk添加的监听器,创建浮动窗口也是在服务中封装好的方法,提供一个调用的方法,
public void initLrc(){
      setLrcChangedListener(new OnLrcChangedListener() {
          @Override
public void onLrcLoaded(List<LrcModel> lrcModels){
              desk.setLrcList(lrcModels);
          }

          @Override

public void onLyricSentenceChanged(int index){
              desk.setIndex(index);
              desk.invalidate();

          }
      });

最主要的就是要在服务中实例化LrcDownloadManager的一个对象,调用加载歌词的方法,并完成歌词的制作(制作成我们需要的格式),这里定义了一个set方法添加监听器是为了方便除服务外地方使用这个功能,
public void setLrcChangedListener(OnLrcChangedListener lrcChangedListener){
        onLrcChangedListeners.add(lrcChangedListener);
        if(this.lrcDownLoadManager!=null){
            this.lrcDownLoadManager.setOnLrcChangedListener(lrcChangedListener);
            loadLrc();
        }
    }
    publicvoidloadLrc(){
        //下载歌词
        lrcDownLoadManager.getSongInfo(mdata.get(POSITION));
    }


做完这些还有最重要的一步,就是更新歌词了,利用handler的定时机制更新当前播放进度,
private Handler mServiceHandler = new Handler(){
        @Overridepublicvoid handleMessage(Message msg) {
            super.handleMessage(msg);
            if(mdata.get(POSITION).getTime()!=0){
                if(msg.what==MSG_TYPE_PROGRESS){//更新进度条,与桌面歌词功能无关
                    if(mState==State.PLAYING){
                        for(OnMusicPlayStateListener l:musicPlayStateListeners){
                            l.onProgressChanged(player.getCurrentPosition());
                        }

//定时每隔100毫秒,更新当前播放位置的索引
                        for(OnLrcChangedListener l:onLrcChangedListeners){
                            if(lrcDownLoadManager!=null){
                                if(l!=null)
                                    l.onLyricSentenceChanged(lrcDownLoadManager
                                            .lrcIndex((int) mdata.get(POSITION).getTime()
                                                    , player.getCurrentPosition()));

                            }
                        }
                       mServiceHandler.sendEmptyMessageDelayed(MSG_TYPE_PROGRESS, 100);//隔500毫秒发song
                    }
                }
            }

        }
    };

最后在添一个移除桌面 歌词的方法

public void removeFloatView(){
        if (wm != null && view != null) {
            wm.removeViewImmediate(view);
            //          wm.removeView(view);//不要调用这个,WindowLeaked
            view = null;
            wm = null;
        }<pre name="code" class="java">public float getShaderSpeed(){
        float speed = getCharacterWidth(infos.get(index).getCurrentContent(),60)/infos.get(index).getCurrentTime()/2;
        return speed;
    }

}

如果要实现歌词逐字渲染的效果,还需要再做些工作,因为我的desk(放歌词的textview)的宽度是match_parent,所以再计算渲染速度的时候就不能用这个宽度,需要写一个方法返回当前文本的宽度,下面的是我自定义盛放歌词的view的全部代码

package com.example.user.myapplication.myScroview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

import com.example.user.myapplication.LrcModel;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by user on 2016/9/30.
 */
public class LrcView2 extends TextView {
    private int indexChanged = 0;
    boolean b = true;
    private float width;                   //歌词视图宽度
    private float height;                 //歌词视图高度
    private Paint currentPaint;          //当前画笔对象
    private Paint notCurrentPaint;      //非当前画笔对象
    private float textHeight = 70;      //文本高度
    private float textMaxSize = 60;
    private float textSize = 55;        //文本大小
    private int index = 0;              //list集合下标
    private List<LrcModel> infos;              //歌词信息
    private float float1 = 0.0f;
    private float float2 = 0.01f;
    private boolean whichLine = true;判断是第一行歌词还是第二行歌词
    private boolean whatLine = true;//判断是否需要切换歌词
    private int nextIndex;

    public LrcView2(Context context) {
        super(context);
        init();
    }

    public LrcView2(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    public LrcView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public void setLrcList(List<LrcModel> infos) {
        this.infos.clear();
        this.infos.addAll(infos);
    }

    private void init() {
        infos = new ArrayList<>();
        setFocusable(true);     //设置可对焦
        //显示歌词部分
        currentPaint = new Paint();
        currentPaint.setAntiAlias(true);    //设置抗锯齿,让文字美观饱满
        currentPaint.setTextAlign(Paint.Align.CENTER);//设置文本对齐方式

        //非高亮部分
        notCurrentPaint = new Paint();
        notCurrentPaint.setAntiAlias(true);
        notCurrentPaint.setTextAlign(Paint.Align.CENTER);
    }

    /**
     * 绘画歌词
     */
    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (canvas == null) {
                return;
            }
            currentPaint.setColor(Color.argb(210, 251, 248, 29));
            notCurrentPaint.setColor(Color.argb(140, 255, 255, 255));
            notCurrentPaint.setTextAlign(Paint.Align.CENTER);
            currentPaint.setTextAlign(Paint.Align.CENTER);
            currentPaint.setTextSize(textMaxSize);
            currentPaint.setTypeface(Typeface.SERIF);

            notCurrentPaint.setTextSize(textSize);
            notCurrentPaint.setTypeface(Typeface.DEFAULT);

        //如果index的大小改变重置float
        if(indexChanged!=index){
            indexChanged=index;
            float1 = 0;
            float2 =  0;

        }
        //设置渲染速度
        if(infos.size()!=0){
            if(float2 < 1.0){
                float1 +=  getShaderSpeed();
                float2 +=  getShaderSpeed();
            }
        }
        Log.i("width", float1 + "");
        if(infos.size()!=0){
            Shader shader = new LinearGradient(getBeginX(),0, getEndX(), 0, new int[] { Color.YELLOW,Color.GREEN},new float[]{float1, float2},Shader.TileMode.CLAMP);
            currentPaint.setShader(shader);
//第一个参数是渲染在X轴的起始位置,第二个是Y轴的起始位置,第三个是X轴的结束位置,第四个是Y轴的结束位置,第五个参数传入一组颜色,数组中的第一个颜色就是渲染后的颜色,第六个参数就是渲染的位置,1.0f表示全部渲染,最后一个参数类似于一些特效吧,<div style="">//可以看出渲染歌词的关键点就是第六个参数了,传入的位置不同渲染的位置就不同,因此我们只需要不停的调用ondraw方法,每次改变float的位置即可,</div>
 }
        float tempY = height / 2;
        tempY = tempY + textHeight;
            try {
                setText("");
                if(infos.size()!=0){//判断当前有没有歌词信息
                    if(infos.get(index).getCurrentContent().equals("")){
                        //当前时间段没有歌词
                        canvas.drawText("......", width /2, height/2 , currentPaint);
                    }else{
                            canvas.drawText(infos.get(index)
                                    .getCurrentContent(), width/2 , height /2, currentPaint);
                            canvas.drawText(infos.get(index+1)
                                    .getCurrentContent(), width/2 , tempY , notCurrentPaint);
                    }
                }else{
                    canvas.drawText("暂无歌词", width /2, height/2 , currentPaint);
                }


            } catch (Exception e) {
                 setText("");
                 setText("正在加载歌词...");
            }

        }

    /**
     * 当view大小改变的时候调用的方法
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.width = w;
        this.height = h;
    }
    //返回渲染的速度
    public float getShaderSpeed(){
        long time;
        if(index==0){
            time = infos.get(index).getCurrentTime();
        }else{
            time = infos.get(index).getCurrentTime()-infos.get(index-1).getCurrentTime();
        }
        float speed = (float)1.0/time*10;
        return speed;
    }
    //返回渲染起始点x位置
    public float getBeginX(){
        return   width/2-getCharacterWidth(infos.get(index).getCurrentContent(),60)/2;
    }
    //返回渲染结束点x位置
    public float getEndX(){
        return   width/2+getCharacterWidth(infos.get(index).getCurrentContent(),60)/2;
    }


    //获取当前歌词文本的宽度
    public float getCharacterWidth(String text,float size){
        if(text==null||text.equals("")){
            return 0;
        }
        Paint paint = new Paint();
        paint.setTextSize(size);
        float text_width= paint.measureText(text);//得到总体长度
        return text_width;
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值