diff --git a/app/build.gradle b/app/build.gradle index 1ff417ac..47ce9a5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,8 +24,8 @@ android { vectorDrawables.useSupportLibrary = true applicationId "code.name.monkey.retromusic" - versionCode 436 - versionName '3.5.300' + versionCode 437 + versionName '3.5.600' multiDexEnabled true diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt index c0a7efff..12e45b46 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt @@ -29,7 +29,6 @@ import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.lyrics.LrcHelper -import code.name.monkey.retromusic.lyrics.LrcView import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.lyrics.Lyrics import code.name.monkey.retromusic.util.LyricUtil @@ -162,7 +161,6 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, } catch (e: Exception) { e.printStackTrace() } - /*val materialDialog = MaterialDialog(this) .show { title(R.string.add_time_framed_lryics) @@ -381,11 +379,7 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ) ) setNoLrcTextColor(resolveColor(requireContext(), attr.textColorPrimary)) - setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener { - override fun onPlay(time: Long, content: String) { - MusicPlayerRemote.seekTo(time.toInt()) - } - }) + setOnPlayIndicatorLineListener { time, _ -> MusicPlayerRemote.seekTo(time.toInt()) } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/mainactivity/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/fragments/mainactivity/FoldersFragment.java index ad3f55bb..7895f70d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/mainactivity/FoldersFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/mainactivity/FoldersFragment.java @@ -60,7 +60,6 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote; import code.name.monkey.retromusic.helper.menu.SongMenuHelper; import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; import code.name.monkey.retromusic.interfaces.CabHolder; -import code.name.monkey.retromusic.interfaces.LoaderIds; import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks; import code.name.monkey.retromusic.misc.DialogAsyncTask; import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; @@ -87,7 +86,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); private static final String PATH = "path"; private static final String CRUMBS = "crumbs"; - private static final int LOADER_ID = LoaderIds.Companion.getFOLDERS_FRAGMENT(); + private static final int LOADER_ID = 5; private SongFileAdapter adapter; private BreadCrumbLayout breadCrumbs; private MaterialCab cab; diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.kt deleted file mode 100644 index 0842990e..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - -package code.name.monkey.retromusic.helper - -import android.media.audiofx.BassBoost -import android.media.audiofx.Equalizer -import android.media.audiofx.Virtualizer -import android.util.Log - -import code.name.monkey.retromusic.interfaces.EqualizerInterface - -/** - * @author Hemanth S (h4h13). - */ - -class EqualizerHelper private constructor() : EqualizerInterface { - override val equalizer: Equalizer - override val bassBoost: BassBoost - override val virtualizer: Virtualizer - - override val bandLevelHigh: Int - override val bandLevelLow: Int - override var isRunning = false - - override val numberOfBands: Int - get() = equalizer.numberOfBands.toInt() - - override var isBassBoostEnabled: Boolean - get() = bassBoost.enabled - set(isEnabled) { - bassBoost.enabled = isEnabled - } - - override var bassBoostStrength: Int - get() = bassBoost.roundedStrength.toInt() - set(strength) = bassBoost.setStrength(strength.toShort()) - - override var isVirtualizerEnabled: Boolean - get() = virtualizer.enabled - set(isEnabled) { - virtualizer.enabled = isEnabled - } - - override var virtualizerStrength: Int - get() = virtualizer.roundedStrength.toInt() - set(strength) = virtualizer.setStrength(strength.toShort()) - - init { - - //Prevent form the reflection api. - if (ourInstance != null) { - throw RuntimeException("Use getInstance() method to get the single instance of this class.") - } - - val i = MusicPlayerRemote.audioSessionId - - equalizer = Equalizer(100, i) - - equalizer.enabled = true - bassBoost = BassBoost(100, i) - virtualizer = Virtualizer(100, i) - - bandLevelHigh = equalizer.bandLevelRange[1].toInt() - bandLevelLow = equalizer.bandLevelRange[0].toInt() - - Log.i(TAG, "onCreate: $bandLevelHigh $bandLevelLow") - isRunning = true - } - - //Make singleton from serialize and deserialize operation. - protected fun readResolve(): EqualizerHelper? { - return instance - } - - override fun getCenterFreq(band: Int): Int { - return equalizer.getCenterFreq(band.toShort()) - } - - - override fun getBandLevel(band: Int): Int { - return equalizer.getBandLevel(band.toShort()).toInt() - } - - override fun setBandLevel(band: Int, level: Int) { - equalizer.setBandLevel(band.toShort(), level.toShort()) - } - - companion object { - private const val TAG = "EqualizerHelper" - - @Volatile - private var ourInstance: EqualizerHelper? = null - - //Double check locking pattern - //Check for the first time - //Check for the second time. - //if there is no instance available... create new one - val instance: EqualizerHelper? - get() { - if (ourInstance == null) { - - synchronized(EqualizerHelper::class.java) { - if (ourInstance == null) { - ourInstance = EqualizerHelper() - } - } - } - return ourInstance - } - } - -} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java deleted file mode 100644 index cdf12103..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - -package code.name.monkey.retromusic.helper; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; - -import code.name.monkey.retromusic.model.Playlist; -import code.name.monkey.retromusic.model.Song; - -public class M3UWriter implements M3UConstants { - - @Nullable - public static File write(@NonNull Context context, - @NonNull File dir, - @NonNull Playlist playlist) throws IOException { - if (!dir.exists()) //noinspection ResultOfMethodCallIgnored - dir.mkdirs(); - File file = new File(dir, playlist.name.concat("." + EXTENSION)); - - ArrayList songs = playlist.getSongs(context); - - if (songs.size() > 0) { - BufferedWriter bw = new BufferedWriter(new FileWriter(file)); - - bw.write(HEADER); - for (Song song : songs) { - bw.newLine(); - bw.write(ENTRY + song.getDuration() + DURATION_SEPARATOR + song.getArtistName() + " - " + song.getTitle()); - bw.newLine(); - bw.write(song.getData()); - } - - bw.close(); - } - return file; - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt new file mode 100644 index 00000000..d7793e56 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Hemanth Savarala. + * + * Licensed under the GNU General Public License v3 + * + * This is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by + * the Free Software Foundation either version 3 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + */ +package code.name.monkey.retromusic.helper + +import android.content.Context +import code.name.monkey.retromusic.model.Playlist +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter +import java.io.IOException + +object M3UWriter : M3UConstants { + @JvmStatic + @Throws(IOException::class) + fun write( + context: Context, + dir: File, + playlist: Playlist + ): File? { + if (!dir.exists()) dir.mkdirs() + val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION) + val songs = playlist.getSongs(context) + if (songs.size > 0) { + val bw = BufferedWriter(FileWriter(file)) + bw.write(M3UConstants.HEADER) + for (song in songs) { + bw.newLine() + bw.write(M3UConstants.ENTRY + song.duration + M3UConstants.DURATION_SEPARATOR + song.artistName + " - " + song.title) + bw.newLine() + bw.write(song.data) + } + bw.close() + } + return file + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.kt deleted file mode 100644 index 914136d4..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - -package code.name.monkey.retromusic.interfaces - -import android.media.audiofx.BassBoost -import android.media.audiofx.Equalizer -import android.media.audiofx.Virtualizer - -/** - * @author Hemanth S (h4h13). - */ - -interface EqualizerInterface { - val bandLevelLow: Int - - val bandLevelHigh: Int - - val numberOfBands: Int - - var isBassBoostEnabled: Boolean - - var bassBoostStrength: Int - - var isVirtualizerEnabled: Boolean - - var virtualizerStrength: Int - - val isRunning: Boolean - - val equalizer: Equalizer - - val bassBoost: BassBoost - - val virtualizer: Virtualizer - - fun getCenterFreq(band: Int): Int - - fun getBandLevel(band: Int): Int - - fun setBandLevel(band: Int, level: Int) - - -} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.kt deleted file mode 100644 index c1463be7..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - -package code.name.monkey.retromusic.interfaces - - -interface LoaderIds { - companion object { - val FOLDERS_FRAGMENT = 5 - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.kt index 7b72bff6..31d417cb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.kt @@ -14,13 +14,9 @@ package code.name.monkey.retromusic.interfaces -import androidx.annotation.ColorInt - /** * @author Aidan Follestad (afollestad) */ interface PaletteColorHolder { - - @get:ColorInt val paletteColor: Int } diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java index bd326af9..c015b1ab 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java @@ -1,17 +1,3 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - package code.name.monkey.retromusic.lyrics; /** @@ -25,20 +11,20 @@ public class Lrc { private long time; private String text; - public long getTime() { - return time; - } - public void setTime(long time) { this.time = time; } - public String getText() { - return text; - } - public void setText(String text) { this.text = text; } + public long getTime() { + return time; + } + + public String getText() { + return text; + } + } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java index 385b7856..cf0e9970 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java @@ -1,17 +1,3 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - package code.name.monkey.retromusic.lyrics; import android.content.Context; @@ -42,8 +28,8 @@ 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,3}])+)(.*)"; - private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2,3})]"; + private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2}])+)(.*)"; + private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2})]"; public static List parseLrcFromAssets(Context context, String fileName) { try { @@ -127,14 +113,8 @@ public class LrcHelper { String mil = timeMatcher.group(3); Lrc lrc = new Lrc(); if (content != null && content.length() != 0) { - if (Integer.parseInt(mil) < 100) { - lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000 - + Long.parseLong(mil) * 10); - } else { - lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000 - + Long.parseLong(mil) - ); - } + lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000 + + Long.parseLong(mil) * 10); lrc.setText(content); lrcs.add(lrc); } @@ -154,4 +134,4 @@ public class LrcHelper { } return time + ""; } -} +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java new file mode 100644 index 00000000..9da2ce30 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java @@ -0,0 +1,663 @@ +package code.name.monkey.retromusic.lyrics; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Looper; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.DecelerateInterpolator; +import android.widget.OverScroller; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; + +import java.util.HashMap; +import java.util.List; + +import code.name.monkey.retromusic.R; + +/** + * Desc : 歌词 + * Author : Lauzy + * Date : 2017/10/13 + * Blog : http://www.jianshu.com/u/e76853f863a9 + * Email : freedompaladin@gmail.com + */ +public class LrcView extends View { + + private static final String DEFAULT_CONTENT = "Empty"; + private List mLrcData; + private TextPaint mTextPaint; + private String mDefaultContent; + private int mCurrentLine; + private float mOffset; + private float mLastMotionX; + private float mLastMotionY; + private int mScaledTouchSlop; + private OverScroller mOverScroller; + private VelocityTracker mVelocityTracker; + private int mMaximumFlingVelocity; + private int mMinimumFlingVelocity; + private float mLrcTextSize; + private float mLrcLineSpaceHeight; + private int mTouchDelay; + private int mNormalColor; + private int mCurrentPlayLineColor; + private float mNoLrcTextSize; + private int mNoLrcTextColor; + //是否拖拽中,否的话响应onClick事件 + private boolean isDragging; + //用户开始操作 + private boolean isUserScroll; + private boolean isAutoAdjustPosition = true; + private Drawable mPlayDrawable; + private boolean isShowTimeIndicator; + private Rect mPlayRect; + private Paint mIndicatorPaint; + private float mIndicatorLineWidth; + private float mIndicatorTextSize; + private int mCurrentIndicateLineTextColor; + private int mIndicatorLineColor; + private float mIndicatorMargin; + private float mIconLineGap; + private float mIconWidth; + private float mIconHeight; + private boolean isEnableShowIndicator = true; + private int mIndicatorTextColor; + private int mIndicatorTouchDelay; + private boolean isCurrentTextBold; + private boolean isLrcIndicatorTextBold; + private HashMap mLrcMap = new HashMap<>(); + private Runnable mHideIndicatorRunnable = new Runnable() { + @Override + public void run() { + isShowTimeIndicator = false; + invalidateView(); + } + }; + private HashMap mStaticLayoutHashMap = new HashMap<>(); + private Runnable mScrollRunnable = new Runnable() { + @Override + public void run() { + isUserScroll = false; + scrollToPosition(mCurrentLine); + } + }; + private OnPlayIndicatorLineListener mOnPlayIndicatorLineListener; + + public LrcView(Context context) { + this(context, null); + } + + public LrcView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView); + mLrcTextSize = typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15)); + mLrcLineSpaceHeight = typedArray.getDimension(R.styleable.LrcView_lrcLineSpaceSize, dp2px(context, 20)); + mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500); + mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500); + mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY); + mCurrentPlayLineColor = typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE); + mNoLrcTextSize = typedArray.getDimension(R.styleable.LrcView_noLrcTextSize, dp2px(context, 20)); + mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK); + mIndicatorLineWidth = typedArray.getDimension(R.styleable.LrcView_indicatorLineHeight, dp2px(context, 0.5f)); + mIndicatorTextSize = typedArray.getDimension(R.styleable.LrcView_indicatorTextSize, sp2px(context, 13)); + mIndicatorTextColor = typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY); + mCurrentIndicateLineTextColor = typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY); + mIndicatorLineColor = typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY); + mIndicatorMargin = typedArray.getDimension(R.styleable.LrcView_indicatorStartEndMargin, dp2px(context, 5)); + mIconLineGap = typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3)); + mIconWidth = typedArray.getDimension(R.styleable.LrcView_playIconWidth, dp2px(context, 20)); + mIconHeight = typedArray.getDimension(R.styleable.LrcView_playIconHeight, dp2px(context, 20)); + mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon); + mPlayDrawable = mPlayDrawable == null ? ContextCompat.getDrawable(context, R.drawable.ic_play_arrow_white_24dp) : mPlayDrawable; + isCurrentTextBold = typedArray.getBoolean(R.styleable.LrcView_isLrcCurrentTextBold, false); + isLrcIndicatorTextBold = typedArray.getBoolean(R.styleable.LrcView_isLrcIndicatorTextBold, false); + typedArray.recycle(); + + setupConfigs(context); + } + + private void setupConfigs(Context context) { + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mMaximumFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); + mMinimumFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); + mOverScroller = new OverScroller(context, new DecelerateInterpolator()); + mOverScroller.setFriction(0.1f); +// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f + + mTextPaint = new TextPaint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextPaint.setTextSize(mLrcTextSize); + mDefaultContent = DEFAULT_CONTENT; + + mIndicatorPaint = new Paint(); + mIndicatorPaint.setAntiAlias(true); + mIndicatorPaint.setStrokeWidth(mIndicatorLineWidth); + mIndicatorPaint.setColor(mIndicatorLineColor); + mPlayRect = new Rect(); + mIndicatorPaint.setTextSize(mIndicatorTextSize); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (changed) { + mPlayRect.left = (int) mIndicatorMargin; + mPlayRect.top = (int) (getHeight() / 2 - mIconHeight / 2); + mPlayRect.right = (int) (mPlayRect.left + mIconWidth); + mPlayRect.bottom = (int) (mPlayRect.top + mIconHeight); + mPlayDrawable.setBounds(mPlayRect); + } + } + + private int getLrcWidth() { + return getWidth() - getPaddingLeft() - getPaddingRight(); + } + + private int getLrcHeight() { + return getHeight(); + } + + private boolean isLrcEmpty() { + return mLrcData == null || getLrcCount() == 0; + } + + private int getLrcCount() { + return mLrcData.size(); + } + + public void setLrcData(List lrcData) { + resetView(DEFAULT_CONTENT); + mLrcData = lrcData; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (isLrcEmpty()) { + drawEmptyText(canvas); + return; + } + int indicatePosition = getIndicatePosition(); + mTextPaint.setTextSize(mLrcTextSize); + mTextPaint.setTextAlign(Paint.Align.CENTER); + float y = getLrcHeight() / 2f; + float x = getLrcWidth() / 2f + getPaddingLeft(); + for (int i = 0; i < getLrcCount(); i++) { + if (i > 0) { + y += (getTextHeight(i - 1) + getTextHeight(i)) / 2f + mLrcLineSpaceHeight; + } + if (mCurrentLine == i) { + mTextPaint.setColor(mCurrentPlayLineColor); + mTextPaint.setFakeBoldText(isCurrentTextBold); + } else if (indicatePosition == i && isShowTimeIndicator) { + mTextPaint.setFakeBoldText(isLrcIndicatorTextBold); + mTextPaint.setColor(mCurrentIndicateLineTextColor); + } else { + mTextPaint.setFakeBoldText(false); + mTextPaint.setColor(mNormalColor); + } + drawLrc(canvas, x, y, i); + } + + if (isShowTimeIndicator) { + mPlayDrawable.draw(canvas); + long time = mLrcData.get(indicatePosition).getTime(); + float timeWidth = mIndicatorPaint.measureText(LrcHelper.formatTime(time)); + mIndicatorPaint.setColor(mIndicatorLineColor); + canvas.drawLine(mPlayRect.right + mIconLineGap, getHeight() / 2f, + getWidth() - timeWidth * 1.3f, getHeight() / 2f, mIndicatorPaint); + int baseX = (int) (getWidth() - timeWidth * 1.1f); + float baseline = getHeight() / 2f - (mIndicatorPaint.descent() - mIndicatorPaint.ascent()) / 2 - mIndicatorPaint.ascent(); + mIndicatorPaint.setColor(mIndicatorTextColor); + canvas.drawText(LrcHelper.formatTime(time), baseX, baseline, mIndicatorPaint); + } + } + + private void drawLrc(Canvas canvas, float x, float y, int i) { + String text = mLrcData.get(i).getText(); + StaticLayout staticLayout = mLrcMap.get(text); + if (staticLayout == null) { + mTextPaint.setTextSize(mLrcTextSize); + staticLayout = new StaticLayout(text, mTextPaint, getLrcWidth(), + Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false); + mLrcMap.put(text, staticLayout); + } + canvas.save(); + canvas.translate(x, y - staticLayout.getHeight() / 2f - mOffset); + staticLayout.draw(canvas); + canvas.restore(); + } + + //中间空文字 + private void drawEmptyText(Canvas canvas) { + mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextPaint.setColor(mNoLrcTextColor); + mTextPaint.setTextSize(mNoLrcTextSize); + canvas.save(); + StaticLayout staticLayout = new StaticLayout(mDefaultContent, mTextPaint, + getLrcWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false); + canvas.translate(getLrcWidth() / 2f + getPaddingLeft(), getLrcHeight() / 2f); + staticLayout.draw(canvas); + canvas.restore(); + } + + public void updateTime(long time) { + if (isLrcEmpty()) { + return; + } + int linePosition = getUpdateTimeLinePosition(time); + if (mCurrentLine != linePosition) { + mCurrentLine = linePosition; + if (isUserScroll) { + invalidateView(); + return; + } + ViewCompat.postOnAnimation(LrcView.this, mScrollRunnable); + } + } + + private int getUpdateTimeLinePosition(long time) { + int linePos = 0; + for (int i = 0; i < getLrcCount(); i++) { + Lrc lrc = mLrcData.get(i); + if (time >= lrc.getTime()) { + if (i == getLrcCount() - 1) { + linePos = getLrcCount() - 1; + } else if (time < mLrcData.get(i + 1).getTime()) { + linePos = i; + break; + } + } + } + return linePos; + } + + private void scrollToPosition(int linePosition) { + float scrollY = getItemOffsetY(linePosition); + final ValueAnimator animator = ValueAnimator.ofFloat(mOffset, scrollY); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mOffset = (float) animation.getAnimatedValue(); + invalidateView(); + } + }); + animator.setDuration(300); + animator.start(); + } + + public int getIndicatePosition() { + int pos = 0; + float min = Float.MAX_VALUE; + //itemOffset 和 mOffset 最小即当前位置 + for (int i = 0; i < mLrcData.size(); i++) { + float offsetY = getItemOffsetY(i); + float abs = Math.abs(offsetY - mOffset); + if (abs < min) { + min = abs; + pos = i; + } + } + return pos; + } + + private float getItemOffsetY(int linePosition) { + float tempY = 0; + for (int i = 1; i <= linePosition; i++) { + tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight; + } + return tempY; + } + + private float getTextHeight(int linePosition) { + String text = mLrcData.get(linePosition).getText(); + StaticLayout staticLayout = mStaticLayoutHashMap.get(text); + if (staticLayout == null) { + mTextPaint.setTextSize(mLrcTextSize); + staticLayout = new StaticLayout(text, mTextPaint, + getLrcWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false); + mStaticLayoutHashMap.put(text, staticLayout); + } + return staticLayout.getHeight(); + } + + private boolean overScrolled() { + return mOffset > getItemOffsetY(getLrcCount() - 1) || mOffset < 0; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isLrcEmpty()) { + return super.onTouchEvent(event); + } + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + removeCallbacks(mScrollRunnable); + removeCallbacks(mHideIndicatorRunnable); + if (!mOverScroller.isFinished()) { + mOverScroller.abortAnimation(); + } + mLastMotionX = event.getX(); + mLastMotionY = event.getY(); + isUserScroll = true; + isDragging = false; + break; + + case MotionEvent.ACTION_MOVE: + float moveY = event.getY() - mLastMotionY; + if (Math.abs(moveY) > mScaledTouchSlop) { + isDragging = true; + isShowTimeIndicator = isEnableShowIndicator; + } + if (isDragging) { + +// if (mOffset < 0) { +// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight); +// } + float maxHeight = getItemOffsetY(getLrcCount() - 1); +// if (mOffset > maxHeight) { +// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight); +// } + if (mOffset < 0 || mOffset > maxHeight) { + moveY /= 3.5f; + } + mOffset -= moveY; + mLastMotionY = event.getY(); + invalidateView(); + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) { + isShowTimeIndicator = false; + invalidateView(); + performClick(); + } + handleActionUp(event); + break; + } +// return isDragging || super.onTouchEvent(event); + return true; + } + + private void handleActionUp(MotionEvent event) { + if (isEnableShowIndicator) { + ViewCompat.postOnAnimationDelayed(LrcView.this, mHideIndicatorRunnable, mIndicatorTouchDelay); + } + if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) { + isShowTimeIndicator = false; + invalidateView(); + if (mOnPlayIndicatorLineListener != null) { + mOnPlayIndicatorLineListener.onPlay(mLrcData.get(getIndicatePosition()).getTime(), + mLrcData.get(getIndicatePosition()).getText()); + } + } + if (overScrolled() && mOffset < 0) { + scrollToPosition(0); + if (isAutoAdjustPosition) { + ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay); + } + return; + } + + if (overScrolled() && mOffset > getItemOffsetY(getLrcCount() - 1)) { + scrollToPosition(getLrcCount() - 1); + if (isAutoAdjustPosition) { + ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay); + } + return; + } + + mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + float yVelocity = mVelocityTracker.getYVelocity(); + float absYVelocity = Math.abs(yVelocity); + if (absYVelocity > mMinimumFlingVelocity) { + mOverScroller.fling(0, (int) mOffset, 0, (int) (-yVelocity), 0, + 0, 0, (int) getItemOffsetY(getLrcCount() - 1), + 0, (int) getTextHeight(0)); + invalidateView(); + } + releaseVelocityTracker(); + if (isAutoAdjustPosition) { + ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay); + } + } + + private boolean onClickPlayButton(MotionEvent event) { + float left = mPlayRect.left; + float right = mPlayRect.right; + float top = mPlayRect.top; + float bottom = mPlayRect.bottom; + float x = event.getX(); + float y = event.getY(); + return mLastMotionX > left && mLastMotionX < right && mLastMotionY > top + && mLastMotionY < bottom && x > left && x < right && y > top && y < bottom; + } + + @Override + public void computeScroll() { + super.computeScroll(); + if (mOverScroller.computeScrollOffset()) { + mOffset = mOverScroller.getCurrY(); + invalidateView(); + } + } + + private void releaseVelocityTracker() { + if (null != mVelocityTracker) { + mVelocityTracker.clear(); + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + public void resetView(String defaultContent) { + if (mLrcData != null) { + mLrcData.clear(); + } + mLrcMap.clear(); + mStaticLayoutHashMap.clear(); + mCurrentLine = 0; + mOffset = 0; + isUserScroll = false; + isDragging = false; + mDefaultContent = defaultContent; + removeCallbacks(mScrollRunnable); + invalidate(); + } + + @Override + public boolean performClick() { + return super.performClick(); + } + + public int dp2px(Context context, float dpVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, context.getResources().getDisplayMetrics()); + } + + public int sp2px(Context context, float spVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + spVal, context.getResources().getDisplayMetrics()); + } + + /** + * 暂停(手动滑动歌词后,不再自动回滚至当前播放位置) + */ + public void pause() { + isAutoAdjustPosition = false; + invalidateView(); + } + + /** + * 恢复(继续自动回滚) + */ + public void resume() { + isAutoAdjustPosition = true; + ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay); + invalidateView(); + } + + + /*------------------Config-------------------*/ + + private void invalidateView() { + if (Looper.getMainLooper().getThread() == Thread.currentThread()) { + invalidate(); + } else { + postInvalidate(); + } + } + + public void setOnPlayIndicatorLineListener(OnPlayIndicatorLineListener onPlayIndicatorLineListener) { + mOnPlayIndicatorLineListener = onPlayIndicatorLineListener; + } + + public void setEmptyContent(String defaultContent) { + mDefaultContent = defaultContent; + invalidateView(); + } + + public void setLrcTextSize(float lrcTextSize) { + mLrcTextSize = lrcTextSize; + invalidateView(); + } + + public void setLrcLineSpaceHeight(float lrcLineSpaceHeight) { + mLrcLineSpaceHeight = lrcLineSpaceHeight; + invalidateView(); + } + + public void setTouchDelay(int touchDelay) { + mTouchDelay = touchDelay; + invalidateView(); + } + + public void setNormalColor(@ColorInt int normalColor) { + mNormalColor = normalColor; + invalidateView(); + } + + public void setCurrentPlayLineColor(@ColorInt int currentPlayLineColor) { + mCurrentPlayLineColor = currentPlayLineColor; + invalidateView(); + } + + public void setNoLrcTextSize(float noLrcTextSize) { + mNoLrcTextSize = noLrcTextSize; + invalidateView(); + } + + public void setNoLrcTextColor(@ColorInt int noLrcTextColor) { + mNoLrcTextColor = noLrcTextColor; + invalidateView(); + } + + public void setIndicatorLineWidth(float indicatorLineWidth) { + mIndicatorLineWidth = indicatorLineWidth; + invalidateView(); + } + + public void setIndicatorTextSize(float indicatorTextSize) { +// mIndicatorTextSize = indicatorTextSize; + mIndicatorPaint.setTextSize(indicatorTextSize); + invalidateView(); + } + + public void setCurrentIndicateLineTextColor(int currentIndicateLineTextColor) { + mCurrentIndicateLineTextColor = currentIndicateLineTextColor; + invalidateView(); + } + + public void setIndicatorLineColor(int indicatorLineColor) { + mIndicatorLineColor = indicatorLineColor; + invalidateView(); + } + + public void setIndicatorMargin(float indicatorMargin) { + mIndicatorMargin = indicatorMargin; + invalidateView(); + } + + public void setIconLineGap(float iconLineGap) { + mIconLineGap = iconLineGap; + invalidateView(); + } + + public void setIconWidth(float iconWidth) { + mIconWidth = iconWidth; + invalidateView(); + } + + public void setIconHeight(float iconHeight) { + mIconHeight = iconHeight; + invalidateView(); + } + + public void setEnableShowIndicator(boolean enableShowIndicator) { + isEnableShowIndicator = enableShowIndicator; + invalidateView(); + } + + public Drawable getPlayDrawable() { + return mPlayDrawable; + } + + public void setPlayDrawable(Drawable playDrawable) { + mPlayDrawable = playDrawable; + mPlayDrawable.setBounds(mPlayRect); + invalidateView(); + } + + public void setIndicatorTextColor(int indicatorTextColor) { + mIndicatorTextColor = indicatorTextColor; + invalidateView(); + } + + public void setLrcCurrentTextBold(boolean bold) { + isCurrentTextBold = bold; + invalidateView(); + } + + public void setLrcIndicatorTextBold(boolean bold) { + isLrcIndicatorTextBold = bold; + invalidateView(); + } + + public interface OnPlayIndicatorLineListener { + void onPlay(long time, String content); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.kt b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.kt deleted file mode 100644 index 9a195139..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.kt +++ /dev/null @@ -1,713 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ -package code.name.monkey.retromusic.lyrics - - -import android.animation.ValueAnimator -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.os.Looper -import android.text.Layout -import android.text.StaticLayout -import android.text.TextPaint -import android.util.AttributeSet -import android.util.TypedValue -import android.view.MotionEvent -import android.view.VelocityTracker -import android.view.View -import android.view.ViewConfiguration -import android.view.animation.DecelerateInterpolator -import android.widget.OverScroller -import androidx.annotation.ColorInt -import androidx.core.content.ContextCompat -import androidx.core.view.ViewCompat -import code.name.monkey.retromusic.BuildConfig -import code.name.monkey.retromusic.R -import java.util.* - -/** - * Desc : 歌词 - * Author : Lauzy - * Date : 2017/10/13 - * Blog : http://www.jianshu.com/u/e76853f863a9 - * Email : freedompaladin@gmail.com - */ -class LrcView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { - private var mLrcData: MutableList? = 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() - private val mHideIndicatorRunnable = Runnable { - isShowTimeIndicator = false - invalidateView() - } - private val mStaticLayoutHashMap = HashMap() - private val mScrollRunnable = Runnable { - isUserScroll = false - scrollToPosition(mCurrentLine) - } - private var mOnPlayIndicatorLineListener: OnPlayIndicatorLineListener? = null - - private val lrcWidth: Int - get() = width - paddingLeft - paddingRight - - private val lrcHeight: Int - get() = height - - private val isLrcEmpty: Boolean - get() = mLrcData == null || lrcCount == 0 - - private val lrcCount: Int - get() = mLrcData!!.size - - //itemOffset 和 mOffset 最小即当前位置 - val indicatePosition: Int - get() { - var pos = 0 - var min = java.lang.Float.MAX_VALUE - for (i in mLrcData!!.indices) { - val offsetY = getItemOffsetY(i) - val abs = Math.abs(offsetY - mOffset) - if (abs < min) { - min = abs - pos = i - } - } - return pos - } - - var playDrawable: Drawable? - get() = mPlayDrawable - set(playDrawable) { - mPlayDrawable = playDrawable - mPlayDrawable!!.bounds = mPlayRect!! - invalidateView() - } - - init { - init(context, attrs) - } - - private fun init(context: Context, attrs: AttributeSet?) { - - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView) - mLrcTextSize = - typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15f).toFloat()) - mLrcLineSpaceHeight = typedArray.getDimension( - R.styleable.LrcView_lrcLineSpaceSize, - dp2px(context, 20f).toFloat() - ) - mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500) - mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500) - mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY) - mCurrentPlayLineColor = - typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE) - mNoLrcTextSize = typedArray.getDimension( - R.styleable.LrcView_noLrcTextSize, - dp2px(context, 20f).toFloat() - ) - mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK) - mIndicatorLineWidth = typedArray.getDimension( - R.styleable.LrcView_indicatorLineHeight, - dp2px(context, 0.5f).toFloat() - ) - mIndicatorTextSize = typedArray.getDimension( - R.styleable.LrcView_indicatorTextSize, - sp2px(context, 13f).toFloat() - ) - mIndicatorTextColor = - typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY) - mCurrentIndicateLineTextColor = - typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY) - mIndicatorLineColor = - typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY) - mIndicatorMargin = typedArray.getDimension( - R.styleable.LrcView_indicatorStartEndMargin, - dp2px(context, 5f).toFloat() - ) - mIconLineGap = - typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3f).toFloat()) - mIconWidth = typedArray.getDimension( - R.styleable.LrcView_playIconWidth, - dp2px(context, 20f).toFloat() - ) - mIconHeight = typedArray.getDimension( - R.styleable.LrcView_playIconHeight, - dp2px(context, 20f).toFloat() - ) - mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon) - mPlayDrawable = if (mPlayDrawable == null) ContextCompat.getDrawable( - context, - R.drawable.ic_play_arrow_white_24dp - ) else mPlayDrawable - typedArray.recycle() - - setupConfigs(context) - } - - private fun setupConfigs(context: Context) { - mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop - mMaximumFlingVelocity = ViewConfiguration.get(context).scaledMaximumFlingVelocity - mMinimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity - mOverScroller = OverScroller(context, DecelerateInterpolator()) - mOverScroller!!.setFriction(0.1f) - // ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f - - mTextPaint = TextPaint() - mTextPaint!!.apply { - isAntiAlias = true - textAlign = Paint.Align.LEFT - textSize = mLrcTextSize - /* if (BuildConfig.FLAVOR != "nofont") { - typeface = ResourcesCompat.getFont(context, R.font.circular) - }*/ - } - mDefaultContent = DEFAULT_CONTENT - - mIndicatorPaint = Paint() - mIndicatorPaint!!.isAntiAlias = true - mIndicatorPaint!!.strokeWidth = mIndicatorLineWidth - mIndicatorPaint!!.color = mIndicatorLineColor - mPlayRect = Rect() - mIndicatorPaint!!.textSize = mIndicatorTextSize - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - super.onLayout(changed, left, top, right, bottom) - if (changed) { - mPlayRect!!.left = mIndicatorMargin.toInt() - mPlayRect!!.top = (height / 2 - mIconHeight / 2).toInt() - mPlayRect!!.right = (mPlayRect!!.left + mIconWidth).toInt() - mPlayRect!!.bottom = (mPlayRect!!.top + mIconHeight).toInt() - mPlayDrawable!!.bounds = mPlayRect!! - } - } - - fun setLrcData(lrcData: MutableList) { - 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" - } -} \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge-land/fragment_player_playback_controls.xml b/app/src/main/res/layout-xlarge-land/fragment_player_playback_controls.xml index a38d8791..0d859ef2 100755 --- a/app/src/main/res/layout-xlarge-land/fragment_player_playback_controls.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_player_playback_controls.xml @@ -170,6 +170,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_lyrics.xml b/app/src/main/res/layout/activity_lyrics.xml index 42adb1f1..cb314357 100644 --- a/app/src/main/res/layout/activity_lyrics.xml +++ b/app/src/main/res/layout/activity_lyrics.xml @@ -57,6 +57,7 @@ diff --git a/app/src/main/res/layout/fragment_card_blur_player_playback_controls.xml b/app/src/main/res/layout/fragment_card_blur_player_playback_controls.xml index 481053aa..8106ce80 100644 --- a/app/src/main/res/layout/fragment_card_blur_player_playback_controls.xml +++ b/app/src/main/res/layout/fragment_card_blur_player_playback_controls.xml @@ -56,6 +56,8 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file