Today, we’re going to do a little bit of a custom LayoutInflate, do something fun, and implement a cool animation. First of all, before we customize the LayoutInflate, we need to analyze the source code of the LayoutInflate and understand how the source code is implemented so that we can customize the LayoutInflate ~~~~ ok, in case you get bored, post the renderings first

Copy little red book.gif

All right, now that I’ve seen the results,

Start with the source code for LayoutInflate.

## layoutsynchronized ## Layoutsynchronized ## Layoutsynchronized ## Layoutsynchronized

LayoutInflate.png

Let’s just extract the key information. 1.LayoutInflate parses an XML file into a View object. GetLayoutInflater () and getSystemService(Class).

2. To create a new LayoutInflate to parse your own XML, use cloneInContext and call setFactor().

Ok, let’s review how we normally convert XML to View.

  • The setContentView ()

We call this method when we set the Activity’s layout XML. Now let’s see what this method does.

public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } ----- this is an Activity method that calls the Window steContentView ----. If all Windows on the phone are PhoneWindow, you can view PhoneWindow ---- directlysetContentView method. public voidsetContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if(cb ! = null && ! isDestroyed()) { cb.onContentChanged(); }} ---- finds the assignment of mLayoutInflater in the constructor public PhoneWindow(Context Context) {super(Context); mLayoutInflater = LayoutInflater.from(context); }Copy the code
  • View.inflate()

The layoutconstruction.inflate () method is also called

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
     LayoutInflater factory = LayoutInflater.from(context);
     return factory.inflate(resource, root);
 }Copy the code
  • LayoutInflate. The from (context). Inflate ()

All Xml transitions in our project are dependent on these three methods, which all end up calling the LayoutInflate method.

Let’s see how we get an instance of the LayoutInflate again. The above three methods of parsing XML into views all retrieve instances of the LayoutInflate. From (context).

 public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}Copy the code

This code looks familiar to all of our services: ActivityService, WindowService, NotificationService, etc. We all know that these system services are singletons and are initialized by the system at application startup time. All right, get out of here

Going back, let’s look at the LayoutInflate source code.

  • The inflate(@layoutres int Resource, @nullable ViewGroup root) method is the method that converts the XML file into a View and is invoked for all XML parsing in our project. The first parameter is the XML resource ID, and the second method is whether the parsed View should be added to the root View.

Get the XML parser XmlResourceParser from Resources.

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        returninflate(parser, root, attachToRoot); } finally { parser.close(); }}Copy the code

XmlResourceParser parses the XML and returns the View

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, Boolean attachToRoot (synchronized (mConstructorArgs) {attachToRoot (mConstructorArgs); Forget about trace.tracebegin (trace.trace_tag_view,"inflate"); final Context inflaterContext = mContext; // Final AttributeSet attrs = xml.asattributeset (parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Lookfor the root node.
            int type; // Empty messages are skippedwhile ((type= parser.next()) ! = XmlPullParser.START_TAG &&type! = xmlpullParser. END_DOCUMENT) {// Empty} // Error proofif (type! = XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() +": No start tag found!"); } // Get the class name, such as TextView Final String name = parser.getName();if (DEBUG) {
                System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *"); } // If the tag is mergeif (TAG_MERGE.equals(name)) {
                if(root == null || ! AttachToRoot) {merge RootView throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true"); } // Merge can optimize layout rInflate(parser, root, inflaterContext, attrs,false);
            } elseFinal View Temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; // If not added to rootView, LayoutParams will be generated if rootView is not emptyif(root ! = null) {if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if(! attachToRoot) { // Set the layout paramsfor temp ifwe are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); }}if (DEBUG) {
                    System.out.println("-----> start inflating children"); } // Parse child nodes rInflateChildren(parser, temp, attrs,true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children"); } // To add to rootView. // to root. Do that now.if(root ! = null && attachToRoot) { root.addView(temp, params); } // Decide whether toreturn the root that was passed in or the
                // top view found in xml.
                if(root == null || ! attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (Exception e) { InflateException ex = new InflateException( parser.getPositionDescription() +":" + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); // return result; }}Copy the code

In this method, you decide whether to optimize the layout using merge, then parse the view of the top-level XML node through createViewFromTag, and you handle whether to add the parsed layout to the rootView. Call the rInflateChildren method to parse the child views and add them to the top-level node temp. Finally, the parsed result is returned.

So let’s first look at createViewFromTag

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, Boolean ignoreThemeAttr) {// Get the namespaceif (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class"); } // Set the theme for the view. Now you know why theme properties like colorPrimary affect control colorsif(! ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0);if(themeResId ! = 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } / / let the view, you can refer to http://blog.csdn.net/qq_22644219/article/details/69367150if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        returnnew BlinkLayout(context, attrs); } try { View view; The temp View is created by calling the oncreateView method of mFactory2 firstif(mFactory2 ! = null) { view = mFactory2.onCreateView(parent, name, context, attrs); }else if(mFactory ! = null) { view = mFactory.onCreateView(name, context, attrs); }else {
            view = null;
        }

        if(view == null && mPrivateFactory ! = null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); }if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('. ')) {
                    view = onCreateView(parent, name, attrs);
                } else{ view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; }}return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class "+ name); ie.initCause(e); throw ie; }}Copy the code

