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

@ -14,7 +14,6 @@
package code.name.monkey.retromusic.dialogs
import android.content.Context
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
@ -22,28 +21,26 @@ import android.view.View
import android.view.ViewGroup
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil
import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment
import kotlinx.android.synthetic.main.dialog_playlist.*
import java.util.*
class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_playlist, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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(actionCancel, false)
@ -51,17 +48,17 @@ class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() {
actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor))
actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!))
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
val songs = arguments!!.getParcelableArrayList<Song>("songs")
actionCancel.setOnClickListener { dismiss() }
actionCreate.setOnClickListener {
if (activity == null) {
return@setOnClickListener
}
if (!actionNewPlaylist!!.text!!.toString().trim { it <= ' ' }.isEmpty()) {
val playlistId = PlaylistsUtil
.createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString())
val playlistId = PlaylistsUtil.createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString())
if (playlistId != -1 && activity != null) {
if (songs != null) {
PlaylistsUtil.addToPlaylist(activity!!, songs, playlistId, true)

View file

@ -48,8 +48,8 @@ class DeletePlaylistDialog : RoundedBottomSheetDialogFragment() {
} else {
Html.fromHtml(getString(R.string.delete_playlist_x, playlists[0].name))
}
dialogTitle.text = content
dialogTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
bannerTitle.text = content
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
actionDelete.apply {
setText(R.string.action_delete)

View file

@ -34,7 +34,7 @@ class DeleteSongsDialog : RoundedBottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dialogTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
//noinspection unchecked,ConstantConditions
val songs = arguments!!.getParcelableArrayList<Song>("songs")
val content: CharSequence
@ -44,7 +44,7 @@ class DeleteSongsDialog : RoundedBottomSheetDialogFragment() {
} else {
getString(R.string.delete_song_x, songs[0].title)
}
dialogTitle.text = Html.fromHtml(content)
bannerTitle.text = Html.fromHtml(content)
}
actionDelete.apply {
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.util.PlaylistsUtil
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() {
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?) {
super.onViewCreated(view, savedInstanceState)
val songs = arguments!!.getParcelableArrayList<Song>("songs")
val title: Int
val content: CharSequence
if (songs!!.size > 1) {
title = R.string.remove_songs_from_playlist_title
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)
} else {
Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size))
}
} else {
title = R.string.remove_song_from_playlist_title
content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs[0].title))
}
bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!))
bannerTitle.text = content;
actionDelete.apply {
setText(title)
setIconResource(R.drawable.ic_delete_white_24dp)
setText(R.string.remove_action)
setTextColor(ThemeStore.textColorSecondary(context))
setOnClickListener {
val playlistSongs = ArrayList<PlaylistSong>()
@ -68,6 +66,7 @@ class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() {
actionCancel.apply {
setIconResource(R.drawable.ic_close_white_24dp)
setTextColor(ThemeStore.textColorSecondary(context))
setOnClickListener { dismiss() }
MaterialUtil.setTint(this, false)

View file

@ -36,10 +36,14 @@ class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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)
val accentColor = ThemeStore.accentColor(context!!)
actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor))
actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!))
actionNewPlaylist.apply {
var playlistId: Long = 0
@ -47,14 +51,10 @@ class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() {
playlistId = arguments!!.getLong("playlist_id")
}
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 {
MaterialUtil.setTint(actionCancel, false)
MaterialUtil.setTint(this, false)
setOnClickListener { dismiss() }
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!!
}
.map { it.filter { albumSearch -> albumSearch.id != album.id } }
.subscribe {
it.remove(album)
if (!it.isEmpty()) {
moreTitle.visibility = View.VISIBLE
moreRecyclerView.visibility = View.VISIBLE
} else {
for (albumFinal in it) {
if (albumFinal.id == album.id)
println("$albumFinal -> $album")
}
if (it.isEmpty()) {
return@subscribe
}
moreTitle.visibility = View.VISIBLE
moreRecyclerView.visibility = View.VISIBLE
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.adapter = albumAdapter

View file

@ -1,6 +1,7 @@
package code.name.monkey.retromusic.ui.activities
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.AsyncTask
import android.os.Bundle
import android.text.InputType
@ -12,6 +13,8 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
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.retromusic.App
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 com.afollestad.materialdialogs.MaterialDialog
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.fragment_lyrics.*
import kotlinx.android.synthetic.main.fragment_synced.*
@ -37,15 +42,22 @@ import java.util.*
class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPager.OnPageChangeListener {
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 onPageSelected(position: Int) {
PreferenceUtil.getInstance().lyricsOptions = position
if (position == 0) fab.text = "Sync lyrics"
else if (position == 1) fab.text = "Lyrics"
}
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()
viewPager.apply {
@ -258,8 +274,8 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
offlineLyrics?.setText(R.string.no_lyrics_found)
return
}
(activity as LyricsActivity).lyricsString = l.data
offlineLyrics?.text = l.data
(activity as LyricsActivity).lyricsString = l.text
offlineLyrics?.text = l.text
}
override fun onCancelled(s: Lyrics?) {
@ -302,11 +318,14 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
private fun setupLyricsView() {
lyricsView.apply {
setOnPlayerClickListener { progress, _ -> MusicPlayerRemote.seekTo(progress.toInt()) }
setDefaultColor(ContextCompat.getColor(context, R.color.md_grey_400))
setHintColor(ThemeStore.textColorPrimary(context))
setHighLightColor(ThemeStore.textColorPrimary(context))
setTextSize(RetroUtil.convertDpToPixel(18f, context).toInt())
setCurrentPlayLineColor(ThemeStore.accentColor(context))
setIndicatorTextColor(ThemeStore.accentColor(context))
setCurrentIndicateLineTextColor(ThemeStore.textColorPrimary(context))
setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener {
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) {
lyricsView.setCurrentTimeMillis(progress.toLong())
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
@ -332,10 +351,8 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage
}
private fun showLyricsLocal(file: File?) {
if (file == null) {
lyricsView.reset()
} else {
lyricsView.setLyricFile(file, "UTF-8")
if (file != null) {
lyricsView.setLrcData(LrcHelper.parseLrcFromFile(file))
}
}
}

View file

@ -107,7 +107,7 @@ class UserInfoActivity : AbsBaseActivity() {
toolbar.apply {
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setBackgroundColor(primaryColor)
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.accentColor(this@UserInfoActivity))
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(this@UserInfoActivity))
setSupportActionBar(this)
}
appBarLayout.setBackgroundColor(primaryColor)

View file

@ -44,7 +44,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(songTextContainer)
MaterialUtil.setTint(songTextContainer,false)
MaterialUtil.setTint(composerContainer, false)
MaterialUtil.setTint(albumTextContainer, 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
val display = activity?.windowManager?.defaultDisplay
val outMetrics = DisplayMetrics()
display?.getMetrics(outMetrics)
//val display = activity?.windowManager?.defaultDisplay
//val outMetrics = DisplayMetrics()
//display?.getMetrics(outMetrics)
val density = resources.displayMetrics.density
val dpWidth = outMetrics.widthPixels / density
//val density = resources.displayMetrics.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) {
final int size = songs.size();
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{
"max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")",
};
final String[] projection = new String[]{"max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")",};
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
Cursor cursor = null;
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;
}
}