Android custom View. What do you need to know?

Source: Internet
Author: User
Tags drawtext

Android custom View. What do you need to know?

Why do we think that custom View is a hurdle for learning Android?
Why do so many Android experts think that custom View is so simple?
Why does google define a View with thousands of lines of code?
I believe that Android students have had such questions.
After reading this article, I hope it will help you with your doubts.

Back to the topic and customize the View, what are the points to master?
Let's first break down the custom View into two types:
1) custom ViewGroup
2) custom View

In fact, ViewGroup eventually inherits the View. Of course, it does a lot of internal operations. The View of the inherited ViewGroup is generally called a container. Today we will not talk about this, but we will have the opportunity to talk about it later.
Let's take a look at the several points that need to be mastered in a custom View. There are two main points:

1. Override the protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {} method.
Ii. Override the protected void onDraw (Canvas canvas) {} Method

It is hard to understand the theory of empty lecture. We still need to use examples to illustrate it. Remember that I wrote a blog about how to implement the tab icon and font color gradient in Android 6.1, each item in the tab is implemented through a custom View, so the following example is used to illustrate the problem.

We can understand the View as a piece of white paper, and the custom View is to draw a pattern we have drawn on this white paper. We can draw any pattern or any position on the White Paper, so the question is, where can I find the white paper? Where is the pattern? How is location calculated?

A) White Paper. As long as we inherit the View, Canvas in onDraw (canvas) is what we call white paper.

