Runt
2022-05-04 57fe689f1862ad0fda96b2e9eb054be6a7c9425d
歌词显示时间控制
9 files modified
4 files added
760 ■■■■ changed files
app/src/main/java/com/auto/lyric/MainActivity.java 75 ●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/base/activities/BaseActivity.java 13 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/base/activities/BaseTitleBarActivity.java 3 ●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/data/LyricObject.java 11 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/data/LyricServer.java 143 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/data/SendText.java 14 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/data/TextBean.java 14 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/service/FloatingWindowService.java 156 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/widgets/LyricView.java 292 ●●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/widgets/Sentence.java 5 ●●●●● patch | view | raw | blame | history
app/src/main/res/layout/activity_main.xml 1 ●●●● patch | view | raw | blame | history
app/src/main/res/layout/float_view.xml 23 ●●●●● patch | view | raw | blame | history
app/src/main/res/values/themes.xml 10 ●●●● patch | view | raw | blame | history
app/src/main/java/com/auto/lyric/MainActivity.java
@@ -1,27 +1,26 @@
package com.auto.lyric;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import android.widget.Toast;
import com.auto.lyric.base.activities.BaseActivity;
import com.auto.lyric.data.LyricServer;
import com.auto.lyric.data.TextBean;
import com.auto.lyric.databinding.ActivityMainBinding;
import com.auto.lyric.databinding.FloatViewBinding;
import com.auto.lyric.service.AutoInputService;
import com.auto.lyric.service.FloatingWindowService;
import com.auto.lyric.vm.MainViewModel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;
public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> {
@@ -33,10 +32,38 @@
        Intent floatService  = new Intent(getApplicationContext(), FloatingWindowService.class);
        binding.button.setOnClickListener(v -> {
            if(binding.edit.getText().toString().trim().length() == 0){
                Toast.makeText(this,"请输入文本", Toast.LENGTH_SHORT).show();
                return;
            }
            ArrayList<TextBean> arrayList = new ArrayList<>();//解析的文本数据
            SimpleDateFormat msFormat = new SimpleDateFormat("mm:ss.SSS");
            String[] strings = binding.edit.getText().toString().split("\n");
            for(String text : strings){
                int index = text.indexOf("]");
                if(index < 8 ){
                    continue;
                }
                String regex = "(?<=\\])";
                Pattern compile = Pattern.compile(regex);
                String[] split = compile.split(text,2);
                long wait = 0;
                try {
                    wait = 28800000 + msFormat.parse(split[0].replace("[","").replace("]","")).getTime();
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                arrayList.add(new TextBean(wait,split[1]));
            }
            if(arrayList.size() == 0){
                return;
            }
            LyricServer.read(Arrays.asList(strings));
            //申请权限
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //开启悬浮窗
               /* if(!Settings.canDrawOverlays(getApplicationContext())) {
                if(!Settings.canDrawOverlays(getApplicationContext())) {
                    //启动Activity让用户授权
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    intent.setData(Uri.parse("package:" + getPackageName()));
@@ -47,10 +74,8 @@
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );
                    startActivity(intent);
                }else{//开启监听服务
                    //startService(floatService);
                }*/
                startService(floatService);
                //showFloatView();
                }
            }
        });
    }