If mFactor or mFactor is not null, then mFactor is called to create the View. If mFactor is null or mFactor creation fails, The View is eventually created by calling the createView method of the LayoutInflate, which passes in the View’s parent, name, context, and attrs.

Let’s move on to the rInflateChildren parsing of child Views

void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, Boolean finishInflate) throws XmlPullParserException, IOException {// Obtain the layout level final int depth = parser.getDepth(); inttype; // Don't worry about XML parsingwhile (((type= parser.next()) ! = XmlPullParser.END_TAG || parser.getDepth() > depth) &&type! = XmlPullParser.END_DOCUMENT) {if (type! = XmlPullParser.START_TAG) {continue; } final String name = parser.getName(); / / requestFocus labels, http://blog.csdn.net/ouyang_peng/article/details/46957281if (TAG_REQUEST_FOCUS.equals(name)) {
            parseRequestFocus(parser, parent);
        } else if(tag_tag.equals (name)) {// Set a tag parseViewTag(parser, parent, attrs) to the parent view. }else if(tag_include.equals (name)) {//include nodeif (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if(tag_merge.equals (name)) {// Merge node throw new InflateException("<merge /> must be the root element");
        } elseLayoutParams Final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs,true); // Add to view viewgroup. addView(view, params); }}if(finishInflate) { parent.onFinishInflate(); }}Copy the code

Let’s get our heads together, The static method of LayoutInflater form retrieves the strength of the LayoutInflater. The inflate parses the XML resource. The createViewFromTag call creates the top view 5. RInflateChildren recursively calls rInflateChildren to create all child views. 6. The rInflate actually creates a view by calling createViewFromTag. MFactory2, mFactory, and mPrivateFactory are used to create the View first. If the View fails, createView will be called. Created in the process of the parent, the name, the context, attrs parameter, and then applied the method of reflection to create a View,

Therefore, all of our View constructors are created by the Factory call to the LayoutInflate. To customize the LayoutInflate resolution, we simply set our custom Factory to the setFactory that calls the LayoutInflate. But the problem is that the LayoutInflate is a system service and a singleton, and we call the setFactory method of the LayoutInflate directly, which affects the creation of any view in the future.

So we need to clone a new LayoutInflate using the cloneInContext method of the LayoutInflate and then set our Factory. As for LayoutInflate, which is an abstract class, and cloneInContext, which is an abstract method, we don’t care because we just create the LayoutInflate with the system.

Now that the source code for the LayoutInflate is done, let’s analyze the animation.

## Animation analysis

Look at the source code for a long time, let’s look at the animation again

Copy little red book.gif

2. The clouds in the sky and buildings on the ground move at a different speed when turning the page. 3. Different background objects move at different speeds. The background objects on the last page spread up and down. The last page is going to disappear.

Solution:

1.ViewPager 2. Set the viewPage PageChangeListener, and set setTranslation for various background objects while scrolling. 3. Set different setTranslation coefficients for different background objects. 4. Frame animation is enough for character walking, start frame animation when viewPage slide is in the SCROLL_STATE_DRAGGING state. 5. Listen to onPageSelected and set it to view.gone.

Solution problem: At a rough count, there are about 50 backgrounds for 6 pages. If you had to get the ids one by one, and then set the sliding speed and sliding direction for different ids, you’d probably go crazy.

Therefore, we need to think of a way to solve this problem. Some kids might say, I’ll write a custom View and set the sliding speed coefficient property. This method does work, but you still have to go to findViewbyid one by one.

So, can we add custom tags to the XML and then customize the parsing? For example, if the cloud in the sky slides in with a damping coefficient of 0.4 and slides out with a damping coefficient of 0.6, we simply set these two parameters in the XML and then use them as appropriate.

## Custom layoutinflater. Factory = layoutinflater. Factory = LayoutInflater The View is created entirely within createViewFromTag, and createViewFromTag is created using Factory first. Then let’s see what a Factory actually does.