/*** Created by moon. zhong on 2015/2/13. */public class CustomView extends View {public CustomView (Context context) {super (context);} public CustomView (Context context, AttributeSet attrs) {super (context, attrs );} public CustomView (Context context, AttributeSet attrs, int defStyleAttr) {super (context, attrs, defStyleAttr);} @ Override protected void onDraw (Canvas canvas Canvas) {// canvas is white paper super. onDraw (canvas );}}

B) What about the pattern? The pattern here is composed of images and text. In other words, it defines a Bitmap member variable and a String member variable.

Private Bitmap mBitmap; private String mName; mName = "direct value here"; mBitmap = BitmapFactory. decodeResource (getResources (), R. drawable. ic_launcher );

Images can be obtained through resource files.

C) computing location
Therefore, the most important thing we think is that the most troublesome part is the location of the computation and drawing. The calculation position must first measure its own size, that is, the first point in the two points that we must master: you need to override the protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {} Method
Let's take a look at how the onMeasure () method of TextView written by google is implemented.

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    int widthMode = MeasureSpec.getMode(widthMeasureSpec);    int heightMode = MeasureSpec.getMode(heightMeasureSpec);    int widthSize = MeasureSpec.getSize(widthMeasureSpec);    int heightSize = MeasureSpec.getSize(heightMeasureSpec);    int width;    int height;    BoringLayout.Metrics boring = UNKNOWN_BORING;    BoringLayout.Metrics hintBoring = UNKNOWN_BORING;    if (mTextDir == null) {        mTextDir = getTextDirectionHeuristic();    }    int des = -1;    boolean fromexisting = false;    if (widthMode == MeasureSpec.EXACTLY) {        // Parent has told us how big to be. So be it.        width = widthSize;    } else {        if (mLayout != null && mEllipsize == null) {            des = desired(mLayout);        }        if (des < 0) {            boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);            if (boring != null) {                mBoring = boring;            }        } else {            fromexisting = true;        }        if (boring == null || boring == UNKNOWN_BORING) {            if (des < 0) {                des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));            }            width = des;        } else {            width = boring.width;        }        final Drawables dr = mDrawables;        if (dr != null) {            width = Math.max(width, dr.mDrawableWidthTop);            width = Math.max(width, dr.mDrawableWidthBottom);        }        if (mHint != null) {            int hintDes = -1;            int hintWidth;            if (mHintLayout != null && mEllipsize == null) {                hintDes = desired(mHintLayout);            }            if (hintDes < 0) {                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);                if (hintBoring != null) {                    mHintBoring = hintBoring;                }            }            if (hintBoring == null || hintBoring == UNKNOWN_BORING) {                if (hintDes < 0) {                    hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));                }                hintWidth = hintDes;            } else {                hintWidth = hintBoring.width;            }            if (hintWidth > width) {                width = hintWidth;            }        }        width += getCompoundPaddingLeft() + getCompoundPaddingRight();        if (mMaxWidthMode == EMS) {            width = Math.min(width, mMaxWidth * getLineHeight());        } else {            width = Math.min(width, mMaxWidth);        }        if (mMinWidthMode == EMS) {            width = Math.max(width, mMinWidth * getLineHeight());        } else {            width = Math.max(width, mMinWidth);        }        // Check against our minimum width        width = Math.max(width, getSuggestedMinimumWidth());        if (widthMode == MeasureSpec.AT_MOST) {            width = Math.min(widthSize, width);        }    }    int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();    int unpaddedWidth = want;    if (mHorizontallyScrolling) want = VERY_WIDE;    int hintWant = want;    int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();    if (mLayout == null) {        makeNewLayout(want, hintWant, boring, hintBoring,                      width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);    } else {        final boolean layoutChanged = (mLayout.getWidth() != want) ||                (hintWidth != hintWant) ||                (mLayout.getEllipsizedWidth() !=                        width - getCompoundPaddingLeft() - getCompoundPaddingRight());        final boolean widthChanged = (mHint == null) &&                (mEllipsize == null) &&                (want > mLayout.getWidth()) &&                (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));        final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);        if (layoutChanged || maximumChanged) {            if (!maximumChanged && widthChanged) {                mLayout.increaseWidthTo(want);            } else {                makeNewLayout(want, hintWant, boring, hintBoring,                        width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);            }        } else {            // Nothing has changed        }    }    if (heightMode == MeasureSpec.EXACTLY) {        // Parent has told us how big to be. So be it.        height = heightSize;        mDesiredHeightAtMeasure = -1;    } else {        int desired = getDesiredHeight();        height = desired;        mDesiredHeightAtMeasure = desired;        if (heightMode == MeasureSpec.AT_MOST) {            height = Math.min(desired, heightSize);        }    }    int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();    if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {        unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));    }    /*     * We didn't let makeNewLayout() register to bring the cursor into view,     * so do it here if there is any possibility that it is needed.     */    if (mMovement != null ||        mLayout.getWidth() > unpaddedWidth ||        mLayout.getHeight() > unpaddedHeight) {        registerForPreDraw();    } else {        scrollTo(0, 0);    }    setMeasuredDimension(width, height);}
Wow! Long! In addition, there are nested methods in the method. If you really want to calculate it, the amount of code will not be less than 500 lines. If you see so many code, the header will be large, I think this is why we feel so difficult when learning Android custom View. In most cases, because we are a custom View, we can say that it is a custom View based on our needs, so many features in it are completely unnecessary, you only need dozens of lines of code. After seeing dozens of lines of code, you can do it right away, and feel confident (^. ^) before rewriting this method, you must first understand a class named MeasureSpec. If you do not know it, it doesn't matter. Let's take a look at this class. Paste the code first and worship it.
public static class MeasureSpec {    private static final int MODE_SHIFT = 30;    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;    public static final int UNSPECIFIED = 0 << MODE_SHIFT;    public static final int EXACTLY     = 1 << MODE_SHIFT;    public static final int AT_MOST     = 2 << MODE_SHIFT;    public static int makeMeasureSpec(int size, int mode) {        if (sUseBrokenMakeMeasureSpec) {            return size + mode;        } else {            return (size & ~MODE_MASK) | (mode & MODE_MASK);        }    }    public static int getMode(int measureSpec) {        return (measureSpec & MODE_MASK);    }    public static int getSize(int measureSpec) {        return (measureSpec & ~MODE_MASK);    }}
Here I have removed some unnecessary code and left only the above lines of code, which looks clear and easy to understand. Let's first make a conversion. We don't need to convert the above member variables into binary ones. Here we represent only a moving location, that is, a simple number, private static final int MODE_SHIFT = 30; 0x3, that is, 11 shifted to 30 places, that is, 30 zeros are supplemented; private static final int MODE_MASK = 1100 0000 0000 0000 0000 0000 0000; 00 shifted left 30-bit public static final int UNSPECIFIED = 0000 0000 0000 0000 0000 0000 0000 0000 0000; 01 shift 30-bit public static final int EXACTLY = 0100 0000 0000 0000 0000 0000 0000 0000; 10 shift 30-bit public static final I Nt AT_MOST = 1000 0000 0000 0000 0000 0000 0000 0000; you will ask, what are the advantages of such writing? After reading the above methods, you will understand that each method has an & operation. So let's take a look at the meanings of these methods, first from the bottom up, first easy and difficult 1. public static int getSize (int measureSpec) {return (measureSpec &~ MODE_MASK);} as the name suggests, get the size through the measureSpec parameter, both of which are int type. How can we get the number of another int type through one int type. When learning java, we know that an int type is 32 bits, and any int type is 32 bits. For example, an int type value 3 also occupies 32 bits, only the 30-bit height is 0. Google also uses this to store two pieces of information in the measureSpec number of the int type: size, 30 bits at the lower of the int type, and mode, it is saved in the 2-digit high of the int type. We have seen several member variables: UNSPECIFIED, EXACTLY, and AT_MOST are three modes. Currently, only these three types of variables are available, so only two digits are required. 2. 'public static int getMode (int measureSpec) {return (measureSpec & MODE_MASK);} '. You can understand this and obtain the modes. But what are the functions of these modes? 1) EXACTLY mode: accurate and accurate. This mode is the easiest to understand and process. It can be understood as a fixed size. For example, when layout_width is defined, it is defined as a fixed value of 10dp, 20dp, or match_parent (the parent control is fixed at this time). At this time, the obtained mode is EXACTLY 2. AT_MOST mode: maximum; this mode is slightly difficult to handle, but it is easy to understand that the size of the View cannot exceed the parent control. If the size of the parent control is not exceeded, the size of the parent control is used, this is generally the case when layout_width is set to warp_content. 3) UNSPECIFIED mode: The size is not specified. In this case, we can hardly use it. What does it mean, that is, the size of the View is equal to the size of the View, it is not restricted by the parent View. It is easy to understand in a few examples. The ScrollView control is. 3. 'public static int makeMeasureSpec (int size, int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size &~ MODE_MASK) | (mode & MODE_MASK) ;}' this method is easy to understand. encapsulate the value of measureSpec. when defining a View, we just fixed the size, you won't be able to get the mode next time, so you have to add the mode yourself. This method is not required in the Custom View, it is used when setting the size of the sub-View, so it is needed if it is a custom ViewGroup. I feel like I have talked so much about it, but I still don't know how to use it. Next I will rewrite the onMeasure () method. After writing it, you will understand that here I will download the annotation in the code.
@ Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {// The method routines here are the same. No matter how many days, the mode and size are obtained first. Int widthMode = MeasureSpec. getMode (widthMeasureSpec); int heightMode = MeasureSpec. getMode (heightMeasureSpec); int widthSize = MeasureSpec. getSize (widthMeasureSpec); int heightSize = MeasureSpec. getSize (heightMeasureSpec); // the actual size int width = 0 and height = 0 to be displayed in the View; // measure the font size measureText () here (); // apply the font width and image width to the maximum width, because the font and image are arranged up and down int contentWidth = Math. max (mBoundText. width (), mIconNormal. getWidth (); // The expected width int desiredWidth = getPaddingLeft () + getPaddingRight () + contentWidth; // determines the mode. Where does this mode come from, the layout_width switch (widthMode) {// if it is AT_MOST, it cannot exceed the width of the parent View in case MeasureSpec. AT_MOST: width = Math. min (widthSize, desiredWidth); break; // if it is accurate, it is recommended that the value is equal to the value. case MeasureSpec. EXACTLY: width = widthSize; break; // in this case, it is purely soy sauce. You can skip case MeasureSpec. UNSPECIFIED: // I am passing by the width = desiredWidth; break;} int contentHeight = mBoundText. height () + mIconNormal. getHeight (); int desiredHeight = getPaddingTop () + getPaddingBottom () + contentHeight; switch (heightMode) {case MeasureSpec. AT_MOST: height = Math. min (heightSize, desiredHeight); break; case MeasureSpec. EXACTLY: height = heightSize; break; case MeasureSpec. UNSPECIFIED: height = contentHeight; break;} // do not forget to call the measurement method setMeasuredDimension (width, height) of the parent class );}
By now, even if the View size is complete, the calculation process of the custom View is similar to that of the preceding method. The next step is to calculate the icon and font position to be displayed. Here we want the image and font to be vertically arranged and displayed in the View Center. Because the width and height of the current View have been measured, the calculation will be very simple. Here we will put it in onDraw () calculation in method d) Draw the icon and the word volume rendering icon, you can use canvas. drawBitmap (Bitmap bitmap, int left, int top, Paint paint) method, bitmap already has, if you do not need to perform special processing on the image, you can pass in null to indicate that the original source image is drawn on white paper, so the left position is poor, and the top has been analyzed before, you need to draw the graph in the middle of the View. Of course, the font must be included here, so you can calculate left and top.

