[Transfer] Custom controls are actually very simple 1/12

To respect the original reprint, please indicate: From AigeStudio (http://blog.csdn.net/aigestudio) Power by Aige. Infringement will be investigated!

Artillery Town Tower

Customize View, many children who are new to Android will be in awe when they hear such a sentence! Because in the eyes of many beginners, being able to draw a View by yourself is a very cool and cool thing! However, in the same way, custom View is often out of reach for beginners. This is because it seems that it is not difficult after reading the source code of many custom Views. Some custom Views are even less than a hundred lines of code. The reason is that even after reading many articles and similar source codes, I still cannot write a domineering View. At this time, many seniors will tell you to read more about the source code of the View class and see how the drawing logic is handled in the View class. If you read it, I can only say that you are a child who is very eager to learn and curious about knowledge. , It is good to understand the principles, but not everything must be investigated thoroughly! If you do Android development, you must understand all the Android source code, I can only laugh! You might as well write a system, right? In the same way, it is not worth advocating that you have to spend a lot of time studying various source codes to write a custom View. Of course, I do not deny the significance of pursuing the principles, but for an ordinary developer, you do not need to delve into it. Not something you should care about, especially an ape with good object-oriented thinking. To give a simple example in life, everyone has used a hair dryer. The hair dryer usually provides three settings: off, cold air, and hot air, right? When you buy a hair dryer, they will only tell you what the three settings of the hair dryer are. , I believe that no fool who buys a hair dryer would take apart the hair dryer, write down the motor, and explain to you one by one what it is! Similarly, when we customize View, Android already provides a lot of methods similar to hair dryer settings. You can just do what you want to do in it. As for how Android itself is implemented internally, you don’t have to worry about it at all! To use the original words of the official document: Just do you things! Beginners who don’t know how to customize View do not understand its principles, but do not understand these “gear”-like methods!

Okay, so much nonsense! Let’s get down to business first and see how the custom View is implemented! Customizing a View class in Android must directly inherit the View class or a subclass of the View class such as TextView, Button, etc. Here we also directly inherit the View and customize a View subclass CustomView:

public class CustomView extends View {
}

There is no parameterless constructor provided in the View class. At this time, our IDE will prompt us that you must explicitly declare a constructor with the same signature list as the parent class:

At this time, we click “Add constructor CustomView(Context context)”, and the IDE will automatically generate a constructor with a Context type signature for us:

public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
}

You don’t need to worry about what Context is, just remember that it contains a lot of different information that shuttles between various components, controls, etc. in Android. To put it inappropriately, it is a messenger full of information. Android needs it to be from Get the required information inside.
In this way we have defined a custom View of our own, and we try to add it to the Activity:

public class MainActivity extends Activity {
private LinearLayout llRoot;//Root layout of the interface
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
llRoot = (LinearLayout) findViewById(R.id.main_root_ll);
llRoot.addView(new CustomView(this));
}
}

After running it, I found nothing, empty! Because our CustomView has nothing to begin with! But there is no problem after adding it to our interface, right? Perfect! Then we can reference it directly in the xml document:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@ + id/main_root_ll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <com.sigestudio.customviewdemo.views.CustomView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
 
</LinearLayout>

At this time we restore the code in the Activity:

public class MainActivity extends Activity {
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

After running it again, I found that the IDE reported an error:

The general meaning is that our CustomView class cannot be parsed and the method cannot be found. Why? When we reference our CustomView class in the xml file, we specify two attributes that come with Android: layout_width and layout_height. When we need to use similar attributes (such as more IDs, padding, margins, etc.) class), you must add an AttributeSet type signature in the constructor of the custom View to parse these attributes:

public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
 
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

Run it again and find that everything is back to normal. Now let’s draw something in our View. After all, there must be something to customize the View, right? Android provides us with an onDraw(Canvas canvas) method to let us draw what we want:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}

What we want to draw can be drawn directly in this method. In the real world, we need two things to draw: a pen (or anything that can be painted) and paper (or anything that can be drawn), the same Well, Android also provides us with these two things: Paint and Canvas, one is the brush and the other is of course the canvas~~, we can see that in the onDraw method, the canvas Canvas is passed in as a signature, that is to say This canvas is prepared for us by Android and does not require you to take care of it. Of course, you can also customize a canvas to draw your own things on it and pass it to the parent class, but generally we do not recommend this! Some people may ask where this canvas came from? I don’t want to go into the details of its principles with you here, otherwise a long discussion will be too cumbersome and dampen the interest of all rookies in learning. But I can tell you this, if there are various small canvases (various controls in the interface) on top of a large canvas (interface), then how to determine the size of these small canvases? Think for yourself haha!
Grass! Off topic again!
You already have the canvas, all you need is a brush, easy! Let’s get a new one! The advantage of being a programmer is that everything can be new by yourself! Girlfriends can also make new ones by themselves, whatever they want! ! ~~

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
}

