This is part 1-5 of HenCoder’s custom draw: Draw order
The previous content is here: HenCoder Android development advanced custom View 1-2 Paint details HenCoder Android development advanced custom View 1-3 HenCoder Android development advanced custom View 1-4 Canvas for rendering assistance
If you haven’t heard of HenCoder, check out this one: HenCoder: An advanced manual for advanced Android engineers
Introduction to the
The previous installments were about art, which apis you can use to draw what. By the end of the last issue, the “art” has been covered, and the next topic is “Tao”, which is “how to arrange these drawings”.
This is the first issue of tao: Drawing order.
In Android, everything is drawn sequentially, with the first drawing overwritten by the second. For example, if you draw a circle and then a square in the overlapping position, the result will be different from drawing a square and then a circle:
In actual projects, it is very common to draw content to cover each other, so how to achieve their own needs to cover the relationship, is the content of this period.
1 before or after super.ondraw ()?
In previous installments I wrote about custom drawing, I simply inherited the View class and overrode its onDraw() method to put the drawing code inside, like this:
public class AppView extends View {...protected void onDraw(Canvas canvas) {
super.onDraw(canvas); .// Custom drawing code}... }Copy the code
This is the most basic form of custom drawing: inherit the View class and completely customize its drawing in onDraw().
In the previous example, I put all the drawing code under super.ondraw (). But it doesn’t matter if you write the code above or below super.ondraw (), or even if you delete the super.ondraw () line, the effect is the same — onDraw() is empty in the View class:
// In the view.java source code, onDraw() is empty
// So classes that inherit directly from View do nothing with their super.ondraw ()
public class View implements Drawable.Callback.KeyEvent.Callback.AccessibilityEventSource {.../**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {}... }Copy the code
However, in addition to inheriting the View class, custom drawing is more commonly done by inheriting a control with some functionality, overriding its onDraw(), adding some drawing code to it, and making an “evolved” control:
Based on EditText, add the Hint Text at the top and the character count at the bottom.
For custom drawing based on existing controls, super.ondraw () should be taken into account: You need to determine if your drawing code should be written above or below super.ondraw () by determining whether the content you draw should cover or be covered by the content of the control, depending on your needs.
Write 1.1 below super.ondraw ()
Write the drawing code below super.ondraw (). Since the drawing code is executed after the original content is drawn, the drawing content overlays the original content of the control.
This is the most common case: adding embellished content to a control. For example, in Debug mode, draw the image size of the ImageView:
public class AppImageView extends ImageView {...protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (DEBUG) {
// Draw the size of the drawable in debug mode. }}}Copy the code
It works. You ever try it?
Of course, there are many other uses, depending on your needs, experience, and imagination.
Write 1.2 above super.ondraw ()
If you write the drawing code above super.ondraw (), the drawing code will be overwritten by the original content of the control because the drawing code will be executed before the original content is drawn.
There are relatively few scenarios for this usage. For example, you can create an “accent” color by drawing solid rectangles underneath the text:
public class AppTextView extends TextView {...protected void onDraw(Canvas canvas) {...// Draw the background of the highlighted text before super.ondraw () draws the text
super.onDraw(canvas); }}Copy the code
2 dispatchDraw() : method for drawing child Views
So far I have only mentioned onDraw(). In fact, there is not only one drawing method, but several, of which onDraw() is only responsible for its own body content drawing. Sometimes, however, the masking relationship you want can’t be achieved by onDraw(), but by another drawing method.
For example, you inherit a LinearLayout, rewrite its onDraw() method, and insert your own drawing code in super.ondraw () so that it can draw some spots inside as an ornament:
public class SpottedLinearLayout extends LinearLayout {...protected void onDraw(Canvas canvas) {
super.onDraw(canvas); .// Draw spots}}Copy the code
Looks good, right?
But you’ll notice that when you add the child View, your spots go away:
<SpottedLinearLayout
android:orientation="vertical"
. >
<ImageView . />
<TextView . />
</SpottedLinearLayout>Copy the code
The reason for this is Android’s drawing order: during drawing, each ViewGroup calls its own onDraw() to draw its body before drawing its child views. In the case of the above example, your LinearLayout will draw its subviews after the dots are drawn. After the child View is drawn, the previously drawn blob is covered by the quilt View.
Specifically, the “draw subview” is invoked by another draw method called dispatchDraw(). That is, during drawing, each View and ViewGroup calls the onDraw() method to draw the body and the dispatchDraw() method to draw the child views.
Note: Although both views and viewgroups have a dispatchDraw() method, a View does not have a child View, so generally the dispatchDraw() method only makes sense for viewgroups (and their subclasses).
Back to the question: How do you get the LinearLayout content to cover the subView? Just let it draw after the child View draws.
2.1 is written under super.dispatchdraw ()
Just rewrite dispatchDraw() and put your draw code under super.dispatchdraw (), and the draw code will happen after the child View draws, so that the draw content will cover the child View.
public class SpottedLinearLayout extends LinearLayout {...// replace onDraw() with dispatchDraw()
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas); .// Draw spots}}Copy the code
What a cute Batman
2.2 Write above super.dispatchdraw ()
Similarly, by writing the draw code above super.dispatchdraw (), the draw will occur after onDraw() and before super.dispatchdraw (), meaning that the draw will occur between the main content and the sub-view. And this…
Overwriting onDraw() after super.ondraw () has the same effect as in 1.1.
Can you see why? I won’t be able to draw it.
3 Description of the drawing process
The two most typical parts of the drawing process are the body and subview mentioned above, but they are not the whole drawing process. In addition, the drawing process includes some other things to draw. Specifically, a complete drawing process draws the following in turn:
- background
- The main body,
onDraw()
) - The child View (
dispatchDraw()
) - Slide edge gradient and slider
- prospects
In general, a View (or ViewGroup) drawing will not contain all of these terms, but it will not escape them, and it will adhere strictly to this order. For example, usually a LinearLayout only has a background and subviews, so it will draw the background first and then the subviews; An ImageView has a body, which may be masked by a translucent foreground that is drawn behind the body. Note that foreground support was only added in Android 6.0 (aka API 23); It used to be there, but only supported FrameLayout, and it wasn’t until 6.0 that support was put into the View class.
The second and third steps have already been described; Step 1 — background, which is drawn in a method called drawBackground(), but this method is private and cannot be overridden. If you want to set the background, It can only be set using its own API (the Android: Background property of the XML layout file and the View.setbackgroundxxx () method of the Java code, which everyone uses very well), rather than custom drawing; The fourth and fifth steps — sliding edge gradient, slider, and foreground — are combined into the onDrawForeground() method, which can be overwritten.
Slide edge gradients and sliders can be set using the Android :scrollbarXXX series properties of XML or the View.setxxxScrollBarxxx () series methods of Java code; Foreground can be set using the Android :foreground property of XML or the view.setforeground () method of Java code. By overwriting onDrawForeground() and inserting the drawing code above or below the super-.ondrawforeground () method, we can control the covering relationship between the drawing content and sliding edge gradient, slider, and foreground.
4 onDrawForeground()
First of all, again, this method was introduced in API 23, so make sure your minSdk is up to 23 before rewriting this method, otherwise your software will not work on a lower version of the phone.
In onDrawForeground(), slide edge gradient, slider bar, and foreground are drawn in sequence. So if you override onDrawForeground() :
4.1 Write under super.onDrawForeground()
If you write the draw code below super.ondrawforeground (), the draw code will be executed after the slide edge gradient, slider, and foreground, which will cover the slide edge gradient, slider, and foreground.
public class AppImageView extends ImageView {...public void onDrawForeground(Canvas canvas) {
super.onDrawForeground(canvas); .// Draw the "New" label}}Copy the code
<! -- Use translucent black as foreground, this is a very common treatment -->
<AppImageView
.
android:foreground="# 88000000" />Copy the code
The label in the upper left corner is not covered by a black mask, but retains its original color.
4.2 Write above super.ondrawforeground ()
If you use super.ondrawforeground (), the drawing will be executed between dispatchDraw() and super.ondrawforeground (), and will cover the subview, But covered by sliding edge gradients, sliders, and foreground:
public class AppImageView extends ImageView {...public void onDrawForeground(Canvas canvas) {...// Draw the "New" label
super.onDrawForeground(canvas); }}Copy the code
The label in the upper left corner is noticeably darker because it is covered by a translucent black mask.
This is the same as overwriting dispatchDraw() and placing the drawing code under super.dispatchdraw () in 2.1: The drawing will cover the sub-view, but will be covered by the sliding edge gradient, slider and foreground.
4.3 Want to insert draw code between slide gradient, slider and foreground?
The answer is simple: no.
Although these three parts are drawn sequentially, they are written together into the onDrawForeground() method, so you can either foreground or foreground them. You can’t insert a drawing between them.
5 Draw () overall scheduling method
In addition to onDraw() dispatchDraw() and onDrawForeground(), there is another method that can be used to implement custom drawing: draw().
Draw () is the overall scheduling method for the drawing process. The entire drawing process of a View takes place in the draw() method. The background, body, subview, slide correlation, and foreground are all drawn in the draw() method.
Draw (); // Draw ();
public void draw(Canvas canvas) {... drawBackground(Canvas);// Draw background (cannot be overridden)
onDraw(Canvas); // Draw the body
dispatchDraw(Canvas); // Draw a subview
onDrawForeground(Canvas); // Draw slide correlation and foreground. }Copy the code
OnDraw () dispatchDraw() onDrawForeground() So their covering relationship is just like what we saw earlier — dispatchDraw() covers what onDraw() draws; The content drawn by onDrawForeground() overcomes the content drawn by dispatchDraw(). Outside of them, the draw() method acts as the overall dispatch. So, you can also override the draw() method to do custom drawing.
Write 5.1 below super.draw()
Since draw() is the master scheduling method, if you write your draw code below super.draw(), it will be executed after all other draws are completed, that is, its draw will override all other draws.
It has the same effect as overriding onDrawForeground() and writing the drawing code in super.ondrawforeground () : it overpowers everything else.
Of course, they all work the same way, but if you overwrite draw() and onDrawForeground(), then draw() will still cover the onDrawForeground(). So strictly speaking, they’re a little bit different.
But this is a mockery…
5.2 Write above super.draw()
Similarly, since draw() is the master scheduling method, if you write draw code above super.draw(), it will be executed before all other draws, so that part of the draw will be covered by everything else, including the background. Yeah, the background will cover it, too.
Don’t you think it’s useless? Why would anyone want to draw something underneath the background? Don’t think so. Sometimes it actually works.
For example, I have an EditText:
The line below it is the background of the EditText. So if I wanted to give the EditText a green base, I couldn’t do it with a green background, because that would just replace the background and cause the line below to disappear:
<EditText
.
android:background="#66BB6A" />Copy the code
EditText: Am I an EditText or a TextView? Stupid can’t tell the difference.
At this point, you can override its draw() method and insert code above super.draw() to paint the bottom of everything in green:
public AppEditText extends EditText {
...
public void draw(Canvas canvas) {
canvas.drawColor(Color.parseColor("#66BB6A")); // Color it green
super.draw(canvas); }}Copy the code
Of course, this isn’t very common, and I haven’t actually written this code in a project. But WHAT I want to say is that as engineers, we don’t know what needs we’re going to have in the future. All we can do is learn as much as we can, learn as much as we can, learn as much as we can about what we can do and how we can do it, so that when the need comes, we can be more at ease and less helpless.
Pay attention to
There are two things to note about the drawing method:
- For the sake of efficiency,
ViewGroup
Bypassing by defaultdraw()
Method, instead, execute directlydispatchDraw()
In order to simplify the drawing process. So if you customize somethingViewGroup
Subclasses of, e.gLinearLayout
) and need to divide in itdispatchDraw()
Draw content outside of any one of your draw methodsmayWill need to callView.setWillNotDraw(false)
This line of code switches to the full drawing process (the reason it is “may” rather than “must” is that some viewgroups are already calledsetWillNotDraw(false)
For exampleScrollView
). - Sometimes, a drawing code will look the same in different drawing methods, so you can rewrite it using a drawing method you like or are used to. There is one exception: if the drawing code can be written in
onDraw()
, can also be written in other drawing methods, so write in preferenceonDraw()
Because Android has optimizations that can be skipped automatically when redrawing is not requiredonDraw()
To improve development efficiency. Only those who enjoy this optimizationonDraw()
One way.
conclusion
That’s all for today: using different drawing methods, and putting drawing code in super when overwriting. Draw a different position above or below the method () to achieve the desired masking relationship. Here’s a chart and a table to summarize:
Well, the picture above has already been posted before, so there is no need to compare it exactly the same.
And don’t forget the two caveats mentioned above:
- in
ViewGroup
Override division in a subclass ofdispatchDraw()
May need to be called when drawing methods other thansetWillNotDraw(false)
; - When the overridden method has more than one choice, it takes precedence
onDraw()
.
Practice project
To avoid forgetting, it is highly recommended that you strike while the iron is hot: HenCoderPracticeDraw5
Here is a diagram of the exercise
Next up
The next issue is the second issue of tao: animation.
I didn’t want to talk about animation, because animation is not really a custom View. However, from the feedback of various channels recently found that many people have a vague grasp of animation, and if animation is not well mastered, the development of custom View will certainly be limited. So all right, let’s add an animation.
By the way, there are three phases of tao. After these three phases, the first part of the custom View: Custom Drawing is over.
Preview figure? What preview? It doesn’t exist.
Feel great?
If you find something interesting after reading it, forward it to your Weibo, wechat group, moments, or official account so that others who need it can see it too.