@@ -93,27 +118,5 @@
        return false;
    }
    private void showFloatView(){
        //View view = LayoutInflater.from(this).inflate(R.layout.float_view,null);
        FloatViewBinding binding = FloatViewBinding.inflate(getLayoutInflater());
        WindowManager manager = (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        }else {
            params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        }
        params.format = PixelFormat.RGBA_8888;
        params.gravity = Gravity.CENTER;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        binding.btnBack.setOnClickListener(v ->{
            stopService(new Intent(this,this.getClass()));
        });
        manager.addView(binding.getRoot(),params);
    }
}
app/src/main/java/com/auto/lyric/base/activities/BaseActivity.java
@@ -243,19 +243,6 @@
                        InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }
    /**
     * 状态栏高度
     * @return
     */
    public int getStatusBarHeight() {
        int result = 0;
        int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resId > 0) {
            result = getResources().getDimensionPixelOffset(resId);
        }
        return result;
    }
    long mExitTime= 0 ;
    /**
app/src/main/java/com/auto/lyric/base/activities/BaseTitleBarActivity.java
@@ -8,6 +8,7 @@
import androidx.viewbinding.ViewBinding;
import com.auto.lyric.base.model.BaseViewModel;
import com.auto.lyric.util.DeviceUtil;
import com.auto.lyric.widgets.TitleBarView;
@@ -54,7 +55,7 @@
    public void setStatusBarTransparent(boolean isBlack) {
        super.setStatusBarTransparent(isBlack);
        final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) titleBarView.getLayoutParams();
        layoutParams.topMargin = layoutParams.topMargin+getStatusBarHeight();
        layoutParams.topMargin = layoutParams.topMargin+ DeviceUtil.getStatusBarHeight(this);
        titleBarView.setLayoutParams(layoutParams);
    }
app/src/main/java/com/auto/lyric/data/LyricObject.java
New file
@@ -0,0 +1,11 @@
package com.auto.lyric.data;
/**
 * Created by Runt (qingingrunt2010@qq.com) on 2022/5/2.
 */
public class LyricObject {
    public int begintime; // 开始时间
    public int endtime; // 结束时间
    public int timeline; // 单句歌词用时
    public String lrc; // 单句歌词
}
app/src/main/java/com/auto/lyric/data/LyricServer.java
New file
@@ -0,0 +1,143 @@
package com.auto.lyric.data;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * Created by Runt (qingingrunt2010@qq.com) on 2022/5/4.
 */
