也可看Android自绘控件开发与性能优化实践——以录音波浪动画为例
直接上代码
基类:
public abstract class RenderView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "RenderView";
//是否正在绘制动画
private boolean isStartAnim = false;
private final static Object surfaceLock = new Object();
private RenderThread renderThread;
/**
* 绘制背景,防止开始时黑屏
* 子View可以执行此方法
* @param canvas
*/
protected abstract void doDrawBackground(Canvas canvas);
/**
* 渲染surfaceView的回调方法。
*
* @param canvas 画布
*/
protected abstract void onRender(Canvas canvas, long millisPassed);
public RenderView(Context context) {
this(context, null);
}
public RenderView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RenderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getHolder().addCallback(this);
}
/*回调/线程*/
private static class RenderThread extends Thread {
private static final long SLEEP_TIME = 16;
private WeakReference<RenderView> renderView;
private boolean running = false;
private boolean destoryed = false;
private boolean isPause = false;
public RenderThread(RenderView renderView) {
super("RenderThread");
Log.e(TAG, "RenderThread: " );
this.renderView = new WeakReference<>(renderView);
}
private SurfaceHolder getSurfaceHolder(){
if (getRenderView() != null ){
return getRenderView().getHolder();
}
return null;
}
private RenderView getRenderView(){
return renderView.get();
}
@Override
public void run() {
long startAt = System.currentTimeMillis();
while (!destoryed) {
// Log.e("whileThread","RenderView动画线程222");
synchronized (surfaceLock) {
//这里并没有真正的结束Thread,防止部分手机连续调用同一Thread出错
while (isPause){
try {
surfaceLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (running) {
if (getSurfaceHolder() != null && getRenderView() != null) {
Canvas canvas = getSurfaceHolder().lockCanvas();
if (canvas != null) {
getRenderView().doDrawBackground(canvas);
if (getRenderView().isStartAnim) {
//这里做真正绘制的事情
getRenderView().render(canvas, System.currentTimeMillis() - startAt);
}
if(getSurfaceHolder().getSurface().isValid()){
getSurfaceHolder().unlockCanvasAndPost(canvas);
}
}
}else {
running = false;
}
}
}
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setRun(boolean isRun) {
this.running = isRun;
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
renderer = onCreateRenderer();
if (renderer != null && renderer.isEmpty()) {
throw new IllegalStateException();
}
renderThread = new RenderThread(this);
}
/**
* 解锁暂停,继续执行绘制任务
* 默认当Resume时不自动启动动画
*/
public void onResume(){
synchronized (surfaceLock){
if (renderThread != null) {
renderThread.isPause = false;
surfaceLock.notifyAll();
}
}
}
//假暂停,并没有结束Thread
public void onPause(){
synchronized (surfaceLock){
if (renderThread != null) {
renderThread.isPause = true;
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//这里可以获取SurfaceView的宽高等信息
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (surfaceLock) { //这里需要加锁,否则doDraw中有可能会crash
renderThread.setRun(false);
renderThread.destoryed = true;
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus && isStartAnim){
startAnim();
}else {
startThread();
}
}
/*绘图*/
public interface IRenderer {
void onRender(Canvas canvas, long millisPassed);
}
private List<IRenderer> renderer;
protected List<IRenderer> onCreateRenderer() {
return null;
}
private void render(Canvas canvas, long millisPassed) {
if (renderer != null) {
for (int i = 0, size = renderer.size(); i < size; i++) {
renderer.get(i).onRender(canvas, millisPassed);
}
} else {
onRender(canvas, millisPassed);
}
}
public void startAnim(){
isStartAnim = true;
startThread();
}
private void startThread(){
if (renderThread != null && !renderThread.running) {
renderThread.setRun(true);
try {
if (renderThread.getState() == Thread.State.NEW) {
renderThread.start();
}
}catch (RuntimeException e){
e.printStackTrace();
}
}
}
public void stopAnim(){
isStartAnim = false;
if (renderThread != null && renderThread.running) {
renderThread.setRun(false);
renderThread.interrupt();
}
}
public boolean isRunning(){
if (renderThread != null) {
return renderThread.running;
}
return false;
}
//释放相关资源,防止内存泄漏
public void release(){
if (getHolder() != null && getHolder().getSurface() != null) {
getHolder().getSurface().release();
getHolder().removeCallback(this);
}
}
}
真正的基于surfaceView的波浪动画view来了:
public class FakeVoiceViewWithSurface extends RenderView {
private static final String TAG = "FakeVoiceViewWithSurface";
private int mWidth;
private int mHeight;
private int lineColor = DEFAULT_LINE_COLOR;
private float maxLineHeight;
private int lineWidth = DEFAULT_LINE_WIDTH;
private int lineGap = DEFAULT_LINE_GAP;
//根据最后宽度来确定有几条跳动的线条
private int lineCount;
private float padding;
private static final int DEFAULT_LINE_WIDTH = SizeUtils.dp2px(2);
private static final int DEFAULT_LINE_COLOR = 0xFF38ABF3;
private static final int DEFAULT_LINE_GAP = 10;
final float DEFAULT_WIDTH = getContext().getResources().getDimension(R.dimen.qb_px_230);
final float DEFAULT_HEIGHT = getContext().getResources().getDimension(R.dimen.qb_px_80);
private Paint linePaint;
private Paint rectPaint;
private List<LineParamBean> lineList = new ArrayList<>();
private RectF rectF;
private boolean status;
private Random random;
private SurfaceHolder surfaceHolder;
// private boolean hadMeasured = false;
private boolean isTransparentMode = false;
//控制向右偏移速度,越小偏移速度越快
private float offsetSpeed = 250F;
private boolean isTrans = false;
//背景色
private int backGroundColor = Color.TRANSPARENT;
public FakeVoiceViewWithSurface(Context context) {
super(context);
}
public FakeVoiceViewWithSurface(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FakeVoiceViewWithSurface(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void doDrawBackground(Canvas canvas) {
//绘制背景
if(isTransparentMode) {
//启用CLEAR模式,所绘制内容不会提交到画布上。
canvas.drawColor(backGroundColor, PorterDuff.Mode.CLEAR);
} else {
canvas.drawColor(backGroundColor);
}
}
@Override
protected void onRender(Canvas canvas, long millisPassed) {
if (isTrans) {
canvas.drawColor(Color.WHITE);
canvas.drawColor(backGroundColor, PorterDuff.Mode.SRC);
}
if(rectF == null || lineList.isEmpty()) {
initDraw();
}
for(int i = 0; i < lineList.size(); i++) {
LineParamBean bean = lineList.get(i);
float offset = (millisPassed % bean.randomTime) / bean.randomTime;
if(offset < 0.5f) {
bean.height = 2 * offset * maxLineHeight;
} else if(offset <= 1.0f) {
bean.height = 2 * (1 - offset) * maxLineHeight;
}
bean.y = (mHeight - bean.height) / 2;
if(!status) {
bean.x = lineGap * (i + 1) + lineWidth * ((float) i + (1 / 2)) + padding;
}
}
status = true;
if (isTrans) rectPaint.setColor(backGroundColor);
canvas.drawRoundRect(rectF, mHeight / 4, mHeight / 4, rectPaint);
for(LineParamBean bean : lineList) {
canvas.drawLine(bean.x, bean.y, bean.x, bean.y + bean.height, linePaint);
}
}
public void setTrans(boolean trans) {
isTrans = trans;
}
public boolean isStart() {
return status;
}
private void initDraw() {
this.mWidth = getMeasuredWidth();
this.mHeight = getMeasuredHeight();
Log.e(TAG, "width:" + mWidth);
Log.e(TAG, "height:" + mHeight);
padding = mWidth / 5;
maxLineHeight = mHeight * 2 / 3;
lineCount = (int) ((this.mWidth - lineGap - padding * 2) / (lineWidth + lineGap));
lineList.clear();
for(int i = 0; i < lineCount; i++) {
LineParamBean bean = new LineParamBean();
lineList.add(bean);
}
if(rectF == null) {
rectF = new RectF();
}
rectF.set(0, 0, this.mWidth, this.mHeight);
}
private void init() {
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setDither(true);
linePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint.setStrokeJoin(Paint.Join.ROUND);
linePaint.setColor(lineColor);
linePaint.setStrokeWidth(lineWidth);
rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
rectPaint.setColor(0xFF2B2A32);
rectPaint.setStyle(Paint.Style.FILL);
random = new Random();
initView();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = resolveSize((int) DEFAULT_WIDTH, widthMeasureSpec);
int height = resolveSize((int) DEFAULT_HEIGHT, heightMeasureSpec);
setMeasuredDimension(width, height);
}
private void stopAllAnim() {
status = false;
}
public void releaseAnimator() {
stopAllAnim();
// clearAll();
}
private void clearAll() {
//防止内存泄漏
lineList.clear();
}
@Override
public void startAnim() {
super.startAnim();
}
@Override
public void stopAnim() {
super.stopAnim();
stopAllAnim();
clearDraw();
}
//清空画布所有内容
private void clearDraw() {
Canvas canvas = null;
try {
canvas = getHolder().lockCanvas(null);
canvas.drawColor(backGroundColor);
clearAll();
} catch(Exception e) {
} finally {
if(canvas != null) {
getHolder().unlockCanvasAndPost(canvas);
}
}
}
/**
* 初始化View
*/
private void initView() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
setZOrderOnTop(true);
surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
setFocusable(true);
setKeepScreenOn(true);
setFocusableInTouchMode(true);
}
}
使用也简单,直接在布局xml使用即可。
然后在对应的Fragment或Activity,需要启动动画的地方,调用接口方法startAnim(),在需要停止动画的地方调用接口方法stopAnim()即可。
以下是另一种的波浪动画:
public class WaveLineView extends RenderView {
private final int DEFAULT_SAMPLING_SIZE = 64;
private final float DEFAULT_OFFSET_SPEED = 250F;
private final int DEFAULT_SENSIBILITY = 5;
//采样点的数量,越高越精细,但是高于一定限度肉眼很难分辨,越高绘制效率越低
private int samplingSize;
//控制向右偏移速度,越小偏移速度越快
private float offsetSpeed;
//平滑改变的音量值
private float volume = 0;
//用户设置的音量,[0,100]
private int targetVolume = 50;
//每次平滑改变的音量单元
private float perVolume;
//灵敏度,越大越灵敏[1,10]
private int sensibility;
//背景色
private int backGroundColor = Color.WHITE;
//波浪线颜色
private int lineColor;
//粗线宽度
private int thickLineWidth;
//细线宽度
private int fineLineWidth;
private final Paint paint = new Paint();
private RectF rectF;
private Paint bgPaint;
{
//防抖动
paint.setDither(true);
//抗锯齿,降低分辨率,提高绘制效率
paint.setAntiAlias(true);
}
private List<Path> paths = new ArrayList<>();
{
for(int i = 0; i < 4; i++) {
paths.add(new Path());
}
}
//不同函数曲线系数
private float[] pathFuncs = {0.6f, 0.35f, 0.1f, -0.1f};
//采样点X坐标
private float[] samplingX;
//采样点位置映射到[-2,2]之间
private float[] mapX;
//画布宽高
private int width, height;
//画布中心的高度
private int centerHeight;
//振幅
private float amplitude;
//存储衰变系数
private SparseArray<Double> recessionFuncs = new SparseArray<>();
//连线动画结束标记
private boolean isPrepareLineAnimEnd = false;
//连线动画位移
private int lineAnimX = 0;
//渐入动画结束标记
private boolean isPrepareAlphaAnimEnd = false;
//渐入动画百分比值[0,1f]
private float prepareAlpha = 0f;
//是否开启准备动画
private boolean isOpenPrepareAnim = false;
private boolean isTransparentMode = false;
public WaveLineView(Context context) {
this(context, null);
}
public WaveLineView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveLineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttr(attrs);
}
private void initAttr(AttributeSet attrs) {
TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.WaveLineView);
backGroundColor = t.getColor(R.styleable.WaveLineView_wlvBackgroundColor, Color.WHITE);
samplingSize = t.getInt(R.styleable.WaveLineView_wlvSamplingSize, DEFAULT_SAMPLING_SIZE);
lineColor = t.getColor(R.styleable.WaveLineView_wlvLineColor, Color.parseColor("#2ED184"));
thickLineWidth = (int) t.getDimension(R.styleable.WaveLineView_wlvThickLineWidth, 6);
fineLineWidth = (int) t.getDimension(R.styleable.WaveLineView_wlvFineLineWidth, 2);
offsetSpeed = t.getFloat(R.styleable.WaveLineView_wlvMoveSpeed, DEFAULT_OFFSET_SPEED);
sensibility = t.getInt(R.styleable.WaveLineView_wlvSensibility, DEFAULT_SENSIBILITY);
isTransparentMode = backGroundColor == Color.TRANSPARENT;
t.recycle();
checkVolumeValue();
checkSensibilityValue();
//将RenderView放到最顶层
setZOrderOnTop(true);
if(getHolder() != null) {
//使窗口支持透明度
getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
rectF = new RectF();
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
@Override
protected void doDrawBackground(Canvas canvas) {
//绘制背景
if(isTransparentMode) {
//启用CLEAR模式,所绘制内容不会提交到画布上。
canvas.drawColor(backGroundColor, PorterDuff.Mode.CLEAR);
} else {
bgPaint.setColor(backGroundColor);
rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
canvas.drawRoundRect(rectF, getMeasuredHeight() / 4, getMeasuredHeight() / 4, bgPaint);
}
}
@Override
public void release() {
super.release();
}
@Override
protected void onRender(Canvas canvas, long millisPassed) {
float offset = millisPassed / offsetSpeed;
if(null == samplingX || null == mapX || null == pathFuncs) {
initDraw(canvas);
}
if(lineAnim(canvas)) {
resetPaths();
softerChangeVolume();
//波形函数的值
float curY;
for(int i = 0; i <= samplingSize; i++) {
float x = samplingX[i];
curY = (float) (amplitude * calcValue(mapX[i], offset));
for(int n = 0; n < paths.size(); n++) {
//四条线分别乘以不同的函数系数
float realY = curY * pathFuncs[n] * volume * 0.01f;
paths.get(n).lineTo(x, centerHeight + realY);
}
}
//连线至终点
for(int i = 0; i < paths.size(); i++) {
paths.get(i).moveTo(width, centerHeight);
}
//绘制曲线
for(int n = 0; n < paths.size(); n++) {
if(n == 0) {
paint.setStrokeWidth(thickLineWidth);
paint.setAlpha((int) (255 * alphaInAnim()));
} else {
paint.setStrokeWidth(fineLineWidth);
paint.setAlpha((int) (100 * alphaInAnim()));
}
canvas.drawPath(paths.get(n), paint);
}
}
}
//检查音量是否合法
private void checkVolumeValue() {
if(targetVolume > 100) {
targetVolume = 100;
}
}
//检查灵敏度值是否合法
private void checkSensibilityValue() {
if(sensibility > 10) {
sensibility = 10;
}
if(sensibility < 1) {
sensibility = 1;
}
}
/**
* 使曲线振幅有较大改变时动画过渡自然
*/
private void softerChangeVolume() {
//这里减去perVolume是为了防止volume频繁在targetVolume上下抖动
if(volume < targetVolume - perVolume) {
volume += perVolume;
} else if(volume > targetVolume + perVolume) {
if(volume < perVolume * 2) {
volume = perVolume * 2;
} else {
volume -= perVolume;
}
} else {
volume = targetVolume;
}
}
/**
* 渐入动画
*
* @return progress of animation
*/
private float alphaInAnim() {
if(!isOpenPrepareAnim) {
return 1;
}
if(prepareAlpha < 1f) {
prepareAlpha += 0.02f;
} else {
prepareAlpha = 1;
}
return prepareAlpha;
}
/**
* 连线动画
*
* @param canvas
* @return whether animation is end
*/
private boolean lineAnim(Canvas canvas) {
if(isPrepareLineAnimEnd || !isOpenPrepareAnim) {
return true;
}
paths.get(0).moveTo(0, centerHeight);
paths.get(1).moveTo(width, centerHeight);
for(int i = 1; i <= samplingSize; i++) {
float x = i * lineAnimX / samplingSize;
paths.get(0).lineTo(x, centerHeight);
paths.get(1).lineTo(width - x, centerHeight);
}
paths.get(0).moveTo(width / 2, centerHeight);
paths.get(1).moveTo(width / 2, centerHeight);
lineAnimX += width / 60;
canvas.drawPath(paths.get(0), paint);
canvas.drawPath(paths.get(1), paint);
if(lineAnimX > width / 2) {
isPrepareLineAnimEnd = true;
return true;
}
return false;
}
/**
* 重置path
*/
private void resetPaths() {
for(int i = 0; i < paths.size(); i++) {
paths.get(i).rewind();
paths.get(i).moveTo(0, centerHeight);
}
}
//初始化参数
private void initParameters() {
lineAnimX = 0;
prepareAlpha = 0f;
isPrepareLineAnimEnd = false;
isPrepareAlphaAnimEnd = false;
samplingX = null;
}
@Override
public void startAnim() {
initParameters();
super.startAnim();
}
@Override
public void stopAnim() {
super.stopAnim();
clearDraw();
}
//清空画布所有内容
public void clearDraw() {
Canvas canvas = null;
try {
canvas = getHolder().lockCanvas(null);
canvas.drawColor(backGroundColor);
resetPaths();
for(int i = 0; i < paths.size(); i++) {
canvas.drawPath(paths.get(i), paint);
}
} catch(Exception e) {
} finally {
if(canvas != null) {
getHolder().unlockCanvasAndPost(canvas);
}
}
}
//初始化绘制参数
private void initDraw(Canvas canvas) {
width = canvas.getWidth();
height = canvas.getHeight();
centerHeight = height >> 1;
//振幅为高度的1/4
amplitude = height / 3.0f;
//适合View的理论最大音量值,和音量不属于同一概念
perVolume = sensibility * 0.35f;
//初始化采样点及映射
//这里因为包括起点和终点,所以需要+1
samplingX = new float[samplingSize + 1];
mapX = new float[samplingSize + 1];
//确定采样点之间的间距
float gap = width / (float) samplingSize;
//采样点的位置
float x;
for(int i = 0; i <= samplingSize; i++) {
x = i * gap;
samplingX[i] = x;
//将采样点映射到[-2,2]
mapX[i] = (x / (float) width) * 4 - 2;
}
paint.setStyle(Paint.Style.STROKE);
paint.setColor(lineColor);
paint.setStrokeWidth(thickLineWidth);
}
/**
* 计算波形函数中x对应的y值
* <p>
* 使用稀疏矩阵进行暂存计算好的衰减系数值,下次使用时直接查找,减少计算量
*
* @param mapX 换算到[-2,2]之间的x值
* @param offset 偏移量
* @return [-1, 1]
*/
private double calcValue(float mapX, float offset) {
int keyX = (int) (mapX * 1000);
offset %= 2;
double sinFunc = Math.sin(Math.PI * mapX - offset * Math.PI);
double recessionFunc;
if(recessionFuncs.indexOfKey(keyX) >= 0) {
recessionFunc = recessionFuncs.get(keyX);
} else {
recessionFunc = 4 / (4 + Math.pow(mapX, 4));
recessionFuncs.put(keyX, recessionFunc);
}
return sinFunc * recessionFunc;
}
/**
* the wave line animation move speed from left to right
* you can use negative number to make the animation from right to left
* the default value is 290F,the smaller, the faster
*
* @param moveSpeed
*/
public void setMoveSpeed(float moveSpeed) {
this.offsetSpeed = moveSpeed;
}
/**
* User set volume, [0,100]
*
* @param volume
*/
public void setVolume(int volume) {
if(Math.abs(targetVolume - volume) > perVolume) {
this.targetVolume = volume;
checkVolumeValue();
}
}
public void setBackGroundColor(int backGroundColor) {
this.backGroundColor = backGroundColor;
}
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
}
/**
* Sensitivity, the bigger the more sensitive [1,10]
* the default value is 5
*
* @param sensibility
*/
public void setSensibility(int sensibility) {
this.sensibility = sensibility;
checkSensibilityValue();
}
}