自定义的歌词界面,实现了播放歌词高亮,滑动歌词高亮,歌词常态高亮。希望对大家有所帮助。同时也希望各路大神提出不足之处。
1.歌词界面View
代码注释很详细,详情看代码。
这里面很多属性可以封装,这里暂时不做处理,后面有时间会重新封装。喜欢的同学可以自己进行封装
public class LrcView extends ScrollView {
private static final String TAG = "LrcView";
private LinearLayout mRootView;
private LinearLayout mLyricList;
private ArrayList<TextView> mLyricItems = new ArrayList<>(); //存放歌词的TextView 一句歌词就存放一个TextView
private ArrayList<String> mLyricTextList = new ArrayList<>(); //存放歌词的集合
private ArrayList<Long> mLyricTimeList = new ArrayList<>(); //存放每句歌词对应的时间集合
private ArrayList<Integer> mLyricItemHeights; //存放每句歌词控件的高度
private int mHeight;
private int mWidth;
private int mPrevSelected = -1; //前一次播放歌词的位置(判断当前时间的位置是否与上一次一致)
private OnLyricScrollChangeListener mListener;
//尝试在res中的color定义颜色 然后直接引用,发现显示出来的颜色和实际想要的效果不一样,因此手动定义。颜色后面有时间追究
private static final String HIGH_LIGHT = "#ECECEC"; //播放歌词高亮的颜色
private static final String HIGH_SCROLLER = "#A9A9A9"; //滑动歌词高亮的颜色
private static final String NOR = "#696969"; //常态歌词的颜色
public LrcView(Context context) {
super(context);
init();
}
public LrcView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LrcView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mRootView = new LinearLayout(getContext()); //设置跟布局
mRootView.setOrientation(LinearLayout.VERTICAL); //设置垂直方向
ViewTreeObserver vto = mRootView.getViewTreeObserver(); //获取视图树
vto.addOnGlobalLayoutListener(() -> {
//View组件视图要在onResume后才能完成,因此监听视图树变化 从而拿到宽高
mHeight = LrcView.this.getHeight();
mWidth = LrcView.this.getWidth();
refreshRootView();
});
addView(mRootView);
}
/**
* 刷新页面
* */
private void refreshRootView() {
mRootView.removeAllViews();
LinearLayout blank1 = new LinearLayout(getContext());
LinearLayout blank2 = new LinearLayout(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mWidth, mHeight / 2);
mRootView.addView(blank1,params);
if (mLyricList != null) {
mRootView.addView(mLyricList);
mRootView.addView(blank2,params);
}
}
private void refreshLyricList() {
if (mLyricList == null) {
mLyricList = new LinearLayout(getContext());
mLyricList.setOrientation(LinearLayout.VERTICAL);
}
mLyricList.removeAllViews();
mLyricItems.clear();
mLyricItemHeights = new ArrayList<>();
mPrevSelected = UsbMusicConstants.NOT_LRC_DISPLAY; //初始化
for (int i = 0; i < mLyricTextList.size(); i++) {
//将每一句歌词放入一个TextView中,并且设置textView的属性
final TextView textView = new TextView(getContext());
textView.setText(mLyricTextList.get(i));
textView.setTextSize(UsbMusicConstants.FONT_SIZE);
textView.setHeight(UsbMusicConstants.FONT_HEIGHT);
textView.setGravity(Gravity.CENTER);
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);
mLyricItemHeights.add(index,textView.getHeight()); //保存每一次TextView的真实高度
}
});
mLyricList.addView(textView);
mLyricItems.add(index,textView);
}
}
/**
* 滑动到指定位置
*
* @param index 指定的坐标
*/
public void scrollToIndex(int index) {
setSelected(index, UsbMusicConstants.NOT_LRC_DISPLAY);
if (index < 0) {
scrollTo(0,0);
return;
}
if (mLyricItemHeights.size() == 0) {
scrollTo(0,0);
return;
}
if (index < mLyricTextList.size()) {
int sum = 0;
int size = index - 1;
if (size < 0) {
size = 0;
}
for (int i = 0; i <= size; i++) {
sum += mLyricItemHeights.get(i);
}
sum += mLyricItemHeights.get(index) / 2;
scrollTo(0, sum);
}
}
/**
* 根据当前的高度计算出歌词坐标
* */
private int getIndex(int length) {
int index = 0;
int sum = 0;
while (sum <= length) {
if (mLyricItemHeights.size() == 0) {
break;
}
sum += mLyricItemHeights.get(index);
index++;
if (index == mLyricItemHeights.size()) {
break;
}
}
return index - UsbMusicConstants.LIST_NUM_ONE;
}
/**
*设置当前歌词的高亮颜色以及颜色类型
*
* @param playIndex 当前播放的坐标
* @param scrollIndex 滚动播放的坐标 -1代表当前并未滑动
*/
@SuppressLint("ResourceAsColor")
public void setSelected(int playIndex, int scrollIndex) {
if (playIndex == mPrevSelected && scrollIndex == -1) {
return;
}
for (int i = 0; i < mLyricItems.size(); i++) {
if (scrollIndex == -1 || playIndex == scrollIndex) {
mLyricItems.get(i).setTextColor(i == playIndex ? Color
.parseColor(HIGH_LIGHT) : Color.parseColor(NOR));
} else {
if (i == playIndex) {
mLyricItems.get(i).setTextColor(Color.parseColor(HIGH_LIGHT));
} else {
mLyricItems.get(i).setTextColor(i == scrollIndex ? Color
.parseColor(HIGH_SCROLLER) : Color.parseColor(NOR));
}
}
}
mPrevSelected = playIndex;
}
/**
* 将指定歌词变为正常颜色
* */
public void recovery(int index) {
mLyricItems.get(index).setTextColor(Color.parseColor(NOR));
}
/**
* Set the lyrics and refresh the interface.
*
* @param textList Each line displays a list of lyrics.
* @param timeList Each line displays a list of the time the lyrics are displayed.
*/
public void setLyricText(ArrayList<String> textList,ArrayList<Long> timeList) {
if (textList.size() != timeList.size()) {
throw new IllegalArgumentException();
}
this.mLyricTextList = textList;
this.mLyricTimeList = timeList;
refreshLyricList();
}
@Override
protected void onScrollChanged(int width, int height, int oldWidth, int oldHeight) {
super.onScrollChanged(width, height, oldWidth, oldHeight);
if (mListener != null) {
mListener.onLyricScrollChange(getIndex(height), getIndex(oldHeight));
}
}
public void setOnLyricScrollChangeListener(OnLyricScrollChangeListener listener) {
this.mListener = listener;
}
public interface OnLyricScrollChangeListener {
void onLyricScrollChange(int index, int oldIndex);
}
}
2.歌词View使用方法
(1)实现当前歌词高亮
** 目前歌词的高亮是通过接口scrollToIndex 来设置。因此我们可以通过监听当前音乐的进度来设置。监听设置的回调代码此处省略。我这边是通过监听mediaPlayer的回调接口,然后在接口实现里面调用hanler,代码如下:**
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == CODE) {
mBinding.lrcView.scrollToIndex(mIndex);
}
return false;
}
});
(2)实现当前歌词滑动播放
** 现在音乐软件都支持音乐滑动,然后滑动后,点击某一处,可实现跳转播放。代码实现如下**
mBinding.lrcView.setOnLyricScrollChangeListener((index, oldIndex) -> {
//index代表当前滚动的位置, old代表老位置
index = Math.max(index, 0);
mScrollIndex = index;
mBinding.currentLrcTime.setText(getFormatTime(mLrcTimeList.get(index))); //获取滚动歌词的时间
});
mBinding.lrcView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacksAndMessages(null);
mIsOnTouch = true;
break;
case MotionEvent.ACTION_UP:
//为了不影响用户体验,我们要加一个滑动停止后,5s后再回到高亮歌词界面
handler.sendEmptyMessageDelayed(UsbMusicConstants.LIST_NUM_TWO,
UsbMusicConstants.FIVE_SECOND);
break;
default:
break;
}
return false;
});
** 对handler修改如下**
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == CODE) {
if (!mIsOnTouch) {
if (mIndex == mLrcContentList.size()) {
handler.removeMessages(CODE);
}
mBinding.lrcView.scrollToIndex(mIndex);
}
} else {
mIsOnTouch = false;
}
return false;
}
});
(3)实现当前滑动歌词高亮
handler做以下修改
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == CODE) {
if (!mIsOnTouch) {
if (mIndex == mLrcContentList.size()) {
handler.removeMessages(CODE);
}
mBinding.lrcView.scrollToIndex(mIndex);
}
} else {
if (mOldScrollIndex != UsbMusicConstants.NOT_LRC_DISPLAY
&& mOldScrollIndex != mIndex) {
mBinding.lrcView.recovery(mOldScrollIndex);
mOldScrollIndex = UsbMusicConstants.NOT_LRC_DISPLAY;
}
mIsOnTouch = false;
mBinding.lrcPlay.setVisibility(View.GONE);
}
return false;
}
});
滑动监听做以下修改:
mBinding.lrcView.setOnLyricScrollChangeListener((index, oldIndex) -> {
//index代表当前滚动的位置, old代表老位置
index = Math.max(index, 0);
mScrollIndex = index;
mBinding.currentLrcTime.setText(getFormatTime(mLrcTimeList.get(index)));
});
mBinding.lrcView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacksAndMessages(null);
mIsOnTouch = true;
mBinding.lrcPlay.setVisibility(View.VISIBLE);
break;
case MotionEvent.ACTION_UP:
handler.sendEmptyMessageDelayed(UsbMusicConstants.LIST_NUM_TWO,
UsbMusicConstants.FIVE_SECOND);
break;
case MotionEvent.ACTION_MOVE:
mOldScrollIndex = mScrollIndex;
mBinding.lrcView.setSelected(mIndex, mScrollIndex);
break;
default:
break;
}
return false;
});