public class LyricServer {
    private static TreeMap<Integer, LyricObject> lrc_map = new TreeMap<>();
    public static TreeMap<Integer, LyricObject> getLrc_map() {
        return lrc_map;
    }
    /**
     * 读取歌词文件
     * @param file 歌词的路径
     *
     */
    public static void read(String file) {
        TreeMap<Integer, LyricObject> lrc_read =new TreeMap<Integer, LyricObject>();
        String data = "";
        try {
            File saveFile=new File(file);
            // System.out.println("是否有歌词文件"+saveFile.isFile());
            if(!saveFile.isFile()){
                return;
            }
            //System.out.println("bllrc==="+blLrc);
            FileInputStream stream = new FileInputStream(saveFile);//  context.openFileInput(file);
            BufferedReader br = new BufferedReader(new InputStreamReader(stream,"GB2312"));
            while ((data = br.readLine()) != null) {
                readText(data,lrc_read);
            }
            stream.close();
        } catch (FileNotFoundException e) {
        } catch (IOException e) {
        }
        data ="";
        initLrc(lrc_read);
    }
    public static void read(List<String> texts){
        TreeMap<Integer, LyricObject> lrc_read =new TreeMap<Integer, LyricObject>();
        for(String str : texts){
            readText(str,lrc_read);
        }
        initLrc(lrc_read);
    }
    private static void readText(String text,TreeMap<Integer, LyricObject> lrc_read){
        Pattern pattern = Pattern.compile("\\d{2}");
        // System.out.println("++++++++++++>>"+data);
        /*text = text.replace("[","");//将前面的替换成后面的
        text = text.replace("]","@");
        String splitdata[] = text.split("@");//分隔*/
        String regex = "(?<=\\])";
        Pattern compile = Pattern.compile(regex);
        String[] splitdata = compile.split(text,2);//分隔
        if(text.endsWith("]") || splitdata.length == 1){
            String str = text;
            str = str.replace("[","");
            str = str.replace("]","");
            str = str.replace(":",".");
            str = str.replace(".","@");
            String timedata[] =str.split("@");
            Matcher matcher = pattern.matcher(timedata[0]);
            if(timedata.length==3 && matcher.matches()){
                int m = Integer.parseInt(timedata[0]);  //分
                int s = Integer.parseInt(timedata[1]);  //秒
                int ms = Integer.parseInt(timedata[2]); //毫秒
                int currTime = (m*60+s)*1000+ms*10;
                LyricObject item1= new LyricObject();
                item1.begintime = currTime;
                item1.lrc       = "";
                lrc_read.put(currTime,item1);
            }
        } else{
            String lrcContenet = splitdata[1];
            String tmpstr = splitdata[0];
            tmpstr = tmpstr.replace("[","");
            tmpstr = tmpstr.replace("]","");
            tmpstr = tmpstr.replace(":",".");
            tmpstr = tmpstr.replace(".","@");
            String timedata[] =tmpstr.split("@");
            Matcher matcher = pattern.matcher(timedata[0]);
            if(timedata.length==3 && matcher.matches()){
                int m = Integer.parseInt(timedata[0]);  //分
                int s = Integer.parseInt(timedata[1]);  //秒
                int ms = Integer.parseInt(timedata[2]); //毫秒
                int currTime = (m*60+s)*1000+ms*10;
                LyricObject item1= new LyricObject();
                item1.begintime = currTime;
                item1.lrc       = lrcContenet;
                lrc_read.put(currTime,item1);// 将currTime当标签  item1当数据 插入TreeMap里
            }
        }
    }
    /*
     * 遍历hashmap 计算每句歌词所需要的时间
     */
    private static void initLrc(TreeMap<Integer, LyricObject> lrc_read){
        lrc_map.clear();
        Iterator<Integer> iterator = lrc_read.keySet().iterator();
        LyricObject oldval  = null;
        int i =0;
        for(Integer key : lrc_read.keySet()){
            LyricObject val = lrc_read.get(key);
            if (oldval == null) {
                oldval = val;
            } else {
                LyricObject item1= new LyricObject();
                item1  = oldval;
                item1.timeline = val.begintime-oldval.begintime;
                lrc_map.put(new Integer(i), item1);
                i++;
                oldval = val;
            }
            if (lrc_read.size() > i) {
                lrc_map.put(new Integer(i), val);
            }
        }
    }
}
app/src/main/java/com/auto/lyric/data/SendText.java
New file
@@ -0,0 +1,14 @@
package com.auto.lyric.data;
import java.util.ArrayList;
/**
 * Created by Runt (qingingrunt2010@qq.com) on 2022/5/1.
 */
public class SendText {
    public ArrayList<String> texts;//文本
    public SendText(ArrayList<String> texts) {
        this.texts = texts;
    }
}
app/src/main/java/com/auto/lyric/data/TextBean.java
New file
@@ -0,0 +1,14 @@
package com.auto.lyric.data;
/**
 * Created by Runt (qingingrunt2010@qq.com) on 2022/5/1.
 */
public class TextBean {
    public TextBean(long waitTime, String text) {
        this.waitTime = waitTime;
        this.text = text;
    }
    public long waitTime;//间隔时间
    public String text;//文本
}
app/src/main/java/com/auto/lyric/service/FloatingWindowService.java
@@ -1,6 +1,9 @@
package com.auto.lyric.service;
import android.app.Service;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
@@ -13,14 +16,21 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.LayoutInflaterFactory;
import com.auto.lyric.R;
import com.auto.lyric.data.LyricServer;
import com.auto.lyric.databinding.FloatViewBinding;
import com.auto.lyric.util.DeviceUtil;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
 * 全局悬浮窗
