需求:
1坤塞、歌詞可以跟隨播放進度進行自動滑動冯勉;
2、可以手指進行歌詞順暢滑動摹芙;
3灼狰、當前歌詞高亮,且置于屏幕的中心浮禾;
實現(xiàn)方式一:
也是網(wǎng)上可以搜到的做多的方式:自定義view交胚,繼承textview; 通過重寫onDraw來繪制每一行text;通過onTouchEvent來控制位置盈电;但是實現(xiàn)之后發(fā)現(xiàn)手指滑動時并不順暢蝴簇。
這種方式可以參考這位作者的文章: http://www.reibang.com/p/0feb6171b0c5
本文提供另一種思路,簡單粗暴匆帚,使用listview來實現(xiàn)熬词,實現(xiàn)思路如下:
實現(xiàn)方式二、整體布局采用scrollview里面包含一個LinearLayout,LinearLayout里面依次放空View(高度為屏幕高度的一半)互拾、ListView(一行一句歌詞)歪今, 空view(高度為屏幕高度的一半)。
為什么要放兩個空view呢颜矿?是因為我們當前在聽的那句歌詞寄猩,要始終處于屏幕的中心。這個是產(chǎn)品設(shè)計骑疆。
這里面有幾個核心點:
1田篇、歌詞解析。簡單分析一下數(shù)據(jù)結(jié)構(gòu):一首歌的歌詞(Lyric)箍铭, 包含一組句子(LyricSentence), 一個句子包含一組詞或字(LyricWord)斯辰,每個LyricWord都對應(yīng)著播放時間(Duration)。我們在拖動播放進度的時候坡疼,通過拖動的百分比可以計算到要播放到的時間, 進而拿到對應(yīng)的句子和詞衣陶。我這里進度控制只控制到句子柄瑰。
2、用來放歌詞的ListView剪况,每一個item是一個TextView, 一個TextView顯示一句即可教沾。這里要注意,我們的ListView是不能有分割線译断,點擊和長按不能有其他顏色授翻,不然出來效果不好。在xml里面設(shè)置:
<ListView
android:id="@+id/lyric_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null" (設(shè)置沒有分割線)
android:cacheColorHint="#00000000" (去除listview拖動背景色)
android:background="@android:color/transparent" (背景透明)
android:listSelector="@android:color/transparent" (選中狀態(tài)下背景透明孙咪,不會變黑)
android:layout_marginRight="7dp"
android:paddingLeft="16dp"
android:paddingRight="7dp"/>
3堪唐、當切換到一首新的時候, 歌詞變化了翎蹈, ListView高度要跟著變化淮菠,否則顯示不全。調(diào)用以下方法重新設(shè)置listview的高度:
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();//這個listItem.getMeasuredHeight()就是每個Item的高度
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
4荤堪、如何設(shè)置空view的高度合陵?空view高度為手機屏幕的一半, mHalfScreenHeight澄阳,我們在onSizeChanged的時候來獲取拥知。
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
if (h != 0) {
mHalfScreenHeight = (int)(h * 0.5);
checkBlankViewHeight();
}
}
然后使用獲取到的mHalfScreenHeight來設(shè)置上下空view的高度。注意有時候在onSizeChanged方法中獲取之后立即去設(shè)置可能不成功碎赢,你可能需要在接收到歌詞進度變化的時候再調(diào)一下checkBlankViewHeight()低剔。
private View mTopBlankView; //上面的空view
private View mBottomBlankView; //下面的空view
private void checkBlankViewHeight() {
if (mHalfScreenHeight != 0 && mTopBlankView.getHeight() == 0) {
setViewHeight(mBottomBlankView, mHalfScreenHeight);
setViewHeight(mTopBlankView, mHalfScreenHeight);
}
}
private void setViewHeight(View view, int height) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = height;
view.setLayoutParams(params);
}
5、切換歌曲揩抡、進度控制户侥。
private LyricAdapter mAdapter; //這個是ListView對應(yīng)的adapter,一個item是一個textview,里面放一句歌詞LyricSentence
boolean isNewSong = false; //是否切換到新的歌曲了
public void setLyric(Lyric lyric) {//切換歌曲镀琉,設(shè)置一首新歌的歌詞
if (lyric == null || mAdapter.getLyric() == lyric) {
return;
}
isNewSong = true;
mAdapter.setLyric(lyric);
setViewHeight(mListView, getListViewHeight(mListView));
}
public void setLyricCurrentPostion(int newPosition) { // 進度控制,設(shè)置當前播放的歌曲的進度
int lastPosition = mAdapter.getCurrentPosition();
checkBlankViewHeight();
// a new song is coming
if (newPosition == 0 && isNewSong) {
isNewSong = false;
setDefaultPosition();
return;
}
if (lastPosition == newPosition) {
return;
}
// 當前進度和上次進度相比蕊唐,需要再移動多少Item的高度屋摔。每個item的高度可以參考上面的3來獲取。
mScrollView.scrollBy(0, (newPosition - lastPosition) * mItemHeight);
postInvalidate();
}
public void setDefaultPosition() {
mScrollView.scrollTo(0, mAdapter.getCurrentPosition() * mItemHeight);
postInvalidate();
}
6替梨、最后你會發(fā)現(xiàn)可能有時候莫名其妙的位置不太對钓试,比如你明明讓他scrollto(0, 0)了但是它好像還是往上跑或者往下跑了。這是因為我們給ScrollView包含的內(nèi)容里面塞了兩個空view副瀑,當這個空view高度被我們從0設(shè)到半屏幕高的時候弓熏,ScrollView檢測到子控件的布局發(fā)生了變化,整個頁面的內(nèi)容超出了屏幕的顯示區(qū)域糠睡,所以進行了自動滾動挽鞠。我們需要寫一個不會自動滾動的ScrollView,重寫scrollview中的如下方法狈孔,并將其返回值設(shè)為0即可信认。
@Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
return 0;
}
寫在最后:
這里只是簡單的實現(xiàn)過程的總結(jié),沒有貼大量源代碼均抽。有機會希望自己可以做一個demo出來嫁赏。
自定義View是很考驗功底的。知易行難油挥。很多坑自己不跳進去不知深淺潦蝇,每從一個坑里爬出來你就離真正的工程師更進了一步。
比如6我就debug了好久深寥,最后通過將進度條顯示出來攘乒,去觀察才發(fā)現(xiàn),原來是ScrollView自己進行了滾動惋鹅,于是就去尋找如何禁止ScrollView的解決方案持灰。但是網(wǎng)上的解決方案那么多,如何才知道哪個是可行的呢负饲?我的方案是快速試錯堤魁,通過對比分析效果,找到可行的方法返十。