package com.sherchen.heartrate.views; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.os.CountDownTimer; import android.util.AttributeSet; import android.view.View; import com.sherchen.heartrate.views.animator.LinearEvaluator; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * The description of use: * <br /> * Created time:2014/6/13 16:01 * Created by Dave */ public class WaveView extends View implements LinearEvaluator.EvaluatorListener, Animator.AnimatorListener{ private static final String LOG_TAG = WaveView.class.getSimpleName(); private static final boolean DEBUG = true; public void debug(String msg){ if(DEBUG) android.util.Log.v(LOG_TAG, msg); } private static final long DURATION_DRAW_WAVE = 330; private static final int WAVE_PEAK_LEFT_HEIGHT = 1; /**The max peek(contains the wave line) that will be shown*/ private static final int MAX_VALID_ENTRIES = 10; /**the view width as 9 weights*/ private static final int WIDTH_TOTAL_WEIGHTS = 9; /**the view height as 9 weights*/ private static final int HEIGHT_TOTAL_WEIGHTS = 9; //The start x and y of wave private float m_StartX, m_StartY; //The peak point, all of numbers are weight, such as (4/total_width_weight)*width, (0/total_height_weight)*height //(8,7) to (6,0) //need to draw line by other software //draw the wave peak when the valid heartrate is gotten. //LLL 0 1 2 3 4 5 6 7 8 9 //0 (7,0)[2] //1 //2 //3 //4 //5 //6 //7 (0, 6)[5] (4,6)[4] //8 //9 (8,6)[1] //10 (5,9)[3] private static final int[][] m_WavePeak_Weight = new int[][]{ new int[]{4, 0, 6, 6}, new int[]{5, 4, HEIGHT_TOTAL_WEIGHTS, 6}, new int[]{7, 5, 0, HEIGHT_TOTAL_WEIGHTS}, new int[]{WIDTH_TOTAL_WEIGHTS, 7, 6, 0} }; //draw the line when the heartrate is not gotten. //LLL 0 1 2 3 4 5 6 7 8 9 //0 //1 //2 //3 //4 //5 //6 //7 (0, 6)[2]<<<<<<<<<<<<(8, 6)[1] //8 //9 //10 private static final int[] m_WaveLine_Weight = new int[]{WIDTH_TOTAL_WEIGHTS, 0, 6, 6}; private List<float[]> m_PeakObjects; private float[] m_LineObjects; private CountDownTimer m_Timer; private Paint m_PeakPaint; private Paint m_EmptyPaint = new Paint(); private Path m_WavePath = new Path(); private WaveViewController m_InnerController; public WaveView(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_HARDWARE, null); setValues(); } private void setValues(){ m_InnerController = new WaveViewController(); m_PeakPaint = new Paint(); m_PeakPaint.setColor(Color.WHITE); m_PeakPaint.setAntiAlias(true); m_PeakPaint.setStyle(Style.STROKE); m_PeakPaint.setStrokeWidth(2.f); } public void startPaint(final long duration, final long interval){ initWave(); float extra = interval - DURATION_DRAW_WAVE; long tmp = interval; if(extra <= 200){ tmp = DURATION_DRAW_WAVE + 200; } final long newInterval = tmp; m_InnerController.saveStartPaintTime(); m_Timer = new CountDownTimer(duration, newInterval) { @Override public void onTick(long millisUntilFinished) { // disable hardware test // int value = 0; // if(m_TrickListener != null){ // value = m_TrickListener.getPainterValue(); // } // emulate test int value = m_InnerController.getEmulateHeartRate(); if(m_TrickListener != null){ m_TrickListener.onPainterTrick(value); } if(getVisibility() != View.VISIBLE){ debug("The view is hidden by others"); } m_InnerController.addPaintValue(value); startWave(value, DURATION_DRAW_WAVE); } @Override public void onFinish() { m_InnerController.computeAverage(); if(m_TrickListener != null){ m_TrickListener.onPainterFinished(); } } }; m_Timer.start(); } private void initWave(){ debug("initWave"); m_WavePath = new Path(); m_WavePath.moveTo(m_StartX, m_StartY); m_InnerController.clearHistory(); invalidate(); } private void startWave(int value, long duration){ doAnimation(value, duration); } private void doAnimation(int value, long duration){ if(value == 0){ Animator animator = getLineAnimator(m_LineObjects, duration); animator.addListener(this); animator.start(); }else{ AnimatorSet set = new AnimatorSet(); List<Animator> peakAnim = getPeakAnimator(duration); set.playSequentially(peakAnim); set.start(); } } private Animator getLineAnimator(float[] evaluatorObjects, long duration){ float startX = evaluatorObjects[0]; float startY = evaluatorObjects[1]; float endX = evaluatorObjects[2]; float endY = evaluatorObjects[3]; LinearEvaluator evaluator = new LinearEvaluator(startY, endY); evaluator.addListener(this); ValueAnimator animator = ValueAnimator.ofObject(evaluator, startX, endX); animator.setDuration((long) (duration * evaluatorObjects[4])); return animator; } private List<Animator> getPeakAnimator(long duration){ int size = m_PeakObjects.size(); List<Animator> peakAnimators = new ArrayList<Animator>(); for(int i=0;i<size;i++){ Animator animator = getLineAnimator(m_PeakObjects.get(i), duration); peakAnimators.add(animator); if(i == size - 1){ animator.addListener(this); } } return peakAnimators; } public interface OnPainterTrickListener{ int getPainterValue(); void onPainterTrick(int value); void onPainterFinished(); } private OnPainterTrickListener m_TrickListener; public void setOnPainterTrickListener(OnPainterTrickListener listener){ m_TrickListener = listener; } public void stopPaint(){ if(m_Timer != null){ m_Timer.cancel(); } recycleWaveBitmap(); } public int getAverage(){ return m_InnerController.getAverage(); } public long getMeasureStartTime(){ return m_InnerController.getMeasureStartTime(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); doCalculate(getWidth(), getHeight()); } private float m_CellWidth; private void doCalculate(int wWidth, int wHeight){ if(m_PeakObjects == null){//Just testing condition. final int validWidth = wWidth; final int validHeight = wHeight; float cellWidth = validWidth / MAX_VALID_ENTRIES; float cellHeight = validHeight; m_CellWidth = cellWidth; caculateByWeights(cellWidth, cellHeight - WAVE_PEAK_LEFT_HEIGHT); } } private void caculateByWeights(float cellWidth, float cellHeight){ m_PeakObjects = new ArrayList<float[]>(); int size = m_WavePeak_Weight.length; for(int i= size - 1;i>=0;i--){ float[] wave = m_InnerController.retreiveWaveLine(m_WavePeak_Weight[i], cellWidth, cellHeight); //debugWaveLine(wave); if(i == size - 1){ m_StartX = wave[0]; m_StartY = wave[1]; } m_PeakObjects.add(wave); } m_LineObjects = m_InnerController.retreiveWaveLine(m_WaveLine_Weight, cellWidth, cellHeight); //debugWaveLine(m_LineObjects); } private void debugWaveLine(float[] waveLine){ debug( "the line Object " + "\n" + "the startX is " + waveLine[0] + "\n" + "the startY is " + waveLine[1] + "\n" + "the endX is " + waveLine[2] + "\n" + "the endY is " + waveLine[3] + "\n" + "the fraction is " + waveLine[4] + "\n" ); } @Override protected void onDraw(Canvas canvas) { if(waveBitmap != null){ canvas.drawBitmap(waveBitmap, 0, 0, m_EmptyPaint); } } private Bitmap waveBitmap; @Override public void onEvalutor(final float x, final float y) { debug("onEvalutor--- x is " + x + " & y is " + y); m_WavePath.lineTo(x, y); recycleWaveBitmap(); waveBitmap = getBitmap(m_WavePath, m_PeakPaint); invalidate(); } private void recycleWaveBitmap() { if(waveBitmap != null){ waveBitmap.recycle(); waveBitmap = null; } } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { m_WavePath.offset(m_CellWidth, 0); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } private Bitmap getBitmap(Path path, Paint m_PeakPaint){ Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.drawPath(path, m_PeakPaint); return bitmap; } private static class WaveViewController{ private List<Integer> m_AllEntries; private long m_StartTime; private int m_Average; private Random m_EmulateRan; private int[] m_EmulateHeartRate = new int[]{ 0, 49, 0, 55, 0, 68, 0, 66, 0, 75 }; // private int[] m_EmulateHeartRate = new int[]{ // 35, 49, 46, // 55, 88, 68, // 35, 66, 67, // 75 //}; //private int[] m_EmulateHeartRate = new int[]{ // 0, 0, 0, // 0, 0, 0, // 0, 0, 0, // 0 //}; private WaveViewController(){ m_AllEntries = new ArrayList<Integer>(); m_EmulateRan = new Random(); } public long getMeasureStartTime() { return m_StartTime; } public int getAverage() { return m_Average; } public int getEmulateHeartRate() { return m_EmulateHeartRate[m_EmulateRan.nextInt(10)]; } public void clearHistory() { m_AllEntries.clear(); } public void computeAverage() { m_Average = calculateAverage(); } private int calculateAverage(){ int size = m_AllEntries.size(); int validSize = 0; int validTotal = 0; for(int i=0;i<size;i++){ if(m_AllEntries.get(i) != 0){ validSize++; validTotal += m_AllEntries.get(i); } } if(validSize != 0){ return validTotal / validSize; }else{ return -1; } } public void addPaintValue(int value) { m_AllEntries.add(0, value); } public void saveStartPaintTime() { m_StartTime = System.currentTimeMillis(); } private float[] retreiveWaveLine(int[] m_WavePeak_Weight, float cellWidth, float cellHeight){ int size = m_WavePeak_Weight.length; if(size != 4){ throw new RuntimeException("wrong size of array"); } float startX, startY, endX = 0f, endY, fractionFromX, fractionToX, fractionFromY, fractionToY; int weightFromX, weightToX, weightFromY, weightToY; weightFromX = m_WavePeak_Weight[0]; weightToX = m_WavePeak_Weight[1]; weightFromY = m_WavePeak_Weight[2]; weightToY = m_WavePeak_Weight[3]; fractionFromX = getFractionOfWeight(weightFromX, WIDTH_TOTAL_WEIGHTS); fractionToX = getFractionOfWeight(weightToX, WIDTH_TOTAL_WEIGHTS); startX = cellWidth * fractionFromX; endX = cellWidth * fractionToX; fractionFromY = getFractionOfWeight(weightFromY, HEIGHT_TOTAL_WEIGHTS); fractionToY = getFractionOfWeight(weightToY, HEIGHT_TOTAL_WEIGHTS); startY = cellHeight * fractionFromY; endY = cellHeight * fractionToY; return new float[]{startX, startY, endX, endY, fractionFromX - fractionToX}; } private float getFractionOfWeight(int weight, int totalWeights){ return (float) weight / totalWeights; } } }