1 of 31

Connect Session 6

Custom Views,

ValueAnimator,

and Miscellaneous Topics

Hussein El Feky | Jan 7-13, 2023

2 of 31

Reminder

Projects 3, 4, 5, 6 and 7 Deadline

(Graduation Date)

23rd February, 2023

3 of 31

Agenda

  • Canvas and Paint
  • View Constructor
  • Custom Attributes
  • invalidate() vs. requestLayout()
  • onMeasure() vs. onDraw()

1

Custom Views

2

ValueAnimator

5

Q&A Time

  • ValueAnimator
  • Animation Interpolators

4

Project 3 Tips

3

Miscellaneous

  • @JvmOverloads
  • Kotlin Delegates
  • Resource Qualifiers
  • Configuration Changes

4 of 31

Prerequisites

  • Familiarity with Android UI
  • Familiarity with basic Kotlin

5 of 31

Canvas and Paint

Paint

Canvas

6 of 31

Canvas API

  • drawLine()
  • drawRect()
  • drawRoundRect()
  • drawCircle()
  • drawArc()
  • drawText()
  • drawBitmap()
  • etc.

7 of 31

Paint API

  • isAntiAlias
  • color
  • strokeWidth
  • textSize
  • textAlign
  • typeface
  • letterSpacing
  • etc.

8 of 31

Paint Antialiasing

9 of 31

Canvas Coordinate Plane

10 of 31

@JvmOverloads

class CustomView @JvmOverloads constructor(

context: Context,

attrs: AttributeSet? = null,

defStyleAttr: Int = 0

) : View(context, attrs, defStyleAttr) {

...

}

IS EQUIVALENT TO

class CustomView : View {

constructor(context: Context) : this(context, null)

constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

constructor(context: Context, attrs: AttributeSet?, defStyleAttrs: Int) : super(context, attrs, defStyleAttrs)

...

}

11 of 31

Custom View Constructor Parameters

  • context: Context

The Context in which the view is running in, through which it can access the current theme, resources, etc.

  • attrs: AttributeSet? = null

This is used when a view is being constructed from an XML file, supplying attributes that were specified in the XML file.

  • defStyleAttr: Int = 0

An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. It can be 0 to not look for defaults.

12 of 31

Custom Attributes Example

<declare-styleable name="RouteView">

<attr name="routeColor" format="color" />

<attr name="orientation" format="enum">

<enum name="vertical" value="0" />

<enum name="horizontal" value="1" />

</attr>

<attr name="shape" format="enum">

<enum name="single" value="0" />

<enum name="middle" value="1" />

<enum name="left" value="2" />

<enum name="right" value="3" />

<enum name="start" value="4" />

<enum name="end" value="5" />

</attr>

</declare-styleable>

13 of 31

View.invalidate() vs. View.requestLayout()

Short Answer

  • View.invalidate()

Draw

  • View.requestLayout()

Measure + Draw

14 of 31

View.onMeasure()

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:

  • MeasureSpec.EXACTLY: The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be. (e.g. Instances specifying fixed width to a view or weights in LinearLayout or match_parent attribute, etc.)
  • MeasureSpec.AT_MOST: The child can be as large as it wants up to the specified size.
  • MeasureSpec.UNSPECIFIED: The parent has not imposed any constraint on the child. It can be whatever size it wants.

15 of 31

View.onDraw()

  • Sizes and positions are calculated in previous steps, so the view can now draw itself in onDraw() based on them.
  • In onDraw(), the Canvas APIs then send OpenGL-ES commands to the GPU to draw what’s drawn on the canvas, on the screen.
  • Never create objects in onDraw() as it can get called multiple times.

16 of 31

RouteView Custom View

17 of 31

DialView (Fan Controller) Custom View

18 of 31

DialView (Fan Controller) Custom View | Angles

19 of 31

DialView (Fan Controller) Custom View | Trigonometry

20 of 31

Delegates.observable

/**

* Returns a property delegate for a read/write property that calls a specified callback

* function when changed.

*/

var number: Int by Delegates.observable(0) { _, oldValue, newValue ->

println("Old Value: $oldValue, New Value: $newValue")

}

fun main() {

number = 20 // Print 0, 20

number = 40 // Print 20, 40

number = 10 // Print 40, 10

}

21 of 31

Delegates.vetoable

/**

* Returns a property delegate for a read/write property that calls a specified callback

* function when changed, allowing the callback to veto the modification.

*/

var max: Int by Delegates.vetoable(0) { _, oldValue, newValue ->

newValue > oldValue

}

fun main() {

println(max) // 0

max = 10

println(max) // 10

max = 5

println(max) // 10

}

22 of 31

ValueAnimator

valueAnimator = ValueAnimator.ofInt(0, 360).setDuration(1000).apply {

addUpdateListener {

progress = it.animatedValue as Int

invalidate()

}

addListener(object : AnimatorListenerAdapter() {

override fun onAnimationStart(animation: Animator) {...}

override fun onAnimationEnd(animation: Animator) {...}

override fun onAnimationPause(animation: Animator) {...}

override fun onAnimationResume(animation: Animator) {...}

override fun onAnimationCancel(animation: Animator) {...}

override fun onAnimationRepeat(animation: Animator) {...}

})

interpolator = LinearInterpolator()

repeatCount = ValueAnimator.INFINITE

repeatMode = ValueAnimator.RESTART

start()

}

23 of 31

Animation Interpolators

24 of 31

Animation Interpolators | Demo

25 of 31

Resource Qualifiers

26 of 31

Next Session

  • MotionLayout
  • Dependency Injection vs. Service Locator
  • Koin

27 of 31

Project 3 (LoadApp) Tips

28 of 31

Project 3 (LoadApp) Tips | Part 1

  • If there is no selected option in the main activity, display a Toast to let the user know to select one.
  • Get the GitHub repositories zip file download link as if you were going to download the project manually from GitHub.
  • For API 26+, make sure to create a notifications channel, for the notifications to work.
  • Add an action button to your notification, and pass the download status to the detail activity.
  • You can implement any animation in the detail activity using MotionLayout even if it was a basic one. This animation should be played automatically once the activity is created.

29 of 31

Project 3 (LoadApp) Tips | Part 2

  • Update the button’s loading bar width and loading circle progress using ValueAnimator once the button state is loading, and cancel the animator when the state is idle.
  • You can use Canvas.drawRect or Canvas.drawRoundRect, Canvas.drawArc and Canvas.drawText to draw the button’s background, circle and text respectively.
  • Make sure to draw the custom view layers in order.
    • Static Background → Loading Background → Loading Circle → Button Text
  • You’re required to add at least two custom attributes to your custom view and also apply them in your layout even if they were unnecessary.
  • The custom attributes you need to add can be anything like button color, circle color, text color, etc.

30 of 31

Resources

31 of 31

Q&A Time

Ask me anything.