I have a small application that intends to make an animation, consisting of a graphic with 3 elements.
The application consists of:
mainactivity that will instantiate an object of the AnimatedCanvas class and that will send a vector of 3 pointF objects.
the AnimatedCanvas class that inherits from view and manages the canvas
An abstract class called basicItems, which represents the minimum information to draw basic elements of a canvas, such as circles, lines...
I come across two situations. In the first one, if the elements that I want to draw and animate are variables of the AnimatedCanvas class, both the drawing and the animation are done as expected.
The second is when I get the coordinates sent from the mainactivity that either not all animations (or none) are performed or some element is not initialized correctly.
The idea is:
draw a sphere for each of the elements in the graph, to be represented, making an animation that makes the spheres go from alpha 0f to 1f
perform an animation on the y axis, so that these spheres move to a certain point on the axis
at the same time that the spheres are animated, draw a line that animates and moves along the y-axis, along with its sphere
Heres my code...
`
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">``your text``
<com.example.prueba_circulo_01.AnimatedCanvasView
android:id="#+id/canvas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="13dp"
/>
</RelativeLayout>
</layout>
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.prueba_circulo_01.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var animateC : AnimatedCanvasView
private val p1 = GraphicPoint("enero", 1f, "uno")
private val p2 = GraphicPoint("enero",2f,"dos")
private val p3 = GraphicPoint("enero", 3f, "tres")
private var listAux = listOf<GraphicPoint>(p1,p2,p3)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.apply {
canvas.addDataPoints(listAux,GraphicType.Default)
}
}
}
BasicItem class
abstract class BasicItem (var x : Float,
var y : Float,
var yFinal : Float,
var paint : Paint,
var alpha: Float
){
abstract fun updateY(value: Float)
abstract fun updateYFinal(final : Float)
abstract fun updateAlpha(value: Float)
}
//----------------------------------------------------------
class VerticalLine1 (x : Float,
y : Float,
yFinal : Float,
paint : Paint,
alpha: Float):BasicItem(x,y,yFinal,paint,alpha){
override fun updateY(value: Float) {
y = value
}
override fun updateYFinal(final: Float) {
yFinal = final
}
override fun updateAlpha(value: Float){
alpha = value
paint.alpha = (255*alpha).toInt()
}
}
//----------------------------------------------------------
class Circle1 ( x : Float,
y : Float,
yFinal : Float,
var radius : Float,
paint : Paint,
alpha : Float):BasicItem(x,y,yFinal,paint,alpha) {
override fun updateY(value: Float) {
y = value
}
override fun updateYFinal(final: Float) {
yFinal = final
}
override fun updateAlpha(value: Float){
alpha = value
paint.alpha = (255*alpha).toInt()
}
}
//----------------------------------------------------------
class StringItem(x : Float,
y : Float,
yFinal : Float,
paint : Paint,
var string: String,
alpha : Float):BasicItem(x,y,yFinal,paint,alpha) {
override fun updateY(value: Float) {
y = value
}
override fun updateYFinal(final: Float) {
yFinal = final
}
override fun updateAlpha(value: Float){
alpha = value
paint.alpha = (255*alpha).toInt()
}
}
AnimatedCanvasView class
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import kotlin.math.abs
import kotlin.math.min
class AnimatedCanvasView(context: Context, attrs: AttributeSet) : View(context, attrs), ValueAnimator.AnimatorUpdateListener {
private val data = mutableListOf<DataPoint>()
private var points = mutableListOf<PointF>()
private val conPoint1 = mutableListOf<PointF>()
private val conPoint2 = mutableListOf<PointF>()
private val graphicPoints = mutableListOf<GraphicPoint>()
private val primaryColorPaint = Paint()
private val borderPathPaint = Paint()
private val blueCircle = Paint()
private val pathPaint = Paint()
private val barPaint = Paint()
private val path = Path()
private val borderPath = Path()
private val barWidth by lazy {
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics
)
}
private val borderPathWidth by lazy {
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics
)
}
private val startX = 0f
private var delay = 500L
private val standardDuration = 3000L
private val standardDelay = 1000L
private val secondDuration = 2000L
private val animatorCircle1 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle2 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle3 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle4 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle5 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle6 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle10 = ValueAnimator.ofFloat(0f,1f)
private lateinit var circle10 : Circle
private lateinit var animatorVerticalLine1 : ValueAnimator
private lateinit var animatorVerticalLine2 : ValueAnimator
private lateinit var animatorVerticalLine3 : ValueAnimator
private var animatorVerticalLine4 : ValueAnimator? = null
private lateinit var animatorVerticalLine5 : ValueAnimator
private lateinit var animatorVerticalLine6 : ValueAnimator
private var curveTopMargin = 32
private val externalRadiusNormal = 30f
private val externalRadiusFinal = 35f
private val internalRadiusNormal = 23f
private val internalRadiusFinal = 33f
private val secondInternalRadius = 15f
private lateinit var circle1 : Circle
private lateinit var circle2 : Circle
private lateinit var circle3 : Circle
private lateinit var circle4 : Circle
private lateinit var circle5 : Circle
private lateinit var circle6 : Circle
private lateinit var internalCircle1 : Circle
private lateinit var internalCircle2 : Circle
private lateinit var internalCircle3 : Circle
private lateinit var secondInternalCircle : Circle
private lateinit var verticalLine1 : VerticalLine
private lateinit var verticalLine2 : VerticalLine
private lateinit var verticalLine3 : VerticalLine
private lateinit var verticalLine4 : VerticalLine
private lateinit var verticalLine5 : VerticalLine
private lateinit var verticalLine6 : VerticalLine
private lateinit var stringCircle1 : StringItem
private lateinit var stringCircle2 : StringItem
private lateinit var stringCircle3 : StringItem
private lateinit var bezierCurve: BezierCurve
private val paintCircleBlack = Paint()
private val paintCircleNormal = Paint()
private val paintCircleFinal = Paint()
private val paintInternal = Paint()
private val paintLine = Paint()
private val paintVerticalLineGray = Paint()
private val paintVerticalLineGreen = Paint()
private val paintSecondInternalRadius = Paint()
private val paintText = Paint()
private val paintBezier = Paint()
private var empezado = false
private val images = mutableListOf<ImageCoordinates>()
val images2 = mutableListOf<imgCoor>()
//lista de animaciones...
private var animatorList = mutableListOf<ValueAnimator>()
private lateinit var animator2 : ValueAnimator
private lateinit var animator3 : ValueAnimator
private lateinit var basicItem: BasicItem
private lateinit var basicItem2: BasicItem
init {
initPaint()
initItemsToDraw()
initAnimators()
}
override fun onAnimationUpdate(animation: ValueAnimator?) {
animation?.apply {
startDelay = standardDelay
addUpdateListener {
basicItem.updateAlpha(it.animatedValue as Float)
invalidate()
}
addListener(object:AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
ValueAnimator.ofFloat(basicItem.y,basicItem.yFinal).apply {
duration=secondDuration
addUpdateListener {
basicItem.updateY(it.animatedValue as Float)
invalidate()
}
start()
}
}
})
//start()
}
}
fun starAnim(){
animator2 = ValueAnimator.ofFloat(0f,1f)
animator2.duration = 1000
animator2.addUpdateListener(this)
animator2.start()
}
private fun initPaint(){
paintCircleBlack.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.BLACK
alpha = 255
}
paintCircleNormal.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
alpha = 0
}
paintCircleFinal.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.DKGRAY
alpha = 0
}
paintInternal.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.WHITE
alpha = 0
}
paintLine.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
strokeWidth = 3f
}
paintVerticalLineGray.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GRAY
strokeWidth = 1f
alpha = 1
}
paintVerticalLineGreen.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
strokeWidth = 3f
// alpha = 255
alpha = 255
}
paintSecondInternalRadius.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.DKGRAY
alpha = 0
}
paintText.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.BLACK
textSize = 50f
alpha = 0
}
paintBezier.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
strokeWidth = 3f
}
borderPathPaint.apply {
isAntiAlias = true
strokeWidth = borderPathWidth
style = Paint.Style.STROKE
color = ContextCompat.getColor(context, R.color.blue_green_alpha_50)
}
barPaint.apply {
isAntiAlias = true
strokeWidth = barWidth
}
pathPaint.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.WHITE
}
blueCircle.apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.purple_700)
}
primaryColorPaint.apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.blue_green)
}
}
//----------------------------------------------------------
private fun initItemsToDraw(){
circle1=Circle(200f,600f,400f,externalRadiusNormal,paintCircleNormal,0f)
circle2=Circle(400f,600f,500f,externalRadiusNormal,paintCircleNormal,0f)
circle3=Circle(600f,600f,300f,externalRadiusFinal,paintCircleFinal,0f)
//circle4=Circle(400f,1500f,600f,externalRadiusFinal,paintCircleFinal,0f)
internalCircle1=Circle(200f,600f,400f,internalRadiusNormal,paintInternal,0f)
internalCircle2=Circle(400f,600f,500f,internalRadiusNormal,paintInternal,0f)
internalCircle3=Circle(600f,600f,300f,internalRadiusFinal,paintInternal,0f)
secondInternalCircle = Circle(600f,600f,300f,secondInternalRadius,paintSecondInternalRadius, 0f)
verticalLine1 = VerticalLine(
x = 200f,
y = 600f,
yFinal = 400f+circle1.radius,
paint = paintVerticalLineGray,
alpha = 1f
)
verticalLine2 = VerticalLine(
x = 400f,
y = 600f,
yFinal = 500f+circle2.radius,
paint = paintVerticalLineGray,
alpha = 1f
)
verticalLine3 = VerticalLine(
x = 600f,
y = 600f,
yFinal = 300f+circle3.radius,
paint = paintVerticalLineGreen,
alpha = 1f
)
stringCircle1 = StringItem(
x = 200f,
y = 600f,
yFinal = 400f+circle1.radius,
paint = paintText,
string = "hola",
alpha = 1f)
bezierCurve = BezierCurve(200f,400f,600f,paintBezier,1f,400f,500f,600f,300f)
}
private fun initAnimators(){
animatorVerticalLine1 = ValueAnimator.ofFloat(verticalLine1.y, verticalLine1.yFinal)
animatorVerticalLine2 = ValueAnimator.ofFloat(verticalLine2.y, verticalLine2.yFinal)
animatorVerticalLine3 = ValueAnimator.ofFloat(verticalLine3.y, verticalLine3.yFinal)
setAnimator(animatorCircle1, circle1)
setAnimator(animatorCircle1, internalCircle1)
setAnimator(animatorCircle2, circle2)
setAnimator(animatorCircle2, internalCircle2)
setAnimator(animatorCircle3, circle3)
setAnimator(animatorCircle3, internalCircle3)
setAnimator(animatorCircle1, secondInternalCircle)
setAnimatorVerticalLine(animatorVerticalLine1, verticalLine1)
setAnimatorVerticalLine(animatorVerticalLine2, verticalLine2)
setAnimatorVerticalLine(animatorVerticalLine3, verticalLine3)
}
private fun initPrueba(){
//basicItem = Circle(600f,1500f,1100f,40f,paintCircleNormal,1f )
basicItem = Circle(points[1].x,height.toFloat() - 16f.toDP(),1100f,40f,paintCircleNormal,1f )
basicItem2 = Circle(points[2].x,height.toFloat() - 16f.toDP(),1100f,70f,paintCircleNormal,1f )
}
private fun initAnimators2(){
try {
//if (animatorVerticalLine4==null) animatorVerticalLine4 = ValueAnimator.ofFloat(verticalLine4.y, verticalLine4.yFinal)
if (animatorVerticalLine4==null) animatorVerticalLine4 = ValueAnimator.ofFloat(1500f, 1000f)
animatorVerticalLine5 = ValueAnimator.ofFloat(verticalLine5.y, verticalLine5.yFinal)
animatorVerticalLine6 = ValueAnimator.ofFloat(verticalLine6.y, verticalLine6.yFinal)
}catch (ex:Exception){}
}
//----------------------------------------------------------
private fun setElementsToAnimators(){
try {
setAnimatorVerticalLine(animatorVerticalLine4, verticalLine4)
setAnimatorVerticalLine(animatorVerticalLine5, verticalLine5)
setAnimatorVerticalLine(animatorVerticalLine6, verticalLine6)
}catch (ex:Exception){}
}
private fun drawHorizontalLine(canvas: Canvas?, paint: Paint) {
canvas?.drawLine(startX, height.toFloat() - 20f.toDP(), width.toFloat(), height.toFloat() - 20f.toDP(), paint)
}
private fun drawBezierCurve(canvas: Canvas?) {
try {
if (points.isEmpty() && conPoint1.isEmpty() && conPoint2.isEmpty()) return
val pointsToDraw = points.filterIndexed { index, _ -> index != 0 }
path.reset()
path.moveTo(pointsToDraw.first().x, pointsToDraw.first().y)
for (i in 1 until pointsToDraw.size) {
path.cubicTo(
conPoint1[i - 1].x, conPoint1[i - 1].y, conPoint2[i - 1].x, conPoint2[i - 1].y,
pointsToDraw[i].x, pointsToDraw[i].y
)
}
borderPath.set(path)
canvas?.drawPath(path, pathPaint.apply {
})
canvas?.drawPath(borderPath, borderPathPaint)
} catch (e: Exception) {
}
}
private fun setAnimator(animatorToSet: ValueAnimator, basicItem: BasicItem){
animatorToSet.apply {
duration = standardDuration
startDelay = standardDelay
addUpdateListener {
basicItem.updateAlpha(it.animatedValue as Float)
invalidate()
}
addListener(object:AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
ValueAnimator.ofFloat(basicItem.y,basicItem.yFinal).apply {
duration=secondDuration
addUpdateListener {
basicItem.updateY(it.animatedValue as Float)
invalidate()
}
start()
}
}
})
start()
}
}
private fun setAnimatorVerticalLine(animatorToSet: ValueAnimator?, verticalLine: VerticalLine){
animatorToSet?.apply {
duration = secondDuration
startDelay = standardDelay + standardDuration
repeatCount=0
addUpdateListener {
verticalLine.yFinal = it.animatedValue as Float
verticalLine.paint.alpha = 255
invalidate()
}
start()
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
try {
canvas?.drawCircle(basicItem.x,basicItem.y,40f,basicItem.paint)
canvas?.drawCircle(basicItem2.x,basicItem2.y,70f,basicItem2.paint)
}catch (ex:Exception){
println(ex)}
drawFirstDraw(canvas)
}
//----------------------------------------------------------
private fun drawFirstDraw(canvas: Canvas?){
canvas?.drawCircle(circle1.x,circle1.y, circle1.radius,paintCircleNormal)
canvas?.drawCircle(circle2.x,circle2.y, circle2.radius,paintCircleNormal)
canvas?.drawCircle(circle3.x,circle3.y, circle3.radius,paintCircleNormal)
canvas?.drawCircle(internalCircle1.x,internalCircle1.y, internalCircle1.radius,paintInternal)
canvas?.drawCircle(internalCircle2.x,internalCircle2.y, internalCircle2.radius,paintInternal)
canvas?.drawCircle(internalCircle3.x,internalCircle3.y, internalCircle3.radius,paintInternal)
canvas?.drawCircle(secondInternalCircle.x,secondInternalCircle.y, secondInternalCircle.radius,paintSecondInternalRadius)
canvas?.drawLine(100f,600f,700f,600f,paintLine)
drawHorizontalLine(canvas,paintLine)
canvas?.drawLine(verticalLine1.x,verticalLine1.y,verticalLine1.x,verticalLine1.yFinal,paintVerticalLineGray)
canvas?.drawLine(verticalLine2.x,verticalLine2.y,verticalLine2.x,verticalLine2.yFinal,paintVerticalLineGray)
canvas?.drawLine(verticalLine3.x,verticalLine3.y,verticalLine3.x,verticalLine3.yFinal,paintVerticalLineGreen)
}
private fun resetDataPoints() {
this.data.clear()
points.clear()
graphicPoints.clear()
conPoint1.clear()
conPoint2.clear()
}
private fun calculatePointsForData() {
if (data.isEmpty()) return
val bottomY = height - 20f.toDP()
val xDiff =
(width.toFloat() / (data.size - 1)) //subtract -1 because we want to include position at right side
val maxData = (data.maxByOrNull { it.amount }?.amount ?: 100f) + 10
for (i in 0 until data.size) {
val y = bottomY - (data[i].amount / maxData * (bottomY - curveTopMargin))
points.add(
PointF(
when (i) {
0 -> xDiff * i + 30
data.size - 1 -> xDiff * i - 30
else ->
xDiff * i
}, y
)
)
}
}
private fun calculateConnectionPointsForBezierCurve() {
try {
val pointsToConnectPoints = points.filterIndexed { index, _ -> index != 0 }
for (i in 1 until pointsToConnectPoints.size) {
val a = pointsToConnectPoints[i - 1]
val b = pointsToConnectPoints[i]
conPoint1.add(
PointF(
a.x + (0.3f * (b.x - a.x)),
a.y + 0.3f * (b.y - a.y) / 6
)
)
conPoint2.add(
PointF(
b.x - (0.3f * (b.x - a.x)),
b.y - 0.3f * (b.y - a.y) / 6
)
)
}
} catch (e: Exception) {
}
}
private fun Float.toDP(): Float =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, resources.displayMetrics)
fun addDataPoints(data: List<GraphicPoint>, graphicType: GraphicType = GraphicType.Default) {
post {
Thread(Runnable {
resetDataPoints()
primaryColorPaint.apply {
color = ContextCompat.getColor(context, graphicType.primaryColor)
}
borderPathPaint.apply {
color = ContextCompat.getColor(context, graphicType.borderColor)
}
graphicPoints.add(GraphicPoint("", 0f, ""))
graphicPoints.addAll(data)
val max = graphicPoints.maxByOrNull { it.amount }?.amount ?: 30f
val dataPoints = graphicPoints.map { graphicPoint ->
DataPoint(if(graphicPoint.amount<=0) 0f else (graphicPoint.amount * 30f) / max)
}.toList()
graphicPoints.maxByOrNull { it.amount }
val oldPoints = points.toList()
if (oldPoints.isEmpty()) {
this.data.addAll(dataPoints)
calculatePointsForData()
calculateConnectionPointsForBezierCurve()
initPrueba()
postInvalidate()
return#Runnable
}
resetDataPoints()
this.data.addAll(dataPoints)
calculatePointsForData()
calculateConnectionPointsForBezierCurve()
val newPoints = points.toList()
val size = oldPoints.size
var maxDiffY = 0f
for (i in 0 until size) {
val abs = abs(oldPoints[i].y - newPoints[i].y)
if (abs > maxDiffY) maxDiffY = abs
}
val loopCount = maxDiffY / 16
val tempPointsForAnimation = mutableListOf<MutableList<PointF>>()
for (i in 0 until size) {
val old = oldPoints[i]
val new = newPoints[i]
val plusOrMinusAmount = abs(new.y - old.y) / maxDiffY * 16
var tempY = old.y
val tempList = mutableListOf<PointF>()
for (j in 0..loopCount.toInt()) {
if (tempY == new.y) {
tempList.add(PointF(new.x, new.y))
} else {
if (new.y > old.y) {
tempY += plusOrMinusAmount
tempY = min(tempY, new.y)
tempList.add(PointF(new.x, tempY))
} else {
tempY -= plusOrMinusAmount
tempY = kotlin.math.max(tempY, new.y)
tempList.add(PointF(new.x, tempY))
}
}
}
tempPointsForAnimation.add(tempList)
}
if (tempPointsForAnimation.isEmpty()) return#Runnable
val first = tempPointsForAnimation[0]
val length = first.size
for (i in 0 until length) {
conPoint1.clear()
conPoint2.clear()
points.clear()
points.addAll(tempPointsForAnimation.map { it[i] })
calculateConnectionPointsForBezierCurve()
postInvalidate()
Thread.sleep(16)
}
}).start()
}
}
data class ImageCoordinates(
var x: Float,
var y: Float,
var alpha:Float,
var drawable:Int)
data class imgCoor(
var x: Float,
var y:Float,
var finalY:Float
)
data class GraphicPoint(
val month: String,
val amount: Float,
val amountText: String,
)
data class DataPoint(val amount: Float)
sealed class GraphicType(val primaryColor: Int, val borderColor: Int) {
object Credit : GraphicType(R.color.trans_color_4, R.color.trans_color_4_alpha_50)
object Default : GraphicType(R.color.blue_green, R.color.blue_green_alpha_50)
}
`
I'm trying to make custom image view that have rounded corners and a custom transition to change a border radius smoothly.
In CircleTransition, I try to get imageCornerRadius but it's always return 0 which ruined the transaction. But in activity, when I get imageCornerRadius, it returns the value in xml file. So how i can get the imageCornerRadius to perform the transition.
This is declare of my custom view
RoundedImageView
custom attribute
<declare-styleable name="RoundedImageView">
<attr name="imageCornerRadius" format="dimension" />
</declare-styleable>
class RoundedImageView : AppCompatImageView {
constructor(context: Context) : super(context) {
Log.d("debug", "first constructor")
}
constructor(context: Context, attrSet: AttributeSet) : super(context, attrSet) {
Log.d("debug", "second constructor")
init(attrSet)
}
constructor(context: Context, attrSet: AttributeSet, defStyleAttr: Int) : super(
context,
attrSet,
defStyleAttr
) {
Log.d("debug", "third constructor")
init(attrSet)
}
private fun init(attrSet: AttributeSet){
context.theme.obtainStyledAttributes(
attrSet,
R.styleable.RoundedImageView,
0,
0
).apply {
try {
imageCornerRadius = getDimensionPixelSize(
R.styleable.RoundedImageView_imageCornerRadius,
0
).toFloat()
} finally {
recycle()
}
}
}
// Custom attr
var imageCornerRadius: Float = 0F
//Attr for drawing
private lateinit var bitmapRect: RectF
val rect = RectF(drawable.bounds)
val holePath = Path()
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Log.d("size changed", "w = $w h = $h")
bitmapRect = RectF(0f, 0f, w.toFloat(), h.toFloat())
}
override fun onDraw(canvas: Canvas?) {
val drawableWidth = this.width
val drawableHeight = this.height
/* Clip */
holePath.apply {
reset()
addRoundRect(
0F,
0F,
rect.right + drawableWidth,
rect.bottom + drawableHeight,
imageCornerRadius,
imageCornerRadius,
Path.Direction.CW
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas?.clipPath(holePath)
} else {
#Suppress("DEPRECATION")
canvas?.clipPath(holePath, Region.Op.REPLACE)
}
// Draw image
super.onDraw(canvas)
}
}
My custom transition change Size, Coordinate, imageCornerRadius
CircleTransition.kt
class CircleTransition() : Transition() {
private val TAG = CircleTransition::class.java.simpleName
private val BOUNDS = TAG + ":viewBounds"
private val CORNER_RADIUS = TAG + ":imageCornerRadius"
private val PROPS = arrayOf(BOUNDS, CORNER_RADIUS)
init {
Log.d("debug", "Circle Transition called")
}
override fun captureStartValues(transitionValues: TransitionValues?) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues?) {
captureValues(transitionValues)
}
fun captureValues(transitionValues: TransitionValues?) {
val view = transitionValues?.view
//get View Bound
val bound = RectF()
bound.left = view?.left?.toFloat() ?: return
bound.top = view.top.toFloat()
bound.right = view.right.toFloat()
bound.bottom = view.bottom.toFloat()
transitionValues.values.put(BOUNDS, bound)
//get view Corner radius
if(view is RoundedImageView){
val cornerRadius = view.imageCornerRadius
transitionValues.values.put(CORNER_RADIUS, cornerRadius)
}
}
override fun getTransitionProperties(): Array<String> {
return PROPS
}
override fun createAnimator(
sceneRoot: ViewGroup?,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) {
return null
}
val view = endValues.view as RoundedImageView
//startScene
val sBound = startValues.values[BOUNDS] as RectF? ?: return null
//How I get imageCornerRadius
val sCornerRadius = startValues.values[CORNER_RADIUS] as Float? ?: return null
val sWidth = sBound.right - sBound.left
val sHeight = sBound.top - sBound.bottom
//endScene
val eBound = endValues.values[BOUNDS] as RectF? ?: return null
//How I get imageCornerRadius
val eCornerRadius = endValues.values[CORNER_RADIUS] as Float? ?: return null
val eWidth = eBound.right - eBound.left
val eHeight = eBound.top - eBound.bottom
if (sBound == eBound && sCornerRadius == eCornerRadius) {
return null
}
val widthAnimator: ValueAnimator =
ValueAnimator.ofInt(sWidth.toInt(), eWidth.toInt()).apply {
addUpdateListener {
val layoutParams = view.layoutParams
layoutParams.width = it.animatedValue as Int
view.layoutParams = layoutParams
}
}
val heightAnimator: ValueAnimator =
ValueAnimator.ofInt(sHeight.toInt() * -1, eHeight.toInt() * -1).apply {
interpolator = AccelerateInterpolator()
addUpdateListener {
val layoutParams = view.layoutParams
layoutParams.height = it.animatedValue as Int
view.layoutParams = layoutParams
}
}
val cornerRadiusAnimator = ValueAnimator.ofFloat(96F, 0F).apply {
addUpdateListener {
view.imageCornerRadius = it.animatedValue as Float
}
}
// set endView have the same size, coorinate like startScene
view.x = sBound.left
view.y = sBound.top
// view.layoutParams = ViewGroup.LayoutParams(sBound.width().toInt(), sBound.height().toInt())
// move view
val startX = sBound.left
val startY = sBound.top
val moveXTo = eBound.left
val moveYTo = eBound.top
val moveXAnimator: Animator =
ObjectAnimator.ofFloat(view, "x", startX, moveXTo.toFloat())
val moveYAnimator: Animator =
ObjectAnimator.ofFloat(view, "y", startY, moveYTo.toFloat()).apply {
addUpdateListener {
view.invalidate()
}
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(
widthAnimator,
heightAnimator,
cornerRadiusAnimator,
moveXAnimator,
moveYAnimator
)
return animatorSet
}
}
I create an override method called draw in SurfaceView. I want to see the paint I set in my SurfaceView but nothing shows up when I touched the screen and trying to draw a line. What should I do to make this work?
private var mPaint: Paint
private val mPaths: ArrayList<Path> = ArrayList<Path>()
private val mEraserPath: Path = Path()
init {
mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.isDither = true
mPaint.style = Paint.Style.STROKE
mPaint.strokeJoin = Paint.Join.ROUND
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeWidth = 3f
mPaint.alpha = 255
mPaint.color = android.graphics.Color.BLACK
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
override fun draw(canvas: Canvas) {
canvas.drawPaint(mPaint)
val action: EditAction? = this.getEditAction()
for (path: Path in mPaths) {
when (action) {
EditAction.COLOR -> {
setPaintColor(this.getStrokeColor()) // android.graphics.Color.BLACK
setPaintSize(this.getStrokeSize()) // 5f
canvas.drawPath(path, mPaint)
}
EditAction.SIZE -> {
setPaintColor(this.getStrokeColor()) // android.graphics.Color.BLACK
setPaintSize(this.getStrokeSize()) // 5f
canvas.drawPath(path, mPaint)
}
EditAction.ERASER -> {
}
}
}
canvas.drawPath(mEraserPath, mPaint)
super.draw(canvas)
}
Instead of using draw, use the SurfaceHolder.Callback functions instead, as shown below. I have mof
class SlowSurfaceView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {
private var mPaint: Paint = Paint()
init {
holder.addCallback(this)
mPaint.isAntiAlias = true
mPaint.isDither = true
mPaint.style = Paint.Style.STROKE
mPaint.strokeJoin = Paint.Join.ROUND
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeWidth = 3f
mPaint.alpha = 255
mPaint.color = android.graphics.Color.RED
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
// Do nothing for now
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
override fun surfaceCreated(holder: SurfaceHolder) {
if (isAttachedToWindow) {
val canvas = holder.lockCanvas()
canvas?.let {
it.drawRect(Rect(100, 100, 200, 200), mPaint)
holder.unlockCanvasAndPost(it)
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(View.resolveSize(desiredWidth, widthMeasureSpec),
View.resolveSize(desiredHeight, heightMeasureSpec))
}
}
Refer to the above modify the code, and hopefully you should get what you want.