From 75ed3d0ce1f7f265e532904ee0d7d24491650a11 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 5 Nov 2016 12:08:35 +0300 Subject: [PATCH] Added java sample + prepared for release --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 2 + .../rm/com/audiogram/AnotherActivity.java | 28 ++ app/src/main/res/layout/activity_another.xml | 30 ++ audiowave/build.gradle | 2 +- .../kotlin/rm/com/audiowave/AudioWaveView.kt | 408 +++++++++--------- build.gradle | 1 + 7 files changed, 268 insertions(+), 205 deletions(-) create mode 100644 app/src/main/java/rm/com/audiogram/AnotherActivity.java create mode 100644 app/src/main/res/layout/activity_another.xml diff --git a/app/build.gradle b/app/build.gradle index 8c73bdb..fd58e9a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,7 +27,7 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile 'com.android.support:appcompat-v7:24.2.1' - compile project(':audiowave') + compile 'com.github.alxrm:Audiogram:0.3' } repositories { mavenCentral() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5e1b397..5a536be 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,8 @@ + + \ No newline at end of file diff --git a/app/src/main/java/rm/com/audiogram/AnotherActivity.java b/app/src/main/java/rm/com/audiogram/AnotherActivity.java new file mode 100644 index 0000000..93a097a --- /dev/null +++ b/app/src/main/java/rm/com/audiogram/AnotherActivity.java @@ -0,0 +1,28 @@ +package rm.com.audiogram; + +import android.app.Activity; +import android.os.Bundle; + +import rm.com.audiowave.AudioWaveView; + +public class AnotherActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_another); + + final AudioWaveView waveView = (AudioWaveView) findViewById(R.id.wave); + final byte[] data = { 1, 3, 37, 117, 69, 0, 0, 58 }; + + waveView.setScaledData(data); + +// waveView.setRawData(data, new Function0() { +// @Override +// public Unit invoke() { +// Log.d("Set raw data", "Callback called"); +// return null; +// } +// }); + } +} diff --git a/app/src/main/res/layout/activity_another.xml b/app/src/main/res/layout/activity_another.xml new file mode 100644 index 0000000..a8b225b --- /dev/null +++ b/app/src/main/res/layout/activity_another.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/audiowave/build.gradle b/audiowave/build.gradle index e0dcb45..82cb271 100644 --- a/audiowave/build.gradle +++ b/audiowave/build.gradle @@ -26,7 +26,7 @@ android { } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.4' } repositories { diff --git a/audiowave/src/main/kotlin/rm/com/audiowave/AudioWaveView.kt b/audiowave/src/main/kotlin/rm/com/audiowave/AudioWaveView.kt index 568da36..ac9d246 100644 --- a/audiowave/src/main/kotlin/rm/com/audiowave/AudioWaveView.kt +++ b/audiowave/src/main/kotlin/rm/com/audiowave/AudioWaveView.kt @@ -11,207 +11,209 @@ import android.view.animation.OvershootInterpolator class AudioWaveView : View { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { - inflateAttrs(attrs) - } - - constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - inflateAttrs(attrs) - } - - var chunkHeight: Int = 0 - get() = if (field == 0) h else Math.abs(field) - set(value) { - field = value - redrawData() - } - - var chunkWidth: Int = dip(2) - set(value) { - field = Math.abs(value) - redrawData() - } - - var chunkSpacing: Int = dip(1) - set(value) { - field = Math.abs(value) - redrawData() - } - - var chunkRadius: Int = 0 - set(value) { - field = Math.abs(value) - redrawData() - } - - var minChunkHeight: Int = dip(2) - set(value) { - field = Math.abs(value) - redrawData() - } - - var waveColor: Int = Color.BLACK - set(value) { - wavePaint = smoothPaint(value.withAlpha(0xAA)) - waveFilledPaint = filterPaint(value) - postInvalidate() - } - - var progress: Float = 0F - set(value) { - require(value in 0..100) { "Progress must be in 0..100" } - - field = Math.abs(value) - postInvalidate() - } - - var scaledData: ByteArray = byteArrayOf() - set(value) { - MAIN_THREAD.postDelayed({ - field = if (value.size <= chunksCount) { - ByteArray(chunksCount).paste(value) - } else { - value - } - - redrawData() - }, initialDelay) - } - - var expansionDuration: Long = 400 - set(value) { - field = Math.max(400, value) - } - - private val chunksCount: Int - get() = w / chunkStep - - private val chunkStep: Int - get() = chunkWidth + chunkSpacing - - private val centerY: Int - get() = h / 2 - - private val progressFactor: Float - get() = progress / 100F - - private val initialDelay: Long - get() = if (handler == null) 50 else 0 - - private var wavePaint = smoothPaint(waveColor.withAlpha(0xAA)) - private var waveFilledPaint = filterPaint(waveColor) - private var waveBitmap: Bitmap? = null - - private var w: Int = 0 - private var h: Int = 0 - - override fun onDraw(canvas: Canvas?) { - super.onDraw(canvas) - val cv = canvas ?: return - - cv.transform { - clipRect(0, 0, w, h) - drawBitmap(waveBitmap, 0F, 0F, wavePaint) - } - - cv.transform { - clipRect(0F, 0F, w * progressFactor, h.toFloat()) - drawBitmap(waveBitmap, 0F, 0F, waveFilledPaint) - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - - waveBitmap.safeRecycle() - waveBitmap = null - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - scaledData = byteArrayOf() - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - w = right - left - h = bottom - top - - if (changed) { - - if (waveBitmap.fits(w, h)) return - - waveBitmap.safeRecycle() - waveBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) - - redrawData() - } - super.onLayout(changed, left, top, right, bottom) - } - - fun setRawData(raw: ByteArray, callback: () -> Unit = {}) { - MAIN_THREAD.postDelayed({ - Sampler.downSampleAsync(raw, chunksCount) { - scaledData = it - animateExpansion() - callback() - } - }, initialDelay) - } - - private fun redrawData(canvas: Canvas? = waveBitmap?.inCanvas(), factor: Float = 1.0F) { - if (waveBitmap == null || canvas == null) return - - waveBitmap.flush() - - scaledData.forEachIndexed { i, chunk -> - val chunkHeight = ((chunk.abs.toFloat() / Byte.MAX_VALUE) * chunkHeight).toInt() - val clampedHeight = Math.max(chunkHeight, minChunkHeight) - val heightDiff = (clampedHeight - minChunkHeight).toFloat() - val animatedDiff = (heightDiff * factor).toInt() - - canvas.drawRoundRect( - rectFOf( - left = chunkSpacing / 2 + i * chunkStep, - top = centerY - minChunkHeight - animatedDiff, - right = chunkSpacing / 2 + i * chunkStep + chunkWidth, - bottom = centerY + minChunkHeight + animatedDiff - ), - chunkRadius.toFloat(), - chunkRadius.toFloat(), - wavePaint - ) - } - - postInvalidate() - } - - private fun animateExpansion() = - ObjectAnimator.ofFloat(0.0F, 1.0F).apply { - duration = expansionDuration - interpolator = OvershootInterpolator() - addUpdateListener { redrawData(factor = it.animatedFraction) } - start() - } - - private fun inflateAttrs(attrs: AttributeSet?) { - val resAttrs = context.theme.obtainStyledAttributes( - attrs, - R.styleable.AudioWaveView, - 0, - 0 - ) ?: return - - with(resAttrs) { - chunkHeight = getDimensionPixelSize(R.styleable.AudioWaveView_chunkHeight, chunkHeight) - chunkWidth = getDimensionPixelSize(R.styleable.AudioWaveView_chunkWidth, chunkWidth) - chunkSpacing = getDimensionPixelSize(R.styleable.AudioWaveView_chunkSpacing, chunkSpacing) - minChunkHeight = getDimensionPixelSize(R.styleable.AudioWaveView_minChunkHeight, minChunkHeight) - chunkRadius = getDimensionPixelSize(R.styleable.AudioWaveView_chunkRadius, chunkRadius) - waveColor = getColor(R.styleable.AudioWaveView_waveColor, waveColor) - progress = getFloat(R.styleable.AudioWaveView_progress, progress) - recycle() - } - } + constructor(context: Context?) : super(context) + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + inflateAttrs(attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + inflateAttrs(attrs) + } + + var chunkHeight: Int = 0 + get() = if (field == 0) h else Math.abs(field) + set(value) { + field = value + redrawData() + } + + var chunkWidth: Int = dip(2) + set(value) { + field = Math.abs(value) + redrawData() + } + + var chunkSpacing: Int = dip(1) + set(value) { + field = Math.abs(value) + redrawData() + } + + var chunkRadius: Int = 0 + set(value) { + field = Math.abs(value) + redrawData() + } + + var minChunkHeight: Int = dip(2) + set(value) { + field = Math.abs(value) + redrawData() + } + + var waveColor: Int = Color.BLACK + set(value) { + wavePaint = smoothPaint(value.withAlpha(0xAA)) + waveFilledPaint = filterPaint(value) + postInvalidate() + } + + var progress: Float = 0F + set(value) { + require(value in 0..100) { "Progress must be in 0..100" } + + field = Math.abs(value) + postInvalidate() + } + + var scaledData: ByteArray = byteArrayOf() + set(value) { + MAIN_THREAD.postDelayed({ + field = if (value.size <= chunksCount) { + ByteArray(chunksCount).paste(value) + } else { + value + } + + redrawData() + }, initialDelay) + } + + var expansionDuration: Long = 400 + set(value) { + field = Math.max(400, value) + } + + private val chunksCount: Int + get() = w / chunkStep + + private val chunkStep: Int + get() = chunkWidth + chunkSpacing + + private val centerY: Int + get() = h / 2 + + private val progressFactor: Float + get() = progress / 100F + + private val initialDelay: Long + get() = if (handler == null) 50 else 0 + + private var wavePaint = smoothPaint(waveColor.withAlpha(0xAA)) + private var waveFilledPaint = filterPaint(waveColor) + private var waveBitmap: Bitmap? = null + + private var w: Int = 0 + private var h: Int = 0 + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + val cv = canvas ?: return + + cv.transform { + clipRect(0, 0, w, h) + drawBitmap(waveBitmap, 0F, 0F, wavePaint) + } + + cv.transform { + clipRect(0F, 0F, w * progressFactor, h.toFloat()) + drawBitmap(waveBitmap, 0F, 0F, waveFilledPaint) + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + waveBitmap.safeRecycle() + waveBitmap = null + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + scaledData = byteArrayOf() + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + w = right - left + h = bottom - top + + if (changed) { + + if (waveBitmap.fits(w, h)) return + + waveBitmap.safeRecycle() + waveBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + + redrawData() + } + super.onLayout(changed, left, top, right, bottom) + } + + fun setRawData(raw: ByteArray, callback: () -> Unit = {}) { + MAIN_THREAD.postDelayed({ + Sampler.downSampleAsync(raw, chunksCount) { + scaledData = it + animateExpansion() + callback() + } + }, initialDelay) + } + + fun setRawData(raw: ByteArray) = setRawData(raw, {}) + + private fun redrawData(canvas: Canvas? = waveBitmap?.inCanvas(), factor: Float = 1.0F) { + if (waveBitmap == null || canvas == null) return + + waveBitmap.flush() + + scaledData.forEachIndexed { i, chunk -> + val chunkHeight = ((chunk.abs.toFloat() / Byte.MAX_VALUE) * chunkHeight).toInt() + val clampedHeight = Math.max(chunkHeight, minChunkHeight) + val heightDiff = (clampedHeight - minChunkHeight).toFloat() + val animatedDiff = (heightDiff * factor).toInt() + + canvas.drawRoundRect( + rectFOf( + left = chunkSpacing / 2 + i * chunkStep, + top = centerY - minChunkHeight - animatedDiff, + right = chunkSpacing / 2 + i * chunkStep + chunkWidth, + bottom = centerY + minChunkHeight + animatedDiff + ), + chunkRadius.toFloat(), + chunkRadius.toFloat(), + wavePaint + ) + } + + postInvalidate() + } + + private fun animateExpansion() = + ObjectAnimator.ofFloat(0.0F, 1.0F).apply { + duration = expansionDuration + interpolator = OvershootInterpolator() + addUpdateListener { redrawData(factor = it.animatedFraction) } + start() + } + + private fun inflateAttrs(attrs: AttributeSet?) { + val resAttrs = context.theme.obtainStyledAttributes( + attrs, + R.styleable.AudioWaveView, + 0, + 0 + ) ?: return + + with(resAttrs) { + chunkHeight = getDimensionPixelSize(R.styleable.AudioWaveView_chunkHeight, chunkHeight) + chunkWidth = getDimensionPixelSize(R.styleable.AudioWaveView_chunkWidth, chunkWidth) + chunkSpacing = getDimensionPixelSize(R.styleable.AudioWaveView_chunkSpacing, chunkSpacing) + minChunkHeight = getDimensionPixelSize(R.styleable.AudioWaveView_minChunkHeight, minChunkHeight) + chunkRadius = getDimensionPixelSize(R.styleable.AudioWaveView_chunkRadius, chunkRadius) + waveColor = getColor(R.styleable.AudioWaveView_waveColor, waveColor) + progress = getFloat(R.styleable.AudioWaveView_progress, progress) + recycle() + } + } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7c232f7..c06fbe7 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ buildscript { allprojects { repositories { jcenter() + maven { url "https://jitpack.io" } } }