Updates edittext views with corner rounded
This commit is contained in:
parent
7a42723b9e
commit
f9f30c8387
46 changed files with 1127 additions and 1286 deletions
|
@ -32,7 +32,7 @@ android {
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
applicationId "code.name.monkey.retromusic"
|
applicationId "code.name.monkey.retromusic"
|
||||||
versionCode 308
|
versionCode 311
|
||||||
versionName '3.1.300'
|
versionName '3.1.300'
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
@ -164,6 +164,7 @@ dependencies {
|
||||||
implementation 'com.github.takahirom.downloadable.calligraphy:downloadable-calligraphy:0.1.3'
|
implementation 'com.github.takahirom.downloadable.calligraphy:downloadable-calligraphy:0.1.3'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||||
implementation project(':appthemehelper')
|
implementation project(':appthemehelper')
|
||||||
|
implementation project(':library')
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<html>
<head>
<style type="text/css">
* {
word-wrap: break-word;
}
{style-placeholder}
a {
color: #{link-color};
}
a:active {
color: #{link-color-active};
}
ul {
list-style-position: outside;
padding-left: 0;
padding-right: 0;
margin-left: 1em;
}
li {
padding-top: 8px;
}
</style>
</head>
<body>
<h4>v3.1.300</h4>
<ul>
<li>Improved home sections loading</li>
<li>Removed library options which are duplicated (it's available from profile menu)</li>
<li>Replaced collapsing Fab with Android Floating Extended Fab</li>
<li>Replaced home with for you</li>
<li>Fixed profile image not loading in about</li>
<li>Improved selecting user profile image</li>
<li>Added bio to enter custom message</li>
<li>Improved some UI screens</li>
</ul>
<h4>v3.1.240</h4>
<ul>
<li>Fix Search not showing from home screen</li>
<li>Fix Volume controls color issue</li>
<li>Fix Seek bar alignment</li>
<li>Added tiny theme</li>
<li>Improved full theme appearances</li>
<li>Now playing theme preview updated</li>
<li>Fix composer error</li>
<li>Bottom Options improved(internal)</li>
</ul>
<h4>v3.1.200</h4>
<ul>
<li>Added composer sort and editing</li>
<li>Fix Crash in Album tag editor while selecting options</li>
<li>Added Filter song length</li>
<li>Added Favourites playlist icon will be accent color</li>
<li>Added Colorful settings icons</li>
<li>Added Corners for dialog</li>
</ul>
<h4>v3.0.570</h4>
<ul>
<li>Fix Album/Artist square image</li>
<li>Fix Delete dialog text format</li>
<li>Fix Profile picture not showing after coming back from folders</li>
<li>Fix Play button color i Simple and Plain themes</li>
<li>Fix Sleep timer dialog crashing</li>
<li>Fix Share song dialog title and text</li>
</ul>
<p>If you see entire app white or dark or black select same theme in settings to fix </p>
<p style="line-height:150%"><a href="https://github.com/h4h13/RetroMusicPlayer/wiki/FAQ">FAQ's</a>
</p>
<p style="line-height:150%">*If you face any UI related issues you clear app data and cache, if its
not working try to
uninstall and install again. </p>
</body>
|
<html>
<head>
<style type="text/css">
* {
word-wrap: break-word;
}
{style-placeholder}
a {
color: #{link-color};
}
a:active {
color: #{link-color-active};
}
ul {
list-style-position: outside;
padding-left: 0;
padding-right: 0;
margin-left: 1em;
}
li {
padding-top: 8px;
}
</style>
</head>
<body>
<h4>v3.1.300</h4>
<ul>
<li>Fix rename playlist text color</li>
<li>Fix same album showing in details page</li>
<li>Fix lyrics text alignment on sync and lyrics reading improved</li>
<li>Improved home sections loading</li>
<li>Removed library options which are duplicated (it's available from profile menu)</li>
<li>Replaced collapsing Fab with Android Floating Extended Fab</li>
<li>Replaced home with for you</li>
<li>Fixed profile image not loading in about</li>
<li>Improved selecting user profile image</li>
<li>Added bio to enter custom message</li>
<li>Improved some UI screens</li>
</ul>
<h4>v3.1.240</h4>
<ul>
<li>Fix Search not showing from home screen</li>
<li>Fix Volume controls color issue</li>
<li>Fix Seek bar alignment</li>
<li>Added tiny theme</li>
<li>Improved full theme appearances</li>
<li>Now playing theme preview updated</li>
<li>Fix composer error</li>
<li>Bottom Options improved(internal)</li>
</ul>
<h4>v3.1.200</h4>
<ul>
<li>Added composer sort and editing</li>
<li>Fix Crash in Album tag editor while selecting options</li>
<li>Added Filter song length</li>
<li>Added Favourites playlist icon will be accent color</li>
<li>Added Colorful settings icons</li>
<li>Added Corners for dialog</li>
</ul>
<h4>v3.0.570</h4>
<ul>
<li>Fix Album/Artist square image</li>
<li>Fix Delete dialog text format</li>
<li>Fix Profile picture not showing after coming back from folders</li>
<li>Fix Play button color i Simple and Plain themes</li>
<li>Fix Sleep timer dialog crashing</li>
<li>Fix Share song dialog title and text</li>
</ul>
<p>If you see entire app white or dark or black select same theme in settings to fix </p>
<p style="line-height:150%"><a href="https://github.com/h4h13/RetroMusicPlayer/wiki/FAQ">FAQ's</a>
</p>
<p style="line-height:150%">*If you face any UI related issues you clear app data and cache, if its
not working try to
uninstall and install again. </p>
</body>
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.dialogs
|
package code.name.monkey.retromusic.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -22,28 +21,26 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import code.name.monkey.appthemehelper.ThemeStore
|
import code.name.monkey.appthemehelper.ThemeStore
|
||||||
import code.name.monkey.appthemehelper.util.MaterialUtil
|
import code.name.monkey.appthemehelper.util.MaterialUtil
|
||||||
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.PlaylistsUtil
|
import code.name.monkey.retromusic.util.PlaylistsUtil
|
||||||
import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment
|
import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment
|
||||||
import kotlinx.android.synthetic.main.dialog_playlist.*
|
import kotlinx.android.synthetic.main.dialog_playlist.*
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() {
|
class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
|
||||||
return inflater.inflate(R.layout.dialog_playlist, container, false)
|
return inflater.inflate(R.layout.dialog_playlist, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val accentColor = ThemeStore.accentColor(Objects.requireNonNull<Context>(context))
|
|
||||||
|
|
||||||
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
|
|
||||||
|
val accentColor = ThemeStore.accentColor(context!!)
|
||||||
|
|
||||||
MaterialUtil.setTint(actionCreate, true)
|
MaterialUtil.setTint(actionCreate, true)
|
||||||
MaterialUtil.setTint(actionCancel, false)
|
MaterialUtil.setTint(actionCancel, false)
|
||||||
|
@ -51,17 +48,17 @@ class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
|
|
||||||
actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor))
|
actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor))
|
||||||
actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!))
|
actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
|
||||||
|
|
||||||
|
|
||||||
|
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
||||||
|
|
||||||
actionCancel.setOnClickListener { dismiss() }
|
actionCancel.setOnClickListener { dismiss() }
|
||||||
actionCreate.setOnClickListener {
|
actionCreate.setOnClickListener {
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
if (!actionNewPlaylist!!.text!!.toString().trim { it <= ' ' }.isEmpty()) {
|
if (!actionNewPlaylist!!.text!!.toString().trim { it <= ' ' }.isEmpty()) {
|
||||||
val playlistId = PlaylistsUtil
|
val playlistId = PlaylistsUtil.createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString())
|
||||||
.createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString())
|
|
||||||
if (playlistId != -1 && activity != null) {
|
if (playlistId != -1 && activity != null) {
|
||||||
if (songs != null) {
|
if (songs != null) {
|
||||||
PlaylistsUtil.addToPlaylist(activity!!, songs, playlistId, true)
|
PlaylistsUtil.addToPlaylist(activity!!, songs, playlistId, true)
|
||||||
|
|
|
@ -48,8 +48,8 @@ class DeletePlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
} else {
|
} else {
|
||||||
Html.fromHtml(getString(R.string.delete_playlist_x, playlists[0].name))
|
Html.fromHtml(getString(R.string.delete_playlist_x, playlists[0].name))
|
||||||
}
|
}
|
||||||
dialogTitle.text = content
|
bannerTitle.text = content
|
||||||
dialogTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
|
|
||||||
actionDelete.apply {
|
actionDelete.apply {
|
||||||
setText(R.string.action_delete)
|
setText(R.string.action_delete)
|
||||||
|
|
|
@ -34,7 +34,7 @@ class DeleteSongsDialog : RoundedBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
dialogTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
//noinspection unchecked,ConstantConditions
|
//noinspection unchecked,ConstantConditions
|
||||||
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
||||||
val content: CharSequence
|
val content: CharSequence
|
||||||
|
@ -44,7 +44,7 @@ class DeleteSongsDialog : RoundedBottomSheetDialogFragment() {
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.delete_song_x, songs[0].title)
|
getString(R.string.delete_song_x, songs[0].title)
|
||||||
}
|
}
|
||||||
dialogTitle.text = Html.fromHtml(content)
|
bannerTitle.text = Html.fromHtml(content)
|
||||||
}
|
}
|
||||||
actionDelete.apply {
|
actionDelete.apply {
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
|
|
|
@ -27,35 +27,33 @@ import code.name.monkey.retromusic.model.PlaylistSong
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.PlaylistsUtil
|
import code.name.monkey.retromusic.util.PlaylistsUtil
|
||||||
import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment
|
import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment
|
||||||
import kotlinx.android.synthetic.main.dialog_remove_from_playlist.*
|
import kotlinx.android.synthetic.main.dialog_delete.*
|
||||||
|
|
||||||
|
|
||||||
class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() {
|
class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_remove_from_playlist, container, false)
|
return inflater.inflate(R.layout.dialog_delete, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
||||||
val title: Int
|
|
||||||
val content: CharSequence
|
val content: CharSequence
|
||||||
if (songs!!.size > 1) {
|
if (songs!!.size > 1) {
|
||||||
title = R.string.remove_songs_from_playlist_title
|
|
||||||
content = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
content = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size), Html.FROM_HTML_MODE_LEGACY)
|
Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size), Html.FROM_HTML_MODE_LEGACY)
|
||||||
} else {
|
} else {
|
||||||
Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size))
|
Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
title = R.string.remove_song_from_playlist_title
|
|
||||||
content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs[0].title))
|
content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs[0].title))
|
||||||
}
|
}
|
||||||
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
bannerTitle.text = content;
|
bannerTitle.text = content;
|
||||||
actionDelete.apply {
|
actionDelete.apply {
|
||||||
setText(title)
|
setIconResource(R.drawable.ic_delete_white_24dp)
|
||||||
|
setText(R.string.remove_action)
|
||||||
setTextColor(ThemeStore.textColorSecondary(context))
|
setTextColor(ThemeStore.textColorSecondary(context))
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val playlistSongs = ArrayList<PlaylistSong>()
|
val playlistSongs = ArrayList<PlaylistSong>()
|
||||||
|
@ -68,6 +66,7 @@ class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
|
||||||
actionCancel.apply {
|
actionCancel.apply {
|
||||||
|
setIconResource(R.drawable.ic_close_white_24dp)
|
||||||
setTextColor(ThemeStore.textColorSecondary(context))
|
setTextColor(ThemeStore.textColorSecondary(context))
|
||||||
setOnClickListener { dismiss() }
|
setOnClickListener { dismiss() }
|
||||||
MaterialUtil.setTint(this, false)
|
MaterialUtil.setTint(this, false)
|
||||||
|
|
|
@ -36,10 +36,14 @@ class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val accentColor = ThemeStore.accentColor(context!!)
|
|
||||||
|
|
||||||
|
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
|
bannerTitle.setText(R.string.rename_playlist_title)
|
||||||
|
|
||||||
MaterialUtil.setTint(actionNewPlaylistContainer, false)
|
MaterialUtil.setTint(actionNewPlaylistContainer, false)
|
||||||
|
val accentColor = ThemeStore.accentColor(context!!)
|
||||||
|
actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor))
|
||||||
|
actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!))
|
||||||
|
|
||||||
actionNewPlaylist.apply {
|
actionNewPlaylist.apply {
|
||||||
var playlistId: Long = 0
|
var playlistId: Long = 0
|
||||||
|
@ -47,14 +51,10 @@ class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() {
|
||||||
playlistId = arguments!!.getLong("playlist_id")
|
playlistId = arguments!!.getLong("playlist_id")
|
||||||
}
|
}
|
||||||
setText(PlaylistsUtil.getNameForPlaylist(activity!!, playlistId))
|
setText(PlaylistsUtil.getNameForPlaylist(activity!!, playlistId))
|
||||||
setHintTextColor(ColorStateList.valueOf(accentColor))
|
|
||||||
setTextColor(ThemeStore.textColorPrimary(context!!))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
|
|
||||||
bannerTitle.setText(R.string.rename_playlist_title)
|
|
||||||
actionCancel.apply {
|
actionCancel.apply {
|
||||||
MaterialUtil.setTint(actionCancel, false)
|
MaterialUtil.setTint(this, false)
|
||||||
setOnClickListener { dismiss() }
|
setOnClickListener { dismiss() }
|
||||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_close_white_24dp)
|
icon = ContextCompat.getDrawable(context, R.drawable.ic_close_white_24dp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,143 +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.exfab;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by hemanths on 3/20/19
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.transition.AutoTransition;
|
|
||||||
import android.transition.Transition;
|
|
||||||
import android.transition.TransitionManager;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.google.android.material.button.MaterialButton;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
import androidx.constraintlayout.widget.ConstraintSet;
|
|
||||||
import code.name.monkey.retromusic.R;
|
|
||||||
|
|
||||||
public class FabIconAnimator {
|
|
||||||
|
|
||||||
private static final String ROTATION_Y_PROPERTY = "rotationY";
|
|
||||||
|
|
||||||
private static final float TWITCH_END = 20F;
|
|
||||||
private static final float TWITCH_START = 0F;
|
|
||||||
private static final int DURATION = 200;
|
|
||||||
private final MaterialButton button;
|
|
||||||
private final ConstraintLayout container;
|
|
||||||
@DrawableRes
|
|
||||||
private int currentIcon;
|
|
||||||
@StringRes
|
|
||||||
private int currentText;
|
|
||||||
private boolean isAnimating;
|
|
||||||
private final Transition.TransitionListener listener = new Transition.TransitionListener() {
|
|
||||||
public void onTransitionStart(Transition transition) {
|
|
||||||
isAnimating = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTransitionEnd(Transition transition) {
|
|
||||||
isAnimating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTransitionCancel(Transition transition) {
|
|
||||||
isAnimating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTransitionPause(Transition transition) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTransitionResume(Transition transition) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public FabIconAnimator(ConstraintLayout container) {
|
|
||||||
this.container = container;
|
|
||||||
this.button = container.findViewById(R.id.fab);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(@DrawableRes int icon, @StringRes int text) {
|
|
||||||
boolean isSame = currentIcon == icon && currentText == text;
|
|
||||||
currentIcon = icon;
|
|
||||||
currentText = text;
|
|
||||||
animateChange(icon, text, isSame);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnClickListener(@Nullable View.OnClickListener clickListener) {
|
|
||||||
if (clickListener == null) {
|
|
||||||
button.setOnClickListener(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AtomicBoolean flag = new AtomicBoolean(true);
|
|
||||||
button.setOnClickListener(view -> {
|
|
||||||
if (!flag.getAndSet(false)) return;
|
|
||||||
clickListener.onClick(view);
|
|
||||||
button.postDelayed(() -> flag.set(true), 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isExtended() { // R.dimen.triple_and_half_margin is 56 dp.
|
|
||||||
return button.getLayoutParams().height != button.getResources().getDimensionPixelSize(R.dimen.triple_and_half_margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExtended(boolean extended) {
|
|
||||||
setExtended(extended, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animateChange(@DrawableRes int icon, @StringRes int text, boolean isSame) {
|
|
||||||
boolean extended = isExtended();
|
|
||||||
button.setText(text);
|
|
||||||
button.setIconResource(icon);
|
|
||||||
setExtended(extended, !isSame);
|
|
||||||
if (!extended) twitch();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setExtended(boolean extended, boolean force) {
|
|
||||||
if (isAnimating || (extended && isExtended() && !force)) return;
|
|
||||||
|
|
||||||
ConstraintSet set = new ConstraintSet();
|
|
||||||
set.clone(container.getContext(), extended ? R.layout.fab_extended : R.layout.fab_collapsed);
|
|
||||||
|
|
||||||
TransitionManager.beginDelayedTransition(container, new AutoTransition()
|
|
||||||
.addListener(listener).setDuration(150));
|
|
||||||
|
|
||||||
if (extended) button.setText(currentText);
|
|
||||||
else button.setText("");
|
|
||||||
|
|
||||||
set.applyTo(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void twitch() {
|
|
||||||
AnimatorSet set = new AnimatorSet();
|
|
||||||
ObjectAnimator twitchA = animateProperty(ROTATION_Y_PROPERTY, TWITCH_START, TWITCH_END);
|
|
||||||
ObjectAnimator twitchB = animateProperty(ROTATION_Y_PROPERTY, TWITCH_END, TWITCH_START);
|
|
||||||
|
|
||||||
set.play(twitchB).after(twitchA);
|
|
||||||
set.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private ObjectAnimator animateProperty(String property, float start, float end) {
|
|
||||||
return ObjectAnimator.ofFloat(container, property, start, end).setDuration(DURATION);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +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.exfab;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.animation.Interpolator;
|
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
|
||||||
|
|
||||||
public class TransientBarBehavior extends CoordinatorLayout.Behavior<View> {
|
|
||||||
|
|
||||||
private static final Interpolator fastOutSlowInInterpolator = new FastOutSlowInInterpolator();
|
|
||||||
|
|
||||||
public TransientBarBehavior(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
|
||||||
return dependency instanceof Snackbar.SnackbarLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
|
||||||
if (child.getVisibility() == View.VISIBLE) {
|
|
||||||
float translationY = this.getViewTranslationYForSnackbar(parent, child);
|
|
||||||
child.setTranslationY(translationY);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
|
||||||
if (dependency instanceof Snackbar.SnackbarLayout && child.getTranslationY() != 0.0F) {
|
|
||||||
ViewCompat.animate(child).translationY(0.0F).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
|
|
||||||
.setInterpolator(fastOutSlowInInterpolator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getViewTranslationYForSnackbar(CoordinatorLayout parent, View child) {
|
|
||||||
float minOffset = 0.0F;
|
|
||||||
List dependencies = parent.getDependencies(child);
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (int z = dependencies.size(); i < z; ++i) {
|
|
||||||
View view = (View) dependencies.get(i);
|
|
||||||
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) {
|
|
||||||
minOffset = Math.min(minOffset, view.getTranslationY() - (float) view.getHeight());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minOffset;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -219,19 +219,20 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
||||||
})
|
})
|
||||||
return@map it.albums!!
|
return@map it.albums!!
|
||||||
}
|
}
|
||||||
|
.map { it.filter { albumSearch -> albumSearch.id != album.id } }
|
||||||
.subscribe {
|
.subscribe {
|
||||||
|
for (albumFinal in it) {
|
||||||
it.remove(album)
|
if (albumFinal.id == album.id)
|
||||||
if (!it.isEmpty()) {
|
println("$albumFinal -> $album")
|
||||||
moreTitle.visibility = View.VISIBLE
|
}
|
||||||
moreRecyclerView.visibility = View.VISIBLE
|
if (it.isEmpty()) {
|
||||||
} else {
|
|
||||||
return@subscribe
|
return@subscribe
|
||||||
}
|
}
|
||||||
|
moreTitle.visibility = View.VISIBLE
|
||||||
|
moreRecyclerView.visibility = View.VISIBLE
|
||||||
moreTitle.text = String.format("More from %s", album.artistName)
|
moreTitle.text = String.format("More from %s", album.artistName)
|
||||||
|
|
||||||
val albumAdapter = HorizontalAlbumAdapter(this, it, false, null)
|
val albumAdapter = HorizontalAlbumAdapter(this, it as ArrayList<Album>, false, null)
|
||||||
moreRecyclerView.layoutManager = GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false)
|
moreRecyclerView.layoutManager = GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false)
|
||||||
moreRecyclerView.adapter = albumAdapter
|
moreRecyclerView.adapter = albumAdapter
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package code.name.monkey.retromusic.ui.activities
|
package code.name.monkey.retromusic.ui.activities
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
|
@ -12,6 +13,8 @@ import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import code.name.monkey.appthemehelper.ThemeStore
|
import code.name.monkey.appthemehelper.ThemeStore
|
||||||
|
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||||
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.appthemehelper.util.TintHelper
|
import code.name.monkey.appthemehelper.util.TintHelper
|
||||||
import code.name.monkey.retromusic.App
|
import code.name.monkey.retromusic.App
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
@ -28,6 +31,8 @@ import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.input.input
|
import com.afollestad.materialdialogs.input.input
|
||||||
|
import com.lauzy.freedom.library.LrcHelper
|
||||||
|
import com.lauzy.freedom.library.LrcView
|
||||||
import kotlinx.android.synthetic.main.activity_lyrics.*
|
import kotlinx.android.synthetic.main.activity_lyrics.*
|
||||||
import kotlinx.android.synthetic.main.fragment_lyrics.*
|
import kotlinx.android.synthetic.main.fragment_lyrics.*
|
||||||
import kotlinx.android.synthetic.main.fragment_synced.*
|
import kotlinx.android.synthetic.main.fragment_synced.*
|
||||||
|
@ -37,15 +42,22 @@ import java.util.*
|
||||||
|
|
||||||
class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPager.OnPageChangeListener {
|
class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPager.OnPageChangeListener {
|
||||||
override fun onPageScrollStateChanged(state: Int) {
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
|
when (state) {
|
||||||
|
ViewPager.SCROLL_STATE_IDLE ->
|
||||||
|
fab.show(true)
|
||||||
|
ViewPager.SCROLL_STATE_DRAGGING,
|
||||||
|
ViewPager.SCROLL_STATE_SETTLING ->
|
||||||
|
fab.hide(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
PreferenceUtil.getInstance().lyricsOptions = position
|
PreferenceUtil.getInstance().lyricsOptions = position
|
||||||
|
if (position == 0) fab.text = "Sync lyrics"
|
||||||
|
else if (position == 1) fab.text = "Lyrics"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View?) {
|
override fun onClick(v: View?) {
|
||||||
|
@ -83,7 +95,11 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TintHelper.setTintAuto(fab, ThemeStore.accentColor(this), true)
|
fab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
|
||||||
|
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
|
||||||
|
fab.setTextColor(this)
|
||||||
|
fab.iconTint = this
|
||||||
|
}
|
||||||
setupWakelock()
|
setupWakelock()
|
||||||
|
|
||||||
viewPager.apply {
|
viewPager.apply {
|
||||||
|
@ -258,8 +274,8 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
|
||||||
offlineLyrics?.setText(R.string.no_lyrics_found)
|
offlineLyrics?.setText(R.string.no_lyrics_found)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
(activity as LyricsActivity).lyricsString = l.data
|
(activity as LyricsActivity).lyricsString = l.text
|
||||||
offlineLyrics?.text = l.data
|
offlineLyrics?.text = l.text
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancelled(s: Lyrics?) {
|
override fun onCancelled(s: Lyrics?) {
|
||||||
|
@ -302,11 +318,14 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
|
||||||
|
|
||||||
private fun setupLyricsView() {
|
private fun setupLyricsView() {
|
||||||
lyricsView.apply {
|
lyricsView.apply {
|
||||||
setOnPlayerClickListener { progress, _ -> MusicPlayerRemote.seekTo(progress.toInt()) }
|
setCurrentPlayLineColor(ThemeStore.accentColor(context))
|
||||||
setDefaultColor(ContextCompat.getColor(context, R.color.md_grey_400))
|
setIndicatorTextColor(ThemeStore.accentColor(context))
|
||||||
setHintColor(ThemeStore.textColorPrimary(context))
|
setCurrentIndicateLineTextColor(ThemeStore.textColorPrimary(context))
|
||||||
setHighLightColor(ThemeStore.textColorPrimary(context))
|
setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener {
|
||||||
setTextSize(RetroUtil.convertDpToPixel(18f, context).toInt())
|
override fun onPlay(time: Long, content: String) {
|
||||||
|
MusicPlayerRemote.seekTo(time.toInt())
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +340,7 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||||
lyricsView.setCurrentTimeMillis(progress.toLong())
|
lyricsView.updateTime(progress.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadLRCLyrics() {
|
private fun loadLRCLyrics() {
|
||||||
|
@ -332,10 +351,8 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLyricsLocal(file: File?) {
|
private fun showLyricsLocal(file: File?) {
|
||||||
if (file == null) {
|
if (file != null) {
|
||||||
lyricsView.reset()
|
lyricsView.setLrcData(LrcHelper.parseLrcFromFile(file))
|
||||||
} else {
|
|
||||||
lyricsView.setLyricFile(file, "UTF-8")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ class UserInfoActivity : AbsBaseActivity() {
|
||||||
toolbar.apply {
|
toolbar.apply {
|
||||||
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
|
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
|
||||||
setBackgroundColor(primaryColor)
|
setBackgroundColor(primaryColor)
|
||||||
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.accentColor(this@UserInfoActivity))
|
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(this@UserInfoActivity))
|
||||||
setSupportActionBar(this)
|
setSupportActionBar(this)
|
||||||
}
|
}
|
||||||
appBarLayout.setBackgroundColor(primaryColor)
|
appBarLayout.setBackgroundColor(primaryColor)
|
||||||
|
|
|
@ -44,7 +44,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
||||||
|
|
||||||
private fun setUpViews() {
|
private fun setUpViews() {
|
||||||
fillViewsWithFileTags()
|
fillViewsWithFileTags()
|
||||||
MaterialUtil.setTint(songTextContainer)
|
MaterialUtil.setTint(songTextContainer,false)
|
||||||
MaterialUtil.setTint(composerContainer, false)
|
MaterialUtil.setTint(composerContainer, false)
|
||||||
MaterialUtil.setTint(albumTextContainer, false)
|
MaterialUtil.setTint(albumTextContainer, false)
|
||||||
MaterialUtil.setTint(artistContainer, false)
|
MaterialUtil.setTint(artistContainer, false)
|
||||||
|
|
|
@ -106,14 +106,14 @@ class PlayerFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks {
|
||||||
snowfall.visibility = if (PreferenceUtil.getInstance().isSnowFall) View.VISIBLE else View.GONE
|
snowfall.visibility = if (PreferenceUtil.getInstance().isSnowFall) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
|
||||||
val display = activity?.windowManager?.defaultDisplay
|
//val display = activity?.windowManager?.defaultDisplay
|
||||||
val outMetrics = DisplayMetrics()
|
//val outMetrics = DisplayMetrics()
|
||||||
display?.getMetrics(outMetrics)
|
//display?.getMetrics(outMetrics)
|
||||||
|
|
||||||
val density = resources.displayMetrics.density
|
//val density = resources.displayMetrics.density
|
||||||
val dpWidth = outMetrics.widthPixels / density
|
//val dpWidth = outMetrics.widthPixels / density
|
||||||
|
|
||||||
playerAlbumCoverContainer?.layoutParams?.height = RetroUtil.convertDpToPixel((dpWidth - getCutOff()), context!!).toInt()
|
//playerAlbumCoverContainer?.layoutParams?.height = RetroUtil.convertDpToPixel((dpWidth - getCutOff()), context!!).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -121,9 +121,7 @@ public class PlaylistsUtil {
|
||||||
public static void addToPlaylist(@NonNull final Context context, @NonNull final List<Song> songs, final int playlistId, final boolean showToastOnFinish) {
|
public static void addToPlaylist(@NonNull final Context context, @NonNull final List<Song> songs, final int playlistId, final boolean showToastOnFinish) {
|
||||||
final int size = songs.size();
|
final int size = songs.size();
|
||||||
final ContentResolver resolver = context.getContentResolver();
|
final ContentResolver resolver = context.getContentResolver();
|
||||||
final String[] projection = new String[]{
|
final String[] projection = new String[]{"max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")",};
|
||||||
"max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")",
|
|
||||||
};
|
|
||||||
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
|
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
int base = 0;
|
int base = 0;
|
||||||
|
|
|
@ -1,890 +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.views;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
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.view.animation.LinearInterpolator;
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.core.content.res.ResourcesCompat;
|
|
||||||
|
|
||||||
import org.mozilla.universalchardet.UniversalDetector;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import code.name.monkey.retromusic.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by zhengken.me on 2016/11/27.
|
|
||||||
* ClassName : LyricView
|
|
||||||
* Description :
|
|
||||||
*/
|
|
||||||
public class LyricView extends View {
|
|
||||||
|
|
||||||
public static final int LEFT = 0;
|
|
||||||
public static final int CENTER = 1;
|
|
||||||
public static final int RIGHT = 2;
|
|
||||||
private static final String TAG = "LyricView";
|
|
||||||
private static final float SLIDE_COEFFICIENT = 0.2f;
|
|
||||||
|
|
||||||
private static final int UNITS_SECOND = 1000;
|
|
||||||
private static final int UNITS_MILLISECOND = 1;
|
|
||||||
|
|
||||||
private static final int FLING_ANIMATOR_DURATION = 500 * UNITS_MILLISECOND;
|
|
||||||
|
|
||||||
private static final int THRESHOLD_Y_VELOCITY = 1600;
|
|
||||||
|
|
||||||
private static final int INDICATOR_ICON_PLAY_MARGIN_LEFT = 7;//dp
|
|
||||||
private static final int INDICATOR_ICON_PLAY_WIDTH = 15;//sp
|
|
||||||
private static final int INDICATOR_LINE_MARGIN = 10;//dp
|
|
||||||
private static final int INDICATOR_TIME_TEXT_SIZE = 10;//sp
|
|
||||||
private static final int INDICATOR_TIME_MARGIN_RIGHT = 7;//dp
|
|
||||||
|
|
||||||
private static final int DEFAULT_TEXT_SIZE = 16;//sp
|
|
||||||
private static final int DEFAULT_MAX_LENGTH = 300;//dp
|
|
||||||
private static final int DEFAULT_LINE_SPACE = 25;//dp
|
|
||||||
|
|
||||||
private int mHintColor;
|
|
||||||
private int mDefaultColor;
|
|
||||||
private int mHighLightColor;
|
|
||||||
private int mTextAlign;
|
|
||||||
|
|
||||||
|
|
||||||
private int mLineCount;
|
|
||||||
private int mTextSize;
|
|
||||||
private float mLineHeight;
|
|
||||||
private LyricInfo mLyricInfo;
|
|
||||||
private String mDefaultHint;
|
|
||||||
private int mMaxLength;
|
|
||||||
|
|
||||||
private TextPaint mTextPaint;
|
|
||||||
private Paint mBtnPlayPaint;
|
|
||||||
private Paint mLinePaint;
|
|
||||||
private Paint mTimerPaint;
|
|
||||||
|
|
||||||
private boolean mFling = false;
|
|
||||||
private ValueAnimator mFlingAnimator;
|
|
||||||
private float mScrollY = 0;
|
|
||||||
private float mLineSpace = 0;
|
|
||||||
private boolean mIsShade;
|
|
||||||
private float mShaderWidth = 0;
|
|
||||||
private int mCurrentPlayLine = 0;
|
|
||||||
private boolean mShowIndicator;
|
|
||||||
|
|
||||||
private VelocityTracker mVelocityTracker;
|
|
||||||
private float mVelocity = 0;
|
|
||||||
private float mDownX;
|
|
||||||
private float mDownY;
|
|
||||||
private float mLastScrollY;
|
|
||||||
private boolean mUserTouch = false;
|
|
||||||
Runnable hideIndicator = () -> {
|
|
||||||
setUserTouch(false);
|
|
||||||
invalidateView();
|
|
||||||
};
|
|
||||||
private int maxVelocity;
|
|
||||||
private int mLineNumberUnderIndicator = 0;
|
|
||||||
private Rect mBtnPlayRect = new Rect();
|
|
||||||
private Rect mTimerRect;
|
|
||||||
private String mDefaultTime = "00:00";
|
|
||||||
private int mLineColor = Color.parseColor("#EFEFEF");
|
|
||||||
private int mBtnColor = Color.parseColor("#EFEFEF");
|
|
||||||
private List<Integer> mLineFeedRecord = new ArrayList<>();
|
|
||||||
private boolean mEnableLineFeed = false;
|
|
||||||
private int mExtraHeight = 0;
|
|
||||||
private int mTextHeight;
|
|
||||||
private String mCurrentLyricFilePath = null;
|
|
||||||
private OnPlayerClickListener mClickListener;
|
|
||||||
|
|
||||||
public LyricView(Context context) {
|
|
||||||
super(context);
|
|
||||||
initMyView(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LyricView(Context context, AttributeSet attributeSet) {
|
|
||||||
super(context, attributeSet);
|
|
||||||
getAttrs(context, attributeSet);
|
|
||||||
initMyView(context);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public LyricView(Context context, AttributeSet attributeSet, int i) {
|
|
||||||
super(context, attributeSet, i);
|
|
||||||
getAttrs(context, attributeSet);
|
|
||||||
initMyView(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
|
||||||
return super.dispatchTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
|
|
||||||
if (mVelocityTracker == null) {
|
|
||||||
mVelocityTracker = VelocityTracker.obtain();
|
|
||||||
}
|
|
||||||
mVelocityTracker.addMovement(event);
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_CANCEL:
|
|
||||||
actionCancel(event);
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
actionDown(event);
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
actionMove(event);
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
actionUp(event);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
invalidateView();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
||||||
super.onLayout(changed, left, top, right, bottom);
|
|
||||||
mBtnPlayRect.set((int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT),
|
|
||||||
(int) (getHeight() * 0.5f - getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f),
|
|
||||||
(int) (getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT)),
|
|
||||||
(int) (getHeight() * 0.5f + getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f));
|
|
||||||
mShaderWidth = getWidth() * 0.4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
if (scrollable()) {
|
|
||||||
if (mShowIndicator) {
|
|
||||||
drawIndicator(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < mLineCount; i++) {
|
|
||||||
float x = 0;
|
|
||||||
switch (mTextAlign) {
|
|
||||||
case LEFT:
|
|
||||||
x = INDICATOR_ICON_PLAY_MARGIN_LEFT + INDICATOR_LINE_MARGIN + mBtnPlayRect.width();
|
|
||||||
break;
|
|
||||||
case CENTER:
|
|
||||||
x = getWidth() * 0.5f;
|
|
||||||
break;
|
|
||||||
case RIGHT:
|
|
||||||
x = getWidth() - INDICATOR_LINE_MARGIN * 2 - mTimerRect.width() - INDICATOR_ICON_PLAY_MARGIN_LEFT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
float y;
|
|
||||||
if (mEnableLineFeed && i > 0) {
|
|
||||||
y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY + mLineFeedRecord.get(i - 1);
|
|
||||||
} else {
|
|
||||||
y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// float y = getHeight() * 0.5f + i * mLineHeight - mScrollY;
|
|
||||||
if (y < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (y > getHeight()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i == mCurrentPlayLine - 1) {
|
|
||||||
mTextPaint.setColor(mHighLightColor);
|
|
||||||
} else if (i == mLineNumberUnderIndicator && mShowIndicator) {
|
|
||||||
mTextPaint.setColor(Color.LTGRAY);
|
|
||||||
} else {
|
|
||||||
mTextPaint.setColor(mDefaultColor);
|
|
||||||
}
|
|
||||||
if (mIsShade && (y > getHeight() - mShaderWidth || y < mShaderWidth)) {
|
|
||||||
if (y < mShaderWidth) {
|
|
||||||
mTextPaint.setAlpha(26 + (int) (23000.0f * y / mShaderWidth * 0.01f));
|
|
||||||
} else {
|
|
||||||
mTextPaint.setAlpha(26 + (int) (23000.0f * (getHeight() - y) / mShaderWidth * 0.01f));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mTextPaint.setAlpha(255);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEnableLineFeed) {
|
|
||||||
StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint,
|
|
||||||
mMaxLength,
|
|
||||||
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(x, y);
|
|
||||||
staticLayout.draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
} else {
|
|
||||||
canvas.drawText(mLyricInfo.songLines.get(i).content, x, y, mTextPaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mTextPaint.setColor(mHintColor);
|
|
||||||
canvas.drawText(mDefaultHint, getMeasuredWidth() / 2, getMeasuredHeight() / 2, mTextPaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getAttrs(Context context, AttributeSet attrs) {
|
|
||||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LyricView);
|
|
||||||
mIsShade = ta.getBoolean(R.styleable.LyricView_fadeInFadeOut, false);
|
|
||||||
mDefaultHint = ta.getString(R.styleable.LyricView_hint) != null
|
|
||||||
? ta.getString(R.styleable.LyricView_hint)
|
|
||||||
: getResources().getString(R.string.default_hint);
|
|
||||||
mHintColor = ta.getColor(R.styleable.LyricView_hintColor, Color.parseColor("#FFFFFF"));
|
|
||||||
mDefaultColor = ta.getColor(R.styleable.LyricView_textColor, Color.parseColor("#8D8D8D"));
|
|
||||||
mHighLightColor = ta.getColor(R.styleable.LyricView_highlightColor, Color.parseColor("#FFFFFF"));
|
|
||||||
mTextSize = ta.getDimensionPixelSize(R.styleable.LyricView_textSize, (int) getRawSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE));
|
|
||||||
mTextAlign = ta.getInt(R.styleable.LyricView_textAlign, CENTER);
|
|
||||||
mMaxLength = ta.getDimensionPixelSize(R.styleable.LyricView_maxLength, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH));
|
|
||||||
mLineSpace = ta.getDimensionPixelSize(R.styleable.LyricView_lineSpace, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_LINE_SPACE));
|
|
||||||
ta.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShade(boolean shade) {
|
|
||||||
mIsShade = shade;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHintColor(int hintColor) {
|
|
||||||
mHintColor = hintColor;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultColor(int defaultColor) {
|
|
||||||
mDefaultColor = defaultColor;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHighLightColor(int highLightColor) {
|
|
||||||
mHighLightColor = highLightColor;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTextAlign(int textAlign) {
|
|
||||||
mTextAlign = textAlign;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLineCount(int lineCount) {
|
|
||||||
mLineCount = lineCount;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTextSize(int textSize) {
|
|
||||||
mTextSize = textSize;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultHint(String defaultHint) {
|
|
||||||
mDefaultHint = defaultHint;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxLength(int maxLength) {
|
|
||||||
mMaxLength = maxLength;
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnPlayerClickListener(OnPlayerClickListener mClickListener) {
|
|
||||||
this.mClickListener = mClickListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlignment(@Alignment int alignment) {
|
|
||||||
mTextAlign = alignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentTimeMillis(long current) {
|
|
||||||
scrollToCurrentTimeMillis(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLyricFile(File file) {
|
|
||||||
|
|
||||||
if (file == null || !file.exists()) {
|
|
||||||
reset();
|
|
||||||
mCurrentLyricFilePath = "";
|
|
||||||
return;
|
|
||||||
} else if (file.getPath().equals(mCurrentLyricFilePath)) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
mCurrentLyricFilePath = file.getPath();
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
|
|
||||||
FileInputStream fis = new FileInputStream(file);
|
|
||||||
byte[] buf = new byte[1024];
|
|
||||||
UniversalDetector detector = new UniversalDetector(null);
|
|
||||||
int nread;
|
|
||||||
while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
|
|
||||||
detector.handleData(buf, 0, nread);
|
|
||||||
}
|
|
||||||
|
|
||||||
detector.dataEnd();
|
|
||||||
String encoding = detector.getDetectedCharset();
|
|
||||||
if (encoding != null) {
|
|
||||||
setLyricFile(file, encoding);
|
|
||||||
} else {
|
|
||||||
setLyricFile(file, "UTF-8");
|
|
||||||
}
|
|
||||||
detector.reset();
|
|
||||||
fis.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLyricFile(File file, String charsetName) {
|
|
||||||
if (file != null && file.exists()) {
|
|
||||||
try {
|
|
||||||
setupLyricResource(new FileInputStream(file), charsetName);
|
|
||||||
|
|
||||||
for (int i = 0; i < mLyricInfo.songLines.size(); i++) {
|
|
||||||
|
|
||||||
StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint,
|
|
||||||
(int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH),
|
|
||||||
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
|
|
||||||
|
|
||||||
if (staticLayout.getLineCount() > 1) {
|
|
||||||
mEnableLineFeed = true;
|
|
||||||
mExtraHeight = mExtraHeight + (staticLayout.getLineCount() - 1) * mTextHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
mLineFeedRecord.add(i, mExtraHeight);
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLineSpace(float lineSpace) {
|
|
||||||
if (mLineSpace != lineSpace) {
|
|
||||||
mLineSpace = getRawSize(TypedValue.COMPLEX_UNIT_DIP, lineSpace);
|
|
||||||
measureLineHeight();
|
|
||||||
mScrollY = measureCurrentScrollY(mCurrentPlayLine);
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
resetView();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void actionCancel(MotionEvent event) {
|
|
||||||
releaseVelocityTracker();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void actionDown(MotionEvent event) {
|
|
||||||
removeCallbacks(hideIndicator);
|
|
||||||
mLastScrollY = mScrollY;
|
|
||||||
mDownX = event.getX();
|
|
||||||
mDownY = event.getY();
|
|
||||||
if (mFlingAnimator != null) {
|
|
||||||
mFlingAnimator.cancel();
|
|
||||||
mFlingAnimator = null;
|
|
||||||
}
|
|
||||||
setUserTouch(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean overScrolled() {
|
|
||||||
|
|
||||||
return scrollable() && (mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0) || mScrollY < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void actionMove(MotionEvent event) {
|
|
||||||
if (scrollable()) {
|
|
||||||
final VelocityTracker tracker = mVelocityTracker;
|
|
||||||
tracker.computeCurrentVelocity(UNITS_SECOND, maxVelocity);
|
|
||||||
mScrollY = mLastScrollY + mDownY - event.getY();
|
|
||||||
mVelocity = tracker.getYVelocity();
|
|
||||||
measureCurrentLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void actionUp(MotionEvent event) {
|
|
||||||
|
|
||||||
postDelayed(hideIndicator, 3 * UNITS_SECOND);
|
|
||||||
|
|
||||||
releaseVelocityTracker();
|
|
||||||
|
|
||||||
if (scrollable()) {
|
|
||||||
if (overScrolled() && mScrollY < 0) {
|
|
||||||
smoothScrollTo(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (overScrolled() && mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)) {
|
|
||||||
smoothScrollTo(mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Math.abs(mVelocity) > THRESHOLD_Y_VELOCITY) {
|
|
||||||
doFlingAnimator(mVelocity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mShowIndicator && clickPlayer(event)) {
|
|
||||||
if (mLineNumberUnderIndicator != mCurrentPlayLine) {
|
|
||||||
mShowIndicator = false;
|
|
||||||
if (mClickListener != null) {
|
|
||||||
setUserTouch(false);
|
|
||||||
mClickListener.onPlayerClicked(mLyricInfo.songLines.get(mLineNumberUnderIndicator).start, mLyricInfo.songLines.get(mLineNumberUnderIndicator).content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String measureCurrentTime() {
|
|
||||||
DecimalFormat format = new DecimalFormat("00");
|
|
||||||
if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 < mLineCount && mLineNumberUnderIndicator > 0) {
|
|
||||||
return format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 % 60);
|
|
||||||
}
|
|
||||||
if (mLyricInfo != null && mLineCount > 0 && (mLineNumberUnderIndicator - 1) >= mLineCount) {
|
|
||||||
return format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 % 60);
|
|
||||||
}
|
|
||||||
if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 <= 0) {
|
|
||||||
return format.format(mLyricInfo.songLines.get(0).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(0).start / 1000 % 60);
|
|
||||||
}
|
|
||||||
return mDefaultTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawIndicator(Canvas canvas) {
|
|
||||||
|
|
||||||
//绘制 播放 按钮
|
|
||||||
Path pathPlay = new Path();
|
|
||||||
float yCoordinate = mBtnPlayRect.left + (float) Math.sqrt(Math.pow(mBtnPlayRect.width(), 2) - Math.pow(mBtnPlayRect.width() * 0.5f, 2));
|
|
||||||
float remainWidth = mBtnPlayRect.right - yCoordinate;
|
|
||||||
|
|
||||||
pathPlay.moveTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f);
|
|
||||||
pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() + mBtnPlayRect.height() * 0.5f);
|
|
||||||
pathPlay.lineTo(yCoordinate, mBtnPlayRect.centerY());
|
|
||||||
pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f);
|
|
||||||
|
|
||||||
canvas.drawPath(pathPlay, mBtnPlayPaint);
|
|
||||||
|
|
||||||
//绘制 分割线
|
|
||||||
Path pathLine = new Path();
|
|
||||||
pathLine.moveTo(mBtnPlayRect.right + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN) - remainWidth, getMeasuredHeight() * 0.5f);
|
|
||||||
pathLine.lineTo(getWidth() - mTimerRect.width() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT) - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN), getHeight() * 0.5f);
|
|
||||||
canvas.drawPath(pathLine, mLinePaint);
|
|
||||||
|
|
||||||
//绘制 时间
|
|
||||||
canvas.drawText(measureCurrentTime(), getWidth() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT), (getHeight() + mTimerRect.height()) * 0.5f, mTimerPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean clickPlayer(MotionEvent event) {
|
|
||||||
if (mBtnPlayRect != null && mDownX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT)) {
|
|
||||||
float upX = event.getX();
|
|
||||||
float upY = event.getY();
|
|
||||||
return upX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doFlingAnimator(float velocity) {
|
|
||||||
|
|
||||||
float distance = (velocity / Math.abs(velocity) * (Math.abs(velocity) * SLIDE_COEFFICIENT));
|
|
||||||
float to = Math.min(Math.max(0, (mScrollY - distance)), (mLineCount - 1) * mLineHeight + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0));
|
|
||||||
|
|
||||||
mFlingAnimator = ValueAnimator.ofFloat(mScrollY, to);
|
|
||||||
mFlingAnimator.addUpdateListener(animation -> {
|
|
||||||
mScrollY = (float) animation.getAnimatedValue();
|
|
||||||
measureCurrentLine();
|
|
||||||
invalidateView();
|
|
||||||
});
|
|
||||||
|
|
||||||
mFlingAnimator.addListener(new AnimatorListenerAdapter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animation) {
|
|
||||||
super.onAnimationStart(animation);
|
|
||||||
mVelocity = 0;
|
|
||||||
mFling = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
super.onAnimationEnd(animation);
|
|
||||||
mFling = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
super.onAnimationCancel(animation);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mFlingAnimator.setDuration(FLING_ANIMATOR_DURATION);
|
|
||||||
mFlingAnimator.setInterpolator(new DecelerateInterpolator());
|
|
||||||
mFlingAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUserTouch(boolean isUserTouch) {
|
|
||||||
if (isUserTouch) {
|
|
||||||
mUserTouch = true;
|
|
||||||
mShowIndicator = true;
|
|
||||||
} else {
|
|
||||||
mUserTouch = false;
|
|
||||||
mShowIndicator = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseVelocityTracker() {
|
|
||||||
if (mVelocityTracker != null) {
|
|
||||||
mVelocityTracker.clear();
|
|
||||||
mVelocityTracker.recycle();
|
|
||||||
mVelocityTracker = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMyView(Context context) {
|
|
||||||
maxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
|
|
||||||
initPaint();
|
|
||||||
initAllBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAllBounds() {
|
|
||||||
setRawTextSize(mTextSize);
|
|
||||||
|
|
||||||
setLineSpace(mLineSpace);
|
|
||||||
measureLineHeight();
|
|
||||||
|
|
||||||
mTimerRect = new Rect();
|
|
||||||
mTimerPaint.getTextBounds(mDefaultTime, 0, mDefaultTime.length(), mTimerRect);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initPaint() {
|
|
||||||
mTextPaint = new TextPaint();
|
|
||||||
mTextPaint.setDither(true);
|
|
||||||
mTextPaint.setAntiAlias(true);
|
|
||||||
|
|
||||||
switch (mTextAlign) {
|
|
||||||
case LEFT:
|
|
||||||
mTextPaint.setTextAlign(Paint.Align.LEFT);
|
|
||||||
break;
|
|
||||||
case CENTER:
|
|
||||||
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
|
||||||
break;
|
|
||||||
case RIGHT:
|
|
||||||
mTextPaint.setTextAlign(Paint.Align.RIGHT);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mBtnPlayPaint = new Paint();
|
|
||||||
mBtnPlayPaint.setDither(true);
|
|
||||||
mBtnPlayPaint.setAntiAlias(true);
|
|
||||||
mBtnPlayPaint.setColor(mBtnColor);
|
|
||||||
mBtnPlayPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
||||||
mBtnPlayPaint.setAlpha(128);
|
|
||||||
|
|
||||||
mLinePaint = new Paint();
|
|
||||||
mLinePaint.setDither(true);
|
|
||||||
mLinePaint.setAntiAlias(true);
|
|
||||||
mLinePaint.setColor(mLineColor);
|
|
||||||
mLinePaint.setAlpha(64);
|
|
||||||
mLinePaint.setStrokeWidth(1.0f);
|
|
||||||
mLinePaint.setStyle(Paint.Style.STROKE);
|
|
||||||
|
|
||||||
mTimerPaint = new Paint();
|
|
||||||
mTimerPaint.setDither(true);
|
|
||||||
mTimerPaint.setAntiAlias(true);
|
|
||||||
mTimerPaint.setColor(Color.WHITE);
|
|
||||||
mTimerPaint.setTextAlign(Paint.Align.RIGHT);
|
|
||||||
mTimerPaint.setTextSize(getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_TIME_TEXT_SIZE));
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private float measureCurrentScrollY(int line) {
|
|
||||||
if (mEnableLineFeed && line > 1) {
|
|
||||||
return (line - 1) * mLineHeight + mLineFeedRecord.get(line - 1);
|
|
||||||
}
|
|
||||||
return (line - 1) * mLineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void invalidateView() {
|
|
||||||
if (Looper.getMainLooper() == Looper.myLooper()) {
|
|
||||||
// 当前线程是主UI线程,直接刷新。
|
|
||||||
invalidate();
|
|
||||||
} else {
|
|
||||||
// 当前线程是非UI线程,post刷新。
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void measureLineHeight() {
|
|
||||||
Rect lineBound = new Rect();
|
|
||||||
mTextPaint.getTextBounds(mDefaultHint, 0, mDefaultHint.length(), lineBound);
|
|
||||||
mTextHeight = lineBound.height();
|
|
||||||
mLineHeight = mTextHeight + mLineSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To measure current showing line number based on the view's scroll Y
|
|
||||||
*/
|
|
||||||
private void measureCurrentLine() {
|
|
||||||
float baseScrollY = mScrollY + mLineHeight * 0.5f;
|
|
||||||
|
|
||||||
if (mEnableLineFeed) {
|
|
||||||
for (int i = mLyricInfo.songLines.size(); i >= 0; i--) {
|
|
||||||
if (baseScrollY > measureCurrentScrollY(i) + mLineSpace * 0.2) {
|
|
||||||
mLineNumberUnderIndicator = i - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mLineNumberUnderIndicator = (int) (baseScrollY / mLineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void smoothScrollTo(float toY) {
|
|
||||||
final ValueAnimator animator = ValueAnimator.ofFloat(mScrollY, toY);
|
|
||||||
animator.addUpdateListener(valueAnimator -> {
|
|
||||||
mScrollY = (Float) valueAnimator.getAnimatedValue();
|
|
||||||
invalidateView();
|
|
||||||
});
|
|
||||||
|
|
||||||
animator.addListener(new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animator) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animator) {
|
|
||||||
mFling = false;
|
|
||||||
measureCurrentLine();
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animator) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animator) {
|
|
||||||
mFling = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
animator.setDuration(640);
|
|
||||||
animator.setInterpolator(new LinearInterpolator());
|
|
||||||
|
|
||||||
animator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean scrollable() {
|
|
||||||
return mLyricInfo != null && mLyricInfo.songLines != null && mLyricInfo.songLines.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scrollToCurrentTimeMillis(long time) {
|
|
||||||
|
|
||||||
int position = 0;
|
|
||||||
if (scrollable()) {
|
|
||||||
for (int i = 0, size = mLineCount; i < size; i++) {
|
|
||||||
LineInfo lineInfo = mLyricInfo.songLines.get(i);
|
|
||||||
if (lineInfo != null && lineInfo.start >= time) {
|
|
||||||
position = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i == mLineCount - 1) {
|
|
||||||
position = mLineCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mCurrentPlayLine != position) {
|
|
||||||
mCurrentPlayLine = position;
|
|
||||||
if (!mFling && !mUserTouch) {
|
|
||||||
smoothScrollTo(measureCurrentScrollY(position));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupLyricResource(InputStream inputStream, String charsetName) {
|
|
||||||
if (inputStream != null) {
|
|
||||||
try {
|
|
||||||
LyricInfo lyricInfo = new LyricInfo();
|
|
||||||
lyricInfo.songLines = new ArrayList<>();
|
|
||||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
|
|
||||||
BufferedReader reader = new BufferedReader(inputStreamReader);
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
analyzeLyric(lyricInfo, line);
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
inputStream.close();
|
|
||||||
inputStreamReader.close();
|
|
||||||
|
|
||||||
mLyricInfo = lyricInfo;
|
|
||||||
mLineCount = mLyricInfo.songLines.size();
|
|
||||||
invalidateView();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 逐行解析歌词内容
|
|
||||||
*/
|
|
||||||
private void analyzeLyric(LyricInfo lyricInfo, String line) {
|
|
||||||
int index = line.lastIndexOf("]");
|
|
||||||
if (line.startsWith("[offset:")) {
|
|
||||||
// time offset
|
|
||||||
lyricInfo.songOffset = Long.parseLong(line.substring(8, index).trim());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (line.startsWith("[ti:")) {
|
|
||||||
// title
|
|
||||||
lyricInfo.songTitle = line.substring(4, index).trim();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (line.startsWith("[ar:")) {
|
|
||||||
// artist
|
|
||||||
lyricInfo.songArtist = line.substring(4, index).trim();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (line.startsWith("[al:")) {
|
|
||||||
// album
|
|
||||||
lyricInfo.songAlbum = line.substring(4, index).trim();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (line.startsWith("[by:")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (index >= 9 && line.trim().length() > index + 1) {
|
|
||||||
// lyrics
|
|
||||||
LineInfo lineInfo = new LineInfo();
|
|
||||||
lineInfo.content = line.substring(10, line.length());
|
|
||||||
lineInfo.start = measureStartTimeMillis(line.substring(0, index));
|
|
||||||
lyricInfo.songLines.add(lineInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从字符串中获得时间值
|
|
||||||
*/
|
|
||||||
private long measureStartTimeMillis(String str) {
|
|
||||||
long minute = Long.parseLong(str.substring(1, 3));
|
|
||||||
long second = Long.parseLong(str.substring(4, 6));
|
|
||||||
long millisecond = Long.parseLong(str.substring(7, 9));
|
|
||||||
return millisecond + second * 1000 + minute * 60 * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetLyricInfo() {
|
|
||||||
if (mLyricInfo != null) {
|
|
||||||
if (mLyricInfo.songLines != null) {
|
|
||||||
mLyricInfo.songLines.clear();
|
|
||||||
mLyricInfo.songLines = null;
|
|
||||||
}
|
|
||||||
mLyricInfo = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetView() {
|
|
||||||
mCurrentPlayLine = 0;
|
|
||||||
resetLyricInfo();
|
|
||||||
invalidateView();
|
|
||||||
mLineCount = 0;
|
|
||||||
mScrollY = 0;
|
|
||||||
mEnableLineFeed = false;
|
|
||||||
mLineFeedRecord.clear();
|
|
||||||
mExtraHeight = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getRawSize(int unit, float size) {
|
|
||||||
Context context = getContext();
|
|
||||||
Resources resources;
|
|
||||||
if (context == null) {
|
|
||||||
resources = Resources.getSystem();
|
|
||||||
} else {
|
|
||||||
resources = context.getResources();
|
|
||||||
}
|
|
||||||
return TypedValue.applyDimension(unit, size, resources.getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setRawTextSize(float size) {
|
|
||||||
if (size != mTextPaint.getTextSize()) {
|
|
||||||
mTextPaint.setTextSize(size);
|
|
||||||
measureLineHeight();
|
|
||||||
mScrollY = measureCurrentScrollY(mCurrentPlayLine);
|
|
||||||
invalidateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntDef({LEFT, CENTER, RIGHT})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface Alignment {
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnPlayerClickListener {
|
|
||||||
void onPlayerClicked(long progress, String content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LyricInfo {
|
|
||||||
List<LineInfo> songLines;
|
|
||||||
|
|
||||||
String songArtist;
|
|
||||||
String songTitle;
|
|
||||||
String songAlbum;
|
|
||||||
|
|
||||||
long songOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LineInfo {
|
|
||||||
String content;
|
|
||||||
long start;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -79,7 +79,7 @@
|
||||||
android:id="@+id/albumTitleContainer"
|
android:id="@+id/albumTitleContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
android:id="@+id/albumTitleContainer"
|
android:id="@+id/albumTitleContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -122,7 +122,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -140,7 +140,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
|
|
@ -55,11 +55,12 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
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"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:src="@drawable/ic_edit_white_24dp" />
|
android:text="@string/edit"
|
||||||
|
app:icon="@drawable/ic_edit_white_24dp" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -69,12 +69,11 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/songTextContainer"
|
android:id="@+id/songTextContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
app:boxBackgroundMode="outline"
|
|
||||||
app:hintEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<code.name.monkey.appthemehelper.common.views.ATEEditText
|
||||||
android:id="@+id/songText"
|
android:id="@+id/songText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -86,10 +85,10 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/albumTextContainer"
|
android:id="@+id/albumTextContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.views.ATEEditText
|
<code.name.monkey.appthemehelper.common.views.ATEEditText
|
||||||
|
@ -103,10 +102,10 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/artistContainer"
|
android:id="@+id/artistContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -123,10 +122,10 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/albumArtistContainer"
|
android:id="@+id/albumArtistContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,10 +143,10 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/composerContainer"
|
android:id="@+id/composerContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -170,11 +169,11 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/genreContainer"
|
android:id="@+id/genreContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,12 +191,12 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/yearContainer"
|
android:id="@+id/yearContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
|
|
||||||
|
@ -216,10 +215,10 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/trackNumberContainer"
|
android:id="@+id/trackNumberContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,10 +236,10 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/lyricsContainer"
|
android:id="@+id/lyricsContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
@ -101,12 +102,12 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/nameContainer"
|
android:id="@+id/nameContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
app:boxBackgroundMode="filled"
|
app:hintAnimationEnabled="true">
|
||||||
app:hintEnabled="true">
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -115,19 +116,20 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@null"
|
android:background="@null"
|
||||||
android:hint="@string/my_name"
|
android:hint="@string/my_name"
|
||||||
|
tools:text="@string/song"
|
||||||
android:inputType="textPersonName|textCapWords|text"
|
android:inputType="textPersonName|textCapWords|text"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/bioContainer"
|
android:id="@+id/bioContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
app:boxBackgroundMode="filled"
|
app:hintAnimationEnabled="true">
|
||||||
app:hintEnabled="true">
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="16dp"
|
android:orientation="vertical"
|
||||||
android:orientation="vertical">
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -7,38 +7,48 @@
|
||||||
android:paddingBottom="16dp">
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||||
android:id="@+id/dialogTitle"
|
android:id="@+id/bannerTitle"
|
||||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:text="@string/remove_song_from_playlist_title" />
|
android:text="@string/remove_song_from_playlist_title" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<LinearLayout
|
||||||
android:id="@+id/actionDelete"
|
|
||||||
style="@style/Widget.MaterialComponents.Button"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:gravity="end"
|
||||||
android:layout_marginEnd="8dp"
|
android:orientation="horizontal">
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:paddingTop="14dp"
|
|
||||||
android:paddingBottom="14dp"
|
|
||||||
android:text="@string/remove_action"
|
|
||||||
android:textAllCaps="false" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/actionCancel"
|
android:id="@+id/actionCancel"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_margin="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:gravity="center_vertical"
|
||||||
android:gravity="start|center_vertical"
|
android:paddingStart="24dp"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="12dp"
|
||||||
android:paddingBottom="14dp"
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
android:text="@android:string/cancel"
|
android:text="@android:string/cancel"
|
||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
app:strokeWidth="2dp" />
|
app:strokeWidth="2dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/actionDelete"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:text="@string/remove_action"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -16,18 +16,11 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/actionNewPlaylistContainer"
|
android:id="@+id/actionNewPlaylistContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
app:boxBackgroundMode="outline"
|
|
||||||
app:boxCollapsedPaddingTop="16dp"
|
|
||||||
app:boxCornerRadiusBottomEnd="8dp"
|
|
||||||
app:boxCornerRadiusBottomStart="8dp"
|
|
||||||
app:boxCornerRadiusTopEnd="8dp"
|
|
||||||
app:boxCornerRadiusTopStart="8dp"
|
|
||||||
app:boxStrokeColor="?android:attr/textColorPrimary"
|
|
||||||
app:boxStrokeWidth="1dp"
|
|
||||||
app:hintEnabled="true">
|
app:hintEnabled="true">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
@ -40,34 +33,38 @@
|
||||||
android:padding="16dp" />
|
android:padding="16dp" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<LinearLayout
|
||||||
android:id="@+id/actionCreate"
|
|
||||||
style="@style/Widget.MaterialComponents.Button"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:gravity="end"
|
||||||
android:layout_marginTop="8dp"
|
android:orientation="horizontal">
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingTop="14dp"
|
|
||||||
android:paddingBottom="14dp"
|
|
||||||
android:text="@string/create_action"
|
|
||||||
app:backgroundTint="@color/md_pink_A400"
|
|
||||||
app:icon="@drawable/ic_playlist_add_white_24dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/actionCancel"
|
android:id="@+id/actionCancel"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_margin="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:paddingStart="24dp"
|
||||||
android:gravity="center_vertical"
|
android:paddingTop="12dp"
|
||||||
android:paddingTop="14dp"
|
android:paddingEnd="24dp"
|
||||||
android:paddingBottom="14dp"
|
android:paddingBottom="12dp"
|
||||||
android:text="@android:string/cancel"
|
android:text="@android:string/cancel"
|
||||||
app:icon="@drawable/ic_close_white_24dp"
|
app:icon="@drawable/ic_close_white_24dp"
|
||||||
app:strokeWidth="2dp" />
|
app:strokeWidth="2dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/actionCreate"
|
||||||
|
style="@style/Widget.MaterialComponents.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:text="@string/create_action"
|
||||||
|
app:backgroundTint="@color/md_pink_A400"
|
||||||
|
app:icon="@drawable/ic_playlist_add_white_24dp" />
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
<include layout="@layout/shadow_statusbar_toolbar" />
|
<include layout="@layout/shadow_statusbar_toolbar" />
|
||||||
|
|
||||||
<code.name.monkey.retromusic.views.StatusBarMarginFrameLayout
|
<code.name.monkey.retromusic.views.FitSystemWindowsLayout
|
||||||
android:id="@+id/safeArea"
|
android:id="@+id/safeArea"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
@ -30,6 +30,13 @@
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<include layout="@layout/status_bar" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<code.name.monkey.retromusic.views.WidthFitSquareLayout
|
||||||
android:id="@+id/playerAlbumCoverContainer"
|
android:id="@+id/playerAlbumCoverContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -41,7 +48,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:layout="@layout/fragment_album_cover" />
|
tools:layout="@layout/fragment_album_cover" />
|
||||||
</FrameLayout>
|
</code.name.monkey.retromusic.views.WidthFitSquareLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -69,5 +76,5 @@
|
||||||
app:navigationIcon="@drawable/ic_close_white_24dp" />
|
app:navigationIcon="@drawable/ic_close_white_24dp" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</code.name.monkey.retromusic.views.StatusBarMarginFrameLayout>
|
</code.name.monkey.retromusic.views.FitSystemWindowsLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -6,15 +6,8 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<code.name.monkey.retromusic.views.LyricView
|
<com.lauzy.freedom.library.LrcView
|
||||||
android:id="@+id/lyricsView"
|
android:id="@+id/lyricsView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent" />
|
||||||
app:fadeInFadeOut="true"
|
|
||||||
app:highlightColor="@color/md_white_1000"
|
|
||||||
app:hint="No Lyrics"
|
|
||||||
app:hintColor="@color/md_grey_400"
|
|
||||||
app:lineSpace="15dp"
|
|
||||||
app:textAlign="left"
|
|
||||||
app:textSize="18sp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -24,21 +24,6 @@
|
||||||
<attr name="url_link" format="string" />
|
<attr name="url_link" format="string" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="LyricView">
|
|
||||||
<attr name="hint" format="string" />
|
|
||||||
<attr name="hintColor" format="color" />
|
|
||||||
<attr name="textColor" format="color" />
|
|
||||||
<attr name="highlightColor" format="color" />
|
|
||||||
<attr name="textSize" format="dimension" />
|
|
||||||
<attr name="maxLength" format="dimension" />
|
|
||||||
<attr name="fadeInFadeOut" format="boolean" />
|
|
||||||
<attr name="lineSpace" format="dimension" />
|
|
||||||
<attr name="textAlign">
|
|
||||||
<enum name="left" value="0" />
|
|
||||||
<enum name="center" value="1" />
|
|
||||||
<enum name="right" value="2" />
|
|
||||||
</attr>
|
|
||||||
</declare-styleable>
|
|
||||||
<declare-styleable name="ContributorsView">
|
<declare-styleable name="ContributorsView">
|
||||||
<attr name="profile_url" format="string" />
|
<attr name="profile_url" format="string" />
|
||||||
<attr name="profile_name" format="string" />
|
<attr name="profile_name" format="string" />
|
||||||
|
|
|
@ -606,5 +606,6 @@
|
||||||
<string name="save">Save</string>
|
<string name="save">Save</string>
|
||||||
<string name="pick_image_intent_text">Pick image</string>
|
<string name="pick_image_intent_text">Pick image</string>
|
||||||
<string name="set_photo">Set a profile photo</string>
|
<string name="set_photo">Set a profile photo</string>
|
||||||
|
<string name="edit">Edit</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/primary_color_desc"
|
android:summary="@string/primary_color_desc"
|
||||||
android:title="@string/primary_color"
|
android:title="@string/primary_color"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATEColorPreference
|
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATEColorPreference
|
||||||
android:key="accent_color"
|
android:key="accent_color"
|
||||||
|
|
|
@ -2,6 +2,7 @@ package code.name.monkey.appthemehelper.util
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
|
@ -10,6 +11,7 @@ import code.name.monkey.appthemehelper.ThemeStore
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
||||||
|
|
||||||
object MaterialUtil {
|
object MaterialUtil {
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
|
@ -39,15 +41,20 @@ object MaterialUtil {
|
||||||
val colorState = ColorStateList.valueOf(accentColor)
|
val colorState = ColorStateList.valueOf(accentColor)
|
||||||
|
|
||||||
if (background) {
|
if (background) {
|
||||||
//textInputLayout.backgroundTintList = colorState
|
textInputLayout.backgroundTintList = colorState
|
||||||
textInputLayout.defaultHintTextColor = colorState
|
textInputLayout.defaultHintTextColor = colorState
|
||||||
} else {
|
} else {
|
||||||
textInputLayout.boxStrokeColor = accentColor
|
textInputLayout.boxStrokeColor = accentColor
|
||||||
textInputLayout.defaultHintTextColor = colorState
|
textInputLayout.defaultHintTextColor = colorState
|
||||||
textInputLayout.isHintAnimationEnabled = true
|
textInputLayout.isHintAnimationEnabled = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun setCursorPointerColor(view: EditText, @ColorInt color: Int) {
|
private fun setCursorPointerColor(view: EditText, @ColorInt color: Int) {
|
||||||
try {
|
try {
|
||||||
//get the pointer resource id
|
//get the pointer resource id
|
||||||
|
|
1
library/.gitignore
vendored
Normal file
1
library/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
41
library/build.gradle
Normal file
41
library/build.gradle
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 27
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.1'
|
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
|
||||||
|
implementation "androidx.core:core-ktx:+"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
21
library/proguard-rules.pro
vendored
Normal file
21
library/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.lauzy.freedom.library;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("com.lauzy.freedom.library.test", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
2
library/src/main/AndroidManifest.xml
Normal file
2
library/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.lauzy.freedom.library"/>
|
30
library/src/main/java/com/lauzy/freedom/library/Lrc.java
Normal file
30
library/src/main/java/com/lauzy/freedom/library/Lrc.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package com.lauzy.freedom.library;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desc : 歌词实体
|
||||||
|
* Author : Lauzy
|
||||||
|
* Date : 2017/10/13
|
||||||
|
* Blog : http://www.jianshu.com/u/e76853f863a9
|
||||||
|
* Email : freedompaladin@gmail.com
|
||||||
|
*/
|
||||||
|
public class Lrc {
|
||||||
|
private long time;
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public void setTime(long time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
137
library/src/main/java/com/lauzy/freedom/library/LrcHelper.java
Normal file
137
library/src/main/java/com/lauzy/freedom/library/LrcHelper.java
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package com.lauzy.freedom.library;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desc : 歌词解析
|
||||||
|
* Author : Lauzy
|
||||||
|
* Date : 2017/10/13
|
||||||
|
* Blog : http://www.jianshu.com/u/e76853f863a9
|
||||||
|
* Email : freedompaladin@gmail.com
|
||||||
|
*/
|
||||||
|
public class LrcHelper {
|
||||||
|
|
||||||
|
private static final String CHARSET = "utf-8";
|
||||||
|
//[03:56.00][03:18.00][02:06.00][01:07.00]原谅我这一生不羁放纵爱自由
|
||||||
|
private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2}])+)(.*)";
|
||||||
|
private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2})]";
|
||||||
|
|
||||||
|
public static List<Lrc> parseLrcFromAssets(Context context, String fileName) {
|
||||||
|
try {
|
||||||
|
return parseInputStream(context.getResources().getAssets().open(fileName));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Lrc> parseLrcFromFile(File file) {
|
||||||
|
try {
|
||||||
|
return parseInputStream(new FileInputStream(file));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Lrc> parseInputStream(InputStream inputStream) {
|
||||||
|
List<Lrc> lrcs = new ArrayList<>();
|
||||||
|
InputStreamReader isr = null;
|
||||||
|
BufferedReader br = null;
|
||||||
|
try {
|
||||||
|
isr = new InputStreamReader(inputStream, CHARSET);
|
||||||
|
br = new BufferedReader(isr);
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
List<Lrc> lrcList = parseLrc(line);
|
||||||
|
if (lrcList != null && lrcList.size() != 0) {
|
||||||
|
lrcs.addAll(lrcList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortLrcs(lrcs);
|
||||||
|
return lrcs;
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (isr != null) {
|
||||||
|
isr.close();
|
||||||
|
}
|
||||||
|
if (br != null) {
|
||||||
|
br.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lrcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sortLrcs(List<Lrc> lrcs) {
|
||||||
|
Collections.sort(lrcs, new Comparator<Lrc>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Lrc o1, Lrc o2) {
|
||||||
|
return (int) (o1.getTime() - o2.getTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Lrc> parseLrc(String lrcLine) {
|
||||||
|
if (lrcLine.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<Lrc> lrcs = new ArrayList<>();
|
||||||
|
Matcher matcher = Pattern.compile(LINE_REGEX).matcher(lrcLine);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String time = matcher.group(1);
|
||||||
|
String content = matcher.group(3);
|
||||||
|
Matcher timeMatcher = Pattern.compile(TIME_REGEX).matcher(time);
|
||||||
|
|
||||||
|
while (timeMatcher.find()) {
|
||||||
|
String min = timeMatcher.group(1);
|
||||||
|
String sec = timeMatcher.group(2);
|
||||||
|
String mil = timeMatcher.group(3);
|
||||||
|
Lrc lrc = new Lrc();
|
||||||
|
if (content != null && content.length() != 0) {
|
||||||
|
lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000
|
||||||
|
+ Long.parseLong(mil) * 10);
|
||||||
|
lrc.setText(content);
|
||||||
|
lrcs.add(lrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lrcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatTime(long time) {
|
||||||
|
int min = (int) (time / 60000);
|
||||||
|
int sec = (int) (time / 1000 % 60);
|
||||||
|
return adjustFormat(min) + ":" + adjustFormat(sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String adjustFormat(int time) {
|
||||||
|
if (time < 10) {
|
||||||
|
return "0" + time;
|
||||||
|
}
|
||||||
|
return time + "";
|
||||||
|
}
|
||||||
|
}
|
620
library/src/main/java/com/lauzy/freedom/library/LrcView.kt
Normal file
620
library/src/main/java/com/lauzy/freedom/library/LrcView.kt
Normal file
|
@ -0,0 +1,620 @@
|
||||||
|
package com.lauzy.freedom.library
|
||||||
|
|
||||||
|
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.content.res.ResourcesCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
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.play_icon) 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
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
BIN
library/src/main/res/drawable-xhdpi/play_icon.png
Normal file
BIN
library/src/main/res/drawable-xhdpi/play_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 707 B |
11
library/src/main/res/font/circular.xml
Normal file
11
library/src/main/res/font/circular.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<font
|
||||||
|
android:font="@font/circular_std_book"
|
||||||
|
android:fontStyle="normal"
|
||||||
|
android:fontWeight="400" />
|
||||||
|
<font
|
||||||
|
android:font="@font/circular_std_black"
|
||||||
|
android:fontWeight="900" />
|
||||||
|
|
||||||
|
</font-family>
|
BIN
library/src/main/res/font/circular_std_black.otf
Executable file
BIN
library/src/main/res/font/circular_std_black.otf
Executable file
Binary file not shown.
BIN
library/src/main/res/font/circular_std_book.otf
Executable file
BIN
library/src/main/res/font/circular_std_book.otf
Executable file
Binary file not shown.
24
library/src/main/res/values/attrs.xml
Normal file
24
library/src/main/res/values/attrs.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="LrcView">
|
||||||
|
<attr name="lrcTextSize" format="dimension"/>
|
||||||
|
<attr name="lrcLineSpaceSize" format="dimension"/>
|
||||||
|
<attr name="lrcNormalTextColor" format="reference|color"/>
|
||||||
|
<attr name="lrcCurrentTextColor" format="reference|color"/>
|
||||||
|
<attr name="lrcTouchDelay" format="integer"/>
|
||||||
|
<attr name="noLrcTextSize" format="dimension"/>
|
||||||
|
<attr name="noLrcTextColor" format="reference|color"/>
|
||||||
|
<attr name="indicatorLineHeight" format="dimension"/>
|
||||||
|
<attr name="indicatorTextSize" format="dimension"/>
|
||||||
|
<attr name="indicatorTextColor" format="reference|color"/>
|
||||||
|
<attr name="currentIndicateLrcColor" format="reference|color"/>
|
||||||
|
<attr name="indicatorTouchDelay" format="integer"/>
|
||||||
|
<attr name="indicatorLineColor" format="reference|color"/>
|
||||||
|
<attr name="indicatorStartEndMargin" format="dimension"/>
|
||||||
|
<attr name="iconLineGap" format="dimension"/>
|
||||||
|
<attr name="playIconWidth" format="dimension"/>
|
||||||
|
<attr name="playIconHeight" format="dimension"/>
|
||||||
|
<attr name="playIcon" format="reference"/>
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
</resources>
|
3
library/src/main/res/values/strings.xml
Normal file
3
library/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">library</string>
|
||||||
|
</resources>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.lauzy.freedom.library;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
include ':app', ':appthemehelper'
|
include ':app', ':appthemehelper', ':library'
|
Loading…
Reference in a new issue