一般歌词文件的格式大概如下:
+ e3 c* a4 j. I/ O: q* _2 s0 @2 O4 \* T0 N: Q$ |$ Z" ~
[ar:艺人名]" Z' e( i' q# X+ | Y7 W
8 x: { D6 K# e+ O
[ti:曲名]
0 w$ `/ C E) b: ^5 U4 x9 y6 O6 ^% w+ F6 s
[al:专辑名]
5 A! c h5 B% E! Q
6 e4 c* \1 W u+ {[by:编者(指编辑LRC歌词的人)]
! r8 h5 z- K$ D& d& j$ q
# t0 o. D4 ^5 e: F v2 i! ?% m[offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。
7 I) {! L+ x) j9 `' a$ x+ }0 I' A7 l* M+ C
但也不一定,有时候并没有前面那些ar:等标识符,所以我们这里也提供了另一种解析方式。% o6 r- `0 F o' _ ^$ ^' U+ N
. i/ ?9 u. q+ c1 v4 m歌词文件中的时间格式则比较统一:[00:00.50]等等,00:表示分钟,00.表示秒数,.50表示毫秒数,当然,我们最后是要将它们转化为毫秒数处理才比较方便。
6 i# h+ p( @8 t1 U1 U0 j3 V0 D M) _+ b$ h/ F% P
处理完歌词文件并得到我们想要的数据后,我们就要考虑如何在手机上滚动显示我们的歌词并且与我们得到的时间同步了。5 g1 a F. `3 w: x9 m0 o2 r
. K* b" A( T7 X0 S) o先是布局文件:
1 {$ O( T7 b3 s Q6 w! w8 T9 W, c$ d' f- }, _" |0 Y) H
/ G- y" k! D8 F3 M5 L# O
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 l2 Z T: G- U: k1 Exmlns:tools="http://schemas.android.com/tools": |) G5 ]+ w2 ]
android:layout_width="match_parent"( o$ s" D4 m2 n3 M- Z3 l( I6 z$ k4 v' B. Z
android:layout_height="match_parent" s3 V) h+ u& P4 R, H) V w
tools:context=".MainActivity" >
/ E: o+ e/ F, t5 x/ i% ~<Button S" s9 j1 f) Q9 ~
android:id="@+id/button"
( T; X' e( t- J+ e, zandroid:layout_width="60dip"
8 M1 ^ O1 I5 }4 |& O4 }android:layout_height="60dip"6 W* H1 E2 s3 D0 c
android:text="@string/停止" />( x( W( L8 c4 h; L
<com.example.slidechange.WordView2 V0 Q& |1 M' `! ^2 k1 R o) K9 J
android:id="@+id/text"! N4 E* p6 d" W1 x
android:layout_width="match_parent"
& T1 a9 e; T; g, q* l2 nandroid:layout_height="match_parent"* g4 q5 J- l( \. L
android:layout_below="@id/button" />. Y1 h! F3 R' f l! t. n w
/ B5 m& ~# u3 E9 r9 |! a
WordView是自定义的TextView,它继承自TextView:
" K* j- B& D$ h$ c0 p0 u0 Y. a# ~4 t7 ?: O" L: E7 p
public class WordView extends TextView {# {6 v+ H8 {; h+ n5 f7 n) J% l
private List mWordsList = new ArrayList();
7 I5 Q. K- @ Pprivate Paint mLoseFocusPaint;
- N! z4 K$ A& Qprivate Paint mOnFocusePaint; j f4 | |" i
private float mX = 0;6 W( u% s* d, s, z
private float mMiddleY = 0;9 k$ I9 s+ l2 \' }9 S1 C
private float mY = 0;: B0 _8 V/ R; e1 {
private static final int DY = 50;
[' w; @$ j9 D% }. q; C& }. oprivate int mIndex = 0;! W2 h: a# V6 b5 G" [
public WordView(Context context) throws IOException {
; p9 f6 Z0 k- y. Gsuper(context);4 Y% O5 }/ B0 f) J6 S+ E6 f! J! _
init();
( A$ O l3 b. z" z}( U1 U2 U5 q0 D. ]' M+ \; i
public WordView(Context context, AttributeSet attrs) throws IOException {
( @' e. g- X5 g) c' c/ psuper(context, attrs);
" }. ^6 Z0 E% |4 Finit();
/ [; \$ ^5 O: b0 Q1 Z}
9 T, I2 H3 I; X0 p2 ^7 k& Mpublic WordView(Context context, AttributeSet attrs, int defStyle)8 O' D' R. E# g6 j7 z) ^" W1 J. V
throws IOException {; L+ @: o5 q. ^9 {3 Q) L1 E
super(context, attrs, defStyle);
8 j, s6 r7 s( L ~* ginit();
% ^. k( r& B n+ l}
( ~! n- Q5 P$ ?# |) t. o+ T@Override
% o4 L8 w0 F7 i) Yprotected void onDraw(Canvas canvas) {% Q( g5 C) N1 i* E
super.onDraw(canvas);" J9 b' o$ V6 O, s6 i
canvas.drawColor(Color.BLACK);' c9 h1 A4 P+ d, [4 w7 O
Paint p = mLoseFocusPaint;
) L& d! T. q% h0 h& }" Zp.setTextAlign(Paint.Align.CENTER);
3 M8 P/ ]) T4 A- X8 lPaint p2 = mOnFocusePaint;
8 `- W* E$ ~8 Y& c$ t! o% jp2.setTextAlign(Paint.Align.CENTER);
4 ]) G' ?7 ]0 g$ P/ tcanvas.drawText(mWordsList.get(mIndex), mX, mMiddleY, p2);( I" d# b3 M9 }' ~
int alphaValue = 25;
2 {9 [+ P2 F4 {& [float tempY = mMiddleY;
2 ?1 g ]3 R2 O0 \% V, Y+ Dfor (int i = mIndex - 1; i >= 0; i--) {
6 m7 a D5 o+ {! Y" `+ EtempY -= DY;; f" S+ C- X+ O% d: g. T1 M* Z8 Q
if (tempY < 0) {
. M _4 k6 ?2 V% Gbreak;
$ j2 D0 F* K0 Q; U; u7 f f2 [4 p% e}
* v+ Z' t% ^) U/ w' h4 E' xp.setColor(Color.argb(255 - alphaValue, 245, 245, 245));' f1 t9 K2 H3 K6 y7 v: D$ r7 \5 P
canvas.drawText(mWordsList.get(i), mX, tempY, p);
2 q$ L( F9 ~- h7 UalphaValue += 25;
0 M0 Z, g# [& p0 U2 o9 E: W}
, [0 R4 ?+ q2 aalphaValue = 25;
6 R8 s1 t& ?: p, k+ NtempY = mMiddleY;
! j. Y% g' W9 P- i% Q5 Q- afor (int i = mIndex + 1, len = mWordsList.size(); i < len; i++) {
* i4 e7 G0 ]% ?* R) etempY += DY;
' `4 Y* r7 v! H2 T/ m$ xif (tempY > mY) {
9 |- T! U' v1 Xbreak;! U- I8 K. O; G
}
6 v& E! K" w4 a. g% v7 J- sp.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
2 J q2 c$ _6 x, ^9 Icanvas.drawText(mWordsList.get(i), mX, tempY, p);6 R8 Z- M9 i: d0 n+ M, r
alphaValue += 25;( G, D; G" @% N" U/ ]
} k* a4 J$ }; j. {0 @* @
mIndex++;
$ ]; R/ v2 N6 U' w8 h$ Q% l}
( I, w4 U0 n1 T; [% Y6 X9 f* N@Override
7 H$ D# s x3 ~, Hprotected void onSizeChanged(int w, int h, int ow, int oh) {
. @- u7 n: L2 s% Fsuper.onSizeChanged(w, h, ow, oh);
7 n6 W" a1 `0 r; Z' \0 D1 y2 m) gmX = w * 0.5f;
' ~1 v3 }1 o. f8 a8 J& B! TmY = h;
9 ` z! V4 b" f% a' hmMiddleY = h * 0.3f;
; m" K8 T! m: P# K0 X}
9 b4 L& X6 }# P3 m& n8 {' y@SuppressLint("SdCardPath")/ t. u8 [ F/ {/ ^: H1 [+ N0 e
private void init() throws IOException {5 |8 s: `" M% G
setFocusable(true);
* t1 Q1 T, M! z: b1 z# gLrcHandle lrcHandler = new LrcHandle();
" m5 z4 X# B9 |9 V6 [+ s- GlrcHandler.readLRC("/sdcard/陪我去流浪.lrc");% r! u" A0 \) `/ s
mWordsList = lrcHandler.getWords();
: N9 `7 ]1 B! P* n3 M& rmLoseFocusPaint = new Paint();4 Z. v5 l( ~' P5 _
mLoseFocusPaint.setAntiAlias(true);
7 w [9 O# b& |0 F" GmLoseFocusPaint.setTextSize(22);
0 F2 p, p+ E4 b$ Q1 emLoseFocusPaint.setColor(Color.WHITE);
# ~, g7 y2 U- g; w* q' i( u9 p+ UmLoseFocusPaint.setTypeface(Typeface.SERIF);
5 `- B1 e) y& x6 a$ g3 rmOnFocusePaint = new Paint();
; O, p; g+ I3 bmOnFocusePaint.setAntiAlias(true);: @1 B- `7 g G5 N z5 W
mOnFocusePaint.setColor(Color.YELLOW);
7 Q0 y" W) i" m/ TmOnFocusePaint.setTextSize(30);4 ]3 `5 F6 l3 C/ B
mOnFocusePaint.setTypeface(Typeface.SANS_SERIF);
& [7 L0 p2 w0 a1 T8 g! l- {}
- W, i0 M, f$ q) G' `: Y}" ~3 O# u; d6 M! X# C3 c- y$ |& {
* |; {" s0 @2 @
最主要的是覆盖TextView的onDraw()和onSizeChanged()。1 ^2 |) B+ b! u: T" j' \
|