演示


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

自定义View

/**
 * 歌词播放的自定义控件
 */
public class LrcView extends TextView {
    private String filePath = "/a.lrc";//文件路径
    private List<Lyrc> lyrcList;//歌词内容
    private int current = 0;//当前行
    private int lineSpacing = 70; //行间距
    //当前正在播放的行
    private Paint currentPaint;
    private int currentColor = Color.BLUE;//颜色
    private int currentSize = 55;//字体大小
    private Typeface currentTypeface = Typeface.DEFAULT_BOLD;//字体,默认的字体+粗体
    //其他行
    private Paint ortherPaint;
    private int ortherColor = Color.GREEN;
    private int ortherSize = 45;
    private Typeface ortherTypeface = Typeface.SERIF;//一种字体类型
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            invalidate();
        }
    };

    public LrcView(Context context) {
        this(context, null);
    }
    public LrcView(Context context, AttributeSet attrs) {
        super(context, attrs);
        lyrcList = LyrcUtil.readLRC(new File(Environment.getExternalStorageDirectory().getPath() + filePath));
        currentPaint = new Paint();
        currentPaint.setColor(currentColor);
        currentPaint.setTextSize(currentSize);
        currentPaint.setTextAlign(Align.CENTER);
        currentPaint.setTypeface(currentTypeface);
        ortherPaint = new Paint();
        ortherPaint.setColor(ortherColor);
        ortherPaint.setTextSize(ortherSize);
        ortherPaint.setTextAlign(Align.CENTER);
        ortherPaint.setTypeface(ortherTypeface);
    }

    public void setLrcType(String filePath, int lineSpacing, int currentColor, int currentSize, int ortherColor, int ortherSize) {
        lyrcList = LyrcUtil.readLRC(new File(filePath));
        this.lineSpacing = lineSpacing;
        
        currentPaint.setColor(currentColor);
        currentPaint.setTextSize(currentSize);
        ortherPaint.setColor(ortherColor);
        ortherPaint.setTextSize(ortherSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (current < lyrcList.size()) {
            if (lyrcList != null && lyrcList.size() > 0) {
                Lyrc lyrc = null;
                //画前面的内容
                for (int i = current - 1; i >= 0; i--) {
                    lyrc = lyrcList.get(i);
                    canvas.drawText(lyrc.lrcString, getWidth() / 2, getHeight() / 2 + lineSpacing * (i - current), ortherPaint);
                }
                //画当前的内容
                lyrc = lyrcList.get(current);
                canvas.drawText(lyrc.lrcString, getWidth() / 2, getHeight() / 2, currentPaint);
                //画后面的内容
                for (int i = current + 1; i < lyrcList.size(); i++) {
                    lyrc = lyrcList.get(i);
                    canvas.drawText(lyrc.lrcString, getWidth() / 2, getHeight() / 2 + lineSpacing * (i - current), ortherPaint);
                }
                //告诉handler当前行的时间
                lyrc = lyrcList.get(current);
                handler.sendEmptyMessageDelayed(10, lyrc.sleepTime);
                //当前行+1
                current++;
            } else canvas.drawText("未找到歌词", getWidth() / 2, getHeight() / 2, currentPaint);
        }
        super.onDraw(canvas);
    }
}

工具类