After instantiating a Paint object, we set anti-aliasing (a bunker algorithm that makes the edges of the image appear more rounded, shiny and dynamic): setAntiAlias(true), but we found that this was another warning from the IDE! ! ! What does “Avoid object allocations during draw/layout operations (preallocate and reuse instead)” say:

Why? Why? To put it bluntly, it is not recommended that you instantiate objects during the draw or layout process! Why? Because the process of draw or layout may be a frequently repeated process, we know that new needs to allocate memory space. If a large number of new objects are used in a frequently repeated process, I don’t know whether the memory will explode or not, but it will be a waste of memory. I am very sure of that! Therefore, Android does not recommend that we instantiate objects in these two processes. Now that we have said this, let’s change it:

public class CustomView extends View {
private Paint mPaint;
 
public CustomView(Context context) {
this(context, null);
}
 
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
 
//Initialize the brush
initPaint();
}
 
/**
* Initialize the brush
*/
private void initPaint() {
// Instantiate the brush and turn on anti-aliasing
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
 
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}

In the real world, we draw with a variety of brushes, including markers, pencils, ballpoint pens, brushes, watercolor pens, highlighters, etc… And the properties of these pens are also different, like pencils, according to charcoal particles The roughness can be divided into 2B, 3B, 4B, 5B, HB and of course SB. Watercolor pens also come in various colors, and marker pens are even more domineering! Similarly, in Android’s paintbrush, it also has some things that are available in reality, and some things that are not available in reality! We can use various setter methods of Paint to set various properties, such as setColor() to set the brush color, setStrokeWidth() to set the stroke line, and setStyle() to set the brush style:

Paint integrates all “painting” attributes, while Canvas defines all things to be drawn. We can draw various things through various drawXXX methods under Canvas, such as drawing a circle drawCircle() and drawing an arc. drawArc(), draw a bitmap drawBitmap(), etc.:

Now that we have a preliminary understanding of Paint and Canvas, we might as well try to draw something on our canvas, such as a ring? Let’s first set the properties of the brush:

/**
 * Initialize the brush
 */
private void initPaint() {
// Instantiate the brush and turn on anti-aliasing
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
/*
* Set the brush style to stroke, circle...of course you can't fill it, otherwise it won't make sense.
*
* There are three brush styles:
* 1.Paint.Style.STROKE: stroke
* 2.Paint.Style.FILL_AND_STROKE: stroke and fill
* 3.Paint.Style.FILL: fill
*/
mPaint.setStyle(Paint.Style.STROKE);
 
//Set the brush color to light gray
mPaint.setColor(Color.LTGRAY);
 
/*
* Set the thickness of the stroke, unit: pixel px
* Note: When setStrokeWidth(0), the stroke width is not 0 but only occupies one pixel.
*/
mPaint.setStrokeWidth(10);
}

Then draw Cricle in our onDraw method:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
 
// draw a circle
canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, 200, mPaint);
}

Pay attention here! drawCircle means that a circle is drawn, but after our brush style is set to stroke, what it draws is a circle! The first two parameters of drawCircle represent the XY coordinates of the center of the circle. Here we use a tool class to obtain the screen size so that the center of the circle can be set at the center of the screen. The third parameter is the radius of the circle, and the fourth parameter is our brush!
One thing to note here: when setting numeric parameters in Android, unless otherwise specified, the parameter unit is generally px pixels.

Okay, let’s run our Demo and see the results:

A very beautiful ring appears before our eyes! Isn’t it great? This is the first View we wrote. Of course, this is only the first step. Although it is only a small step, it will definitely be a big step that affects the progress of mankind! …Fuck!

But simply drawing a circle may not satisfy everyone’s appetite, right? So let’s try to make it move? For example, how to achieve this by making its radius constantly change from small to large? If you understand the principle of animation, you will know that an animation is composed of countless consecutive pictures. The rapid switching between these pictures and the temporary vision of our eyes give us the illusion of “moving”. Then it is very simple to realize the principle. Can we just keep changing the radius of the ring and re-draw and display it? Similarly, Android provides a method called invalidate() to allow us to redraw our View. Now let’s restructure our code, add an int member variable as a reference to the radius value, provide a setter method to set the radius value externally, and call the invalidate() method to redraw the View after setting the value:

public class CustomView extends View {
private Paint mPaint;// brush
private Context mContext; // Context reference
 
private int radius;//Ring radius
 
public CustomView(Context context) {
this(context, null);
}
 
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
 
//Initialize the brush
initPaint();
}
 
/**
* Initialize the brush
*/
private void initPaint() {
// Instantiate the brush and turn on anti-aliasing
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
/*
* Set the brush style to stroke, circle...of course you can't fill it, otherwise it won't make sense.
*
* There are three brush styles:
* 1.Paint.Style.STROKE: stroke
* 2.Paint.Style.FILL_AND_STROKE: stroke and fill
* 3.Paint.Style.FILL: fill
*/
mPaint.setStyle(Paint.Style.STROKE);
 
//Set the brush color to light gray
mPaint.setColor(Color.LTGRAY);
 
/*
* Set the thickness of the stroke, unit: pixel px
* Note: When setStrokeWidth(0), the stroke width is not 0 but only occupies one pixel.
*/
mPaint.setStrokeWidth(10);
}
 
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
 
