Code refactor

This commit is contained in:
Hemanth S 2020-06-09 23:34:22 +05:30
parent 3c7328f9c6
commit 10874744f3
28 changed files with 771 additions and 1205 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.helper;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.Song;
public class M3UWriter implements M3UConstants {
@Nullable
public static File write(@NonNull Context context,
@NonNull File dir,
@NonNull Playlist playlist) throws IOException {
if (!dir.exists()) //noinspection ResultOfMethodCallIgnored
dir.mkdirs();
File file = new File(dir, playlist.name.concat("." + EXTENSION));
ArrayList<Song> songs = playlist.getSongs(context);
if (songs.size() > 0) {
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
bw.write(HEADER);
for (Song song : songs) {
bw.newLine();
bw.write(ENTRY + song.getDuration() + DURATION_SEPARATOR + song.getArtistName() + " - " + song.getTitle());
bw.newLine();
bw.write(song.getData());
}
bw.close();
}
return file;
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.helper
import android.content.Context
import code.name.monkey.retromusic.model.Playlist
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.IOException
object M3UWriter : M3UConstants {
@JvmStatic
@Throws(IOException::class)
fun write(
context: Context,
dir: File,
playlist: Playlist
): File? {
if (!dir.exists()) dir.mkdirs()
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
val songs = playlist.getSongs(context)
if (songs.size > 0) {
val bw = BufferedWriter(FileWriter(file))
bw.write(M3UConstants.HEADER)
for (song in songs) {
bw.newLine()
bw.write(M3UConstants.ENTRY + song.duration + M3UConstants.DURATION_SEPARATOR + song.artistName + " - " + song.title)
bw.newLine()
bw.write(song.data)
}
bw.close()
}
return file
}
}

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.interfaces
import android.media.audiofx.BassBoost
import android.media.audiofx.Equalizer
import android.media.audiofx.Virtualizer
/**
* @author Hemanth S (h4h13).
*/
interface EqualizerInterface {
val bandLevelLow: Int
val bandLevelHigh: Int
val numberOfBands: Int
var isBassBoostEnabled: Boolean
var bassBoostStrength: Int
var isVirtualizerEnabled: Boolean
var virtualizerStrength: Int
val isRunning: Boolean
val equalizer: Equalizer
val bassBoost: BassBoost
val virtualizer: Virtualizer
fun getCenterFreq(band: Int): Int
fun getBandLevel(band: Int): Int
fun setBandLevel(band: Int, level: Int)
}

View file

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

View file

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

View file

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

View file

@ -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<Lrc> 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.setText(content);
lrcs.add(lrc);
}

View file

@ -0,0 +1,663 @@
package code.name.monkey.retromusic.lyrics;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.OverScroller;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import java.util.HashMap;
import java.util.List;
import code.name.monkey.retromusic.R;
/**
* Desc : 歌词
* Author : Lauzy
* Date : 2017/10/13
* Blog : http://www.jianshu.com/u/e76853f863a9
* Email : freedompaladin@gmail.com
*/
public class LrcView extends View {
private static final String DEFAULT_CONTENT = "Empty";
private List<Lrc> mLrcData;
private TextPaint mTextPaint;
private String mDefaultContent;
private int mCurrentLine;
private float mOffset;
private float mLastMotionX;
private float mLastMotionY;
private int mScaledTouchSlop;
private OverScroller mOverScroller;
private VelocityTracker mVelocityTracker;
private int mMaximumFlingVelocity;
private int mMinimumFlingVelocity;
private float mLrcTextSize;
private float mLrcLineSpaceHeight;
private int mTouchDelay;
private int mNormalColor;
private int mCurrentPlayLineColor;
private float mNoLrcTextSize;
private int mNoLrcTextColor;
//是否拖拽中否的话响应onClick事件
private boolean isDragging;
//用户开始操作
private boolean isUserScroll;
private boolean isAutoAdjustPosition = true;
private Drawable mPlayDrawable;
private boolean isShowTimeIndicator;
private Rect mPlayRect;
private Paint mIndicatorPaint;
private float mIndicatorLineWidth;
private float mIndicatorTextSize;
private int mCurrentIndicateLineTextColor;
private int mIndicatorLineColor;
private float mIndicatorMargin;
private float mIconLineGap;
private float mIconWidth;
private float mIconHeight;
private boolean isEnableShowIndicator = true;
private int mIndicatorTextColor;
private int mIndicatorTouchDelay;
private boolean isCurrentTextBold;
private boolean isLrcIndicatorTextBold;
private HashMap<String, StaticLayout> mLrcMap = new HashMap<>();
private Runnable mHideIndicatorRunnable = new Runnable() {
@Override
public void run() {
isShowTimeIndicator = false;
invalidateView();
}
};
private HashMap<String, StaticLayout> mStaticLayoutHashMap = new HashMap<>();
private Runnable mScrollRunnable = new Runnable() {
@Override
public void run() {
isUserScroll = false;
scrollToPosition(mCurrentLine);
}
};
private OnPlayIndicatorLineListener mOnPlayIndicatorLineListener;
public LrcView(Context context) {
this(context, null);
}
public LrcView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView);
mLrcTextSize = typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15));
mLrcLineSpaceHeight = typedArray.getDimension(R.styleable.LrcView_lrcLineSpaceSize, dp2px(context, 20));
mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500);
mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500);
mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY);
mCurrentPlayLineColor = typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE);
mNoLrcTextSize = typedArray.getDimension(R.styleable.LrcView_noLrcTextSize, dp2px(context, 20));
mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK);
mIndicatorLineWidth = typedArray.getDimension(R.styleable.LrcView_indicatorLineHeight, dp2px(context, 0.5f));
mIndicatorTextSize = typedArray.getDimension(R.styleable.LrcView_indicatorTextSize, sp2px(context, 13));
mIndicatorTextColor = typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY);
mCurrentIndicateLineTextColor = typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY);
mIndicatorLineColor = typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY);
mIndicatorMargin = typedArray.getDimension(R.styleable.LrcView_indicatorStartEndMargin, dp2px(context, 5));
mIconLineGap = typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3));
mIconWidth = typedArray.getDimension(R.styleable.LrcView_playIconWidth, dp2px(context, 20));
mIconHeight = typedArray.getDimension(R.styleable.LrcView_playIconHeight, dp2px(context, 20));
mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon);
mPlayDrawable = mPlayDrawable == null ? ContextCompat.getDrawable(context, R.drawable.ic_play_arrow_white_24dp) : mPlayDrawable;
isCurrentTextBold = typedArray.getBoolean(R.styleable.LrcView_isLrcCurrentTextBold, false);
isLrcIndicatorTextBold = typedArray.getBoolean(R.styleable.LrcView_isLrcIndicatorTextBold, false);
typedArray.recycle();
setupConfigs(context);
}
private void setupConfigs(Context context) {
mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMaximumFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
mMinimumFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
mOverScroller = new OverScroller(context, new DecelerateInterpolator());
mOverScroller.setFriction(0.1f);
// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(mLrcTextSize);
mDefaultContent = DEFAULT_CONTENT;
mIndicatorPaint = new Paint();
mIndicatorPaint.setAntiAlias(true);
mIndicatorPaint.setStrokeWidth(mIndicatorLineWidth);
mIndicatorPaint.setColor(mIndicatorLineColor);
mPlayRect = new Rect();
mIndicatorPaint.setTextSize(mIndicatorTextSize);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mPlayRect.left = (int) mIndicatorMargin;
mPlayRect.top = (int) (getHeight() / 2 - mIconHeight / 2);
mPlayRect.right = (int) (mPlayRect.left + mIconWidth);
mPlayRect.bottom = (int) (mPlayRect.top + mIconHeight);
mPlayDrawable.setBounds(mPlayRect);
}
}
private int getLrcWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
private int getLrcHeight() {
return getHeight();
}
private boolean isLrcEmpty() {
return mLrcData == null || getLrcCount() == 0;
}
private int getLrcCount() {
return mLrcData.size();
}
public void setLrcData(List<Lrc> lrcData) {
resetView(DEFAULT_CONTENT);
mLrcData = lrcData;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isLrcEmpty()) {
drawEmptyText(canvas);
return;
}
int indicatePosition = getIndicatePosition();
mTextPaint.setTextSize(mLrcTextSize);
mTextPaint.setTextAlign(Paint.Align.CENTER);
float y = getLrcHeight() / 2f;
float x = getLrcWidth() / 2f + getPaddingLeft();
for (int i = 0; i < getLrcCount(); i++) {
if (i > 0) {
y += (getTextHeight(i - 1) + getTextHeight(i)) / 2f + mLrcLineSpaceHeight;
}
if (mCurrentLine == i) {
mTextPaint.setColor(mCurrentPlayLineColor);
mTextPaint.setFakeBoldText(isCurrentTextBold);
} else if (indicatePosition == i && isShowTimeIndicator) {
mTextPaint.setFakeBoldText(isLrcIndicatorTextBold);
mTextPaint.setColor(mCurrentIndicateLineTextColor);
} else {
mTextPaint.setFakeBoldText(false);
mTextPaint.setColor(mNormalColor);
}
drawLrc(canvas, x, y, i);
}
if (isShowTimeIndicator) {
mPlayDrawable.draw(canvas);
long time = mLrcData.get(indicatePosition).getTime();
float timeWidth = mIndicatorPaint.measureText(LrcHelper.formatTime(time));
mIndicatorPaint.setColor(mIndicatorLineColor);
canvas.drawLine(mPlayRect.right + mIconLineGap, getHeight() / 2f,
getWidth() - timeWidth * 1.3f, getHeight() / 2f, mIndicatorPaint);
int baseX = (int) (getWidth() - timeWidth * 1.1f);
float baseline = getHeight() / 2f - (mIndicatorPaint.descent() - mIndicatorPaint.ascent()) / 2 - mIndicatorPaint.ascent();
mIndicatorPaint.setColor(mIndicatorTextColor);
canvas.drawText(LrcHelper.formatTime(time), baseX, baseline, mIndicatorPaint);
}
}
private void drawLrc(Canvas canvas, float x, float y, int i) {
String text = mLrcData.get(i).getText();
StaticLayout staticLayout = mLrcMap.get(text);
if (staticLayout == null) {
mTextPaint.setTextSize(mLrcTextSize);
staticLayout = new StaticLayout(text, mTextPaint, getLrcWidth(),
Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
mLrcMap.put(text, staticLayout);
}
canvas.save();
canvas.translate(x, y - staticLayout.getHeight() / 2f - mOffset);
staticLayout.draw(canvas);
canvas.restore();
}
//中间空文字
private void drawEmptyText(Canvas canvas) {
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(mNoLrcTextColor);
mTextPaint.setTextSize(mNoLrcTextSize);
canvas.save();
StaticLayout staticLayout = new StaticLayout(mDefaultContent, mTextPaint,
getLrcWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
canvas.translate(getLrcWidth() / 2f + getPaddingLeft(), getLrcHeight() / 2f);
staticLayout.draw(canvas);
canvas.restore();
}
public void updateTime(long time) {
if (isLrcEmpty()) {
return;
}
int linePosition = getUpdateTimeLinePosition(time);
if (mCurrentLine != linePosition) {
mCurrentLine = linePosition;
if (isUserScroll) {
invalidateView();
return;
}
ViewCompat.postOnAnimation(LrcView.this, mScrollRunnable);
}
}
private int getUpdateTimeLinePosition(long time) {
int linePos = 0;
for (int i = 0; i < getLrcCount(); i++) {
Lrc lrc = mLrcData.get(i);
if (time >= lrc.getTime()) {
if (i == getLrcCount() - 1) {
linePos = getLrcCount() - 1;
} else if (time < mLrcData.get(i + 1).getTime()) {
linePos = i;
break;
}
}
}
return linePos;
}
private void scrollToPosition(int linePosition) {
float scrollY = getItemOffsetY(linePosition);
final ValueAnimator animator = ValueAnimator.ofFloat(mOffset, scrollY);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOffset = (float) animation.getAnimatedValue();
invalidateView();
}
});
animator.setDuration(300);
animator.start();
}
public int getIndicatePosition() {
int pos = 0;
float min = Float.MAX_VALUE;
//itemOffset mOffset 最小即当前位置
for (int i = 0; i < mLrcData.size(); i++) {
float offsetY = getItemOffsetY(i);
float abs = Math.abs(offsetY - mOffset);
if (abs < min) {
min = abs;
pos = i;
}
}
return pos;
}
private float getItemOffsetY(int linePosition) {
float tempY = 0;
for (int i = 1; i <= linePosition; i++) {
tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight;
}
return tempY;
}
private float getTextHeight(int linePosition) {
String text = mLrcData.get(linePosition).getText();
StaticLayout staticLayout = mStaticLayoutHashMap.get(text);
if (staticLayout == null) {
mTextPaint.setTextSize(mLrcTextSize);
staticLayout = new StaticLayout(text, mTextPaint,
getLrcWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
mStaticLayoutHashMap.put(text, staticLayout);
}
return staticLayout.getHeight();
}
private boolean overScrolled() {
return mOffset > getItemOffsetY(getLrcCount() - 1) || mOffset < 0;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isLrcEmpty()) {
return super.onTouchEvent(event);
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
removeCallbacks(mScrollRunnable);
removeCallbacks(mHideIndicatorRunnable);
if (!mOverScroller.isFinished()) {
mOverScroller.abortAnimation();
}
mLastMotionX = event.getX();
mLastMotionY = event.getY();
isUserScroll = true;
isDragging = false;
break;
case MotionEvent.ACTION_MOVE:
float moveY = event.getY() - mLastMotionY;
if (Math.abs(moveY) > mScaledTouchSlop) {
isDragging = true;
isShowTimeIndicator = isEnableShowIndicator;
}
if (isDragging) {
// if (mOffset < 0) {
// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight);
// }
float maxHeight = getItemOffsetY(getLrcCount() - 1);
// if (mOffset > maxHeight) {
// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight);
// }
if (mOffset < 0 || mOffset > maxHeight) {
moveY /= 3.5f;
}
mOffset -= moveY;
mLastMotionY = event.getY();
invalidateView();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) {
isShowTimeIndicator = false;
invalidateView();
performClick();
}
handleActionUp(event);
break;
}
// return isDragging || super.onTouchEvent(event);
return true;
}
private void handleActionUp(MotionEvent event) {
if (isEnableShowIndicator) {
ViewCompat.postOnAnimationDelayed(LrcView.this, mHideIndicatorRunnable, mIndicatorTouchDelay);
}
if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {
isShowTimeIndicator = false;
invalidateView();
if (mOnPlayIndicatorLineListener != null) {
mOnPlayIndicatorLineListener.onPlay(mLrcData.get(getIndicatePosition()).getTime(),
mLrcData.get(getIndicatePosition()).getText());
}
}
if (overScrolled() && mOffset < 0) {
scrollToPosition(0);
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
}
return;
}
if (overScrolled() && mOffset > getItemOffsetY(getLrcCount() - 1)) {
scrollToPosition(getLrcCount() - 1);
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
}
return;
}
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
float yVelocity = mVelocityTracker.getYVelocity();
float absYVelocity = Math.abs(yVelocity);
if (absYVelocity > mMinimumFlingVelocity) {
mOverScroller.fling(0, (int) mOffset, 0, (int) (-yVelocity), 0,
0, 0, (int) getItemOffsetY(getLrcCount() - 1),
0, (int) getTextHeight(0));
invalidateView();
}
releaseVelocityTracker();
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
}
}
private boolean onClickPlayButton(MotionEvent event) {
float left = mPlayRect.left;
float right = mPlayRect.right;
float top = mPlayRect.top;
float bottom = mPlayRect.bottom;
float x = event.getX();
float y = event.getY();
return mLastMotionX > left && mLastMotionX < right && mLastMotionY > top
&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mOverScroller.computeScrollOffset()) {
mOffset = mOverScroller.getCurrY();
invalidateView();
}
}
private void releaseVelocityTracker() {
if (null != mVelocityTracker) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
public void resetView(String defaultContent) {
if (mLrcData != null) {
mLrcData.clear();
}
mLrcMap.clear();
mStaticLayoutHashMap.clear();
mCurrentLine = 0;
mOffset = 0;
isUserScroll = false;
isDragging = false;
mDefaultContent = defaultContent;
removeCallbacks(mScrollRunnable);
invalidate();
}
@Override
public boolean performClick() {
return super.performClick();
}
public int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}
public int sp2px(Context context, float spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, context.getResources().getDisplayMetrics());
}
/**
* 暂停手动滑动歌词后不再自动回滚至当前播放位置
*/
public void pause() {
isAutoAdjustPosition = false;
invalidateView();
}
/**
* 恢复继续自动回滚
*/
public void resume() {
isAutoAdjustPosition = true;
ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
invalidateView();
}
/*------------------Config-------------------*/
private void invalidateView() {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
invalidate();
} else {
postInvalidate();
}
}
public void setOnPlayIndicatorLineListener(OnPlayIndicatorLineListener onPlayIndicatorLineListener) {
mOnPlayIndicatorLineListener = onPlayIndicatorLineListener;
}
public void setEmptyContent(String defaultContent) {
mDefaultContent = defaultContent;
invalidateView();
}
public void setLrcTextSize(float lrcTextSize) {
mLrcTextSize = lrcTextSize;
invalidateView();
}
public void setLrcLineSpaceHeight(float lrcLineSpaceHeight) {
mLrcLineSpaceHeight = lrcLineSpaceHeight;
invalidateView();
}
public void setTouchDelay(int touchDelay) {
mTouchDelay = touchDelay;
invalidateView();
}
public void setNormalColor(@ColorInt int normalColor) {
mNormalColor = normalColor;
invalidateView();
}
public void setCurrentPlayLineColor(@ColorInt int currentPlayLineColor) {
mCurrentPlayLineColor = currentPlayLineColor;
invalidateView();
}
public void setNoLrcTextSize(float noLrcTextSize) {
mNoLrcTextSize = noLrcTextSize;
invalidateView();
}
public void setNoLrcTextColor(@ColorInt int noLrcTextColor) {
mNoLrcTextColor = noLrcTextColor;
invalidateView();
}
public void setIndicatorLineWidth(float indicatorLineWidth) {
mIndicatorLineWidth = indicatorLineWidth;
invalidateView();
}
public void setIndicatorTextSize(float indicatorTextSize) {
// mIndicatorTextSize = indicatorTextSize;
mIndicatorPaint.setTextSize(indicatorTextSize);
invalidateView();
}
public void setCurrentIndicateLineTextColor(int currentIndicateLineTextColor) {
mCurrentIndicateLineTextColor = currentIndicateLineTextColor;
invalidateView();
}
public void setIndicatorLineColor(int indicatorLineColor) {
mIndicatorLineColor = indicatorLineColor;
invalidateView();
}
public void setIndicatorMargin(float indicatorMargin) {
mIndicatorMargin = indicatorMargin;
invalidateView();
}
public void setIconLineGap(float iconLineGap) {
mIconLineGap = iconLineGap;
invalidateView();
}
public void setIconWidth(float iconWidth) {
mIconWidth = iconWidth;
invalidateView();
}
public void setIconHeight(float iconHeight) {
mIconHeight = iconHeight;
invalidateView();
}
public void setEnableShowIndicator(boolean enableShowIndicator) {
isEnableShowIndicator = enableShowIndicator;
invalidateView();
}
public Drawable getPlayDrawable() {
return mPlayDrawable;
}
public void setPlayDrawable(Drawable playDrawable) {
mPlayDrawable = playDrawable;
mPlayDrawable.setBounds(mPlayRect);
invalidateView();
}
public void setIndicatorTextColor(int indicatorTextColor) {
mIndicatorTextColor = indicatorTextColor;
invalidateView();
}
public void setLrcCurrentTextBold(boolean bold) {
isCurrentTextBold = bold;
invalidateView();
}
public void setLrcIndicatorTextBold(boolean bold) {
isLrcIndicatorTextBold = bold;
invalidateView();
}
public interface OnPlayIndicatorLineListener {
void onPlay(long time, String content);
}
}