@@ -31,9 +41,29 @@
    private WindowManager manager;
    private WindowManager.LayoutParams params;
    FloatViewBinding binding;
    Handler handler;
    final String TAG = "FloatingWindowService";
    final int THREAD_STOP = 0, THREAD_START = 1 , KEYBOARD_SEND = 100,UPDATE_TIME = 200;
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    SimpleDateFormat msFormat = new SimpleDateFormat("mm:ss.SSS");
    boolean pause;
    int progress;
    Handler handler;
    Timer timer;
    ServiceTask task;
    class ServiceTask extends TimerTask{
        @Override
        public void run() {
            int index = binding.lyric.getIndex(progress+=10);
            binding.lyric.setOffsetY(220 - index * (binding.lyric.getSIZEWORD() + 44));
            binding.lyric.invalidate();
            handler.sendEmptyMessage(UPDATE_TIME);
            if(index == LyricServer.getLrc_map().size()-1){
                handler.sendEmptyMessage(THREAD_STOP);
            }
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
@@ -47,19 +77,14 @@
        }else {
            params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        }
        binding.btnBack.setEnabled(false);
        binding.btnFast.setEnabled(false);
        binding.btnPause.setEnabled(false);
        params.format = PixelFormat.RGBA_8888;
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = DeviceUtil.convertDpToPixel(200,getBaseContext());
        handler = new Handler(this.getMainLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                binding.lyric.setText(msg.obj.toString());
                int offset = binding.lyric.getLineCount() * binding.lyric.getLineHeight();
            }
        };
        binding.close.setOnClickListener(v ->{
            stopService(new Intent(this,this.getClass()));
            stopService(new Intent(this,AutoInputService.class));
@@ -75,14 +100,58 @@
                    || event.getAction() == MotionEvent.ACTION_UP){
                    params.y = (int) event.getRawY() - DeviceUtil.getStatusBarHeight(getBaseContext()) - startY;
                    manager.updateViewLayout(binding.getRoot(), params);
                    Log.e(TAG,"ACTION_MOVE V:"+params.y);
                    //Log.e(TAG,"ACTION_MOVE V:"+params.y);
                }
                Log.e(TAG,"onTouch event:"+event);
                Log.e(TAG,"onTouch getRawY:"+event.getRawY());
//                Log.e(TAG,"onTouch event:"+event);
//                Log.e(TAG,"onTouch getRawY:"+event.getRawY());
                return true;
            }
        });
        binding.btnPause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(pause){
                    pause = false;
                    binding.btnPause.setText("暂停");
                    binding.btnBack.setEnabled(true);
                    binding.btnFast.setEnabled(true);
                    start();
                }else{
                    pause = true;
                    binding.btnPause.setText("继续");
                    binding.btnBack.setEnabled(false);
                    binding.btnFast.setEnabled(false);
                    timer.cancel();
                }
            }
        });
        binding.btnFast.setOnClickListener(v -> progress+=700);
        binding.btnBack.setOnClickListener(v -> progress-=700);
        binding.btnStart.setOnClickListener(v -> {
            try {
                if(new Date().getTime() > dateFormat.parse("2022-09-30 04:00:00").getTime()){
                    Toast.makeText(getApplicationContext(),"软件使用时间已过期", Toast.LENGTH_SHORT).show();
                    return;
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
            if(binding.btnStart.getText().equals("开启")){
                progress = 0 ;
                start();
                startService(new Intent(this,AutoInputService.class));
            }else{
                binding.btnStart.setText("开启");
                binding.btnBack.setEnabled(false);
                binding.btnFast.setEnabled(false);
                binding.btnPause.setEnabled(false);
                progress = 0 ;
                timer.cancel();
                stopService(new Intent(this,AutoInputService.class));
            }
        });
        initHandler();
    }
    @Nullable
@@ -91,10 +160,41 @@
        return null;
    }
    private void initHandler(){
        handler = new Handler(this.getMainLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                if(msg.what == THREAD_START){//开始
                    AutoInputService.flag = true;
                    binding.btnPause.performClick();
                }else if(msg.what == THREAD_STOP){//停止
                    AutoInputService.flag = false;
                    binding.btnStart.performClick();
                }else if(msg.what == KEYBOARD_SEND){//发送文本
                    copy(msg.obj.toString());
                    Intent intent = new Intent();
                    intent.setAction(AutoInputService.class.getName());
                    intent.putExtra("action",AutoInputService.ACTION_PASTE);
                    sendBroadcast(intent);
                    intent.putExtra("action",AutoInputService.ACTION_SEND);
                    sendBroadcast(intent);
                }else if(msg.what == -1){//显示在输入框中
                    //Toast.makeText(getBaseContext(),"上一句--"+msg.obj,Toast.LENGTH_SHORT).show();
                }else if(msg.what == UPDATE_TIME){
                    binding.timer.setText(msFormat.format(progress));
                }
            }
        };
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand flags:"+flags+" startId:"+startId+ " intent:"+intent);
        manager.addView(binding.getRoot(),params);
        binding.lyric.setTextSize();
        return super.onStartCommand(intent, flags, startId);
    }