// draw a circle
canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, radius, mPaint);
}
 
public synchronized void setRadiu(int radiu) {
this.radiu = radius;
 
// redraw
invalidate();
}
}

OK, let’s open a thread in the Activity and use the Handler to set the radius value periodically and refresh the interface:

public class MainActivity extends Activity {
private CustomView mCustomView; // Our custom View
 
private int radius; // Radius value
 
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//Set the radius value of the custom View
mCustomView.setRadiu(radiu);
}
};
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
// Get the control
mCustomView = (CustomView) findViewById(R.id.main_cv);
 
/*
* Open thread
*/
new Thread(new Runnable() {
@Override
public void run() {
/*
* Ensure that the thread continues to execute and refresh the interface
*/
while (true) {
try {
/*
* If the radius is less than 200, add it automatically. Otherwise, reset the radius value after it is greater than 200 to achieve reciprocation.
*/
if (radiu <= 200) {
radius + = 10;
 
//Send message to Handler for processing
mHandler.obtainMessage().sendToTarget();
} else {
radius = 0;
}
 
// Pause for 40 milliseconds each time
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
 
@Override
protected void onDestroy() {
super.onDestroy();
//Clear the Handler reference after the interface is destroyed
mHandler.removeCallbacksAndMessages(null);
}
}

I won’t demonstrate the effect after running it, but the project source code will be shared.
But there is a problem. It is unscientific for me to have to deal with some logic in Activity for such a progress bar-like effect! What a waste of code! We also need a Handler to pass information, Fuck! Can’t it be done in a custom View at once? The answer is yes, we modify the CustomView code to implement the Runnable interface, which is much better:

public class CustomView extends View implements Runnable {
private Paint mPaint;// brush
private Context mContext; // Context reference
 
private int radius;//Ring radius
 
public CustomView(Context context) {
this(context, null);
}
 
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
 
//Initialize the brush
initPaint();
}
 
/**
* Initialize the brush
*/
private void initPaint() {
// Instantiate the brush and turn on anti-aliasing
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
/*
* Set the brush style to stroke, circle...of course you can't fill it, otherwise it won't make sense.
*
* There are three brush styles:
* 1.Paint.Style.STROKE: stroke
* 2.Paint.Style.FILL_AND_STROKE: stroke and fill
* 3.Paint.Style.FILL: fill
*/
mPaint.setStyle(Paint.Style.STROKE);
 
//Set the brush color to light gray
mPaint.setColor(Color.LTGRAY);
 
/*
* Set the thickness of the stroke, unit: pixel px
* Note: When setStrokeWidth(0), the stroke width is not 0 but only occupies one pixel.
*/
mPaint.setStrokeWidth(10);
}
 
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
 
// draw a circle
canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, radius, mPaint);
}
 
@Override
public void run() {
/*
* Ensure that the thread continues to execute and refresh the interface
*/
while (true) {
try {
/*
* If the radius is less than 200, add it automatically. Otherwise, reset the radius value after it is greater than 200 to achieve reciprocation.
*/
if (radiu <= 200) {
radius + = 10;
 
// Refresh View
invalidate();
} else {
radius = 0;
}
 
// Pause for 40 milliseconds each time
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

And our Activity can also get rid of cumbersome code logic:

public class MainActivity extends Activity {
private CustomView mCustomView; // Our custom View
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
// Get the control
mCustomView = (CustomView) findViewById(R.id.main_cv);
 
/*
* Open thread
*/
new Thread(mCustomView).start();
}
}

Run it and see! Fuck! ! ! An error was reported:

Why! Because we updated the UI in a non-UI thread! In Android, non-UI threads cannot directly update the UI. What should I do? Use Handler? NO! Android provides us with a more convenient method: postInvalidate(); use it to replace our original invalidate():

@Override
public void run() {
/*
* Ensure that the thread continues to execute and refresh the interface
*/
while (true) {
try {
/*
* If the radius is less than 200, add it automatically. Otherwise, reset the radius value after it is greater than 200 to achieve reciprocation.
*/
if (radiu <= 200) {
radius + = 10;
 
// Refresh View
postInvalidate();
} else {
radius = 0;
}
 
// Pause for 40 milliseconds each time
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

The operating effect remains unchanged.

Source code address: Portal

Warm reminder: Customizing controls is actually very simple. This series of articles is updated every Monday and Thursday~

Wonderful preview of the next episode: Paint provides us with a large number of setter methods to set the properties of the brush, and Canvas also provides a large number of drawXXX methods to tell us what we can draw. So friends, you know how to use these methods. What cool effects can it bring us? Please lock this station and stay tuned: Customizing controls is actually very simple 1/6

Custom controls are actually very simple 1/6 updated