I’m participating in nuggets Creators Camp # 4, click here to learn more and learn together!
preface
In real business development, when we need to show some simple ICONS, we always ask the UI to help us cut up a picture and show it directly. However, once we encounter some complicated graphs or special requirements, such as: time-sharing graphs of stocks, line charts, etc., the UI will not help us at this time, we need to draw by our own custom View.
This kind of custom View is often more complex, not only to deal with the tedious calculation and drawing work, but also to deal with the user interaction work. Today I’m going to show you how to use custom Drawable to make our complex custom View more hierarchical and readable.
A custom Drawable
Drawable, Drawable, Drawable, Drawable, Drawable, Drawable
Drawable is an abstract class for Drawable objects. Compared to View, it is more pure. It only deals with drawing and does not handle user interaction events, so it is suitable for background drawing.
SRC /main/res/drawable, we can use getDrawable(int ID) and apply it to android:drawable and Android :icon properties. Draw it and display it on the phone screen.
For example, ShapeDrawable, StateListDrawable, etc. In addition to implementing a Drawable in XML, we can also dynamically create a Drawable in code. If you’re interested, check out some of the summaries of Drawable from my other article, which I won’t expand on here.
With a brief overview of Drawable, let’s take a quick look at the Drawable source code.
public abstract class Drawable {
public abstract void draw(@NonNull Canvas var1);
public abstract void setAlpha(int var1);
public abstract void setColorFilter(@Nullable ColorFilter var1);
public abstract int getOpacity(a); ·· omit code ··}Copy the code
In addition to being an abstract class, Drawable has four abstract methods that need to be implemented:
setAlpha
: in order toDrawable
To specify aalpha
Value, 0 indicates complete transparency and 255 indicates complete opacity.setColorFilter
: in order toDrawable
Specifies optional color filters. inDrawable
thedraw
Each output pixel of the drawing content is mixed intoCanvas
Render target ofbeforeWill besetColorFilter
Modification. passnull
Removes any existing color filters.getOpacity
Returns theDrawable
Transparency, such as:PixelFormat.TRANSLUCENT
(translucent),PixelFormat.TRANSPARENT
(transparent)PixelFormat.OPAQUE
(opaque),PixelFormat.UNKNOWN
Unknown.draw
: Draws within boundaries (throughsetBounds()
),alpha
withcolorFilter
The impact.
Case analysis
Now that you’ve learned about Drawable, let’s get to the focus of this article, customizing Drawable to make complex custom views more hierarchical.
Let’s take this Ant Fortune-fund chart (hereafter called fundChart) as an example for analysis.
We can extract a custom Drawable by its layer structure, as in:
- The abscissa time and the ordinate rate of return can be drawn one
A custom Drawable
, such as:CoordinateDrawable
. - A timeshare chart of prices can be drawn
A custom Drawable
, such as:TimeSharingDrawable
. - Buy and sell orders can be drawn at one point
A custom Drawable
, such as:OrderTagDrawable
. - The gesture slides to the specified point coordinates with a yield that can be drawn out
A custom Drawable
, such as:SpecificPointDrawable
.
The dimensions of these custom drawables are then determined in fundChart and drawn in sequence in onDraw().
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Draw in sequence.
//1. The abscissa time and the ordinate rate of return
coordinateDrawable.draw(canvas)
//2. Timesharing of prices
timeSharingDrawable.draw(canvas)
//3. Buy and sell order point
orderTagDrawable.draw(canvas)
//4. Gesture slide to the specified point coordinates and yield
specificPointDrawable.draw(canvas)
}
Copy the code
In addition, our custom Drawable is only used to deal with the drawing related work, not the interaction events with the user, so the interaction events with the user still need to be processed in fundChart.
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.actionMasked) { MotionEvent.ACTION_DOWN -> { ... } MotionEvent.ACTION_POINTER_DOWN -> { ... } MotionEvent.ACTION_POINTER_UP -> { ... } MotionEvent.ACTION_MOVE -> { ... } MotionEvent.ACTION_UP -> { ... } MotionEvent.ACTION_CANCEL -> { ... }}return true
}
Copy the code
After retrieving the user’s interactive gesture, determine the next step and process the corresponding custom Drawable.
The actual case
We analyzed the case, and then we actually took a simple case to practice.
Implement a custom View, draw a basketball in it, when the user clicks the screen, the basketball rolls to the user clicks the coordinates.
We can simplify the implementation steps into two steps:
- Draw a basketball.
- Get the coordinates of the position that the user clicked, and then use the property animation to scroll the basketball to that position.
Draw the basketball
Let’s start with the step of drawing the basketball. Here, the drawing of the basketball does not need to interact with the user, so we can draw it into a custom Drawable, such as BallDrawalbe.
class BallDrawable : Drawable() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = Color.parseColor("#D2691E")}private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeWidth = 1f.px
color = Color.BLACK
}
override fun draw(canvas: Canvas) {
val radius = bounds.width().toFloat() / 2
canvas.drawCircle(
bounds.width().toFloat() / 2,
bounds.height().toFloat() / 2,
radius,
paint
)
//the vertical line of the ball
canvas.drawLine(
bounds.width().toFloat() / 2.0f,
bounds.width().toFloat() / 2,
bounds.height().toFloat(),
linePaint
)
//the transverse line of the ball
canvas.drawLine(
0f,
bounds.height().toFloat() / 2,
bounds.width().toFloat(),
bounds.height().toFloat() / 2,
linePaint
)
val path = Path()
val sinValue = kotlin.math.sin(Math.toRadians(45.0)).toFloat()
//left curve
path.moveTo(radius - sinValue * radius,
radius - sinValue * radius
)
path.cubicTo(radius - sinValue * radius,
radius - sinValue * radius,
radius,
radius,
radius - sinValue * radius,
radius + sinValue * radius
)
//right curve
path.moveTo(radius + sinValue * radius,
radius - sinValue * radius
)
path.cubicTo(radius + sinValue * radius,
radius - sinValue * radius,
radius,
radius,
radius + sinValue * radius,
radius + sinValue * radius
)
canvas.drawPath(path, linePaint)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun getOpacity(a): Int {
return when (paint.alpha) {
0xff -> PixelFormat.OPAQUE
0x00 -> PixelFormat.TRANSPARENT
else -> PixelFormat.TRANSLUCENT
}
}
override fun setColorFilter(colorFilter: ColorFilter?). {
paint.colorFilter = colorFilter
}
}
Copy the code
A brief description of the drawing of the basketball is given. First we use getBounds() to determine the size and radius, then draw a yellow circle, and then draw the black line on the basketball, which is essentially two straight lines plus two left and right arcs to form a basketball.
Scroll to basketball
Draw after good basketball, then there is need to get users to click on the coordinates of the screen, for better, for example, here I put in a custom View (CustomBallMovingSiteView. Kt) to complete.
class CustomBallMovingSiteView(context: Context, attributeSet: AttributeSet? , defStyleAttr:Int) :
FrameLayout(context, attributeSet, defStyleAttr) {
constructor(context: Context) : this(context, null.0)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
private lateinit var ballContainerIv: ImageView
private val ballDrawable = BallDrawable()
private val radius = 50
private var rippleAlpha = 0
private var rippleRadius = 10f
private var rawTouchEventX = 0f
private var rawTouchEventY = 0f
private var touchEventX = 0f
private var touchEventY = 0f
private var lastTouchEventX = 0f
private var lastTouchEventY = 0f
private val ripplePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
isDither = true
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = 2f.px
alpha = rippleAlpha
}
init {
initView(context, attributeSet)
}
private fun initView(context: Context, attributeSet: AttributeSet?). {
//generate a ball by dynamic
ballContainerIv = ImageView(context).apply {
layoutParams = LayoutParams(radius * 2, radius * 2).apply {
gravity = Gravity.CENTER
}
setImageDrawable(ballDrawable)
//setBackgroundColor(Color.BLUE)
}
addView(ballContainerIv)
setWillNotDraw(false)}override fun onTouchEvent(event: MotionEvent?).: Boolean{ lastTouchEventX = touchEventX lastTouchEventY = touchEventY event? .let { rawTouchEventX = it.x rawTouchEventY = it.y touchEventX = it.x - radius touchEventY = it.y - radius } ObjectAnimator.ofFloat(this."rippleValue".0f.1f).apply {
duration = 1000
start()
}
val path = Path().apply {
moveTo(lastTouchEventX, lastTouchEventY)
quadTo(
lastTouchEventX,
lastTouchEventY,
touchEventX,
touchEventY
)
}
val oaMoving = ObjectAnimator.ofFloat(ballContainerIv, "x"."y", path)
val oaRotating = ObjectAnimator.ofFloat(ballContainerIv, "rotation".0f.360f)
AnimatorSet().apply {
duration = 1000
playTogether(oaMoving, oaRotating)
start()
}
return super.onTouchEvent(event)
}
fun setRippleValue(currentValue: Float) {
rippleRadius = currentValue * radius
rippleAlpha = ((1 - currentValue) * 255).toInt()
invalidate()
}
override fun onDraw(canvas: Canvas?). {
super.onDraw(canvas)
ripplePaint.alpha = rippleAlpha
//draw ripple for click eventcanvas? .drawCircle(rawTouchEventX, rawTouchEventY, rippleRadius, ripplePaint) } }Copy the code
To recap: first we will dynamically generate a View with a background set to the BallDrawable() we just drew to form a basketball. Then the onTouchEvent() method is used to get the user’s click coordinates, and then the property animation is used to scroll the basketball to the coordinates.
For more code, see Github Drawable_Leaning’s basketball rolling.
conclusion
In this article we learned about customizing Drawable. We learned that Drawable is only used for drawing, not for user interaction events. So, in our complex custom View, we can split it up, and some backgrounds, decorations, and so on can be completely drawn by custom Drawable. This will make our complex custom View layer more clear, code readability greatly improved.
If you want to learn more about Drawable, check out the summary of Drawable. In addition, if you want to refer to all of the source code in this article, you can click on Github Drawable_Learning to see it. You are welcome to give me a little star.
In fact, the biggest purpose of sharing articles is to wait for someone to point out my mistakes. If you find any mistakes, please point them out without reservation and consult with an open mind.
In addition, if you think this article is good and helpful, please give me a like as encouragement, thank you ~ Peace~!