做的好的国外网站,网站创作思路,网站运营需要哪些人员,wordpress上传html代码Android – [SelfView] 自定义多行歌词滚动显示器
流畅、丝滑的滚动歌词控件* 1. 背景透明#xff1b;* 2. 外部可控制进度变化#xff1b;* 3. 支持屏幕拖动调节进度#xff08;回调给外部#xff09;#xff1b;效果
歌词文件#xff08;.lrc#xff09;
一. 使用…Android – [SelfView] 自定义多行歌词滚动显示器
流畅、丝滑的滚动歌词控件* 1. 背景透明* 2. 外部可控制进度变化* 3. 支持屏幕拖动调节进度回调给外部效果
歌词文件.lrc
一. 使用
com.nepalese.harinetest.player.lrc.VirgoLrcViewandroid:idid/lrcViewandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent/private VirgoLrcView lrcView;lrcView findViewById(R.id.lrcView);
initLrc();//
private void initLrc(){
//设置歌词文件 .lrc
//lrcView.setLrc(FileUtils.readTxtResource(getApplicationContext(), R.raw.shaonian, utf-8));lrcView.setLrc(R.raw.shaonian);lrcView.seekTo(0);lrcView.setCallback(new VirgoLrcView.LrcCallback() {Overridepublic void onUpdateTime(long time) {//拖动歌词返回的时间点}Overridepublic void onFinish() {stopTask();}});
}public void onStartPlay(View view) {startTask();
}public void onStopPlay(View view) {stopTask();
}//使用计时器模拟歌曲播放时进度刷新
private long curTime 0;
private final Runnable timeTisk new Runnable() {Overridepublic void run() {curTime INTERVAL_FLASH;lrcView.seekTo(curTime);}
};private void startTask() {stopTask();handler.post(timeTisk);
}private void stopTask() {handler.removeCallbacks(timeTisk);
}private final long INTERVAL_FLASH 400L;
private final Handler handler new Handler(Looper.myLooper()) {Overridepublic void handleMessage(NonNull Message msg) {super.handleMessage(msg);}
};二. 码源
attr.xml
declare-styleable nameVirgoLrcViewattr namevlTextColorM formatcolor|reference /attr namevlTextColorS formatcolor|reference /attr namevlTextSize formatdimension|reference /attr namevlLineSpace formatdimension|reference /
/declare-styleableVirgoLrcView.java
import android.animation.Animator;
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.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;import androidx.annotation.Nullable;
import androidx.annotation.RawRes;import com.nepalese.harinetest.R;
import com.nepalese.harinetest.utils.CommonUtil;import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** Created by Administrator on 2024/11/26.* Usage:更流畅、丝滑的滚动歌词控件* 1. 背景透明* 2. 外部可控制进度变化* 3. 支持屏幕拖动调节进度回调给外部*/public class VirgoLrcView extends View {private static final String TAG VirgoLrcView;private static final float PADD_VALUE 25f;//时间线两边缩进值private static final float TEXT_RATE 1.25f;//当前行字体放大比例private static final long INTERVAL_ANIMATION 400L;//动画时长private static final String DEFAULT_TEXT 暂无歌词快去下载吧;private final Context context;private Paint paint;//画笔, 仅一个private ValueAnimator animator;//动画private ListLrcBean lineList;//歌词行private LrcCallback callback;//手动滑动进度刷新回调//可设置变量private int textColorMain;//选中字体颜色private int textColorSec;//其他字体颜色private float textSize;//字体大小private float lineSpace;//行间距private float selectTextSize;//当前选中行字体大小private int width, height;//控件宽高private int curLine;//当前行数private int locateLine;//滑动时居中行数private int underRows;//中分下需显示行数private float itemHeight;//一行字行间距private float centerY;//居中yprivate float startY;//首行yprivate float oldY;//划屏时起始按压点yprivate float offsetY;//动画已偏移量private float offsetY2;//每次手动滑动偏移量private long maxTime;//歌词显示最大时间private boolean isDown;//按压界面private boolean isReverse;//往回滚动public VirgoLrcView(Context context) {this(context, null);}public VirgoLrcView(Context context, Nullable AttributeSet attrs) {this(context, attrs, 0);}public VirgoLrcView(Context context, Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.context context;init(attrs);}private void init(AttributeSet attrs) {TypedArray ta getContext().obtainStyledAttributes(attrs, R.styleable.VirgoLrcView);textColorMain ta.getColor(R.styleable.VirgoLrcView_vlTextColorM, Color.CYAN);textColorSec ta.getColor(R.styleable.VirgoLrcView_vlTextColorS, Color.GRAY);textSize ta.getDimension(R.styleable.VirgoLrcView_vlTextSize, 45f);lineSpace ta.getDimension(R.styleable.VirgoLrcView_vlLineSpace, 28f);ta.recycle();selectTextSize textSize * TEXT_RATE;curLine 0;maxTime 0;isDown false;isReverse false;lineList new ArrayList();paint new Paint();paint.setTextSize(textSize);paint.setAntiAlias(true);calculateItem();}Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);if (width 0 || height 0) {initLayout();}}//控件大小变化时需重置计算private void initLayout() {width getWidth();height getHeight();centerY (height - itemHeight) / 2.0f;startY centerY;underRows (int) Math.ceil(height / itemHeight / 3);Log.d(TAG, itemHeight: itemHeight , underRows: underRows);}Overrideprotected void onDraw(Canvas canvas) {//提示无歌词if (lineList.isEmpty()) {paint.setColor(textColorMain);paint.setTextSize(selectTextSize);canvas.drawText(DEFAULT_TEXT, getStartX(DEFAULT_TEXT, paint), centerY, paint);return;}if (isDown) {paint.setTextSize(textSize);paint.setColor(textColorSec);//画时间if (locateLine 0) {canvas.drawText(lineList.get(locateLine).getStrTime(), PADD_VALUE, centerY, paint);}//画选择线canvas.drawLine(PADD_VALUE, centerY, width - PADD_VALUE, centerY, paint);//手动滑动drawTexts(canvas, startY - offsetY2);} else {//自动滚动if (isReverse) {drawTexts(canvas, startY offsetY);} else {drawTexts(canvas, startY - offsetY);}}}private void drawTexts(Canvas canvas, float tempY) {for (int i 0; i lineList.size(); i) {float y tempY i * itemHeight;if (y 0 || y height) {continue;}if (curLine i) {paint.setTextSize(selectTextSize);paint.setColor(textColorMain);} else {paint.setTextSize(textSize);paint.setColor(textColorSec);}canvas.drawText(lineList.get(i).getLrc(), getStartX(lineList.get(i).getLrc(), paint), y, paint);}}Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isDown true;if (animator ! null) {if (animator.isRunning()) {//停止动画animator.end();}}locateLine -1;oldY event.getY();break;case MotionEvent.ACTION_MOVE:offsetY2 oldY - event.getY();calculateCurLine(oldY - event.getY());//定位时间啊invalidate();break;case MotionEvent.ACTION_UP:isDown false;postNewLine();break;}return true;}//计算滑动后当前居中的行private void calculateCurLine(float y) {int offLine (int) Math.floor(y / itemHeight);if (offLine 0) {return;}locateLine curLine offLine;if (locateLine lineList.size() - 1) {//最后一行locateLine lineList.size() - 1;} else if (locateLine 0) {//第一行locateLine 0;}}//回调通知自身不跳转进度private void postNewLine() {//返回当前行对应的时间线if (callback null) {return;}if (locateLine 0) {callback.onUpdateTime(lineList.get(locateLine).getTime());}}Overrideprotected void onDetachedFromWindow() {releaseBase();super.onDetachedFromWindow();}/*** 移除控件注销资源*/private void releaseBase() {cancelAnim();if (lineList ! null) {lineList.clear();lineList null;}if (callback ! null) {callback null;}}private void calculateItem() {itemHeight getTextHeight() lineSpace;}//计算使文字水平居中private float getStartX(String str, Paint paint) {return (width - paint.measureText(str)) / 2.0f;}//获取文字高度private float getTextHeight() {Paint.FontMetrics fm paint.getFontMetrics();return fm.descent - fm.ascent;}//解析歌词private void parseLrc(InputStreamReader inputStreamReader) {BufferedReader reader new BufferedReader(inputStreamReader);String line;try {while ((line reader.readLine()) ! null) {parseLine(line);}} catch (IOException e) {e.printStackTrace();}try {inputStreamReader.close();} catch (IOException e) {e.printStackTrace();}try {reader.close();} catch (IOException e) {e.printStackTrace();}maxTime lineList.get(lineList.size() - 1).getTime() 1000;//多加一秒}private long parseTime(String time) {// 00:01.10String[] min time.split(:);String[] sec min[1].split(\\.);long minInt Long.parseLong(min[0].replaceAll(\\D, ).replaceAll(\r, ).replaceAll(\n, ).trim());long secInt Long.parseLong(sec[0].replaceAll(\\D, ).replaceAll(\r, ).replaceAll(\n, ).trim());long milInt Long.parseLong(sec[1].replaceAll(\\D, ).replaceAll(\r, ).replaceAll(\n, ).trim());return minInt * 60 * 1000 secInt * 1000 milInt;// * 10;}private void parseLine(String line) {Matcher matcher Pattern.compile(\\[\\d.].).matcher(line);// 如果形如[xxx]后面啥也没有的则return空if (!matcher.matches()) {long time;String str;String con line.replace(\\[, ).replace(\\], );if (con.matches(^\\d.)) {//timetime parseTime(con);str ;} else {return;}lineList.add(new LrcBean(time, str, con));return;}//[00:23.24]让自己变得快乐line line.replaceAll(\\[, );String[] result line.split(]);lineList.add(new LrcBean(parseTime(result[0]), result[1], result[0]));}private void reset() {lineList.clear();curLine 0;maxTime 0;isReverse false;cancelAnim();}///动画//*** 更新动画** param lineNum 需跳转行数*/private void updateAnim(int lineNum) {if (lineNum 0) {return;} else if (lineNum 1) {//自然变化if (curLine lineList.size() - underRows) {//停止动画 仅变更颜色cancelAnim();invalidate();return;}}isReverse lineNum 0;cancelAnim();setAnimator(Math.abs(lineNum));doAnimation();}/*** 注销已有动画*/protected void cancelAnim() {if (animator ! null) {animator.removeAllListeners();animator.end();animator null;}}/*** 动态创建动画** param lineNum 需跳转行数*/private void setAnimator(int lineNum) {animator ValueAnimator.ofFloat(0, itemHeight * lineNum);//一行animator.setDuration(INTERVAL_ANIMATION);animator.setInterpolator(new LinearInterpolator());//插值器设为线性}/*** 监听动画*/private void doAnimation() {if (animator null) {return;}animator.addListener(new Animator.AnimatorListener() {Overridepublic void onAnimationStart(Animator animation) {offsetY 0;}Overridepublic void onAnimationEnd(Animator animation) {if (isReverse) {startY offsetY;} else {startY - offsetY;}offsetY 0;invalidate();}Overridepublic void onAnimationCancel(Animator animation) {}Overridepublic void onAnimationRepeat(Animator animation) {}});animator.addUpdateListener(animation - {float av (float) animation.getAnimatedValue();if (av 0) {return;}offsetY av;invalidate();});animator.start();}public interface LrcCallback {void onUpdateTime(long time);void onFinish();}/*** 滑动监听** param callback LrcCallback*/public void setCallback(LrcCallback callback) {this.callback callback;}public void setTextColorMain(int textColorMain) {this.textColorMain textColorMain;}public void setTextColorSec(int textColorSec) {this.textColorSec textColorSec;}public void setTextSize(float textSize) {this.textSize textSize;this.selectTextSize textSize * TEXT_RATE;paint.setTextSize(textSize);calculateItem();}public void setLineSpace(float lineSpace) {this.lineSpace lineSpace;calculateItem();}/*** 设置歌词** param lrc 解析后的string*/public void setLrc(String lrc) {if (TextUtils.isEmpty(lrc)) {return;}reset();parseLrc(new InputStreamReader(new ByteArrayInputStream(lrc.getBytes())));}/*** 设置歌词** param resId 资源文件id*/public void setLrc(RawRes int resId) {reset();parseLrc(new InputStreamReader(context.getResources().openRawResource(resId), StandardCharsets.UTF_8));}/*** 设置歌词** param path lrc文件路径*/public void setLrcFile(String path) {File file new File(path);if (file.exists()) {reset();String format;if (CommonUtil.isUtf8(file)) {format UTF-8;} else {format GBK;}FileInputStream inputStream null;try {inputStream new FileInputStream(file);} catch (FileNotFoundException e) {e.printStackTrace();}InputStreamReader inputStreamReader null;//utf-8 GBKtry {inputStreamReader new InputStreamReader(inputStream, format);} catch (UnsupportedEncodingException e) {e.printStackTrace();}parseLrc(inputStreamReader);}}/*** 调整播放位置** param time ms*/public void seekTo(long time) {if (isDown) {//拖动歌词时暂不处理return;}if (time 0) {//刷新invalidate();return;} else if (time maxTime) {//超最大时间通知结束if (callback ! null) {callback.onFinish();}return;}for (int i 0; i lineList.size(); i) {if (i lineList.size() - 1) {if (time lineList.get(i).getTime() time lineList.get(i 1).getTime()) {int temp i - curLine;curLine i;updateAnim(temp);break;}} else {//last lineint temp i - curLine;curLine i;updateAnim(temp);break;}}}
}