@@ -102,7 +202,35 @@
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy ");
        timer.cancel();
        manager.removeView(binding.getRoot());
    }
    /**
     * 开始
     */
    private void start(){
        timer = new Timer();
        task = new ServiceTask();
        timer.schedule(task,0,10);
        binding.btnStart.setText("停止");
        binding.btnPause.setText("暂停");
        binding.btnBack.setEnabled(true);
        binding.btnFast.setEnabled(true);
        binding.btnPause.setEnabled(true);
    }
    //复制
    private void copy(String data) {
        // 获取系统剪贴板
        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        // 创建一个剪贴数据集,包含一个普通文本数据条目(需要复制的数据),其他的还有
        ClipData clipData = ClipData.newPlainText(null, data);
        // 把数据集设置(复制)到剪贴板
        clipboard.setPrimaryClip(clipData);
    }
}
app/src/main/java/com/auto/lyric/widgets/LyricView.java
@@ -1,151 +1,209 @@
package com.auto.lyric.widgets;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.widget.TextView;
import android.util.Log;
import android.view.MotionEvent;
import com.auto.lyric.R;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.auto.lyric.data.LyricObject;
import com.auto.lyric.data.LyricServer;
import com.auto.lyric.util.DeviceUtil;
/**
 * 歌词
 * Created by Runt (qingingrunt2010@qq.com) on 2022/4/30.
 */