View file

@ -1,713 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.lyrics
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Looper
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.util.AttributeSet
import android.util.TypedValue
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import android.widget.OverScroller
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import java.util.*
/**
* Desc : 歌词
* Author : Lauzy
* Date : 2017/10/13
* Blog : http://www.jianshu.com/u/e76853f863a9
* Email : freedompaladin@gmail.com
*/
class LrcView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var mLrcData: MutableList<Lrc>? = null
private var mTextPaint: TextPaint? = null
private var mDefaultContent: String? = null
private var mCurrentLine: Int = 0
private var mOffset: Float = 0.toFloat()
private var mLastMotionX: Float = 0.toFloat()
private var mLastMotionY: Float = 0.toFloat()
private var mScaledTouchSlop: Int = 0
private var mOverScroller: OverScroller? = null
private var mVelocityTracker: VelocityTracker? = null
private var mMaximumFlingVelocity: Int = 0
private var mMinimumFlingVelocity: Int = 0
private var mLrcTextSize: Float = 0.toFloat()
private var mLrcLineSpaceHeight: Float = 0.toFloat()
private var mTouchDelay: Int = 0
private var mNormalColor: Int = 0
private var mCurrentPlayLineColor: Int = 0
private var mNoLrcTextSize: Float = 0.toFloat()
private var mNoLrcTextColor: Int = 0
//是否拖拽中否的话响应onClick事件
private var isDragging: Boolean = false
//用户开始操作
private var isUserScroll: Boolean = false
private var isAutoAdjustPosition = true
private var mPlayDrawable: Drawable? = null
private var isShowTimeIndicator: Boolean = false
private var mPlayRect: Rect? = null
private var mIndicatorPaint: Paint? = null
private var mIndicatorLineWidth: Float = 0.toFloat()
private var mIndicatorTextSize: Float = 0.toFloat()
private var mCurrentIndicateLineTextColor: Int = 0
private var mIndicatorLineColor: Int = 0
private var mIndicatorMargin: Float = 0.toFloat()
private var mIconLineGap: Float = 0.toFloat()
private var mIconWidth: Float = 0.toFloat()
private var mIconHeight: Float = 0.toFloat()
private var isEnableShowIndicator = true
private var mIndicatorTextColor: Int = 0
private var mIndicatorTouchDelay: Int = 0
private val mLrcMap = HashMap<String, StaticLayout>()
private val mHideIndicatorRunnable = Runnable {
isShowTimeIndicator = false
invalidateView()
}
private val mStaticLayoutHashMap = HashMap<String, StaticLayout>()
private val mScrollRunnable = Runnable {
isUserScroll = false
scrollToPosition(mCurrentLine)
}
private var mOnPlayIndicatorLineListener: OnPlayIndicatorLineListener? = null
private val lrcWidth: Int
get() = width - paddingLeft - paddingRight
private val lrcHeight: Int
get() = height
private val isLrcEmpty: Boolean
get() = mLrcData == null || lrcCount == 0
private val lrcCount: Int
get() = mLrcData!!.size
//itemOffset 和 mOffset 最小即当前位置
val indicatePosition: Int
get() {
var pos = 0
var min = java.lang.Float.MAX_VALUE
for (i in mLrcData!!.indices) {
val offsetY = getItemOffsetY(i)
val abs = Math.abs(offsetY - mOffset)
if (abs < min) {
min = abs
pos = i
}
}
return pos
}
var playDrawable: Drawable?
get() = mPlayDrawable
set(playDrawable) {
mPlayDrawable = playDrawable
mPlayDrawable!!.bounds = mPlayRect!!
invalidateView()
}
init {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView)
mLrcTextSize =
typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15f).toFloat())
mLrcLineSpaceHeight = typedArray.getDimension(
R.styleable.LrcView_lrcLineSpaceSize,
dp2px(context, 20f).toFloat()
)
mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500)
mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500)
mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY)
mCurrentPlayLineColor =
typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE)
mNoLrcTextSize = typedArray.getDimension(
R.styleable.LrcView_noLrcTextSize,
dp2px(context, 20f).toFloat()
)
mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK)
mIndicatorLineWidth = typedArray.getDimension(
R.styleable.LrcView_indicatorLineHeight,
dp2px(context, 0.5f).toFloat()
)
mIndicatorTextSize = typedArray.getDimension(
R.styleable.LrcView_indicatorTextSize,
sp2px(context, 13f).toFloat()
)
mIndicatorTextColor =
typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY)
mCurrentIndicateLineTextColor =
typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY)
mIndicatorLineColor =
typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY)
mIndicatorMargin = typedArray.getDimension(
R.styleable.LrcView_indicatorStartEndMargin,
dp2px(context, 5f).toFloat()
)
mIconLineGap =
typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3f).toFloat())
mIconWidth = typedArray.getDimension(
R.styleable.LrcView_playIconWidth,
dp2px(context, 20f).toFloat()
)
mIconHeight = typedArray.getDimension(
R.styleable.LrcView_playIconHeight,
dp2px(context, 20f).toFloat()
)
mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon)
mPlayDrawable = if (mPlayDrawable == null) ContextCompat.getDrawable(
context,
R.drawable.ic_play_arrow_white_24dp
) else mPlayDrawable
typedArray.recycle()
setupConfigs(context)
}
private fun setupConfigs(context: Context) {
mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
mMaximumFlingVelocity = ViewConfiguration.get(context).scaledMaximumFlingVelocity
mMinimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
mOverScroller = OverScroller(context, DecelerateInterpolator())
mOverScroller!!.setFriction(0.1f)
// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f
mTextPaint = TextPaint()
mTextPaint!!.apply {
isAntiAlias = true
textAlign = Paint.Align.LEFT
textSize = mLrcTextSize
/* if (BuildConfig.FLAVOR != "nofont") {
typeface = ResourcesCompat.getFont(context, R.font.circular)
}*/
}
mDefaultContent = DEFAULT_CONTENT
mIndicatorPaint = Paint()
mIndicatorPaint!!.isAntiAlias = true
mIndicatorPaint!!.strokeWidth = mIndicatorLineWidth
mIndicatorPaint!!.color = mIndicatorLineColor
mPlayRect = Rect()
mIndicatorPaint!!.textSize = mIndicatorTextSize
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
mPlayRect!!.left = mIndicatorMargin.toInt()
mPlayRect!!.top = (height / 2 - mIconHeight / 2).toInt()
mPlayRect!!.right = (mPlayRect!!.left + mIconWidth).toInt()
mPlayRect!!.bottom = (mPlayRect!!.top + mIconHeight).toInt()
mPlayDrawable!!.bounds = mPlayRect!!
}
}
fun setLrcData(lrcData: MutableList<Lrc>) {
resetView(DEFAULT_CONTENT)
mLrcData = lrcData
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (isLrcEmpty) {
drawEmptyText(canvas)
return
}
val indicatePosition = indicatePosition
mTextPaint!!.textSize = mLrcTextSize
mTextPaint!!.textAlign = Paint.Align.LEFT
var y = (lrcHeight / 2).toFloat()
val x = dip2px(context, 16f).toFloat()
for (i in 0 until lrcCount) {
if (i > 0) {
y += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
}
if (mCurrentLine == i) {
mTextPaint!!.color = mCurrentPlayLineColor
} else if (indicatePosition == i && isShowTimeIndicator) {
mTextPaint!!.color = mCurrentIndicateLineTextColor
} else {
mTextPaint!!.color = mNormalColor
}
drawLrc(canvas, x, y, i)
}
if (isShowTimeIndicator) {
mPlayDrawable!!.draw(canvas)
val time = mLrcData!![indicatePosition].time
val timeWidth = mIndicatorPaint!!.measureText(LrcHelper.formatTime(time))
mIndicatorPaint!!.color = mIndicatorLineColor
canvas.drawLine(
mPlayRect!!.right + mIconLineGap, (height / 2).toFloat(),
width - timeWidth * 1.3f, (height / 2).toFloat(), mIndicatorPaint!!
)
val baseX = (width - timeWidth * 1.1f).toInt()
val baseline =
(height / 2).toFloat() - (mIndicatorPaint!!.descent() - mIndicatorPaint!!.ascent()) / 2 - mIndicatorPaint!!.ascent()
mIndicatorPaint!!.color = mIndicatorTextColor
canvas.drawText(
LrcHelper.formatTime(time),
baseX.toFloat(),
baseline,
mIndicatorPaint!!
)
}
}
private fun dip2px(context: Context, dpVale: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpVale * scale + 0.5f).toInt()
}
private fun drawLrc(canvas: Canvas, x: Float, y: Float, i: Int) {
val text = mLrcData!![i].text
var staticLayout: StaticLayout? = mLrcMap[text]
if (staticLayout == null) {
mTextPaint!!.textSize = mLrcTextSize
staticLayout = StaticLayout(
text,
mTextPaint,
lrcWidth,
Layout.Alignment.ALIGN_NORMAL,
1f,
0f,
false
)
mLrcMap[text] = staticLayout
}
canvas.save()
canvas.translate(x, y - (staticLayout.height / 2).toFloat() - mOffset)
staticLayout.draw(canvas)
canvas.restore()
}
//中间空文字
private fun drawEmptyText(canvas: Canvas) {
mTextPaint!!.textAlign = Paint.Align.LEFT
mTextPaint!!.color = mNoLrcTextColor
mTextPaint!!.textSize = mNoLrcTextSize
canvas.save()
val staticLayout = StaticLayout(
mDefaultContent, mTextPaint,
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
)
val margin = dip2px(context, 16f).toFloat();
canvas.translate(margin, margin)
staticLayout.draw(canvas)
canvas.restore()
}
fun updateTime(time: Long) {
if (isLrcEmpty) {
return
}
val linePosition = getUpdateTimeLinePosition(time)
if (mCurrentLine != linePosition) {
mCurrentLine = linePosition
if (isUserScroll) {
invalidateView()
return
}
ViewCompat.postOnAnimation(this@LrcView, mScrollRunnable)
}
}
private fun getUpdateTimeLinePosition(time: Long): Int {
var linePos = 0
for (i in 0 until lrcCount) {
val lrc = mLrcData!![i]
if (time >= lrc.time) {
if (i == lrcCount - 1) {
linePos = lrcCount - 1
} else if (time < mLrcData!![i + 1].time) {
linePos = i
break
}
}
}
return linePos
}
private fun scrollToPosition(linePosition: Int) {
val scrollY = getItemOffsetY(linePosition)
val animator = ValueAnimator.ofFloat(mOffset, scrollY)
animator.addUpdateListener { animation ->
mOffset = animation.animatedValue as Float
invalidateView()
}
animator.duration = 300
animator.start()
}
private fun getItemOffsetY(linePosition: Int): Float {
var tempY = 0f
for (i in 1..linePosition) {
tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
}
return tempY
}
private fun getTextHeight(linePosition: Int): Float {
val text = mLrcData!![linePosition].text
var staticLayout: StaticLayout? = mStaticLayoutHashMap[text]
if (staticLayout == null) {
mTextPaint!!.textSize = mLrcTextSize
staticLayout = StaticLayout(
text, mTextPaint,
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
)
mStaticLayoutHashMap[text] = staticLayout
}
return staticLayout.height.toFloat()
}
private fun overScrolled(): Boolean {
return mOffset > getItemOffsetY(lrcCount - 1) || mOffset < 0
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (isLrcEmpty) {
return super.onTouchEvent(event)
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain()
}
mVelocityTracker!!.addMovement(event)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
removeCallbacks(mScrollRunnable)
removeCallbacks(mHideIndicatorRunnable)
if (!mOverScroller!!.isFinished) {
mOverScroller!!.abortAnimation()
}
mLastMotionX = event.x
mLastMotionY = event.y
isUserScroll = true
isDragging = false
}
MotionEvent.ACTION_MOVE -> {
var moveY = event.y - mLastMotionY
if (Math.abs(moveY) > mScaledTouchSlop) {
isDragging = true
isShowTimeIndicator = isEnableShowIndicator
}
if (isDragging) {
// if (mOffset < 0) {
// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight);
// }
val maxHeight = getItemOffsetY(lrcCount - 1)
// if (mOffset > maxHeight) {
// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight);
// }
if (mOffset < 0 || mOffset > maxHeight) {
moveY /= 3.5f
}
mOffset -= moveY
mLastMotionY = event.y
invalidateView()
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) {
isShowTimeIndicator = false
invalidateView()
performClick()
}
handleActionUp(event)
}
}
// return isDragging || super.onTouchEvent(event);
return true
}
private fun handleActionUp(event: MotionEvent) {
if (isEnableShowIndicator) {
ViewCompat.postOnAnimationDelayed(
this@LrcView,
mHideIndicatorRunnable,
mIndicatorTouchDelay.toLong()
)
}
if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {
isShowTimeIndicator = false
invalidateView()
if (mOnPlayIndicatorLineListener != null) {
mOnPlayIndicatorLineListener!!.onPlay(
mLrcData!![indicatePosition].time,
mLrcData!![indicatePosition].text
)
}
}
if (overScrolled() && mOffset < 0) {
scrollToPosition(0)
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(
this@LrcView,
mScrollRunnable,
mTouchDelay.toLong()
)
}
return
}
if (overScrolled() && mOffset > getItemOffsetY(lrcCount - 1)) {
scrollToPosition(lrcCount - 1)
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(
this@LrcView,
mScrollRunnable,
mTouchDelay.toLong()
)
}
return
}
mVelocityTracker!!.computeCurrentVelocity(1000, mMaximumFlingVelocity.toFloat())
val YVelocity = mVelocityTracker!!.yVelocity
val absYVelocity = Math.abs(YVelocity)
if (absYVelocity > mMinimumFlingVelocity) {
mOverScroller!!.fling(
0, mOffset.toInt(), 0, (-YVelocity).toInt(), 0,
0, 0, getItemOffsetY(lrcCount - 1).toInt(),
0, getTextHeight(0).toInt()
)
invalidateView()
}
releaseVelocityTracker()
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
}
}
private fun onClickPlayButton(event: MotionEvent): Boolean {
val left = mPlayRect!!.left.toFloat()
val right = mPlayRect!!.right.toFloat()
val top = mPlayRect!!.top.toFloat()
val bottom = mPlayRect!!.bottom.toFloat()
val x = event.x
val y = event.y
return (mLastMotionX > left && mLastMotionX < right && mLastMotionY > top
&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom)
}
override fun computeScroll() {
super.computeScroll()
if (mOverScroller!!.computeScrollOffset()) {
mOffset = mOverScroller!!.currY.toFloat()
invalidateView()
}
}
private fun releaseVelocityTracker() {
if (null != mVelocityTracker) {
mVelocityTracker!!.clear()
mVelocityTracker!!.recycle()
mVelocityTracker = null
}
}
fun resetView(defaultContent: String) {
if (mLrcData != null) {
mLrcData!!.clear()
}
mLrcMap.clear()
mStaticLayoutHashMap.clear()
mCurrentLine = 0
mOffset = 0f
isUserScroll = false
isDragging = false
mDefaultContent = defaultContent
removeCallbacks(mScrollRunnable)
invalidate()
}
override fun performClick(): Boolean {
return super.performClick()
}
fun dp2px(context: Context, dpVal: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.resources.displayMetrics
).toInt()
}
fun sp2px(context: Context, spVal: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
spVal, context.resources.displayMetrics
).toInt()
}
/**
* 暂停手动滑动歌词后不再自动回滚至当前播放位置
*/
fun pause() {
isAutoAdjustPosition = false
invalidateView()
}
/**
* 恢复继续自动回滚
*/
fun resume() {
isAutoAdjustPosition = true
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
invalidateView()
}
/*------------------Config-------------------*/
private fun invalidateView() {
if (Looper.getMainLooper().thread === Thread.currentThread()) {
invalidate()
} else {
postInvalidate()
}
}
fun setOnPlayIndicatorLineListener(onPlayIndicatorLineListener: OnPlayIndicatorLineListener) {
mOnPlayIndicatorLineListener = onPlayIndicatorLineListener
}
fun setEmptyContent(defaultContent: String) {
mDefaultContent = defaultContent
invalidateView()
}
fun setLrcTextSize(lrcTextSize: Float) {
mLrcTextSize = lrcTextSize
invalidateView()
}
fun setLrcLineSpaceHeight(lrcLineSpaceHeight: Float) {
mLrcLineSpaceHeight = lrcLineSpaceHeight
invalidateView()
}
fun setTouchDelay(touchDelay: Int) {
mTouchDelay = touchDelay
invalidateView()
}
fun setNormalColor(@ColorInt normalColor: Int) {
mNormalColor = normalColor
invalidateView()
}
fun setCurrentPlayLineColor(@ColorInt currentPlayLineColor: Int) {
mCurrentPlayLineColor = currentPlayLineColor
invalidateView()
}
fun setNoLrcTextSize(noLrcTextSize: Float) {
mNoLrcTextSize = noLrcTextSize
invalidateView()
}
fun setNoLrcTextColor(@ColorInt noLrcTextColor: Int) {
mNoLrcTextColor = noLrcTextColor
invalidateView()
}
fun setIndicatorLineWidth(indicatorLineWidth: Float) {
mIndicatorLineWidth = indicatorLineWidth
invalidateView()
}
fun setIndicatorTextSize(indicatorTextSize: Float) {
// mIndicatorTextSize = indicatorTextSize;
mIndicatorPaint!!.textSize = indicatorTextSize
invalidateView()
}
fun setCurrentIndicateLineTextColor(currentIndicateLineTextColor: Int) {
mCurrentIndicateLineTextColor = currentIndicateLineTextColor
invalidateView()
}
fun setIndicatorLineColor(indicatorLineColor: Int) {
mIndicatorLineColor = indicatorLineColor
invalidateView()
}
fun setIndicatorMargin(indicatorMargin: Float) {
mIndicatorMargin = indicatorMargin
invalidateView()
}
fun setIconLineGap(iconLineGap: Float) {
mIconLineGap = iconLineGap
invalidateView()
}
fun setIconWidth(iconWidth: Float) {
mIconWidth = iconWidth
invalidateView()
}
fun setIconHeight(iconHeight: Float) {
mIconHeight = iconHeight
invalidateView()
}
fun setEnableShowIndicator(enableShowIndicator: Boolean) {
isEnableShowIndicator = enableShowIndicator
invalidateView()
}
fun setIndicatorTextColor(indicatorTextColor: Int) {
mIndicatorTextColor = indicatorTextColor
invalidateView()
}
interface OnPlayIndicatorLineListener {
fun onPlay(time: Long, content: String)
}
companion object {
private const val DEFAULT_CONTENT = "Empty"
}
}

