實現音樂播放器歌詞顯示效果
阿新 • • 發佈:2019-01-09
這兩天有個任務,說是要寫一個QQ音樂播放器歌詞的那種效果,畢竟剛學自定義View,沒有什麼思路,然後就Google.寫了一個歌詞效果,效果圖在後面,下面是我整理的程式碼。
首先實現這種效果有兩種方式
1.自定義View裡過載onDraw方法,自己繪製歌詞
2.用ScrollView實現
第一種方式比較精確,但要支援滑動之後跳轉播放的話難度很大,所以我選擇第二種,自定義ScrollView
我也不多說,直接上程式碼,程式碼中有註釋
一.自定義LycicView extends ScrollView
裡面包括一個空白布局,高度是LycicView的一半,再是一個佈局存放歌詞的,最後是一個空白布局高度是LycicView的一半
這裡動態的向第二個佈局裡面添加了顯示歌詞的TextView,並利用ViewTreeObserver得到每個textview的高度,方便知道每個textview歌詞所要滑動到的高度
public class LycicView extends ScrollView { LinearLayout rootView;//父佈局 LinearLayout lycicList;//垂直佈局 ArrayList<TextView> lyricItems = new ArrayList<TextView>();//每項的歌詞集合 ArrayList<String> lyricTextList = new ArrayList<String>();//每行歌詞文字集合,建議先去看看手機音樂裡的歌詞格式和內容 ArrayList<Long> lyricTimeList = new ArrayList<Long>();//每行歌詞所對應的時間集合 ArrayList<Integer> lyricItemHeights;//每行歌詞TextView所要顯示的高度 int height;//控制元件高度 int width;//控制元件寬度 int prevSelected = 0;//前一個選擇的歌詞所在的item public LycicView(Context context) { super(context); init(); } public LycicView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public LycicView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ rootView = new LinearLayout(getContext()); rootView.setOrientation(LinearLayout.VERTICAL); //建立檢視樹,會在onLayout執行後立即得到正確的高度等引數 ViewTreeObserver vto = rootView.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { height = LycicView.this.getHeight(); width = LycicView.this.getWidth(); refreshRootView(); } }); addView(rootView);//把佈局加進去 } /** * */ void refreshRootView(){ rootView.removeAllViews();//重新整理,先把之前包含的所有的view清除 //建立兩個空白view LinearLayout blank1 = new LinearLayout(getContext()); LinearLayout blank2 = new LinearLayout(getContext()); //高度平分 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width,height/2); rootView.addView(blank1,params); if(lycicList !=null){ rootView.addView(lycicList);//加入一個歌詞顯示佈局 rootView.addView(blank2,params); } } /** *設定歌詞, */ void refreshLyicList(){ if(lycicList == null){ lycicList = new LinearLayout(getContext()); lycicList.setOrientation(LinearLayout.VERTICAL); //重新整理,重新新增 lycicList.removeAllViews(); lyricItems.clear(); lyricItemHeights = new ArrayList<Integer>(); prevSelected = 0; //為每行歌詞建立一個TextView for(int i = 0;i<lyricTextList.size();i++){ final TextView textView = new TextView(getContext()); textView.setText(lyricTextList.get(i)); //居中顯示 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_HORIZONTAL; textView.setLayoutParams(params); //對高度進行測量 ViewTreeObserver vto = textView.getViewTreeObserver(); final int index = i; vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);//api 要在16以上 >=16 lyricItemHeights.add(index,textView.getHeight());//將高度新增到對應的item位置 } }); lycicList.addView(textView); lyricItems.add(index,textView); } } } /** * 滾動到index位置 */ public void scrollToIndex(int index){ if(index < 0){ scrollTo(0,0); } //計算index對應的textview的高度 if(index < lyricTextList.size()){ int sum = 0; for(int i = 0;i<=index-1;i++){ sum+=lyricItemHeights.get(i); } //加上index這行高度的一半 sum+=lyricItemHeights.get(index)/2; scrollTo(0,sum); } } /** * 歌詞一直滑動,小於歌詞總長度 * @param length * @return */ int getIndex(int length){ int index = 0; int sum = 0; while(sum <= length){ sum+=lyricItemHeights.get(index); index++; } //從1開始,所以得到的是總item,腳標就得減一 return index - 1; } /** * 設定選擇的index,選中的顏色 * @param index */ void setSelected(int index){ //如果和之前選的一樣就不變 if(index == prevSelected){ return; } for(int i = 0;i<lyricItems.size();i++){ //設定選中和沒選中的的顏色 if(i == index){ lyricItems.get(i).setTextColor(Color.BLUE); }else{ lyricItems.get(i).setTextColor(Color.WHITE); } prevSelected = index; } } /** * 設定歌詞,並呼叫之前寫的refreshLyicList()方法設定view * @param textList * @param timeList */ public void setLyricText(ArrayList<String> textList,ArrayList<Long> timeList){ //因為你從歌詞lrc裡面可以看出,每行歌詞前面都對應有時間,所以兩者必須相等 if(textList.size() != timeList.size()){ throw new IllegalArgumentException(); } this.lyricTextList = textList; this.lyricTimeList = timeList; refreshLyicList(); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); //滑動時,不往回彈,滑到哪就定位到哪 setSelected(getIndex(t)); if(listener != null){ listener.onLyricScrollChange(getIndex(t),getIndex(oldt)); } } OnLyricScrollChangeListener listener; public void setOnLyricScrollChangeListener(OnLyricScrollChangeListener l){ this.listener = l; } /** * 向外部提供介面 */ public interface OnLyricScrollChangeListener{ void onLyricScrollChange(int index,int oldindex); } }
二..MainActivity中的佈局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/img01" tools:context=".MainActivity"> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="number" android:ems="10" android:id="@+id/editText" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="scroll to" android:id="@+id/button" android:layout_alignTop="@+id/editText" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_above="@+id/editText"> <custom.LycicView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/view" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> <View android:layout_width="match_parent" android:layout_height="2dp" android:background="@null" android:id="@+id/imageView" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> <View android:layout_below="@id/imageView" android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="6dp" android:background="#999999" android:id="@+id/imageView2" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> </RelativeLayout> </RelativeLayout>
具體實現程式碼如下:
public class MainActivity extends AppCompatActivity {
LycicView view;
EditText editText;
Button btn;
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if(msg.what == 1){
if(lrc_index == list.size()){
handler.removeMessages(1);
}
lrc_index++;
System.out.println("******"+lrc_index+"*******");
view.scrollToIndex(lrc_index);
handler.sendEmptyMessageDelayed(1,4000);
}
return false;
}
});
private ArrayList<LrcMusic> lrcs;
private ArrayList<String> list;
private ArrayList<Long> list1;
private int lrc_index;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initEvents();
}
private void initViews(){
view = (LycicView) findViewById(R.id.view);
editText = (EditText) findViewById(R.id.editText);
btn = (Button) findViewById(R.id.button);
}
private void initEvents(){
InputStream is = getResources().openRawResource(R.raw.eason_tenyears);
// BufferedReader br = new BufferedReader(new InputStreamReader(is));
list = new ArrayList<String>();
list1 = new ArrayList<>();
lrcs = Utils.redLrc(is);
for(int i = 0; i< lrcs.size(); i++){
list.add(lrcs.get(i).getLrc());
System.out.println(lrcs.get(i).getLrc()+"=====");
list1.add(0l);//lrcs.get(i).getTime()
}
view.setLyricText(list, list1);
view.postDelayed(new Runnable() {
@Override
public void run() {
view.scrollToIndex(0);
}
},1000);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = editText.getText().toString();
int index = 0;
index = Integer.parseInt(text);
view.scrollToIndex(index);
}
});
view.setOnLyricScrollChangeListener(new LycicView.OnLyricScrollChangeListener() {
@Override
public void onLyricScrollChange(final int index, int oldindex) {
editText.setText(""+index);
lrc_index = index;
System.out.println("===="+index+"======");
//滾動handle不能放在這,因為,這是滾動監聽事件,滾動到下一次,handle又會發送一次訊息,出現意想不到的效果
}
});
handler.sendEmptyMessageDelayed(1,4000);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
handler.removeCallbacksAndMessages(null);
System.out.println("取消了");
break;
case MotionEvent.ACTION_UP:
System.out.println("開始了");
handler.sendEmptyMessageDelayed(1,2000);
break;
case MotionEvent.ACTION_CANCEL://時間別消耗了
break;
}
return false;
}
});
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
}
}
其中utils類和LycicMusic是一個工具類和存放Music資訊實體類
Utils類
public class Utils {
public static ArrayList<LrcMusic> redLrc(InputStream in) {
ArrayList<LrcMusic> alist = new ArrayList<LrcMusic>();
//File f = new File(path.replace(".mp3", ".lrc"));
try {
//FileInputStream fs = new FileInputStream(f);
InputStreamReader input = new InputStreamReader(in, "utf-8");
BufferedReader br = new BufferedReader(input);
String s = "";
while ((s = br.readLine()) != null) {
if (!TextUtils.isEmpty(s)) {
String lyLrc = s.replace("[", "");
String[] data_ly = lyLrc.split("]");
if (data_ly.length > 1) {
String time = data_ly[0];
String lrc = data_ly[1];
LrcMusic lrcMusic = new LrcMusic(lrcData(time), lrc);
alist.add(lrcMusic);
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return alist;
}
public static int lrcData(String time) {
time = time.replace(":", "#");
time = time.replace(".", "#");
String[] mTime = time.split("#");
//[03:31.42]
int mtime = Integer.parseInt(mTime[0]);
int stime = Integer.parseInt(mTime[1]);
int mitime = Integer.parseInt(mTime[2]);
int ctime = (mtime*60+stime)*1000+mitime*10;
return ctime;
}
}
LrcMusic實體類
public class LrcMusic {
private int time;
private String lrc;
public LrcMusic() {
}
public LrcMusic(int time, String lrc) {
this.time = time;
this.lrc = lrc;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public String getLrc() {
return lrc;
}
public void setLrc(String lrc) {
this.lrc = lrc;
}
}
效果圖: