Android -- [SelfView] 自定义多行歌词滚动显示器

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 List<LrcBean> 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;//居中y private float startY;//首行y private float oldY;//划屏时起始按压点y private 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(); } @Override protected 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); } @Override protected 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); } } @Override public 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()); } } @Override protected 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.10 String[] 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
上一篇:【论文阅读】国际开源发展经验及其对我国开源创新体系建设的启示


下一篇:html小白初学