View file

@ -170,6 +170,8 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_toLeftOf="@id/songTotalTime"

View file

@ -1,152 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:elevation="0dp"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Toolbar"
app:navigationIcon="@drawable/ic_keyboard_backspace_black_24dp"
app:title="@string/profile"
app:titleTextAppearance="@style/ToolbarTextAppearanceNormal" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="128dp"
android:layout_marginEnd="128dp"
android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.google.android.material.card.MaterialCardView
android:id="@+id/imageContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="24dp"
app:cardCornerRadius="24dp"
app:layout_constraintDimensionRatio="21:10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bannerImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/material_design_default" />
<View
android:id="@+id/bannerSelect"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/twenty_percent_black_overlay"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?roundSelector"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="@id/bannerImage"
app:layout_constraintEnd_toEndOf="@id/bannerImage"
app:layout_constraintStart_toStartOf="@id/bannerImage"
app:layout_constraintTop_toTopOf="@id/bannerImage"
app:srcCompat="@drawable/ic_add_photo_white_24dp"
app:tint="@color/md_white_1000" />
<code.name.monkey.retromusic.views.RetroShapeableImageView
android:id="@+id/userImage"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginStart="24dp"
android:layout_marginBottom="24dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:retroCornerSize="36dp"
app:srcCompat="@drawable/ic_account_white_24dp" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="16dp"
android:gravity="center"
android:padding="12dp"
android:src="@drawable/ic_add_photo_white_24dp"
app:layout_constraintBottom_toBottomOf="@id/userImage"
app:layout_constraintEnd_toEndOf="@id/userImage"
app:layout_constraintStart_toStartOf="@id/userImage"
app:layout_constraintTop_toTopOf="@id/userImage"
app:tint="?android:attr/colorControlNormal" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/nameContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:hintEnabled="true"
app:layout_constraintBottom_toBottomOf="@id/next"
app:layout_constraintEnd_toStartOf="@id/next"
app:layout_constraintStart_toStartOf="@id/imageContainer"
app:layout_constraintTop_toTopOf="@id/next">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="@string/my_name"
android:inputType="textPersonName|textCapWords|text"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:padding="16dp"
android:text="@string/save"
app:icon="@drawable/ic_save_white_24dp"
app:iconGravity="textStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/imageContainer"
app:layout_constraintStart_toEndOf="@id/nameContainer"
app:layout_constraintTop_toBottomOf="@id/imageContainer" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -57,6 +57,7 @@
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"