Hook you can supply that is called when inflating from a LayoutInflater.

You can use this to customize the tag names available in your XML layout files.

  • Layoutinflaters are called when they parse a layout
  • Can be used to read custom tags in XML.

Now, let’s define the Factory idea. It’s very simple. 1. Inherit LayoutInflater. Factory22 2. Implement the abstract method onCreateView 3. CreateView 4 inside the onCreateView using the createView method of the LayoutInflate. Once created, the attrs attribute of the view is read and held as a tag to the viewTag.

The key codes are as follows:

@Override public View onCreateView(String name, Context context, AttributeSet attrs) {// Create a View View View = createViewOrFailQuietly(name, context, attrs); // The instantiation is completeif(view ! = null) {// Get a custom attribute associated with the view via the tagsetViewTag(view, context, attrs); / / all with a View of the custom attribute stored, switch for animation called when mParallaxView. GetParallaxViews (). The add (View); }return view;
}Copy the code

The method to create a view, notice here, the XML tag in the system view only class name, custom view is the full path. “And “android.widget.” and “android.view.” package, so for the View that only write abbreviations, need to traverse these two paths.

private View createViewOrFailQuietly(String name, Context context, AttributeSet attrs) { //1. Custom control label names are dotted, so no prefix is required when creating themif (name.contains(".")) { createViewOrFailQuietly(name, null, context, attrs); } //2. Prefix the system viewfor (String prefix : sClassPrefix) {
        View view = createViewOrFailQuietly(name, prefix, context, attrs);
        if(view ! = null) {returnview; }}returnnull; } private View createViewOrFailQuietly(String name, String prefix, Context context, AttributeSet attrs) {try {// Create a view using system inflater to read system attributesreturn inflater.createView(name, prefix, attrs);
    } catch (Exception e) {
        returnnull; }}Copy the code

Read attrs attributes, tag views containing attrs attributes and save them.

 private void setViewTag(View view, Context context, AttributeSet attrs) {/ / all custom attributes TypedArray a = context. ObtainStyledAttributes (attrs, R.s tyleable. AnimationView);if (a != null && a.length() > 0) {
        //获取自定义属性的值
        ParallaxViewTag tag = new ParallaxViewTag();
        tag.xIn = a.getFloat(R.styleable.AnimationView_x_in, 0f);
        tag.xOut = a.getFloat(R.styleable.AnimationView_x_out, 0f);
        tag.yIn = a.getFloat(R.styleable.AnimationView_y_in, 0f);
        tag.yOut = a.getFloat(R.styleable.AnimationView_y_in, 0f);

        //index
        view.setTag(view.getId(), tag);
        a.recycle();
    }

}Copy the code

Ok, we custom LayoutInflater. The Factory has finished, so, we can directly call LayoutInflate. CloneInContext (context) to obtain a new LayoutInflate, And then setFactor(customFactor). The code is as follows:

 @Override
public View onCreateView(LayoutInflater original, ViewGroup container,
                         Bundle savedInstanceState) {
    Bundle args = getArguments();
    int layoutId = args.getInt("layoutId");
    LayoutInflater layoutInflater = original.cloneInContext(getActivity());
    layoutInflater.setFactory(new ParallaxFactory(layoutInflater, this));
    return layoutInflater.inflate(layoutId, null);
}Copy the code

Listen for the slide event of the ViewPager and get the list of views with the custom attrs attribute for the current slide page. And then according to slip out of the proportion of the screen view TranslationY / * attribute parameter do TranslationX operation. Here I post the code repository address, interested partners can run the code to see

Making portal

It looks as if there is no use, is to show a wave of SAO operation. Write a custom view, inherit the ImageView, set several custom attrs attributes, read the attributes in the constructor and save them to the class variable, provide the read method, and also listen for the slide of the ViewPager.

The ultimate goal of sharing this article is not to implement this animation, but to take a look at the source code for LayoutInflate and see how the XML file is parsed into a View…

## Known bugs:

  • Animations will not work after the v4 version is upgraded to 19.1.0
  • Importing the AppCompat package will report XML parsing errors.

There are bugs caused by version upgrade. If I have time, I will find out the causes of these two bugs, and I will update them here after finding them.


The effect comes from the brain institute video courses, a very good set of courses, interested partners can go to learn, suitable for android programmers have a certain basis, advanced advanced very effective oh. Tencent class has free open classes, or go to the brain institute to study, the official website courses seem to charge, of course, the cost is not high for programmers, students who can’t afford the course fees can go to Tencent class to study, or some treasure……