Skip to content

Commit 345dfa2

Browse files
author
hzwangchenyan
committed
修复显示多行歌词时动画不流畅
1 parent beaa7b6 commit 345dfa2

File tree

4 files changed

+143
-126
lines changed

4 files changed

+143
-126
lines changed

lrcview/src/main/java/me/wcy/lrcview/LrcEntry.java

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class LrcEntry implements Comparable<LrcEntry> {
2424
private long time;
2525
private String text;
2626
private StaticLayout staticLayout;
27+
private int offset = Integer.MIN_VALUE;
2728

2829
private LrcEntry(long time, String text) {
2930
this.time = time;
@@ -49,6 +50,14 @@ int getHeight() {
4950
return staticLayout.getHeight();
5051
}
5152

53+
public int getOffset() {
54+
return offset;
55+
}
56+
57+
public void setOffset(int offset) {
58+
this.offset = offset;
59+
}
60+
5261
@Override
5362
public int compareTo(LrcEntry entry) {
5463
if (entry == null) {

lrcview/src/main/java/me/wcy/lrcview/LrcView.java

+74-101
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ public class LrcView extends View {
3535
private String mLabel;
3636
private float mLrcPadding;
3737
private ValueAnimator mAnimator;
38-
private float mAnimateOffset;
39-
private long mNextTime = 0L;
38+
private float mOffset;
4039
private int mCurrentLine = 0;
4140
private Object mFlag;
4241

@@ -92,62 +91,40 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
9291
protected void onDraw(Canvas canvas) {
9392
super.onDraw(canvas);
9493

95-
canvas.translate(0, mAnimateOffset);
96-
97-
// 中心Y坐标
98-
float centerY = getHeight() / 2;
99-
100-
mPaint.setColor(mCurrentColor);
94+
canvas.translate(0, mOffset);
10195

10296
// 无歌词文件
10397
if (!hasLrc()) {
98+
mPaint.setColor(mCurrentColor);
10499
@SuppressLint("DrawAllocation")
105100
StaticLayout staticLayout = new StaticLayout(mLabel, mPaint, (int) getLrcWidth(),
106101
Layout.Alignment.ALIGN_CENTER, 1f, 0f, false);
107-
drawText(canvas, staticLayout, centerY - staticLayout.getHeight() / 2);
102+
drawText(canvas, staticLayout, getHeight() / 2);
108103
return;
109104
}
110105

111-
// 画当前行
112-
float currY = centerY - mLrcEntryList.get(mCurrentLine).getHeight() / 2;
113-
drawText(canvas, mLrcEntryList.get(mCurrentLine).getStaticLayout(), currY);
114-
115-
// 画当前行上面的
116-
mPaint.setColor(mNormalColor);
117-
float upY = currY;
118-
for (int i = mCurrentLine - 1; i >= 0; i--) {
119-
upY -= mDividerHeight + mLrcEntryList.get(i).getHeight();
120-
drawText(canvas, mLrcEntryList.get(i).getStaticLayout(), upY);
121-
122-
if (upY <= 0) {
123-
break;
106+
float y = 0;
107+
for (int i = 0; i < mLrcEntryList.size(); i++) {
108+
if (i > 0) {
109+
y += (mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) / 2 + mDividerHeight;
124110
}
125-
}
126-
127-
// 画当前行下面的
128-
float downY = currY + mLrcEntryList.get(mCurrentLine).getHeight() + mDividerHeight;
129-
for (int i = mCurrentLine + 1; i < mLrcEntryList.size(); i++) {
130-
drawText(canvas, mLrcEntryList.get(i).getStaticLayout(), downY);
131-
132-
if (downY + mLrcEntryList.get(i).getHeight() >= getHeight()) {
133-
break;
134-
}
135-
136-
downY += mLrcEntryList.get(i).getHeight() + mDividerHeight;
111+
mPaint.setColor((i == mCurrentLine) ? mCurrentColor : mNormalColor);
112+
drawText(canvas, mLrcEntryList.get(i).getStaticLayout(), y);
137113
}
138114
}
139115

116+
/**
117+
* 画一行歌词
118+
*
119+
* @param y 歌词中心 Y 坐标
120+
*/
140121
private void drawText(Canvas canvas, StaticLayout staticLayout, float y) {
141122
canvas.save();
142-
canvas.translate(mLrcPadding, y);
123+
canvas.translate(mLrcPadding, y - staticLayout.getHeight() / 2);
143124
staticLayout.draw(canvas);
144125
canvas.restore();
145126
}
146127

147-
private float getLrcWidth() {
148-
return getWidth() - mLrcPadding * 2;
149-
}
150-
151128
/**
152129
* 设置歌词为空时屏幕中央显示的文字,如“暂无歌词”
153130
*/
@@ -226,11 +203,7 @@ private void onLrcLoaded(List<LrcEntry> entryList) {
226203
mLrcEntryList.addAll(entryList);
227204
}
228205

229-
if (hasLrc()) {
230-
initEntryList();
231-
initNextTime();
232-
}
233-
206+
initEntryList();
234207
invalidate();
235208
}
236209

@@ -243,23 +216,14 @@ public void updateTime(final long time) {
243216
runOnUi(new Runnable() {
244217
@Override
245218
public void run() {
246-
// 避免重复绘制
247-
if (time < mNextTime) {
219+
if (!hasLrc()) {
248220
return;
249221
}
250-
for (int i = mCurrentLine; i < mLrcEntryList.size(); i++) {
251-
if (mLrcEntryList.get(i).getTime() > time) {
252-
mNextTime = mLrcEntryList.get(i).getTime();
253-
mCurrentLine = (i < 1) ? 0 : (i - 1);
254-
newline(i, true);
255-
break;
256-
} else if (i == mLrcEntryList.size() - 1) {
257-
// 最后一行
258-
mCurrentLine = mLrcEntryList.size() - 1;
259-
mNextTime = Long.MAX_VALUE;
260-
newline(i, true);
261-
break;
262-
}
222+
223+
int line = findShowLine(time);
224+
if (line != mCurrentLine) {
225+
mCurrentLine = line;
226+
newline(line);
263227
}
264228
}
265229
});
@@ -269,26 +233,34 @@ public void run() {
269233
* 将歌词滚动到指定时间
270234
*
271235
* @param time 指定的时间
236+
* @deprecated 请使用 {@link #updateTime(long)} 代替
272237
*/
273-
public void onDrag(final long time) {
274-
runOnUi(new Runnable() {
275-
@Override
276-
public void run() {
277-
for (int i = 0; i < mLrcEntryList.size(); i++) {
278-
if (mLrcEntryList.get(i).getTime() > time) {
279-
if (i == 0) {
280-
mCurrentLine = i;
281-
initNextTime();
282-
} else {
283-
mCurrentLine = i - 1;
284-
mNextTime = mLrcEntryList.get(i).getTime();
285-
}
286-
newline(i, false);
287-
break;
288-
}
238+
public void onDrag(long time) {
239+
updateTime(time);
240+
}
241+
242+
/**
243+
* 二分法查找当前应该显示的行数(最后一个 <= time 的行数)
244+
*/
245+
private int findShowLine(long time) {
246+
int left = 0;
247+
int right = mLrcEntryList.size();
248+
while (left <= right) {
249+
int middle = (left + right) / 2;
250+
long middleTime = mLrcEntryList.get(middle).getTime();
251+
252+
if (time < middleTime) {
253+
right = middle - 1;
254+
} else {
255+
if (middle + 1 >= mLrcEntryList.size() || time < mLrcEntryList.get(middle + 1).getTime()) {
256+
return middle;
289257
}
258+
259+
left = middle + 1;
290260
}
291-
});
261+
}
262+
263+
return 0;
292264
}
293265

294266
/**
@@ -301,16 +273,14 @@ public boolean hasLrc() {
301273
}
302274

303275
private void reset() {
276+
endAnimation();
304277
mLrcEntryList.clear();
305278
mCurrentLine = 0;
306-
mNextTime = 0L;
307-
308-
stopAnimation();
309279
invalidate();
310280
}
311281

312282
private void initEntryList() {
313-
if (getWidth() == 0) {
283+
if (!hasLrc() || getWidth() == 0) {
314284
return;
315285
}
316286

@@ -319,51 +289,54 @@ private void initEntryList() {
319289
for (LrcEntry lrcEntry : mLrcEntryList) {
320290
lrcEntry.init(mPaint, (int) getLrcWidth());
321291
}
322-
}
323292

324-
private void initNextTime() {
325-
if (mLrcEntryList.size() > 1) {
326-
mNextTime = mLrcEntryList.get(1).getTime();
327-
} else {
328-
mNextTime = Long.MAX_VALUE;
329-
}
293+
mOffset = getHeight() / 2;
330294
}
331295

332296
/**
333297
* 换行动画<br>
334298
* 属性动画只能在主线程使用
335299
*/
336-
private void newline(int line, boolean animate) {
337-
stopAnimation();
300+
private void newline(int line) {
301+
endAnimation();
338302

339-
if (line <= 0 || !animate) {
340-
invalidate();
341-
return;
342-
}
343-
344-
float prevHeight = mLrcEntryList.get(line - 1).getHeight();
345-
float currHeight = mLrcEntryList.get(line).getHeight();
346-
float totalOffset = (prevHeight + currHeight) / 2 + mDividerHeight;
303+
int offset = getOffset(line);
347304

348-
mAnimator = ValueAnimator.ofFloat(totalOffset, 0.0f);
349-
mAnimator.setDuration(mAnimationDuration * mLrcEntryList.get(line).getStaticLayout().getLineCount());
305+
mAnimator = ValueAnimator.ofFloat(mOffset, offset);
306+
mAnimator.setDuration(mAnimationDuration);
350307
mAnimator.setInterpolator(new LinearInterpolator());
351308
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
352309
@Override
353310
public void onAnimationUpdate(ValueAnimator animation) {
354-
mAnimateOffset = (float) animation.getAnimatedValue();
311+
mOffset = (float) animation.getAnimatedValue();
355312
invalidate();
356313
}
357314
});
358315
mAnimator.start();
359316
}
360317

361-
private void stopAnimation() {
318+
private void endAnimation() {
362319
if (mAnimator != null && mAnimator.isRunning()) {
363320
mAnimator.end();
364321
}
365322
}
366323

324+
private int getOffset(int line) {
325+
if (mLrcEntryList.get(line).getOffset() == Integer.MIN_VALUE) {
326+
int offset = getHeight() / 2;
327+
for (int i = 1; i <= line; i++) {
328+
offset -= (mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) / 2 + mDividerHeight;
329+
}
330+
mLrcEntryList.get(line).setOffset(offset);
331+
}
332+
333+
return mLrcEntryList.get(line).getOffset();
334+
}
335+
336+
private float getLrcWidth() {
337+
return getWidth() - mLrcPadding * 2;
338+
}
339+
367340
private void runOnUi(Runnable r) {
368341
if (Looper.myLooper() == Looper.getMainLooper()) {
369342
r.run();

0 commit comments

Comments
 (0)