public class LyrcUtil {
    private static List<Lyrc> lyrcList;
    /**
     * 读取文件
     */
    public static List<Lyrc> readLRC(File f) {
        try {
            if (f == null || !f.exists()) {
                lyrcList = null;
            } else {
                lyrcList = new Vector<Lyrc>();
                InputStream is = new BufferedInputStream(new FileInputStream(f));
                BufferedReader br = new BufferedReader(new InputStreamReader(is, getCharset(f)));
                String strTemp = "";
                while ((strTemp = br.readLine()) != null) {
                    strTemp = analyzeLRC(strTemp);
                }
                br.close();
                is.close();
                // 对歌词进行排序
                Collections.sort(lyrcList, new Sort());
                // 计算每行歌词的停留时间
                for (int i = 0; i < lyrcList.size(); i++) {
                    Lyrc one = lyrcList.get(i);
                    if (i + 1 < lyrcList.size()) {
                        Lyrc two = lyrcList.get(i + 1);
                        one.sleepTime = two.timePoint - one.timePoint;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return lyrcList;
    }
    /**
     * 处理一行内容
     */
    private static String analyzeLRC(String text) {
        try {
            int pos1 = text.indexOf("[");
            int pos2 = text.indexOf("]");
            if (pos1 >= 0 && pos2 != -1) {
                Long time[] = new Long[getPossiblyTagCount(text)];
                time[0] = timeToLong(text.substring(pos1 + 1, pos2));
                if (time[0] == -1) return "";
                String strLineRemaining = text;
                int i = 1;
                while (pos1 >= 0 && pos2 != -1) {
                    strLineRemaining = strLineRemaining.substring(pos2 + 1);
                    pos1 = strLineRemaining.indexOf("[");
                    pos2 = strLineRemaining.indexOf("]");
                    if (pos2 != -1) {
                        time[i] = timeToLong(strLineRemaining.substring(pos1 + 1, pos2));
                        if (time[i] == -1) return ""; // LRCText
                        i++;
                    }
                }
                Lyrc tl = null;
                for (int j = 0; j < time.length; j++) {
                    if (time[j] != null) {
                        tl = new Lyrc();
                        tl.timePoint = time[j].intValue();
                        tl.lrcString = strLineRemaining;
                        lyrcList.add(tl);
                    }
                }
                return strLineRemaining;
            } else return "";
        } catch (Exception e) {
            return "";
        }
    }
    private static int getPossiblyTagCount(String Line) {
        String strCount1[] = Line.split("\\[");
        String strCount2[] = Line.split("\\]");
        if (strCount1.length == 0 && strCount2.length == 0) return 1;
        else if (strCount1.length > strCount2.length) return strCount1.length;
        else return strCount2.length;
    }
    /**
     * 时间转换
     */
    public static long timeToLong(String Time) {
        try {
            String[] s1 = Time.split(":");
            int min = Integer.parseInt(s1[0]);
            String[] s2 = s1[1].split("\\.");
            int sec = Integer.parseInt(s2[0]);
            int mill = 0;
            if (s2.length > 1) mill = Integer.parseInt(s2[1]);
            return min * 60 * 1000 + sec * 1000 + mill * 10;
        } catch (Exception e) {
            return -1;
        }
    }
    /**
     * 判断文件编码
     */
    public static String getCharset(File file) {
        String charset = "GBK";
        byte[] first3Bytes = new byte[3];
        try {
            boolean checked = false;
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            bis.mark(0);
            int read = bis.read(first3Bytes, 0, 3);
            if (read == -1) return charset;
            if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
                charset = "UTF-16LE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) {
                charset = "UTF-16BE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB && first3Bytes[2] == (byte) 0xBF) {
                charset = "UTF-8";
                checked = true;
            }
            bis.reset();
            if (!checked) {
                int loc = 0;
                while ((read = bis.read()) != -1) {
                    loc++;
                    if (read >= 0xF0) break;
                    if (0x80 <= read && read <= 0xBF) break;
                    if (0xC0 <= read && read <= 0xDF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) continue;
                        else break;
                    } else if (0xE0 <= read && read <= 0xEF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            read = bis.read();
                            if (0x80 <= read && read <= 0xBF) {
                                charset = "UTF-8";
                                break;
                            } else break;
                        } else break;
                    }
                }
            }
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return charset;
    }
    private static class Sort implements Comparator<Lyrc> {
        public Sort() {
        }
        public int compare(Lyrc tl1, Lyrc tl2) {
            return sortUp(tl1, tl2);
        }
        private int sortUp(Lyrc tl1, Lyrc tl2) {
            if (tl1.timePoint < tl2.timePoint) return -1;
            else if (tl1.timePoint > tl2.timePoint) return 1;
            else return 0;
        }
    }
}

bean

public class Lyrc {
    public String lrcString;
    public int sleepTime;
    public int timePoint;
}

a.lrc

[00:00] 护花使者
[00:03] 李克勤
[00:38][00:05] 这晚在街中偶遇心中的她
[00:41][00:09] 两脚决定不听叫唤跟她归家
[00:44][00:12] 深宵的冷风 不准吹去她
[00:47][00:15] 她那幽幽眼神快要对我说话
[00:50][00:18] 纤纤身影飘飘身影默默转来吧
[00:54][00:21] 对我说浪漫情人爱我吗
[00:57][00:24] 贪心的晚风 竟敢拥吻她
[01:00][00:27] 将她秀发温温柔柔每缕每缕放下
[01:03][00:30] 卑污的晚风 不应抚慰她
[01:06][00:33] 我已决意一生护着心中的她

使用

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LrcView lrcView = new LrcView(this);
        lrcView.setLrcType(Environment.getExternalStorageDirectory().getPath() +"/a.lrc", dp2px(25), 0xffff00ff, dp2px(20), 0xff0000ff, dp2px(16));
        setContentView(lrcView);
    }
    /** 
        * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 
        */
    public int dp2px(float dpValue) {
        float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

自定义控件 TextView 歌词 Lrc的更多相关文章

  1. IOS 解析歌词lrc

    最近在捣鼓音乐播放器,过程中学到了一些东西,写下来分享一下,首先是歌词的解析 首先我们看看lrc(不贴维基了怕打不开 歌词文件一般是这样的格式 1.[分钟:秒.毫秒] 歌词 2. [分钟:秒] 歌词 ...

  2. 自定义控件TextView

    public class defineTextView extends TextView { Context context; public defineTextView(Context contex ...

  3. Android 自定义控件-TextView

    很多时候系统自带的View满足不了设计的要求,就需要自定义View控件.自定义View首先要实现一个继承自View的类.添加类的构造方法,override父类的方法,如onDraw,(onMeasur ...

  4. LRC歌词原理和实现高仿Android网易云音乐

    大家好,我们是爱学啊,今天给大家带来一篇关于LRC歌词原理和在Android上如何实现歌词逐行滚动的效果,本文来自[Android开发项目实战我的云音乐]课程:逐字滚动下一篇文章讲解. 效果图 相信大 ...

  5. 网页音乐播放器javascript实现,可以显示歌词

    可以显示歌词,但是歌词和歌曲都要实现自己下载下来.只能播放一首歌,歌词还得是lrc格式的代码写的很罗嗦,急切希望帮改改CSS的代码​1.代码:<html >    <head> ...

  6. Ubuntu下实现歌词解析

    我们要明确目的,实现歌曲歌词同步. 1.将歌词文件一次性去取到内存中.(以周董的“简单爱”为例) a.用fopen打开歌词文件 FILE *fp  = fopen(“简单爱.lrc”,"r& ...

  7. Android Toolbar 开发总结

    初识 Toolbar Toolbar是在 Android 5.0 开始推出的一个 Material Design 风格的导航控件 ,Google 非常推荐大家使用 Toolbar 来作为Android ...

  8. Android M 控件:Snackbar、Toolbar、TabLayout、NavigationView

    Snackbar Snackbar提供了一个介于Toast和AlertDialog之间轻量级控件,它可以很方便的提供消息的提示和动作反馈.Snackbar的使用与Toast的使用基本相同: Snack ...

  9. [MFC] MFC音乐播放器 傻瓜级教程 网络 搜索歌曲 下载

    >目录< >——————————————————————< 1.建立工程  1.建立一个MFC工程,命名为Tao_Music 2.选择为基本对话框 3.包含Windows So ...

随机推荐

  1. jQuery 的ready事件和 JavaScript 的load事件对比

    为了理解2个事件的异同,先了解一下HTML文档加载顺序 HTML DOM文档加载步骤 HTML DOM文档加载是按顺序执行的,这与浏览器的渲染方式有关,一般浏览器渲染操作的顺序大致按如下几个步骤 1, ...

  2. grails框架中在使用domain的save方法保存时保存不成功

    1.如果报错,自行根据异常查找错误,这里不说明 2.如果为报错,我遇到的就是domain中的字段属性与数据库中为同步 (1)你的domain是新的,在增加新的字段属性时未使用update更新数据库,造 ...

  3. 关于PHP参数的引用传递和值传递

    如果希望编写一个名为increment()的函数来增加一个变量的值,我们可能会按如下方式编写这个函数: 这段代码是没有用的.下面测试代码的输出结果是“10”. $value 的内容没有被修改.这要归因 ...

  4. KeyPress事件

    在做一个小demo的时候,发现在文本框中输入一个数字,按下“+”,数字增加了,但是“+”仍旧存在的问题,解决方案:提前执行键盘press事件 private void txtNum_KeyPress( ...

  5. net Core 使用MyCat分布式数据库,实现读写分离

    net Core 使用MyCat分布式数据库,实现读写分离 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题, ...

  6. PHP简单获取数据库查询结果并返回JSON

    <?php header("Content-type:text/html;charset=utf-8"); //连接数据库 $con = mysql_connect(&quo ...

  7. Scut:脚本引擎

    Scut 可以执行 C#.Python.Lua 三种类型的脚步,Scut 是如何加载并传递参数的呢? 首先值得注意的是:Scut 在编译时就会将逻辑层脚本源码复制到bin/Script的目录下. 1. ...

  8. IIS principle

    IIS Request | | | Application Pool Config | | | W3WP | | | Many Module | | | AppDomain:这才是.NET的入口 | ...

  9. poj 2513Colored Sticks

    http://poj.org/problem?id=2513 #include<cstdio> #include<cstdlib> #include<cstring> ...

  10. U盘安装,FTP安装CENTOS--错误信息:Unable to read package metadata.This may be due to a missing repodata directory.

    考察repodata下的repomd.xml里的文件和同一目录下的那些文件是不是一一对应的.主要看有没后缀.如果不一致,则要用XML里的文件后缀加上去. 弄了我好多次. http://renzhenx ...