|
@@ -0,0 +1,344 @@
|
|
|
+package com.example.audiotrack
|
|
|
+
|
|
|
+import android.content.Context
|
|
|
+import android.graphics.Canvas
|
|
|
+import android.graphics.Rect
|
|
|
+import android.graphics.drawable.Drawable
|
|
|
+import android.os.Build
|
|
|
+import android.support.v4.content.ContextCompat
|
|
|
+import android.text.Layout
|
|
|
+import android.text.StaticLayout
|
|
|
+import android.text.TextPaint
|
|
|
+import android.text.TextUtils
|
|
|
+import android.util.AttributeSet
|
|
|
+import android.view.MotionEvent
|
|
|
+
|
|
|
+class EditAudioTrackView : AudioTrackView {
|
|
|
+ interface OnEditScrollListener {
|
|
|
+ fun onScroll(scrollRatio: Double)
|
|
|
+ }
|
|
|
+
|
|
|
+ var headDrawable: Drawable? = null
|
|
|
+
|
|
|
+ var innerHeight: Int = 0
|
|
|
+
|
|
|
+ var dialogTextSize: Int = 0
|
|
|
+
|
|
|
+ var playProgress: Float = 0f
|
|
|
+ var playProgressText: String = ""
|
|
|
+ var playProgressDialogColor: Int = 0
|
|
|
+
|
|
|
+ var scrollProgressText: String = ""
|
|
|
+ var scrollProgressDialogColor: Int = 0
|
|
|
+
|
|
|
+ private var playX: Double = outerLeft
|
|
|
+
|
|
|
+ private val textPaint = TextPaint()
|
|
|
+
|
|
|
+ private var isTouchHeadDrawable: Boolean = false
|
|
|
+ private var isTouchEndDrawable: Boolean = false
|
|
|
+ private var onHeadScrollListener: OnEditScrollListener? = null
|
|
|
+ private var onEndScrollListener: OnEditScrollListener? = null
|
|
|
+
|
|
|
+ constructor(context: Context) : super(context)
|
|
|
+
|
|
|
+ constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
|
|
+ initAttr(attributeSet)
|
|
|
+ }
|
|
|
+
|
|
|
+ constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr) {
|
|
|
+ initAttr(attributeSet)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun initAttr(attributeSet: AttributeSet) {
|
|
|
+ val typedArray = context.theme.obtainStyledAttributes(attributeSet, R.styleable.EditAudioTrackView, 0, 0)
|
|
|
+ headDrawable = typedArray.getDrawable(R.styleable.EditAudioTrackView_headDrawable)
|
|
|
+ innerHeight = typedArray.getDimensionPixelSize(R.styleable.EditAudioTrackView_innerHeight, 0)
|
|
|
+ playProgress = typedArray.getFloat(R.styleable.EditAudioTrackView_playProgress, 0f)
|
|
|
+ playProgressText = typedArray.getString(R.styleable.EditAudioTrackView_playProgressText)
|
|
|
+ playProgressDialogColor = typedArray.getColor(R.styleable.EditAudioTrackView_playProgressDialogColor, ContextCompat.getColor(context, android.R.color.white))
|
|
|
+ scrollProgressText = typedArray.getString(R.styleable.EditAudioTrackView_scrollProgressText)
|
|
|
+ scrollProgressDialogColor = typedArray.getColor(R.styleable.EditAudioTrackView_scrollProgressDialogColor, ContextCompat.getColor(context, android.R.color.white))
|
|
|
+ dialogTextSize = typedArray.getDimensionPixelSize(R.styleable.EditAudioTrackView_dialogTextSize, resources.getDimensionPixelSize(R.dimen.dialog_text_size))
|
|
|
+ }
|
|
|
+
|
|
|
+ init {
|
|
|
+ scrollable = false
|
|
|
+ }
|
|
|
+
|
|
|
+ fun setOnHeadScrollListener(onHeadScrollListener: OnEditScrollListener) {
|
|
|
+ this.onHeadScrollListener = onHeadScrollListener
|
|
|
+ }
|
|
|
+
|
|
|
+ fun setOnEndScrollListener(onEndScrollListener: OnEditScrollListener) {
|
|
|
+ this.onEndScrollListener = onEndScrollListener
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDraw(canvas: Canvas?) {
|
|
|
+ super.onDraw(canvas)
|
|
|
+
|
|
|
+ canvas?.let {
|
|
|
+ drawHead(canvas, startX, startY)
|
|
|
+ drawEnd(canvas, startX, startY, innerHeight)
|
|
|
+
|
|
|
+ drawTimerDialog(playX.toFloat(),
|
|
|
+ ((resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_height) - resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_low)).toFloat()),
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_low),
|
|
|
+ playProgressDialogColor,
|
|
|
+ playProgressText,
|
|
|
+ canvas)
|
|
|
+
|
|
|
+ if (isTouchHeadDrawable) {
|
|
|
+ drawTimerDialog(startX.toFloat(),
|
|
|
+ 0f,
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_height),
|
|
|
+ scrollProgressDialogColor,
|
|
|
+ scrollProgressText,
|
|
|
+ canvas
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isTouchEndDrawable) {
|
|
|
+ drawTimerDialog((startX + innerWidth).toFloat(),
|
|
|
+ 0f,
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_height),
|
|
|
+ scrollProgressDialogColor,
|
|
|
+ scrollProgressText,
|
|
|
+ canvas
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun drawBackground(canvas: Canvas, left: Int, right: Int) {
|
|
|
+ val backgroundStartY = (resources.getDimensionPixelSize(R.dimen.dialog_height) + resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_height))
|
|
|
+
|
|
|
+ background.bounds = Rect(left,
|
|
|
+ backgroundStartY,
|
|
|
+ right,
|
|
|
+ (backgroundStartY + innerHeight))
|
|
|
+ background.draw(canvas)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun drawHead(canvas: Canvas, drawX: Double, drawY: Double) {
|
|
|
+ headDrawable?.let {
|
|
|
+ val drawCenterY = drawY + innerHeight / 2
|
|
|
+ val drawRadius = it.intrinsicWidth / 2
|
|
|
+ val shadowRange = resources.getDimension(R.dimen.shadow_range)
|
|
|
+
|
|
|
+ paint.color = ContextCompat.getColor(context, R.color.shadow_layer2)
|
|
|
+ canvas.drawCircle(drawX.toFloat(),
|
|
|
+ (drawCenterY + 2 * shadowRange).toFloat(),
|
|
|
+ drawRadius + 2 * shadowRange,
|
|
|
+ paint)
|
|
|
+
|
|
|
+ paint.color = ContextCompat.getColor(context, R.color.shadow_layer1)
|
|
|
+ canvas.drawCircle(drawX.toFloat(),
|
|
|
+ (drawCenterY + shadowRange).toFloat(),
|
|
|
+ drawRadius + shadowRange,
|
|
|
+ paint)
|
|
|
+
|
|
|
+ paint.color = ContextCompat.getColor(context, android.R.color.white)
|
|
|
+ canvas.drawCircle(drawX.toFloat(), drawCenterY.toFloat(), drawRadius.toFloat(), paint)
|
|
|
+
|
|
|
+ it.bounds = Rect(
|
|
|
+ (drawX - drawRadius).toInt(),
|
|
|
+ (drawCenterY - drawRadius).toInt(),
|
|
|
+ (drawX + drawRadius).toInt(),
|
|
|
+ (drawCenterY + drawRadius).toInt())
|
|
|
+ it.draw(canvas)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun drawTimerDialog(startX: Float, startY: Float, marginBottom: Int, color: Int, text: String, canvas: Canvas) {
|
|
|
+ paint.color = color
|
|
|
+ textPaint.color = ContextCompat.getColor(context, android.R.color.black)
|
|
|
+ textPaint.isAntiAlias = true
|
|
|
+ textPaint.textSize = dialogTextSize.toFloat()
|
|
|
+
|
|
|
+ val staticLayout: StaticLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
+ val staticLayoutBuilder = StaticLayout.Builder.obtain(text,
|
|
|
+ 0,
|
|
|
+ text.length,
|
|
|
+ textPaint,
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_text_width))
|
|
|
+ staticLayoutBuilder.setEllipsizedWidth(resources.getDimensionPixelSize(R.dimen.dialog_width))
|
|
|
+ staticLayoutBuilder.setEllipsize(TextUtils.TruncateAt.END)
|
|
|
+ staticLayoutBuilder.setMaxLines(1)
|
|
|
+ staticLayoutBuilder.build()
|
|
|
+ } else {
|
|
|
+ StaticLayout(text,
|
|
|
+ 0,
|
|
|
+ text.length,
|
|
|
+ textPaint,
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_text_width),
|
|
|
+ Layout.Alignment.ALIGN_NORMAL,
|
|
|
+ 0f,
|
|
|
+ 0f,
|
|
|
+ true,
|
|
|
+ TextUtils.TruncateAt.END,
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_width))
|
|
|
+ }
|
|
|
+
|
|
|
+ canvas.drawLine(startX,
|
|
|
+ startY + resources.getDimensionPixelSize(R.dimen.dialog_height),
|
|
|
+ startX + resources.getDimensionPixelSize(R.dimen.dialog_pointer_width),
|
|
|
+ startY + resources.getDimensionPixelSize(R.dimen.dialog_height) + marginBottom + height,
|
|
|
+ paint
|
|
|
+ )
|
|
|
+
|
|
|
+ canvas.drawRoundRect(startX - resources.getDimensionPixelSize(R.dimen.dialog_width) / 2,
|
|
|
+ startY,
|
|
|
+ startX + resources.getDimensionPixelSize(R.dimen.dialog_width) / 2,
|
|
|
+ startY + resources.getDimensionPixelSize(R.dimen.dialog_height),
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_round_radius).toFloat(),
|
|
|
+ resources.getDimensionPixelSize(R.dimen.dialog_round_radius).toFloat(),
|
|
|
+ paint
|
|
|
+ )
|
|
|
+
|
|
|
+ canvas.save()
|
|
|
+ canvas.translate(startX - staticLayout.width / 2, startY + resources.getDimensionPixelSize(R.dimen.dialog_height) / 2 - staticLayout.height / 2)
|
|
|
+ staticLayout.draw(canvas)
|
|
|
+ canvas.restore()
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
|
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
|
+ var height = height
|
|
|
+
|
|
|
+ when (MeasureSpec.getMode(heightMeasureSpec)) {
|
|
|
+ MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> {
|
|
|
+ height = (innerHeight
|
|
|
+ + resources.getDimensionPixelSize(R.dimen.dialog_height)
|
|
|
+ + resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_height))
|
|
|
+ }
|
|
|
+ MeasureSpec.EXACTLY -> {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setMeasuredDimension(width, height)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
|
+ super.onSizeChanged(w, h, oldw, oldh)
|
|
|
+
|
|
|
+ endDrawable?.let {
|
|
|
+ outerLeft = (resources.getDimensionPixelSize(R.dimen.dialog_width) / 2).toDouble()
|
|
|
+ outerRight = (width - (resources.getDimensionPixelSize(R.dimen.dialog_width) / 2)).toDouble()
|
|
|
+ }
|
|
|
+
|
|
|
+ startX = outerLeft
|
|
|
+ startY = ((resources.getDimensionPixelSize(R.dimen.dialog_height)
|
|
|
+ + resources.getDimensionPixelSize(R.dimen.dialog_margin_bottom_height)).toDouble())
|
|
|
+
|
|
|
+ innerWidth = (innerWidthRatio * (outerRight - outerLeft))
|
|
|
+ playX = (outerRight - outerLeft) * playProgress
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
|
|
|
+ when (event?.actionMasked) {
|
|
|
+ MotionEvent.ACTION_UP -> {
|
|
|
+ isTouchEndDrawable = false
|
|
|
+ isTouchHeadDrawable = false
|
|
|
+ invalidate()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return super.onTouchEvent(event)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDown(p0: MotionEvent?): Boolean {
|
|
|
+ return onDownHeadDrawable(p0) || onDownEndDrawable(p0) || super.onDown(p0)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun onDownHeadDrawable(p0: MotionEvent?): Boolean {
|
|
|
+ var drawableStartX = startX
|
|
|
+ var drawableEndX = startX
|
|
|
+
|
|
|
+ headDrawable?.let {
|
|
|
+ drawableStartX = startX - it.intrinsicWidth / 2 - resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ drawableEndX = startX + it.intrinsicWidth / 2 + resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ }
|
|
|
+
|
|
|
+ p0?.let {
|
|
|
+ if ((it.x in drawableStartX..drawableEndX) && (it.y in startY..(startY + innerHeight))) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun onDownEndDrawable(p0: MotionEvent?): Boolean {
|
|
|
+ val drawableX = if (startX + innerWidth > outerRight) outerRight else startX + innerWidth
|
|
|
+ var drawableStartX = drawableX
|
|
|
+ var drawableEndX = drawableX
|
|
|
+
|
|
|
+ endDrawable?.let {
|
|
|
+ drawableStartX = drawableX - it.intrinsicWidth / 2 - resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ drawableEndX = drawableX + it.intrinsicWidth / 2 + resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ }
|
|
|
+
|
|
|
+ p0?.let {
|
|
|
+ if ((it.x in drawableStartX..drawableEndX) && (it.y in startY..(startY + innerHeight))) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onScroll(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
|
|
|
+ isTouchHeadDrawable = onScrollHeadDrawable(p1, p2)
|
|
|
+ isTouchEndDrawable = onScrollEndDrawable(p1, p2)
|
|
|
+ return isTouchHeadDrawable || isTouchEndDrawable || super.onScroll(p0, p1, p2, p3)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun onScrollHeadDrawable(p1: MotionEvent?, p2: Float): Boolean {
|
|
|
+ var drawableStartX = startX
|
|
|
+ var drawableEndX = startX
|
|
|
+
|
|
|
+ headDrawable?.let {
|
|
|
+ drawableStartX = startX - it.intrinsicWidth / 2 - resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ drawableEndX = startX + it.intrinsicWidth / 2 + resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ }
|
|
|
+
|
|
|
+ p1?.let {
|
|
|
+ if ((it.x in drawableStartX..drawableEndX) && (it.y in startY..(startY + innerHeight))) {
|
|
|
+ if (startX - p2 in outerLeft..(startX + innerWidth)) {
|
|
|
+ innerWidth = (innerWidth + p2)
|
|
|
+ startX -= p2
|
|
|
+ }
|
|
|
+
|
|
|
+ onHeadScrollListener?.onScroll((startX - outerLeft) / (outerRight - outerLeft))
|
|
|
+
|
|
|
+ invalidate()
|
|
|
+
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun onScrollEndDrawable(p1: MotionEvent?, p2: Float): Boolean {
|
|
|
+ val drawableX = if (startX + innerWidth > outerRight) outerRight else startX + innerWidth
|
|
|
+ var drawableStartX = drawableX
|
|
|
+ var drawableEndX = drawableX
|
|
|
+ endDrawable?.let {
|
|
|
+ drawableStartX = drawableX - it.intrinsicWidth / 2 - resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ drawableEndX = drawableX + it.intrinsicWidth / 2 + resources.getDimensionPixelSize(R.dimen.extra_touch_event_range)
|
|
|
+ }
|
|
|
+
|
|
|
+ p1?.let {
|
|
|
+ if ((it.x in drawableStartX..drawableEndX) && (it.y in startY..(startY + innerHeight))) {
|
|
|
+ if ((startX + innerWidth - p2) in startX..outerRight) {
|
|
|
+ innerWidth = (innerWidth - p2)
|
|
|
+ }
|
|
|
+
|
|
|
+ onEndScrollListener?.onScroll((startX + innerWidth - outerLeft) / (outerRight - outerLeft))
|
|
|
+
|
|
|
+ invalidate()
|
|
|
+
|
|
|
+ return true
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+}
|