Code refactor
This commit is contained in:
parent
3c7328f9c6
commit
10874744f3
28 changed files with 771 additions and 1205 deletions
|
@ -24,8 +24,8 @@ android {
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
applicationId "code.name.monkey.retromusic"
|
applicationId "code.name.monkey.retromusic"
|
||||||
versionCode 436
|
versionCode 437
|
||||||
versionName '3.5.300'
|
versionName '3.5.600'
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
|
||||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||||
import code.name.monkey.retromusic.lyrics.LrcHelper
|
import code.name.monkey.retromusic.lyrics.LrcHelper
|
||||||
import code.name.monkey.retromusic.lyrics.LrcView
|
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.model.lyrics.Lyrics
|
import code.name.monkey.retromusic.model.lyrics.Lyrics
|
||||||
import code.name.monkey.retromusic.util.LyricUtil
|
import code.name.monkey.retromusic.util.LyricUtil
|
||||||
|
@ -162,7 +161,6 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*val materialDialog = MaterialDialog(this)
|
/*val materialDialog = MaterialDialog(this)
|
||||||
.show {
|
.show {
|
||||||
title(R.string.add_time_framed_lryics)
|
title(R.string.add_time_framed_lryics)
|
||||||
|
@ -381,11 +379,7 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
setNoLrcTextColor(resolveColor(requireContext(), attr.textColorPrimary))
|
setNoLrcTextColor(resolveColor(requireContext(), attr.textColorPrimary))
|
||||||
setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener {
|
setOnPlayIndicatorLineListener { time, _ -> MusicPlayerRemote.seekTo(time.toInt()) }
|
||||||
override fun onPlay(time: Long, content: String) {
|
|
||||||
MusicPlayerRemote.seekTo(time.toInt())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,6 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote;
|
||||||
import code.name.monkey.retromusic.helper.menu.SongMenuHelper;
|
import code.name.monkey.retromusic.helper.menu.SongMenuHelper;
|
||||||
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper;
|
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper;
|
||||||
import code.name.monkey.retromusic.interfaces.CabHolder;
|
import code.name.monkey.retromusic.interfaces.CabHolder;
|
||||||
import code.name.monkey.retromusic.interfaces.LoaderIds;
|
|
||||||
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks;
|
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks;
|
||||||
import code.name.monkey.retromusic.misc.DialogAsyncTask;
|
import code.name.monkey.retromusic.misc.DialogAsyncTask;
|
||||||
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
|
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
|
||||||
|
@ -87,7 +86,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
|
||||||
FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton()));
|
FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton()));
|
||||||
private static final String PATH = "path";
|
private static final String PATH = "path";
|
||||||
private static final String CRUMBS = "crumbs";
|
private static final String CRUMBS = "crumbs";
|
||||||
private static final int LOADER_ID = LoaderIds.Companion.getFOLDERS_FRAGMENT();
|
private static final int LOADER_ID = 5;
|
||||||
private SongFileAdapter adapter;
|
private SongFileAdapter adapter;
|
||||||
private BreadCrumbLayout breadCrumbs;
|
private BreadCrumbLayout breadCrumbs;
|
||||||
private MaterialCab cab;
|
private MaterialCab cab;
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package code.name.monkey.retromusic.helper
|
|
||||||
|
|
||||||
import android.media.audiofx.BassBoost
|
|
||||||
import android.media.audiofx.Equalizer
|
|
||||||
import android.media.audiofx.Virtualizer
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
import code.name.monkey.retromusic.interfaces.EqualizerInterface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Hemanth S (h4h13).
|
|
||||||
*/
|
|
||||||
|
|
||||||
class EqualizerHelper private constructor() : EqualizerInterface {
|
|
||||||
override val equalizer: Equalizer
|
|
||||||
override val bassBoost: BassBoost
|
|
||||||
override val virtualizer: Virtualizer
|
|
||||||
|
|
||||||
override val bandLevelHigh: Int
|
|
||||||
override val bandLevelLow: Int
|
|
||||||
override var isRunning = false
|
|
||||||
|
|
||||||
override val numberOfBands: Int
|
|
||||||
get() = equalizer.numberOfBands.toInt()
|
|
||||||
|
|
||||||
override var isBassBoostEnabled: Boolean
|
|
||||||
get() = bassBoost.enabled
|
|
||||||
set(isEnabled) {
|
|
||||||
bassBoost.enabled = isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
override var bassBoostStrength: Int
|
|
||||||
get() = bassBoost.roundedStrength.toInt()
|
|
||||||
set(strength) = bassBoost.setStrength(strength.toShort())
|
|
||||||
|
|
||||||
override var isVirtualizerEnabled: Boolean
|
|
||||||
get() = virtualizer.enabled
|
|
||||||
set(isEnabled) {
|
|
||||||
virtualizer.enabled = isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
override var virtualizerStrength: Int
|
|
||||||
get() = virtualizer.roundedStrength.toInt()
|
|
||||||
set(strength) = virtualizer.setStrength(strength.toShort())
|
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
//Prevent form the reflection api.
|
|
||||||
if (ourInstance != null) {
|
|
||||||
throw RuntimeException("Use getInstance() method to get the single instance of this class.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val i = MusicPlayerRemote.audioSessionId
|
|
||||||
|
|
||||||
equalizer = Equalizer(100, i)
|
|
||||||
|
|
||||||
equalizer.enabled = true
|
|
||||||
bassBoost = BassBoost(100, i)
|
|
||||||
virtualizer = Virtualizer(100, i)
|
|
||||||
|
|
||||||
bandLevelHigh = equalizer.bandLevelRange[1].toInt()
|
|
||||||
bandLevelLow = equalizer.bandLevelRange[0].toInt()
|
|
||||||
|
|
||||||
Log.i(TAG, "onCreate: $bandLevelHigh $bandLevelLow")
|
|
||||||
isRunning = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Make singleton from serialize and deserialize operation.
|
|
||||||
protected fun readResolve(): EqualizerHelper? {
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCenterFreq(band: Int): Int {
|
|
||||||
return equalizer.getCenterFreq(band.toShort())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun getBandLevel(band: Int): Int {
|
|
||||||
return equalizer.getBandLevel(band.toShort()).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setBandLevel(band: Int, level: Int) {
|
|
||||||
equalizer.setBandLevel(band.toShort(), level.toShort())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "EqualizerHelper"
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var ourInstance: EqualizerHelper? = null
|
|
||||||
|
|
||||||
//Double check locking pattern
|
|
||||||
//Check for the first time
|
|
||||||
//Check for the second time.
|
|
||||||
//if there is no instance available... create new one
|
|
||||||
val instance: EqualizerHelper?
|
|
||||||
get() {
|
|
||||||
if (ourInstance == null) {
|
|
||||||
|
|
||||||
synchronized(EqualizerHelper::class.java) {
|
|
||||||
if (ourInstance == null) {
|
|
||||||
ourInstance = EqualizerHelper()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ourInstance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package code.name.monkey.retromusic.helper;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import code.name.monkey.retromusic.model.Playlist;
|
|
||||||
import code.name.monkey.retromusic.model.Song;
|
|
||||||
|
|
||||||
public class M3UWriter implements M3UConstants {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static File write(@NonNull Context context,
|
|
||||||
@NonNull File dir,
|
|
||||||
@NonNull Playlist playlist) throws IOException {
|
|
||||||
if (!dir.exists()) //noinspection ResultOfMethodCallIgnored
|
|
||||||
dir.mkdirs();
|
|
||||||
File file = new File(dir, playlist.name.concat("." + EXTENSION));
|
|
||||||
|
|
||||||
ArrayList<Song> songs = playlist.getSongs(context);
|
|
||||||
|
|
||||||
if (songs.size() > 0) {
|
|
||||||
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
|
|
||||||
|
|
||||||
bw.write(HEADER);
|
|
||||||
for (Song song : songs) {
|
|
||||||
bw.newLine();
|
|
||||||
bw.write(ENTRY + song.getDuration() + DURATION_SEPARATOR + song.getArtistName() + " - " + song.getTitle());
|
|
||||||
bw.newLine();
|
|
||||||
bw.write(song.getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
bw.close();
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
package code.name.monkey.retromusic.helper
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import code.name.monkey.retromusic.model.Playlist
|
||||||
|
import java.io.BufferedWriter
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
object M3UWriter : M3UConstants {
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun write(
|
||||||
|
context: Context,
|
||||||
|
dir: File,
|
||||||
|
playlist: Playlist
|
||||||
|
): File? {
|
||||||
|
if (!dir.exists()) dir.mkdirs()
|
||||||
|
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
|
||||||
|
val songs = playlist.getSongs(context)
|
||||||
|
if (songs.size > 0) {
|
||||||
|
val bw = BufferedWriter(FileWriter(file))
|
||||||
|
bw.write(M3UConstants.HEADER)
|
||||||
|
for (song in songs) {
|
||||||
|
bw.newLine()
|
||||||
|
bw.write(M3UConstants.ENTRY + song.duration + M3UConstants.DURATION_SEPARATOR + song.artistName + " - " + song.title)
|
||||||
|
bw.newLine()
|
||||||
|
bw.write(song.data)
|
||||||
|
}
|
||||||
|
bw.close()
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package code.name.monkey.retromusic.interfaces
|
|
||||||
|
|
||||||
import android.media.audiofx.BassBoost
|
|
||||||
import android.media.audiofx.Equalizer
|
|
||||||
import android.media.audiofx.Virtualizer
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Hemanth S (h4h13).
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface EqualizerInterface {
|
|
||||||
val bandLevelLow: Int
|
|
||||||
|
|
||||||
val bandLevelHigh: Int
|
|
||||||
|
|
||||||
val numberOfBands: Int
|
|
||||||
|
|
||||||
var isBassBoostEnabled: Boolean
|
|
||||||
|
|
||||||
var bassBoostStrength: Int
|
|
||||||
|
|
||||||
var isVirtualizerEnabled: Boolean
|
|
||||||
|
|
||||||
var virtualizerStrength: Int
|
|
||||||
|
|
||||||
val isRunning: Boolean
|
|
||||||
|
|
||||||
val equalizer: Equalizer
|
|
||||||
|
|
||||||
val bassBoost: BassBoost
|
|
||||||
|
|
||||||
val virtualizer: Virtualizer
|
|
||||||
|
|
||||||
fun getCenterFreq(band: Int): Int
|
|
||||||
|
|
||||||
fun getBandLevel(band: Int): Int
|
|
||||||
|
|
||||||
fun setBandLevel(band: Int, level: Int)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package code.name.monkey.retromusic.interfaces
|
|
||||||
|
|
||||||
|
|
||||||
interface LoaderIds {
|
|
||||||
companion object {
|
|
||||||
val FOLDERS_FRAGMENT = 5
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,13 +14,9 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.interfaces
|
package code.name.monkey.retromusic.interfaces
|
||||||
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Aidan Follestad (afollestad)
|
* @author Aidan Follestad (afollestad)
|
||||||
*/
|
*/
|
||||||
interface PaletteColorHolder {
|
interface PaletteColorHolder {
|
||||||
|
|
||||||
@get:ColorInt
|
|
||||||
val paletteColor: Int
|
val paletteColor: Int
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,3 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package code.name.monkey.retromusic.lyrics;
|
package code.name.monkey.retromusic.lyrics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,20 +11,20 @@ public class Lrc {
|
||||||
private long time;
|
private long time;
|
||||||
private String text;
|
private String text;
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(long time) {
|
public void setTime(long time) {
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setText(String text) {
|
public void setText(String text) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,17 +1,3 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package code.name.monkey.retromusic.lyrics;
|
package code.name.monkey.retromusic.lyrics;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -42,8 +28,8 @@ public class LrcHelper {
|
||||||
|
|
||||||
private static final String CHARSET = "utf-8";
|
private static final String CHARSET = "utf-8";
|
||||||
//[03:56.00][03:18.00][02:06.00][01:07.00]原谅我这一生不羁放纵爱自由
|
//[03:56.00][03:18.00][02:06.00][01:07.00]原谅我这一生不羁放纵爱自由
|
||||||
private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2,3}])+)(.*)";
|
private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2}])+)(.*)";
|
||||||
private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2,3})]";
|
private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2})]";
|
||||||
|
|
||||||
public static List<Lrc> parseLrcFromAssets(Context context, String fileName) {
|
public static List<Lrc> parseLrcFromAssets(Context context, String fileName) {
|
||||||
try {
|
try {
|
||||||
|
@ -127,14 +113,8 @@ public class LrcHelper {
|
||||||
String mil = timeMatcher.group(3);
|
String mil = timeMatcher.group(3);
|
||||||
Lrc lrc = new Lrc();
|
Lrc lrc = new Lrc();
|
||||||
if (content != null && content.length() != 0) {
|
if (content != null && content.length() != 0) {
|
||||||
if (Integer.parseInt(mil) < 100) {
|
lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000
|
||||||
lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000
|
+ Long.parseLong(mil) * 10);
|
||||||
+ Long.parseLong(mil) * 10);
|
|
||||||
} else {
|
|
||||||
lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000
|
|
||||||
+ Long.parseLong(mil)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
lrc.setText(content);
|
lrc.setText(content);
|
||||||
lrcs.add(lrc);
|
lrcs.add(lrc);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,663 @@
|
||||||
|
package code.name.monkey.retromusic.lyrics;
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.StaticLayout;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.widget.OverScroller;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desc : 歌词
|
||||||
|
* Author : Lauzy
|
||||||
|
* Date : 2017/10/13
|
||||||
|
* Blog : http://www.jianshu.com/u/e76853f863a9
|
||||||
|
* Email : freedompaladin@gmail.com
|
||||||
|
*/
|
||||||
|
public class LrcView extends View {
|
||||||
|
|
||||||
|
private static final String DEFAULT_CONTENT = "Empty";
|
||||||
|
private List<Lrc> mLrcData;
|
||||||
|
private TextPaint mTextPaint;
|
||||||
|
private String mDefaultContent;
|
||||||
|
private int mCurrentLine;
|
||||||
|
private float mOffset;
|
||||||
|
private float mLastMotionX;
|
||||||
|
private float mLastMotionY;
|
||||||
|
private int mScaledTouchSlop;
|
||||||
|
private OverScroller mOverScroller;
|
||||||
|
private VelocityTracker mVelocityTracker;
|
||||||
|
private int mMaximumFlingVelocity;
|
||||||
|
private int mMinimumFlingVelocity;
|
||||||
|
private float mLrcTextSize;
|
||||||
|
private float mLrcLineSpaceHeight;
|
||||||
|
private int mTouchDelay;
|
||||||
|
private int mNormalColor;
|
||||||
|
private int mCurrentPlayLineColor;
|
||||||
|
private float mNoLrcTextSize;
|
||||||
|
private int mNoLrcTextColor;
|
||||||
|
//是否拖拽中,否的话响应onClick事件
|
||||||
|
private boolean isDragging;
|
||||||
|
//用户开始操作
|
||||||
|
private boolean isUserScroll;
|
||||||
|
private boolean isAutoAdjustPosition = true;
|
||||||
|
private Drawable mPlayDrawable;
|
||||||
|
private boolean isShowTimeIndicator;
|
||||||
|
private Rect mPlayRect;
|
||||||
|
private Paint mIndicatorPaint;
|
||||||
|
private float mIndicatorLineWidth;
|
||||||
|
private float mIndicatorTextSize;
|
||||||
|
private int mCurrentIndicateLineTextColor;
|
||||||
|
private int mIndicatorLineColor;
|
||||||
|
private float mIndicatorMargin;
|
||||||
|
private float mIconLineGap;
|
||||||
|
private float mIconWidth;
|
||||||
|
private float mIconHeight;
|
||||||
|
private boolean isEnableShowIndicator = true;
|
||||||
|
private int mIndicatorTextColor;
|
||||||
|
private int mIndicatorTouchDelay;
|
||||||
|
private boolean isCurrentTextBold;
|
||||||
|
private boolean isLrcIndicatorTextBold;
|
||||||
|
private HashMap<String, StaticLayout> mLrcMap = new HashMap<>();
|
||||||
|
private Runnable mHideIndicatorRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
isShowTimeIndicator = false;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private HashMap<String, StaticLayout> mStaticLayoutHashMap = new HashMap<>();
|
||||||
|
private Runnable mScrollRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
isUserScroll = false;
|
||||||
|
scrollToPosition(mCurrentLine);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private OnPlayIndicatorLineListener mOnPlayIndicatorLineListener;
|
||||||
|
|
||||||
|
public LrcView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LrcView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs) {
|
||||||
|
|
||||||
|
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView);
|
||||||
|
mLrcTextSize = typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15));
|
||||||
|
mLrcLineSpaceHeight = typedArray.getDimension(R.styleable.LrcView_lrcLineSpaceSize, dp2px(context, 20));
|
||||||
|
mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500);
|
||||||
|
mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500);
|
||||||
|
mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY);
|
||||||
|
mCurrentPlayLineColor = typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE);
|
||||||
|
mNoLrcTextSize = typedArray.getDimension(R.styleable.LrcView_noLrcTextSize, dp2px(context, 20));
|
||||||
|
mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK);
|
||||||
|
mIndicatorLineWidth = typedArray.getDimension(R.styleable.LrcView_indicatorLineHeight, dp2px(context, 0.5f));
|
||||||
|
mIndicatorTextSize = typedArray.getDimension(R.styleable.LrcView_indicatorTextSize, sp2px(context, 13));
|
||||||
|
mIndicatorTextColor = typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY);
|
||||||
|
mCurrentIndicateLineTextColor = typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY);
|
||||||
|
mIndicatorLineColor = typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY);
|
||||||
|
mIndicatorMargin = typedArray.getDimension(R.styleable.LrcView_indicatorStartEndMargin, dp2px(context, 5));
|
||||||
|
mIconLineGap = typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3));
|
||||||
|
mIconWidth = typedArray.getDimension(R.styleable.LrcView_playIconWidth, dp2px(context, 20));
|
||||||
|
mIconHeight = typedArray.getDimension(R.styleable.LrcView_playIconHeight, dp2px(context, 20));
|
||||||
|
mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon);
|
||||||
|
mPlayDrawable = mPlayDrawable == null ? ContextCompat.getDrawable(context, R.drawable.ic_play_arrow_white_24dp) : mPlayDrawable;
|
||||||
|
isCurrentTextBold = typedArray.getBoolean(R.styleable.LrcView_isLrcCurrentTextBold, false);
|
||||||
|
isLrcIndicatorTextBold = typedArray.getBoolean(R.styleable.LrcView_isLrcIndicatorTextBold, false);
|
||||||
|
typedArray.recycle();
|
||||||
|
|
||||||
|
setupConfigs(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupConfigs(Context context) {
|
||||||
|
mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||||
|
mMaximumFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
|
||||||
|
mMinimumFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
|
||||||
|
mOverScroller = new OverScroller(context, new DecelerateInterpolator());
|
||||||
|
mOverScroller.setFriction(0.1f);
|
||||||
|
// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f
|
||||||
|
|
||||||
|
mTextPaint = new TextPaint();
|
||||||
|
mTextPaint.setAntiAlias(true);
|
||||||
|
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
mTextPaint.setTextSize(mLrcTextSize);
|
||||||
|
mDefaultContent = DEFAULT_CONTENT;
|
||||||
|
|
||||||
|
mIndicatorPaint = new Paint();
|
||||||
|
mIndicatorPaint.setAntiAlias(true);
|
||||||
|
mIndicatorPaint.setStrokeWidth(mIndicatorLineWidth);
|
||||||
|
mIndicatorPaint.setColor(mIndicatorLineColor);
|
||||||
|
mPlayRect = new Rect();
|
||||||
|
mIndicatorPaint.setTextSize(mIndicatorTextSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
super.onLayout(changed, left, top, right, bottom);
|
||||||
|
if (changed) {
|
||||||
|
mPlayRect.left = (int) mIndicatorMargin;
|
||||||
|
mPlayRect.top = (int) (getHeight() / 2 - mIconHeight / 2);
|
||||||
|
mPlayRect.right = (int) (mPlayRect.left + mIconWidth);
|
||||||
|
mPlayRect.bottom = (int) (mPlayRect.top + mIconHeight);
|
||||||
|
mPlayDrawable.setBounds(mPlayRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLrcWidth() {
|
||||||
|
return getWidth() - getPaddingLeft() - getPaddingRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLrcHeight() {
|
||||||
|
return getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLrcEmpty() {
|
||||||
|
return mLrcData == null || getLrcCount() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLrcCount() {
|
||||||
|
return mLrcData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLrcData(List<Lrc> lrcData) {
|
||||||
|
resetView(DEFAULT_CONTENT);
|
||||||
|
mLrcData = lrcData;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
if (isLrcEmpty()) {
|
||||||
|
drawEmptyText(canvas);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int indicatePosition = getIndicatePosition();
|
||||||
|
mTextPaint.setTextSize(mLrcTextSize);
|
||||||
|
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
float y = getLrcHeight() / 2f;
|
||||||
|
float x = getLrcWidth() / 2f + getPaddingLeft();
|
||||||
|
for (int i = 0; i < getLrcCount(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
y += (getTextHeight(i - 1) + getTextHeight(i)) / 2f + mLrcLineSpaceHeight;
|
||||||
|
}
|
||||||
|
if (mCurrentLine == i) {
|
||||||
|
mTextPaint.setColor(mCurrentPlayLineColor);
|
||||||
|
mTextPaint.setFakeBoldText(isCurrentTextBold);
|
||||||
|
} else if (indicatePosition == i && isShowTimeIndicator) {
|
||||||
|
mTextPaint.setFakeBoldText(isLrcIndicatorTextBold);
|
||||||
|
mTextPaint.setColor(mCurrentIndicateLineTextColor);
|
||||||
|
} else {
|
||||||
|
mTextPaint.setFakeBoldText(false);
|
||||||
|
mTextPaint.setColor(mNormalColor);
|
||||||
|
}
|
||||||
|
drawLrc(canvas, x, y, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowTimeIndicator) {
|
||||||
|
mPlayDrawable.draw(canvas);
|
||||||
|
long time = mLrcData.get(indicatePosition).getTime();
|
||||||
|
float timeWidth = mIndicatorPaint.measureText(LrcHelper.formatTime(time));
|
||||||
|
mIndicatorPaint.setColor(mIndicatorLineColor);
|
||||||
|
canvas.drawLine(mPlayRect.right + mIconLineGap, getHeight() / 2f,
|
||||||
|
getWidth() - timeWidth * 1.3f, getHeight() / 2f, mIndicatorPaint);
|
||||||
|
int baseX = (int) (getWidth() - timeWidth * 1.1f);
|
||||||
|
float baseline = getHeight() / 2f - (mIndicatorPaint.descent() - mIndicatorPaint.ascent()) / 2 - mIndicatorPaint.ascent();
|
||||||
|
mIndicatorPaint.setColor(mIndicatorTextColor);
|
||||||
|
canvas.drawText(LrcHelper.formatTime(time), baseX, baseline, mIndicatorPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawLrc(Canvas canvas, float x, float y, int i) {
|
||||||
|
String text = mLrcData.get(i).getText();
|
||||||
|
StaticLayout staticLayout = mLrcMap.get(text);
|
||||||
|
if (staticLayout == null) {
|
||||||
|
mTextPaint.setTextSize(mLrcTextSize);
|
||||||
|
staticLayout = new StaticLayout(text, mTextPaint, getLrcWidth(),
|
||||||
|
Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
|
||||||
|
mLrcMap.put(text, staticLayout);
|
||||||
|
}
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(x, y - staticLayout.getHeight() / 2f - mOffset);
|
||||||
|
staticLayout.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
//中间空文字
|
||||||
|
private void drawEmptyText(Canvas canvas) {
|
||||||
|
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
mTextPaint.setColor(mNoLrcTextColor);
|
||||||
|
mTextPaint.setTextSize(mNoLrcTextSize);
|
||||||
|
canvas.save();
|
||||||
|
StaticLayout staticLayout = new StaticLayout(mDefaultContent, mTextPaint,
|
||||||
|
getLrcWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
|
||||||
|
canvas.translate(getLrcWidth() / 2f + getPaddingLeft(), getLrcHeight() / 2f);
|
||||||
|
staticLayout.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTime(long time) {
|
||||||
|
if (isLrcEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int linePosition = getUpdateTimeLinePosition(time);
|
||||||
|
if (mCurrentLine != linePosition) {
|
||||||
|
mCurrentLine = linePosition;
|
||||||
|
if (isUserScroll) {
|
||||||
|
invalidateView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ViewCompat.postOnAnimation(LrcView.this, mScrollRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getUpdateTimeLinePosition(long time) {
|
||||||
|
int linePos = 0;
|
||||||
|
for (int i = 0; i < getLrcCount(); i++) {
|
||||||
|
Lrc lrc = mLrcData.get(i);
|
||||||
|
if (time >= lrc.getTime()) {
|
||||||
|
if (i == getLrcCount() - 1) {
|
||||||
|
linePos = getLrcCount() - 1;
|
||||||
|
} else if (time < mLrcData.get(i + 1).getTime()) {
|
||||||
|
linePos = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return linePos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scrollToPosition(int linePosition) {
|
||||||
|
float scrollY = getItemOffsetY(linePosition);
|
||||||
|
final ValueAnimator animator = ValueAnimator.ofFloat(mOffset, scrollY);
|
||||||
|
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
mOffset = (float) animation.getAnimatedValue();
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animator.setDuration(300);
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndicatePosition() {
|
||||||
|
int pos = 0;
|
||||||
|
float min = Float.MAX_VALUE;
|
||||||
|
//itemOffset 和 mOffset 最小即当前位置
|
||||||
|
for (int i = 0; i < mLrcData.size(); i++) {
|
||||||
|
float offsetY = getItemOffsetY(i);
|
||||||
|
float abs = Math.abs(offsetY - mOffset);
|
||||||
|
if (abs < min) {
|
||||||
|
min = abs;
|
||||||
|
pos = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getItemOffsetY(int linePosition) {
|
||||||
|
float tempY = 0;
|
||||||
|
for (int i = 1; i <= linePosition; i++) {
|
||||||
|
tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight;
|
||||||
|
}
|
||||||
|
return tempY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getTextHeight(int linePosition) {
|
||||||
|
String text = mLrcData.get(linePosition).getText();
|
||||||
|
StaticLayout staticLayout = mStaticLayoutHashMap.get(text);
|
||||||
|
if (staticLayout == null) {
|
||||||
|
mTextPaint.setTextSize(mLrcTextSize);
|
||||||
|
staticLayout = new StaticLayout(text, mTextPaint,
|
||||||
|
getLrcWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
|
||||||
|
mStaticLayoutHashMap.put(text, staticLayout);
|
||||||
|
}
|
||||||
|
return staticLayout.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean overScrolled() {
|
||||||
|
return mOffset > getItemOffsetY(getLrcCount() - 1) || mOffset < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if (isLrcEmpty()) {
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
if (mVelocityTracker == null) {
|
||||||
|
mVelocityTracker = VelocityTracker.obtain();
|
||||||
|
}
|
||||||
|
mVelocityTracker.addMovement(event);
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
removeCallbacks(mScrollRunnable);
|
||||||
|
removeCallbacks(mHideIndicatorRunnable);
|
||||||
|
if (!mOverScroller.isFinished()) {
|
||||||
|
mOverScroller.abortAnimation();
|
||||||
|
}
|
||||||
|
mLastMotionX = event.getX();
|
||||||
|
mLastMotionY = event.getY();
|
||||||
|
isUserScroll = true;
|
||||||
|
isDragging = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
float moveY = event.getY() - mLastMotionY;
|
||||||
|
if (Math.abs(moveY) > mScaledTouchSlop) {
|
||||||
|
isDragging = true;
|
||||||
|
isShowTimeIndicator = isEnableShowIndicator;
|
||||||
|
}
|
||||||
|
if (isDragging) {
|
||||||
|
|
||||||
|
// if (mOffset < 0) {
|
||||||
|
// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight);
|
||||||
|
// }
|
||||||
|
float maxHeight = getItemOffsetY(getLrcCount() - 1);
|
||||||
|
// if (mOffset > maxHeight) {
|
||||||
|
// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight);
|
||||||
|
// }
|
||||||
|
if (mOffset < 0 || mOffset > maxHeight) {
|
||||||
|
moveY /= 3.5f;
|
||||||
|
}
|
||||||
|
mOffset -= moveY;
|
||||||
|
mLastMotionY = event.getY();
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) {
|
||||||
|
isShowTimeIndicator = false;
|
||||||
|
invalidateView();
|
||||||
|
performClick();
|
||||||
|
}
|
||||||
|
handleActionUp(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// return isDragging || super.onTouchEvent(event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleActionUp(MotionEvent event) {
|
||||||
|
if (isEnableShowIndicator) {
|
||||||
|
ViewCompat.postOnAnimationDelayed(LrcView.this, mHideIndicatorRunnable, mIndicatorTouchDelay);
|
||||||
|
}
|
||||||
|
if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {
|
||||||
|
isShowTimeIndicator = false;
|
||||||
|
invalidateView();
|
||||||
|
if (mOnPlayIndicatorLineListener != null) {
|
||||||
|
mOnPlayIndicatorLineListener.onPlay(mLrcData.get(getIndicatePosition()).getTime(),
|
||||||
|
mLrcData.get(getIndicatePosition()).getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (overScrolled() && mOffset < 0) {
|
||||||
|
scrollToPosition(0);
|
||||||
|
if (isAutoAdjustPosition) {
|
||||||
|
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overScrolled() && mOffset > getItemOffsetY(getLrcCount() - 1)) {
|
||||||
|
scrollToPosition(getLrcCount() - 1);
|
||||||
|
if (isAutoAdjustPosition) {
|
||||||
|
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
|
||||||
|
float yVelocity = mVelocityTracker.getYVelocity();
|
||||||
|
float absYVelocity = Math.abs(yVelocity);
|
||||||
|
if (absYVelocity > mMinimumFlingVelocity) {
|
||||||
|
mOverScroller.fling(0, (int) mOffset, 0, (int) (-yVelocity), 0,
|
||||||
|
0, 0, (int) getItemOffsetY(getLrcCount() - 1),
|
||||||
|
0, (int) getTextHeight(0));
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
releaseVelocityTracker();
|
||||||
|
if (isAutoAdjustPosition) {
|
||||||
|
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onClickPlayButton(MotionEvent event) {
|
||||||
|
float left = mPlayRect.left;
|
||||||
|
float right = mPlayRect.right;
|
||||||
|
float top = mPlayRect.top;
|
||||||
|
float bottom = mPlayRect.bottom;
|
||||||
|
float x = event.getX();
|
||||||
|
float y = event.getY();
|
||||||
|
return mLastMotionX > left && mLastMotionX < right && mLastMotionY > top
|
||||||
|
&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void computeScroll() {
|
||||||
|
super.computeScroll();
|
||||||
|
if (mOverScroller.computeScrollOffset()) {
|
||||||
|
mOffset = mOverScroller.getCurrY();
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseVelocityTracker() {
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.clear();
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetView(String defaultContent) {
|
||||||
|
if (mLrcData != null) {
|
||||||
|
mLrcData.clear();
|
||||||
|
}
|
||||||
|
mLrcMap.clear();
|
||||||
|
mStaticLayoutHashMap.clear();
|
||||||
|
mCurrentLine = 0;
|
||||||
|
mOffset = 0;
|
||||||
|
isUserScroll = false;
|
||||||
|
isDragging = false;
|
||||||
|
mDefaultContent = defaultContent;
|
||||||
|
removeCallbacks(mScrollRunnable);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean performClick() {
|
||||||
|
return super.performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int dp2px(Context context, float dpVal) {
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
dpVal, context.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sp2px(Context context, float spVal) {
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
|
||||||
|
spVal, context.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停(手动滑动歌词后,不再自动回滚至当前播放位置)
|
||||||
|
*/
|
||||||
|
public void pause() {
|
||||||
|
isAutoAdjustPosition = false;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复(继续自动回滚)
|
||||||
|
*/
|
||||||
|
public void resume() {
|
||||||
|
isAutoAdjustPosition = true;
|
||||||
|
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------Config-------------------*/
|
||||||
|
|
||||||
|
private void invalidateView() {
|
||||||
|
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
|
||||||
|
invalidate();
|
||||||
|
} else {
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPlayIndicatorLineListener(OnPlayIndicatorLineListener onPlayIndicatorLineListener) {
|
||||||
|
mOnPlayIndicatorLineListener = onPlayIndicatorLineListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmptyContent(String defaultContent) {
|
||||||
|
mDefaultContent = defaultContent;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLrcTextSize(float lrcTextSize) {
|
||||||
|
mLrcTextSize = lrcTextSize;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLrcLineSpaceHeight(float lrcLineSpaceHeight) {
|
||||||
|
mLrcLineSpaceHeight = lrcLineSpaceHeight;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTouchDelay(int touchDelay) {
|
||||||
|
mTouchDelay = touchDelay;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNormalColor(@ColorInt int normalColor) {
|
||||||
|
mNormalColor = normalColor;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentPlayLineColor(@ColorInt int currentPlayLineColor) {
|
||||||
|
mCurrentPlayLineColor = currentPlayLineColor;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoLrcTextSize(float noLrcTextSize) {
|
||||||
|
mNoLrcTextSize = noLrcTextSize;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoLrcTextColor(@ColorInt int noLrcTextColor) {
|
||||||
|
mNoLrcTextColor = noLrcTextColor;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndicatorLineWidth(float indicatorLineWidth) {
|
||||||
|
mIndicatorLineWidth = indicatorLineWidth;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndicatorTextSize(float indicatorTextSize) {
|
||||||
|
// mIndicatorTextSize = indicatorTextSize;
|
||||||
|
mIndicatorPaint.setTextSize(indicatorTextSize);
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentIndicateLineTextColor(int currentIndicateLineTextColor) {
|
||||||
|
mCurrentIndicateLineTextColor = currentIndicateLineTextColor;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndicatorLineColor(int indicatorLineColor) {
|
||||||
|
mIndicatorLineColor = indicatorLineColor;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndicatorMargin(float indicatorMargin) {
|
||||||
|
mIndicatorMargin = indicatorMargin;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconLineGap(float iconLineGap) {
|
||||||
|
mIconLineGap = iconLineGap;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconWidth(float iconWidth) {
|
||||||
|
mIconWidth = iconWidth;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconHeight(float iconHeight) {
|
||||||
|
mIconHeight = iconHeight;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableShowIndicator(boolean enableShowIndicator) {
|
||||||
|
isEnableShowIndicator = enableShowIndicator;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getPlayDrawable() {
|
||||||
|
return mPlayDrawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayDrawable(Drawable playDrawable) {
|
||||||
|
mPlayDrawable = playDrawable;
|
||||||
|
mPlayDrawable.setBounds(mPlayRect);
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndicatorTextColor(int indicatorTextColor) {
|
||||||
|
mIndicatorTextColor = indicatorTextColor;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLrcCurrentTextBold(boolean bold) {
|
||||||
|
isCurrentTextBold = bold;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLrcIndicatorTextBold(boolean bold) {
|
||||||
|
isLrcIndicatorTextBold = bold;
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnPlayIndicatorLineListener {
|
||||||
|
void onPlay(long time, String content);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,713 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Hemanth Savarala.
|
|
||||||
*
|
|
||||||
* Licensed under the GNU General Public License v3
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
package code.name.monkey.retromusic.lyrics
|
|
||||||
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Looper
|
|
||||||
import android.text.Layout
|
|
||||||
import android.text.StaticLayout
|
|
||||||
import android.text.TextPaint
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.VelocityTracker
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewConfiguration
|
|
||||||
import android.view.animation.DecelerateInterpolator
|
|
||||||
import android.widget.OverScroller
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import code.name.monkey.retromusic.BuildConfig
|
|
||||||
import code.name.monkey.retromusic.R
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Desc : 歌词
|
|
||||||
* Author : Lauzy
|
|
||||||
* Date : 2017/10/13
|
|
||||||
* Blog : http://www.jianshu.com/u/e76853f863a9
|
|
||||||
* Email : freedompaladin@gmail.com
|
|
||||||
*/
|
|
||||||
class LrcView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0
|
|
||||||
) : View(context, attrs, defStyleAttr) {
|
|
||||||
private var mLrcData: MutableList<Lrc>? = null
|
|
||||||
private var mTextPaint: TextPaint? = null
|
|
||||||
private var mDefaultContent: String? = null
|
|
||||||
private var mCurrentLine: Int = 0
|
|
||||||
private var mOffset: Float = 0.toFloat()
|
|
||||||
private var mLastMotionX: Float = 0.toFloat()
|
|
||||||
private var mLastMotionY: Float = 0.toFloat()
|
|
||||||
private var mScaledTouchSlop: Int = 0
|
|
||||||
private var mOverScroller: OverScroller? = null
|
|
||||||
private var mVelocityTracker: VelocityTracker? = null
|
|
||||||
private var mMaximumFlingVelocity: Int = 0
|
|
||||||
private var mMinimumFlingVelocity: Int = 0
|
|
||||||
private var mLrcTextSize: Float = 0.toFloat()
|
|
||||||
private var mLrcLineSpaceHeight: Float = 0.toFloat()
|
|
||||||
private var mTouchDelay: Int = 0
|
|
||||||
private var mNormalColor: Int = 0
|
|
||||||
private var mCurrentPlayLineColor: Int = 0
|
|
||||||
private var mNoLrcTextSize: Float = 0.toFloat()
|
|
||||||
private var mNoLrcTextColor: Int = 0
|
|
||||||
|
|
||||||
//是否拖拽中,否的话响应onClick事件
|
|
||||||
private var isDragging: Boolean = false
|
|
||||||
|
|
||||||
//用户开始操作
|
|
||||||
private var isUserScroll: Boolean = false
|
|
||||||
private var isAutoAdjustPosition = true
|
|
||||||
private var mPlayDrawable: Drawable? = null
|
|
||||||
private var isShowTimeIndicator: Boolean = false
|
|
||||||
private var mPlayRect: Rect? = null
|
|
||||||
private var mIndicatorPaint: Paint? = null
|
|
||||||
private var mIndicatorLineWidth: Float = 0.toFloat()
|
|
||||||
private var mIndicatorTextSize: Float = 0.toFloat()
|
|
||||||
private var mCurrentIndicateLineTextColor: Int = 0
|
|
||||||
private var mIndicatorLineColor: Int = 0
|
|
||||||
private var mIndicatorMargin: Float = 0.toFloat()
|
|
||||||
private var mIconLineGap: Float = 0.toFloat()
|
|
||||||
private var mIconWidth: Float = 0.toFloat()
|
|
||||||
private var mIconHeight: Float = 0.toFloat()
|
|
||||||
private var isEnableShowIndicator = true
|
|
||||||
private var mIndicatorTextColor: Int = 0
|
|
||||||
private var mIndicatorTouchDelay: Int = 0
|
|
||||||
private val mLrcMap = HashMap<String, StaticLayout>()
|
|
||||||
private val mHideIndicatorRunnable = Runnable {
|
|
||||||
isShowTimeIndicator = false
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
private val mStaticLayoutHashMap = HashMap<String, StaticLayout>()
|
|
||||||
private val mScrollRunnable = Runnable {
|
|
||||||
isUserScroll = false
|
|
||||||
scrollToPosition(mCurrentLine)
|
|
||||||
}
|
|
||||||
private var mOnPlayIndicatorLineListener: OnPlayIndicatorLineListener? = null
|
|
||||||
|
|
||||||
private val lrcWidth: Int
|
|
||||||
get() = width - paddingLeft - paddingRight
|
|
||||||
|
|
||||||
private val lrcHeight: Int
|
|
||||||
get() = height
|
|
||||||
|
|
||||||
private val isLrcEmpty: Boolean
|
|
||||||
get() = mLrcData == null || lrcCount == 0
|
|
||||||
|
|
||||||
private val lrcCount: Int
|
|
||||||
get() = mLrcData!!.size
|
|
||||||
|
|
||||||
//itemOffset 和 mOffset 最小即当前位置
|
|
||||||
val indicatePosition: Int
|
|
||||||
get() {
|
|
||||||
var pos = 0
|
|
||||||
var min = java.lang.Float.MAX_VALUE
|
|
||||||
for (i in mLrcData!!.indices) {
|
|
||||||
val offsetY = getItemOffsetY(i)
|
|
||||||
val abs = Math.abs(offsetY - mOffset)
|
|
||||||
if (abs < min) {
|
|
||||||
min = abs
|
|
||||||
pos = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
|
|
||||||
var playDrawable: Drawable?
|
|
||||||
get() = mPlayDrawable
|
|
||||||
set(playDrawable) {
|
|
||||||
mPlayDrawable = playDrawable
|
|
||||||
mPlayDrawable!!.bounds = mPlayRect!!
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
init(context, attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun init(context: Context, attrs: AttributeSet?) {
|
|
||||||
|
|
||||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView)
|
|
||||||
mLrcTextSize =
|
|
||||||
typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15f).toFloat())
|
|
||||||
mLrcLineSpaceHeight = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_lrcLineSpaceSize,
|
|
||||||
dp2px(context, 20f).toFloat()
|
|
||||||
)
|
|
||||||
mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500)
|
|
||||||
mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500)
|
|
||||||
mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY)
|
|
||||||
mCurrentPlayLineColor =
|
|
||||||
typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE)
|
|
||||||
mNoLrcTextSize = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_noLrcTextSize,
|
|
||||||
dp2px(context, 20f).toFloat()
|
|
||||||
)
|
|
||||||
mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK)
|
|
||||||
mIndicatorLineWidth = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_indicatorLineHeight,
|
|
||||||
dp2px(context, 0.5f).toFloat()
|
|
||||||
)
|
|
||||||
mIndicatorTextSize = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_indicatorTextSize,
|
|
||||||
sp2px(context, 13f).toFloat()
|
|
||||||
)
|
|
||||||
mIndicatorTextColor =
|
|
||||||
typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY)
|
|
||||||
mCurrentIndicateLineTextColor =
|
|
||||||
typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY)
|
|
||||||
mIndicatorLineColor =
|
|
||||||
typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY)
|
|
||||||
mIndicatorMargin = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_indicatorStartEndMargin,
|
|
||||||
dp2px(context, 5f).toFloat()
|
|
||||||
)
|
|
||||||
mIconLineGap =
|
|
||||||
typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3f).toFloat())
|
|
||||||
mIconWidth = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_playIconWidth,
|
|
||||||
dp2px(context, 20f).toFloat()
|
|
||||||
)
|
|
||||||
mIconHeight = typedArray.getDimension(
|
|
||||||
R.styleable.LrcView_playIconHeight,
|
|
||||||
dp2px(context, 20f).toFloat()
|
|
||||||
)
|
|
||||||
mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon)
|
|
||||||
mPlayDrawable = if (mPlayDrawable == null) ContextCompat.getDrawable(
|
|
||||||
context,
|
|
||||||
R.drawable.ic_play_arrow_white_24dp
|
|
||||||
) else mPlayDrawable
|
|
||||||
typedArray.recycle()
|
|
||||||
|
|
||||||
setupConfigs(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupConfigs(context: Context) {
|
|
||||||
mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
|
||||||
mMaximumFlingVelocity = ViewConfiguration.get(context).scaledMaximumFlingVelocity
|
|
||||||
mMinimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
|
|
||||||
mOverScroller = OverScroller(context, DecelerateInterpolator())
|
|
||||||
mOverScroller!!.setFriction(0.1f)
|
|
||||||
// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f
|
|
||||||
|
|
||||||
mTextPaint = TextPaint()
|
|
||||||
mTextPaint!!.apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
textAlign = Paint.Align.LEFT
|
|
||||||
textSize = mLrcTextSize
|
|
||||||
/* if (BuildConfig.FLAVOR != "nofont") {
|
|
||||||
typeface = ResourcesCompat.getFont(context, R.font.circular)
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
mDefaultContent = DEFAULT_CONTENT
|
|
||||||
|
|
||||||
mIndicatorPaint = Paint()
|
|
||||||
mIndicatorPaint!!.isAntiAlias = true
|
|
||||||
mIndicatorPaint!!.strokeWidth = mIndicatorLineWidth
|
|
||||||
mIndicatorPaint!!.color = mIndicatorLineColor
|
|
||||||
mPlayRect = Rect()
|
|
||||||
mIndicatorPaint!!.textSize = mIndicatorTextSize
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
||||||
super.onLayout(changed, left, top, right, bottom)
|
|
||||||
if (changed) {
|
|
||||||
mPlayRect!!.left = mIndicatorMargin.toInt()
|
|
||||||
mPlayRect!!.top = (height / 2 - mIconHeight / 2).toInt()
|
|
||||||
mPlayRect!!.right = (mPlayRect!!.left + mIconWidth).toInt()
|
|
||||||
mPlayRect!!.bottom = (mPlayRect!!.top + mIconHeight).toInt()
|
|
||||||
mPlayDrawable!!.bounds = mPlayRect!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setLrcData(lrcData: MutableList<Lrc>) {
|
|
||||||
resetView(DEFAULT_CONTENT)
|
|
||||||
mLrcData = lrcData
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
super.onDraw(canvas)
|
|
||||||
if (isLrcEmpty) {
|
|
||||||
drawEmptyText(canvas)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val indicatePosition = indicatePosition
|
|
||||||
mTextPaint!!.textSize = mLrcTextSize
|
|
||||||
mTextPaint!!.textAlign = Paint.Align.LEFT
|
|
||||||
var y = (lrcHeight / 2).toFloat()
|
|
||||||
val x = dip2px(context, 16f).toFloat()
|
|
||||||
for (i in 0 until lrcCount) {
|
|
||||||
if (i > 0) {
|
|
||||||
y += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
|
|
||||||
}
|
|
||||||
if (mCurrentLine == i) {
|
|
||||||
mTextPaint!!.color = mCurrentPlayLineColor
|
|
||||||
} else if (indicatePosition == i && isShowTimeIndicator) {
|
|
||||||
mTextPaint!!.color = mCurrentIndicateLineTextColor
|
|
||||||
} else {
|
|
||||||
mTextPaint!!.color = mNormalColor
|
|
||||||
}
|
|
||||||
drawLrc(canvas, x, y, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isShowTimeIndicator) {
|
|
||||||
mPlayDrawable!!.draw(canvas)
|
|
||||||
val time = mLrcData!![indicatePosition].time
|
|
||||||
val timeWidth = mIndicatorPaint!!.measureText(LrcHelper.formatTime(time))
|
|
||||||
mIndicatorPaint!!.color = mIndicatorLineColor
|
|
||||||
canvas.drawLine(
|
|
||||||
mPlayRect!!.right + mIconLineGap, (height / 2).toFloat(),
|
|
||||||
width - timeWidth * 1.3f, (height / 2).toFloat(), mIndicatorPaint!!
|
|
||||||
)
|
|
||||||
val baseX = (width - timeWidth * 1.1f).toInt()
|
|
||||||
val baseline =
|
|
||||||
(height / 2).toFloat() - (mIndicatorPaint!!.descent() - mIndicatorPaint!!.ascent()) / 2 - mIndicatorPaint!!.ascent()
|
|
||||||
mIndicatorPaint!!.color = mIndicatorTextColor
|
|
||||||
canvas.drawText(
|
|
||||||
LrcHelper.formatTime(time),
|
|
||||||
baseX.toFloat(),
|
|
||||||
baseline,
|
|
||||||
mIndicatorPaint!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dip2px(context: Context, dpVale: Float): Int {
|
|
||||||
val scale = context.resources.displayMetrics.density
|
|
||||||
return (dpVale * scale + 0.5f).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawLrc(canvas: Canvas, x: Float, y: Float, i: Int) {
|
|
||||||
val text = mLrcData!![i].text
|
|
||||||
var staticLayout: StaticLayout? = mLrcMap[text]
|
|
||||||
if (staticLayout == null) {
|
|
||||||
mTextPaint!!.textSize = mLrcTextSize
|
|
||||||
staticLayout = StaticLayout(
|
|
||||||
text,
|
|
||||||
mTextPaint,
|
|
||||||
lrcWidth,
|
|
||||||
Layout.Alignment.ALIGN_NORMAL,
|
|
||||||
1f,
|
|
||||||
0f,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
mLrcMap[text] = staticLayout
|
|
||||||
}
|
|
||||||
canvas.save()
|
|
||||||
canvas.translate(x, y - (staticLayout.height / 2).toFloat() - mOffset)
|
|
||||||
staticLayout.draw(canvas)
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
//中间空文字
|
|
||||||
private fun drawEmptyText(canvas: Canvas) {
|
|
||||||
mTextPaint!!.textAlign = Paint.Align.LEFT
|
|
||||||
mTextPaint!!.color = mNoLrcTextColor
|
|
||||||
mTextPaint!!.textSize = mNoLrcTextSize
|
|
||||||
canvas.save()
|
|
||||||
val staticLayout = StaticLayout(
|
|
||||||
mDefaultContent, mTextPaint,
|
|
||||||
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
|
|
||||||
)
|
|
||||||
val margin = dip2px(context, 16f).toFloat();
|
|
||||||
canvas.translate(margin, margin)
|
|
||||||
staticLayout.draw(canvas)
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTime(time: Long) {
|
|
||||||
if (isLrcEmpty) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val linePosition = getUpdateTimeLinePosition(time)
|
|
||||||
if (mCurrentLine != linePosition) {
|
|
||||||
mCurrentLine = linePosition
|
|
||||||
if (isUserScroll) {
|
|
||||||
invalidateView()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ViewCompat.postOnAnimation(this@LrcView, mScrollRunnable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getUpdateTimeLinePosition(time: Long): Int {
|
|
||||||
var linePos = 0
|
|
||||||
for (i in 0 until lrcCount) {
|
|
||||||
val lrc = mLrcData!![i]
|
|
||||||
if (time >= lrc.time) {
|
|
||||||
if (i == lrcCount - 1) {
|
|
||||||
linePos = lrcCount - 1
|
|
||||||
} else if (time < mLrcData!![i + 1].time) {
|
|
||||||
linePos = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return linePos
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scrollToPosition(linePosition: Int) {
|
|
||||||
val scrollY = getItemOffsetY(linePosition)
|
|
||||||
val animator = ValueAnimator.ofFloat(mOffset, scrollY)
|
|
||||||
animator.addUpdateListener { animation ->
|
|
||||||
mOffset = animation.animatedValue as Float
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
animator.duration = 300
|
|
||||||
animator.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getItemOffsetY(linePosition: Int): Float {
|
|
||||||
var tempY = 0f
|
|
||||||
for (i in 1..linePosition) {
|
|
||||||
tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
|
|
||||||
}
|
|
||||||
return tempY
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTextHeight(linePosition: Int): Float {
|
|
||||||
val text = mLrcData!![linePosition].text
|
|
||||||
var staticLayout: StaticLayout? = mStaticLayoutHashMap[text]
|
|
||||||
if (staticLayout == null) {
|
|
||||||
mTextPaint!!.textSize = mLrcTextSize
|
|
||||||
staticLayout = StaticLayout(
|
|
||||||
text, mTextPaint,
|
|
||||||
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
|
|
||||||
)
|
|
||||||
mStaticLayoutHashMap[text] = staticLayout
|
|
||||||
}
|
|
||||||
return staticLayout.height.toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun overScrolled(): Boolean {
|
|
||||||
return mOffset > getItemOffsetY(lrcCount - 1) || mOffset < 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
if (isLrcEmpty) {
|
|
||||||
return super.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
if (mVelocityTracker == null) {
|
|
||||||
mVelocityTracker = VelocityTracker.obtain()
|
|
||||||
}
|
|
||||||
mVelocityTracker!!.addMovement(event)
|
|
||||||
when (event.action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
removeCallbacks(mScrollRunnable)
|
|
||||||
removeCallbacks(mHideIndicatorRunnable)
|
|
||||||
if (!mOverScroller!!.isFinished) {
|
|
||||||
mOverScroller!!.abortAnimation()
|
|
||||||
}
|
|
||||||
mLastMotionX = event.x
|
|
||||||
mLastMotionY = event.y
|
|
||||||
isUserScroll = true
|
|
||||||
isDragging = false
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
var moveY = event.y - mLastMotionY
|
|
||||||
if (Math.abs(moveY) > mScaledTouchSlop) {
|
|
||||||
isDragging = true
|
|
||||||
isShowTimeIndicator = isEnableShowIndicator
|
|
||||||
}
|
|
||||||
if (isDragging) {
|
|
||||||
|
|
||||||
// if (mOffset < 0) {
|
|
||||||
// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight);
|
|
||||||
// }
|
|
||||||
val maxHeight = getItemOffsetY(lrcCount - 1)
|
|
||||||
// if (mOffset > maxHeight) {
|
|
||||||
// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight);
|
|
||||||
// }
|
|
||||||
if (mOffset < 0 || mOffset > maxHeight) {
|
|
||||||
moveY /= 3.5f
|
|
||||||
}
|
|
||||||
mOffset -= moveY
|
|
||||||
mLastMotionY = event.y
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
|
|
||||||
if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) {
|
|
||||||
isShowTimeIndicator = false
|
|
||||||
invalidateView()
|
|
||||||
performClick()
|
|
||||||
}
|
|
||||||
handleActionUp(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return isDragging || super.onTouchEvent(event);
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleActionUp(event: MotionEvent) {
|
|
||||||
if (isEnableShowIndicator) {
|
|
||||||
ViewCompat.postOnAnimationDelayed(
|
|
||||||
this@LrcView,
|
|
||||||
mHideIndicatorRunnable,
|
|
||||||
mIndicatorTouchDelay.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {
|
|
||||||
isShowTimeIndicator = false
|
|
||||||
invalidateView()
|
|
||||||
if (mOnPlayIndicatorLineListener != null) {
|
|
||||||
mOnPlayIndicatorLineListener!!.onPlay(
|
|
||||||
mLrcData!![indicatePosition].time,
|
|
||||||
mLrcData!![indicatePosition].text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (overScrolled() && mOffset < 0) {
|
|
||||||
scrollToPosition(0)
|
|
||||||
if (isAutoAdjustPosition) {
|
|
||||||
ViewCompat.postOnAnimationDelayed(
|
|
||||||
this@LrcView,
|
|
||||||
mScrollRunnable,
|
|
||||||
mTouchDelay.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overScrolled() && mOffset > getItemOffsetY(lrcCount - 1)) {
|
|
||||||
scrollToPosition(lrcCount - 1)
|
|
||||||
if (isAutoAdjustPosition) {
|
|
||||||
ViewCompat.postOnAnimationDelayed(
|
|
||||||
this@LrcView,
|
|
||||||
mScrollRunnable,
|
|
||||||
mTouchDelay.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mVelocityTracker!!.computeCurrentVelocity(1000, mMaximumFlingVelocity.toFloat())
|
|
||||||
val YVelocity = mVelocityTracker!!.yVelocity
|
|
||||||
val absYVelocity = Math.abs(YVelocity)
|
|
||||||
if (absYVelocity > mMinimumFlingVelocity) {
|
|
||||||
mOverScroller!!.fling(
|
|
||||||
0, mOffset.toInt(), 0, (-YVelocity).toInt(), 0,
|
|
||||||
0, 0, getItemOffsetY(lrcCount - 1).toInt(),
|
|
||||||
0, getTextHeight(0).toInt()
|
|
||||||
)
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
releaseVelocityTracker()
|
|
||||||
if (isAutoAdjustPosition) {
|
|
||||||
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onClickPlayButton(event: MotionEvent): Boolean {
|
|
||||||
val left = mPlayRect!!.left.toFloat()
|
|
||||||
val right = mPlayRect!!.right.toFloat()
|
|
||||||
val top = mPlayRect!!.top.toFloat()
|
|
||||||
val bottom = mPlayRect!!.bottom.toFloat()
|
|
||||||
val x = event.x
|
|
||||||
val y = event.y
|
|
||||||
return (mLastMotionX > left && mLastMotionX < right && mLastMotionY > top
|
|
||||||
&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun computeScroll() {
|
|
||||||
super.computeScroll()
|
|
||||||
if (mOverScroller!!.computeScrollOffset()) {
|
|
||||||
mOffset = mOverScroller!!.currY.toFloat()
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun releaseVelocityTracker() {
|
|
||||||
if (null != mVelocityTracker) {
|
|
||||||
mVelocityTracker!!.clear()
|
|
||||||
mVelocityTracker!!.recycle()
|
|
||||||
mVelocityTracker = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resetView(defaultContent: String) {
|
|
||||||
if (mLrcData != null) {
|
|
||||||
mLrcData!!.clear()
|
|
||||||
}
|
|
||||||
mLrcMap.clear()
|
|
||||||
mStaticLayoutHashMap.clear()
|
|
||||||
mCurrentLine = 0
|
|
||||||
mOffset = 0f
|
|
||||||
isUserScroll = false
|
|
||||||
isDragging = false
|
|
||||||
mDefaultContent = defaultContent
|
|
||||||
removeCallbacks(mScrollRunnable)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun performClick(): Boolean {
|
|
||||||
return super.performClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dp2px(context: Context, dpVal: Float): Int {
|
|
||||||
return TypedValue.applyDimension(
|
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
|
||||||
dpVal, context.resources.displayMetrics
|
|
||||||
).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sp2px(context: Context, spVal: Float): Int {
|
|
||||||
return TypedValue.applyDimension(
|
|
||||||
TypedValue.COMPLEX_UNIT_SP,
|
|
||||||
spVal, context.resources.displayMetrics
|
|
||||||
).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 暂停(手动滑动歌词后,不再自动回滚至当前播放位置)
|
|
||||||
*/
|
|
||||||
fun pause() {
|
|
||||||
isAutoAdjustPosition = false
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复(继续自动回滚)
|
|
||||||
*/
|
|
||||||
fun resume() {
|
|
||||||
isAutoAdjustPosition = true
|
|
||||||
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*------------------Config-------------------*/
|
|
||||||
|
|
||||||
private fun invalidateView() {
|
|
||||||
if (Looper.getMainLooper().thread === Thread.currentThread()) {
|
|
||||||
invalidate()
|
|
||||||
} else {
|
|
||||||
postInvalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnPlayIndicatorLineListener(onPlayIndicatorLineListener: OnPlayIndicatorLineListener) {
|
|
||||||
mOnPlayIndicatorLineListener = onPlayIndicatorLineListener
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setEmptyContent(defaultContent: String) {
|
|
||||||
mDefaultContent = defaultContent
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setLrcTextSize(lrcTextSize: Float) {
|
|
||||||
mLrcTextSize = lrcTextSize
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setLrcLineSpaceHeight(lrcLineSpaceHeight: Float) {
|
|
||||||
mLrcLineSpaceHeight = lrcLineSpaceHeight
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTouchDelay(touchDelay: Int) {
|
|
||||||
mTouchDelay = touchDelay
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNormalColor(@ColorInt normalColor: Int) {
|
|
||||||
mNormalColor = normalColor
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCurrentPlayLineColor(@ColorInt currentPlayLineColor: Int) {
|
|
||||||
mCurrentPlayLineColor = currentPlayLineColor
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNoLrcTextSize(noLrcTextSize: Float) {
|
|
||||||
mNoLrcTextSize = noLrcTextSize
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNoLrcTextColor(@ColorInt noLrcTextColor: Int) {
|
|
||||||
mNoLrcTextColor = noLrcTextColor
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIndicatorLineWidth(indicatorLineWidth: Float) {
|
|
||||||
mIndicatorLineWidth = indicatorLineWidth
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIndicatorTextSize(indicatorTextSize: Float) {
|
|
||||||
// mIndicatorTextSize = indicatorTextSize;
|
|
||||||
mIndicatorPaint!!.textSize = indicatorTextSize
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCurrentIndicateLineTextColor(currentIndicateLineTextColor: Int) {
|
|
||||||
mCurrentIndicateLineTextColor = currentIndicateLineTextColor
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIndicatorLineColor(indicatorLineColor: Int) {
|
|
||||||
mIndicatorLineColor = indicatorLineColor
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIndicatorMargin(indicatorMargin: Float) {
|
|
||||||
mIndicatorMargin = indicatorMargin
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIconLineGap(iconLineGap: Float) {
|
|
||||||
mIconLineGap = iconLineGap
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIconWidth(iconWidth: Float) {
|
|
||||||
mIconWidth = iconWidth
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIconHeight(iconHeight: Float) {
|
|
||||||
mIconHeight = iconHeight
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setEnableShowIndicator(enableShowIndicator: Boolean) {
|
|
||||||
isEnableShowIndicator = enableShowIndicator
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIndicatorTextColor(indicatorTextColor: Int) {
|
|
||||||
mIndicatorTextColor = indicatorTextColor
|
|
||||||
invalidateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnPlayIndicatorLineListener {
|
|
||||||
fun onPlay(time: Long, content: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DEFAULT_CONTENT = "Empty"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -170,6 +170,8 @@
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:layout_toLeftOf="@id/songTotalTime"
|
android:layout_toLeftOf="@id/songTotalTime"
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/appBarLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:elevation="0dp"
|
|
||||||
app:elevation="0dp">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
style="@style/Toolbar"
|
|
||||||
app:navigationIcon="@drawable/ic_keyboard_backspace_black_24dp"
|
|
||||||
app:title="@string/profile"
|
|
||||||
app:titleTextAppearance="@style/ToolbarTextAppearanceNormal" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="128dp"
|
|
||||||
android:layout_marginEnd="128dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/imageContainer"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_margin="24dp"
|
|
||||||
app:cardCornerRadius="24dp"
|
|
||||||
app:layout_constraintDimensionRatio="21:10"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/bannerImage"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
app:layout_constraintDimensionRatio="16:9"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/material_design_default" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/bannerSelect"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:background="@color/twenty_percent_black_overlay"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:background="?roundSelector"
|
|
||||||
android:padding="16dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/bannerImage"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/bannerImage"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/bannerImage"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/bannerImage"
|
|
||||||
app:srcCompat="@drawable/ic_add_photo_white_24dp"
|
|
||||||
app:tint="@color/md_white_1000" />
|
|
||||||
|
|
||||||
<code.name.monkey.retromusic.views.RetroShapeableImageView
|
|
||||||
android:id="@+id/userImage"
|
|
||||||
android:layout_width="72dp"
|
|
||||||
android:layout_height="72dp"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:retroCornerSize="36dp"
|
|
||||||
app:srcCompat="@drawable/ic_account_white_24dp" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="12dp"
|
|
||||||
android:src="@drawable/ic_add_photo_white_24dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/userImage"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/userImage"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/userImage"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/userImage"
|
|
||||||
app:tint="?android:attr/colorControlNormal" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/nameContainer"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
app:hintEnabled="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/next"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/next"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/imageContainer"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/next">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@null"
|
|
||||||
android:hint="@string/my_name"
|
|
||||||
android:inputType="textPersonName|textCapWords|text"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
android:id="@+id/next"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:text="@string/save"
|
|
||||||
app:icon="@drawable/ic_save_white_24dp"
|
|
||||||
app:iconGravity="textStart"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/imageContainer"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/nameContainer"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/imageContainer" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
|
@ -57,6 +57,7 @@
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
android:id="@+id/imageContainer"
|
android:id="@+id/imageContainer"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:cardCornerRadius="24dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:cardUseCompatPadding="true"
|
app:cardUseCompatPadding="true"
|
||||||
app:layout_constraintDimensionRatio="21:10"
|
app:layout_constraintDimensionRatio="21:10"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
@ -42,6 +42,8 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:splitTrack="false"
|
android:splitTrack="false"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
|
@ -56,6 +56,8 @@
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
@ -92,6 +92,8 @@
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playPauseButton"
|
app:layout_constraintBottom_toTopOf="@+id/playPauseButton"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/progressSlider"
|
android:id="@+id/progressSlider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
android:maxHeight="2dp"
|
||||||
|
android:progressDrawable="@drawable/color_progress_seek"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||||
|
|
|
@ -14,23 +14,25 @@
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<declare-styleable name="LrcView">
|
<declare-styleable name="LrcView">
|
||||||
<attr name="lrcTextSize" format="dimension" />
|
<attr name="lrcTextSize" format="dimension"/>
|
||||||
<attr name="lrcLineSpaceSize" format="dimension" />
|
<attr name="lrcLineSpaceSize" format="dimension"/>
|
||||||
<attr name="lrcNormalTextColor" format="reference|color" />
|
<attr name="lrcNormalTextColor" format="reference|color"/>
|
||||||
<attr name="lrcCurrentTextColor" format="reference|color" />
|
<attr name="lrcCurrentTextColor" format="reference|color"/>
|
||||||
<attr name="lrcTouchDelay" format="integer" />
|
<attr name="lrcTouchDelay" format="integer"/>
|
||||||
<attr name="noLrcTextSize" format="dimension" />
|
<attr name="noLrcTextSize" format="dimension"/>
|
||||||
<attr name="noLrcTextColor" format="reference|color" />
|
<attr name="noLrcTextColor" format="reference|color"/>
|
||||||
<attr name="indicatorLineHeight" format="dimension" />
|
<attr name="indicatorLineHeight" format="dimension"/>
|
||||||
<attr name="indicatorTextSize" format="dimension" />
|
<attr name="indicatorTextSize" format="dimension"/>
|
||||||
<attr name="indicatorTextColor" format="reference|color" />
|
<attr name="indicatorTextColor" format="reference|color"/>
|
||||||
<attr name="currentIndicateLrcColor" format="reference|color" />
|
<attr name="currentIndicateLrcColor" format="reference|color"/>
|
||||||
<attr name="indicatorTouchDelay" format="integer" />
|
<attr name="indicatorTouchDelay" format="integer"/>
|
||||||
<attr name="indicatorLineColor" format="reference|color" />
|
<attr name="indicatorLineColor" format="reference|color"/>
|
||||||
<attr name="indicatorStartEndMargin" format="dimension" />
|
<attr name="indicatorStartEndMargin" format="dimension"/>
|
||||||
<attr name="iconLineGap" format="dimension" />
|
<attr name="iconLineGap" format="dimension"/>
|
||||||
<attr name="playIconWidth" format="dimension" />
|
<attr name="playIconWidth" format="dimension"/>
|
||||||
<attr name="playIconHeight" format="dimension" />
|
<attr name="playIconHeight" format="dimension"/>
|
||||||
<attr name="playIcon" format="reference" />
|
<attr name="playIcon" format="reference"/>
|
||||||
|
<attr name="isLrcCurrentTextBold" format="boolean"/>
|
||||||
|
<attr name="isLrcIndicatorTextBold" format="boolean"/>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue