Android中歌词显示的实现

之前想写一个音乐播放器,但是一直不明白歌词应该怎么显示。

用UI Automator观察了一下各大音乐播放器,发现QQ音乐和百度音乐是在ScrollView里显示歌词。不清楚ScrollView里面具体是什么,都支持滑动到某个位置之后跳转播放

Flyme 4自带音乐是在ListView里显示,内部嵌套了多个TextView,一行一行显示歌词,支持滑动到某个位置之后跳转到该位置播放(魅族的UI做得的确很不错)

MIUI 6自带音乐的实现方式略奇葩,在ScrollView里嵌套了三个TextView,分别显示之前的所有歌词,当前显示的歌词和之后的所有歌词。实现起来很容易,但是效果不够理想,也不支持跳转播放

还有其他的实现方式,比如在自定义View里重载onDraw方法,自己绘制歌词……这样比较精确,但要支持滑动之后跳转播放的话难度很大。

还是用ScrollView实现吧……


定义一个LyricView类,用来实现歌词显示及滑动到某个位置之后跳转播放

大概思路是这样:在ScrollView中套一个LinearLayout,LinearLayout中放置三个View:

1.一个空白的View,高度是LyricView高度的一半 2.GridView,只有一列,用来显示歌词内容,但不能滑动 3.一个空白的View,高度是LyricView高度的一半

实现滑动后跳转到某个位置播放,要获取GridView里面每一个TextView的高度,然后通过当前所在位置计算现在在LyricView正中间的是哪一行,也就是要跳转到的行


如果布局文件已经在XML中定义好,要获取一个View的高度,直接调用其getWidth()和getHeight()方法就可以了。但是这里每一个TextView都是动态显示的,没有在XML中进行定义。而且每个TextView的高度可能会随歌词长度变化而变化。

最大的难点在于,每个View的实际高度只能在ViewGroup的onLayout方法执行完毕之后才能获取。然而直接获取GridView或ListView的适配器中,getView方法返回的View的高度是不正确的。(这时候获取的高度都是0,因为getView方法返回的对象实际上还没有被显示。)

要获取每个View的正确高度,要通过android.view.ViewTreeObserver类实现。

源码中对ViewTreeObserver的说明:

/**
 * A view tree observer is used to register listeners that can be notified of global
 * changes in the view tree. Such global events include, but are not limited to,
 * layout of the whole tree, beginning of the drawing pass, touch mode change....
 *
 * A ViewTreeObserver should never be instantiated by applications as it is provided
 * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
 * for more information.
 */

注册监听器,用于监听view tree中的全局变化。这样的全局事件包括但不限于整个view树的显示,绘制过程的开始和触控模式变化

ViewTreeObserver对象不能通过应用进行实例化,因为它是通过view层次提供的。


获取View对应的ViewTreeObserver,然后添加ViewTreeObserver.OnGlobalLayoutListener可实现在onLayout执行完毕之后,获取View的正确大小。

重写GridView的Adapter中的getView方法,添加OnGlobalLayoutListener获取每行高度。

@Override
            public View getView(final int position, View convertView, ViewGroup parent) {
                final TextView textView = new TextView(context);
                textView.setText(strs.get(position));
                //设置每行的文字居中
                textView.setGravity(Gravity.CENTER);

                //当前位置的文本设为黑色
                if(position == currentpos){
                    textView.setTextColor(Color.BLACK);
                }

                //获取TextView对应的ViewTreeObserver
                final ViewTreeObserver vto = textView.getViewTreeObserver();
                //添加OnGlobalLayoutListener,在TextView显示之后获取宽度,高度
                vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                        int height = textView.getHeight();
                        int width = textView.getWidth();
                        heights[position] = height;
                    }
                });
                return textView;
            }
同样的方法也可以用在其他View上。


直接将GridView嵌套进LinearLayout的话,只能看到一行内容。

第二个难点在于,如何让GridView不滑动,也就是平铺显示所有内容。

重载GridView的onMeasure方法:

public class NoScrollGridView extends GridView {
    public NoScrollGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NoScrollGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandMeasureSpec);
    }
}

过了5个月终于把代码传上去了= =

Demo: https://github.com/entalent/LyricViewDemo

你可能感兴趣的:(Android)