之前想写一个音乐播放器,但是一直不明白歌词应该怎么显示。
用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.
*/
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