View file

@ -26,6 +26,8 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -30,7 +30,7 @@
android:id="@+id/imageContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="24dp"
app:cardCornerRadius="16dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="21:10"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -42,6 +42,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:splitTrack="false"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -56,6 +56,8 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="match_parent"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="20dp"

View file

@ -28,6 +28,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent"

View file

@ -92,6 +92,8 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"

View file

@ -28,6 +28,8 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -29,6 +29,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent"

View file

@ -39,6 +39,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent"

View file

@ -26,6 +26,8 @@
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintBottom_toTopOf="@+id/playPauseButton"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -27,6 +27,8 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -14,23 +14,25 @@
<resources>
<declare-styleable name="LrcView">
<attr name="lrcTextSize" format="dimension" />
<attr name="lrcLineSpaceSize" format="dimension" />
<attr name="lrcNormalTextColor" format="reference|color" />
<attr name="lrcCurrentTextColor" format="reference|color" />
<attr name="lrcTouchDelay" format="integer" />
<attr name="noLrcTextSize" format="dimension" />
<attr name="noLrcTextColor" format="reference|color" />
<attr name="indicatorLineHeight" format="dimension" />
<attr name="indicatorTextSize" format="dimension" />
<attr name="indicatorTextColor" format="reference|color" />
<attr name="currentIndicateLrcColor" format="reference|color" />
<attr name="indicatorTouchDelay" format="integer" />
<attr name="indicatorLineColor" format="reference|color" />
<attr name="indicatorStartEndMargin" format="dimension" />
<attr name="iconLineGap" format="dimension" />
<attr name="playIconWidth" format="dimension" />
<attr name="playIconHeight" format="dimension" />
<attr name="playIcon" format="reference" />
<attr name="lrcTextSize" format="dimension"/>
<attr name="lrcLineSpaceSize" format="dimension"/>
<attr name="lrcNormalTextColor" format="reference|color"/>
<attr name="lrcCurrentTextColor" format="reference|color"/>
<attr name="lrcTouchDelay" format="integer"/>
<attr name="noLrcTextSize" format="dimension"/>
<attr name="noLrcTextColor" format="reference|color"/>
<attr name="indicatorLineHeight" format="dimension"/>
<attr name="indicatorTextSize" format="dimension"/>
<attr name="indicatorTextColor" format="reference|color"/>
<attr name="currentIndicateLrcColor" format="reference|color"/>
<attr name="indicatorTouchDelay" format="integer"/>
<attr name="indicatorLineColor" format="reference|color"/>
<attr name="indicatorStartEndMargin" format="dimension"/>
<attr name="iconLineGap" format="dimension"/>
<attr name="playIconWidth" format="dimension"/>
<attr name="playIconHeight" format="dimension"/>
<attr name="playIcon" format="reference"/>
<attr name="isLrcCurrentTextBold" format="boolean"/>
<attr name="isLrcIndicatorTextBold" format="boolean"/>
</declare-styleable>
</resources>