Int left = (mViewWidth-mIconNormal. getWidth ()/2;
Int top = (mViewHeight-mIconNormal. getHeight ()-mBoundText. height ()/2;

MViewWidth ---> View width, mIconNormal ---> image width, mBoundText. height () ---> the font height. It is a little more troublesome to draw a font or a font than to draw an image. Because a Paint brush is needed to draw a font, a Paint brush is defined here, new directly
MTextPaintNormal = new Paint (); // set the font size of mTextPaintNormal. setTextSize (TypedValue. applyDimension (TypedValue. COMPLEX_UNIT_SP, mTextSize, getResources (). getDisplayMetrics (); // sets the paint brush color, that is, the font color mTextPaintNormal. setColor (mTextColorNormal); // you can specify the mTextPaintNormal parameter. setAntiAlias (true );
The Canvas method canvas. drawText (mTextValue, x, y, mTextPaintNormal) is also called. The font content to be drawn for mTextValue, the position to be drawn for mTextPaintNormal paint, x, and y
Float x = (mViewWidth-mBoundText. width ()/2.0f; float y = (mViewHeight + mIconNormal. getHeight () + mBoundText. height ()/2.0F; in general, the code is still quite small. The onDraw code is also pasted below @ Override protected void onDraw (Canvas canvas) {drawBitmap (canvas); drawText (canvas);} private void drawBitmap (Canvas canvas Canvas) {int left = (mViewWidth-mIconNormal. getWidth ()/2; int top = (mViewHeight-mIconNormal. getHeight ()-mBoundText. height ()/2; canvas. drawBitmap (mIconNormal, left, top, null);} private void drawText (Canvas canvas) {float x = (mViewWidth-mBoundText. width ()/2.0f; float y = (mViewHeight + mIconNormal. getHeight () + mBoundText. height ()/2.0F; canvas. drawText (mTextValue, x, y, mTextPaintNormal );}

''
Summary:
The onMeasure () method is not a problem as long as it understands the MeasureSpec class, And the MeasureSpec method is also very simple. The onDraw () method needs to understand the painting method of the Canvas class and query through a simple Api, we can basically achieve what we need. For custom views, if you rewrite the measurement and onDraw methods, you will have this skill. If you need to learn more, customize personalized and more brilliant views, you have to have a deep understanding of Canvas, painting, and other methods,

For the source code, go to my other blog, Android 6.1 tab bar icon and font color gradient implementation

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.