Updates edittext views with corner rounded

This commit is contained in:
h4h13 2019-03-28 19:02:53 +05:30
parent 7a42723b9e
commit f9f30c8387
46 changed files with 1127 additions and 1286 deletions

View file

@ -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()

View file

@ -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>

View file

@ -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)

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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)
} }

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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")
} }
} }
} }

View file

@ -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)

View file

@ -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)

View file

@ -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()
} }

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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">

View file

@ -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

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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" />

View file

@ -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>

View file

@ -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"

View file

@ -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
View file

@ -0,0 +1 @@
/build

41
library/build.gradle Normal file
View 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
View 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

View file

@ -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());
}
}

View file

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lauzy.freedom.library"/>

View 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;
}
}

View 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 + "";
}
}

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

View 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>

Binary file not shown.

Binary file not shown.

View 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>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">library</string>
</resources>

View file

@ -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);
}
}

View file

@ -1 +1 @@
include ':app', ':appthemehelper' include ':app', ':appthemehelper', ':library'