public class LyricView extends androidx.appcompat.widget.AppCompatTextView {
    private Paint mPaint;
    private float mX;
    private Paint mPathPaint;
    public int index = 0;
    private List<Sentence>  list;
    public float mTouchHistoryY;
    private int mY;
    private float middleY;//
    private static final int DY = 40; //
    private float mX;       //屏幕X轴的中点,此值固定,保持歌词在X中间显示
    private float offsetY;      //歌词在Y轴上的偏移量,此值会根据歌词的滚动变小
    private float touchY;   //当触摸歌词View时,保存为当前触点的Y轴坐标
    private int lrcIndex=0; //保存歌词TreeMap的下标
    private  int SIZEWORD=0;//显示歌词文字的大小值
    private  int INTERVAL=45;//歌词每行的间隔
    Paint paint=new Paint();//画笔,用于画不是高亮的歌词
    Paint paintHL=new Paint();  //画笔,用于画高亮的歌词,即当前唱到这句歌词
    public LyricView(Context context) {
        super(context);
        init();
    }
    public LyricView(Context context, AttributeSet attr) {
        super(context, attr);
    public LyricView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public LyricView(Context context, AttributeSet attr, int i) {
        super(context, attr, i);
        init();
    }
    private void init() {
        setFocusable(true);
        if(list==null){
            list=new ArrayList<Sentence>();
            Sentence sen=new Sentence(0," ");
            list.add(0, sen);
        }
        //
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(24);
        mPaint.setColor(Color.BLACK);
        mPaint.setAlpha(80);
        mPaint.setTypeface(Typeface.SERIF);
        //
        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setColor(Color.RED);
        mPathPaint.setTextSize(24);
        mPathPaint.setTypeface(Typeface.SANS_SERIF);
    }
    /* (non-Javadoc)
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    @Override
    protected void onDraw(Canvas canvas) {
        if(LyricServer.getLrc_map().size() > 0){
            paintHL.setTextSize(SIZEWORD);
            paint.setTextSize(SIZEWORD);
            LyricObject temp= LyricServer.getLrc_map().get(lrcIndex);
            canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*lrcIndex, paintHL);
            // 画当前歌词之前的歌词
            for(int i=lrcIndex-1;i>=0;i--){
                temp=LyricServer.getLrc_map().get(i);
                if(offsetY+(SIZEWORD+INTERVAL)*i<0){
                    break;
                }
                canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint);
            }
            // 画当前歌词之后的歌词
            for(int i=lrcIndex+1;i<LyricServer.getLrc_map().size();i++){
                temp=LyricServer.getLrc_map().get(i);
                if(offsetY+(SIZEWORD+INTERVAL)*i>600){
                    break;
                }
                canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint);
            }
        }
        else{
            paint.setTextSize(25);
            canvas.drawText("找不到歌词", mX, 310, paint);
        }
        super.onDraw(canvas);
        canvas.drawColor(0xEFeffff);
        Paint p = mPaint;
        Paint p2 = mPathPaint;
        p.setTextAlign(Paint.Align.LEFT);
        if (index == -1)
            return;
        p2.setTextAlign(Paint.Align.LEFT);
        //
        canvas.drawText(list.get(index).getName(), mX, middleY, p2);
        float tempY = middleY;
        //
        for (int i = index - 1; i  >= 0; i--) {
            tempY = tempY - DY;
            if (tempY < 0) {
    }
    /* (non-Javadoc)
     * @see android.view.View#onTouchEvent(android.view.MotionEvent)
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        float tt=event.getY();
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                touchY=tt-touchY;
                offsetY=offsetY+touchY;
                Log.e("LyricView","offsety:"+offsetY);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                break;
            }
            canvas.drawText(list.get(i).getName(), mX, tempY, p);
        touchY=tt;
        return true;
        }
        tempY = middleY;
        //
        for (int i = index + 1; i < list.size(); i++) {
            //
            tempY = tempY + DY;
            if (tempY  > mY) {
                break;
    public void init(){
        offsetY=320;
        paint=new Paint();
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setColor(Color.GREEN);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setAlpha(180);
        paintHL=new Paint();
        paintHL.setTextAlign(Paint.Align.CENTER);
        paintHL.setColor(Color.RED);
        paintHL.setAntiAlias(true);
        paintHL.setAlpha(255);
            }
            canvas.drawText(list.get(i).getName(), mX, tempY, p);
        }
    }
    protected void onSizeChanged(int w, int h, int ow, int oh) {
        super.onSizeChanged(w, h, ow, oh);
        mX = w * 0.3f;
        mY = h;
        middleY = h * 0.5f;
    }
    public long updateIndex(int index) {
        if (index == -1)
            return -1;
        this.index=index;
        return index;
    }
    public List<Sentence>  getList() {
        return list;
    }
    public void setList(List<Sentence>  list) {
        this.list = list;
    }
    public void updateUI(){
        new Thread(new updateThread()).start();
    }
    class updateThread implements Runnable {
        long time = 300;
        int i=0;
        public void run() {
            while (true) {
                long sleeptime = updateIndex(i);
                time += sleeptime;
                mHandler.post(mUpdateResults);
                if (sleeptime == -1)
    /**
     * 根据歌词里面最长的那句来确定歌词字体的大小
     */
    public void setTextSize(){
        if(LyricServer.getLrc_map().size() == 0){
                    return;
                try {
                    Thread.sleep(time);
                    i++;
                    if(i==getList().size())
                    {
                        i=0;
                        time = 300;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
        int max=LyricServer.getLrc_map().get(0).lrc.length();
        for(int i=1;i<LyricServer.getLrc_map().size();i++){
            LyricObject lrcStrLength=LyricServer.getLrc_map().get(i);
            if(max<lrcStrLength.lrc.length()){
                max=lrcStrLength.lrc.length();
                }
            }
        SIZEWORD = DeviceUtil.convertDpToPixel(320/max,getContext());
    }
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mX = w * 0.5f;
        super.onSizeChanged(w, h, oldw, oldh);
    }
    /**
     *  歌词滚动的速度
     *
     * @return 返回歌词滚动的速度
     */
    public Float SpeedLrc(){
        float speed=0;
        if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex>220){
            speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20);
        } else if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex < 120){
            Log.i("speed", "speed is too fast!!!");
            speed = 0;
        }
        //      if(speed<0.2){
        //          speed=0.2f;
        //      }
        return speed;
    }
    /**
     * 按当前的歌曲的播放时间,从歌词里面获得那一句
     * @param time 当前歌曲的播放时间
     * @return 返回当前歌词的索引值
     */
    public int getIndex(int time){
        int index=0;
        for(int i=0;i<LyricServer.getLrc_map().size();i++){
            LyricObject temp=LyricServer.getLrc_map().get(i);
            if(temp.begintime<time){
                ++index;
        }
    }
    Handler mHandler = new Handler();
    Runnable mUpdateResults = new Runnable() {
        public void run() {
            invalidate(); //
        lrcIndex = index-1;
        if(lrcIndex<0){
            lrcIndex=0;
        }
    };
        return lrcIndex;
    }
    /**
     * @return the offsetY
     */
    public float getOffsetY() {
        return offsetY;
    }
    /**
     * @param offsetY the offsetY to set
     */
    public void setOffsetY(float offsetY) {
        this.offsetY = offsetY;
    }
    /**
     * @return 返回歌词文字的大小
     */
    public int getSIZEWORD() {
        return SIZEWORD;
    }
    /**
     * 设置歌词文字的大小
     * @param sIZEWORD the sIZEWORD to set
     */
    public void setSIZEWORD(int sIZEWORD) {
        SIZEWORD = sIZEWORD;
    }
}
app/src/main/java/com/auto/lyric/widgets/Sentence.java
@@ -6,19 +6,24 @@
public class Sentence {
    private String name;
    private int index;
    public Sentence(int index,String name){
        this.name=name;
        this.index=index;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getIndex() {
        return index;
    }
    public void setIndex(int index) {
        this.index = index;
    }
app/src/main/res/layout/activity_main.xml
@@ -16,6 +16,7 @@
        android:padding="10dp"
        android:gravity="left"
        android:scrollbars="vertical"
        android:textCursorDrawable="@null"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent" />
app/src/main/res/layout/float_view.xml
@@ -64,5 +64,28 @@
        android:textColor="@color/black"
        app:layout_constraintTop_toBottomOf="@id/lyric"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@id/btn_pause"
        app:layout_constraintRight_toLeftOf="@id/btn_start" />
    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开启"
        android:textColor="@color/black"
        app:layout_constraintTop_toBottomOf="@id/lyric"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
    <TextView
        android:id="@+id/timer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:text="0.000"
        android:textSize="15sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/values/themes.xml
@@ -2,12 +2,12 @@
    <!-- Base application theme. -->
    <style name="Theme.AutoLyric" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/white</item>
        <item name="colorPrimaryVariant">@color/color_gray</item>
        <item name="colorOnPrimary">@color/white</item>
        <item name="colorPrimary">@color/black</item>
        <item name="colorPrimaryVariant">@color/gray_normal</item>
        <item name="colorOnPrimary">@color/gray_pressed</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/white</item>
        <item name="colorSecondaryVariant">@color/color_gray</item>
        <item name="colorSecondary">@color/gray_normal</item>
        <item name="colorSecondaryVariant">@color/gray_pressed</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>