Implementation of ScrollView Rolling Animation Summary (Thunderstorm) with Android's Custom Animation Framework

What is a framework? It must be for others. Since we want to use it for others, we need to consider what kind of framework others will use, not be scolded, and will always use. To achieve the above, our framework must be powerful, able to solve the pain points of each of us in the development process, stable, will not have a negative impact on the program, simple to use, clear structure, easy to understand, the ultimate goal is to be easy to expand and so on. For today's handwritten animation framework play, first look at the effect, as follows:


The effect is that every scrolling view has different animation, including zooming, transparency gradient, color change, movement, etc. How can we achieve it? Analyse it, first of all, start animation when it slides out, then we can customize ScrollView to monitor the sliding, and display animation when the inner sub-view slides out of the screen. But we all know that ScrollView can only contain one sub-ViewGroup subclass, which can place multiple Views. So how can we operate the sub-Views on the next layer in ScrollView? This is a problem. Then the next question is, since this is a framework, how can people set different animations more easily for each ViewGroup subclass View (perhaps TextView, ImageView, etc.)? Even if the animation is set, how can it be executed? Next, let's solve the above problems one by one.

 

First of all, how can users set different animations for each View? You may think that we can set up a custom ViewGroup for each sub-View layer and animate each custom ViewGroup. Then the code in the ScrollView sub-class ViewGroup will think as follows:

<MyViewGrop
    anitation1=""
    anitation2=""
    anitation3="">

    <TextView />
</MyViewGrop>

<MyViewGrop
    anitation1=""
    anitation2=""
    anitation3="">

    <Button />
</MyViewGrop>

<MyViewGrop
    anitation1=""
    anitation2=""
    anitation3="">

    <Button />
</MyViewGrop>

<MyViewGrop
    anitation1=""
    anitation2=""
    anitation3="">

    <ImageView />
</MyViewGrop>

Yes, it can be done, but does it feel like a bit of a pitfall? We need to add a layer of packaging every day to add a View. You need to know that these are all done by users, so how can we make it easier and more convenient for users to implement? We can try a bold approach: add custom attributes to each system control, and then dynamically add a layer of custom ViewGroup to the outer layer of each system control to get the custom attributes of the internal system control, copy them to the custom ViewGroup, and animate the external package class at the appropriate time. Well, if you have an idea, then it should be realized. First create the attrs file under the values folder and write the supported custom animation attributes. The code is as follows:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyFrameLayout">
        <attr name="anitation_alpha" format="boolean" />//Whether transparency animation is supported or not;
        <attr name="anitation_scaleX" format="boolean" />//Whether X-axis zoom animation is supported or not;
        <attr name="anitation_scaleY" format="boolean" />//Do you support Y-axis zoom animation?
        <attr name="bgColorStart" format="color" />//The starting color value of the background gradient color;
        <attr name="bgColorEnd" format="color" />//The end color value of the background gradient color appears in pairs with bgColorStart.
        <attr name="anitation_translation">//Mobile animation, is an enumeration type, supporting four values up and down.
            <flag name="left" value="0x01" />
            <flag name="top" value="0x02" />
            <flag name="right" value="0x04" />
            <flag name="bottom" value="0x08" />
        </attr>
    </declare-styleable>
</resources>

Above are several animation attributes that I have customized. Of course, you can also customize many other attributes. What does each attribute mean? The annotations have been given. There are no more explanations here. Next, we inherit FrameLayout to write a custom control. Its function is to wrap each system View (TextView, ImageView, etc.), get the custom attributes inside the system View and save them to ourselves, and execute the corresponding animation according to the saved values at the right time. From the above explanation, we know that he needs to define member variables to save custom attribute values. Look at the code or:

public class MyFrameLayout extends FrameLayout implements MyFrameLayoutAnitationCallBack {
    //From which direction to start animation;
    private static final int TRANSLATION_LEFT = 0x01;
    private static final int TRANSLATION_TOP = 0x02;
    private static final int TRANSLATION_RIGHT = 0x04;
    private static final int TRANSLATION_BOTTOM = 0x08;

    //Whether transparency is supported or not;
    private boolean mAlphaSupport;
    //The initial value of color change;
    private int mBgColorStart;
    private int mBgColorEnd;

    //Whether support X-Y axis zooming;
    private boolean mScaleXSupport;
    private boolean mScaleYSupport;
    //Moving value;
    private int mTranslationValue;
    //Current View width and height;
    private int mHeight, mWidth;
    /**
     * Color estimator;
     */
    private static ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();

    public MyFrameLayout(@NonNull Context context) {
        super(context);
    }

    public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public boolean ismAlphaSupport() {
        return mAlphaSupport;
    }

    public void setmAlphaSupport(boolean mAlphaSupport) {
        this.mAlphaSupport = mAlphaSupport;
    }

    public int getmBgColorStart() {
        return mBgColorStart;
    }

    public void setmBgColorStart(int mBgColorStart) {
        this.mBgColorStart = mBgColorStart;
    }

    public int getmBgColorEnd() {
        return mBgColorEnd;
    }

    public void setmBgColorEnd(int mBgColorEnd) {
        this.mBgColorEnd = mBgColorEnd;
    }

    public boolean ismScaleXSupport() {
        return mScaleXSupport;
    }

    public void setmScaleXSupport(boolean mScaleXSupport) {
        this.mScaleXSupport = mScaleXSupport;
    }

    public boolean ismScaleYSupport() {
        return mScaleYSupport;
    }

    public void setmScaleYSupport(boolean mScaleYSupport) {
        this.mScaleYSupport = mScaleYSupport;
    }

    public int getmTranslationValue() {
        return mTranslationValue;
    }

    public void setmTranslationValue(int mTranslationValue) {
        this.mTranslationValue = mTranslationValue;
    }
}

Next, we need to consider another question, when do we create MyFrameLayout and read the custom attributes of the child View (System View) that needs to be wrapped and assign them to ourselves? We know that according to the previous idea, we want to do the following:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        custom_anitation1=""
        custom_anitation2=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        custom_anitation1=""
        custom_anitation2=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        custom_anitation1=""
        custom_anitation2=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        custom_anitation1=""
        custom_anitation1=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        custom_anitation1=""
        custom_anitation2=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

We need to read the values of custom_anitation 1, custom_anitation 2 and dynamically generate MyFrameLayout to save these values. How do we read them? Yes, if we want to achieve this effect, we need to customize the outer LinearLayout to read the animated values, and create MyFrameLayout to save the values and add the system sub-View to the created MyFrameLayout object, most importantly After that, the MyFrameLayout object is saved into a custom MyLinearLayout. The idea is good. Can it be realized? Let's take a step by step. The first problem we need to solve is how to read the attributes of the sub-View from the external View, and how to add objects to the system. Let's see how the system code creates parsing views based on XMl, and which class? Of course, the inflate () method of the LayoutInflater class, flip through, and you'll eventually find such a piece of code:

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);
viewGroup.addView(view, params);

This code is the key, and the first line knows that creating the corresponding View through Tag, such as <TextView> defined in XMl, will be resolved into a TextView object here. The second line gets the parent layout, and the third line calls generateLayoutParams to create LayoutParams, where the attributes of the child control are in attrs. The fifth line adds the child control to the parent control. Yes, this is the key to achieve the above functions. We can get the attributes of the child control by rewriting generateLayoutParams (attrs). By rewriting addView, we can get the attributes of the child control. params) We can add View dynamically, because when users define system View and add custom attributes, they are in LinearLayout, so we need to do these operations in the customized LinearLayout. Let's say, look at the following code:

/**
 * @Explain: Custom LinearLayout, the main function is to determine whether each sub-View contains custom animation properties.
 *     @1.If it includes parsing custom attributes, assigning values to custom LinearLayout.LayoutParams and creating MyFrameLayout assignment package View;
 *     @2.Do not include custom attributes, do not handle.
 * @Author:LYL
 * @Version:
 * @Time:2017/6/14
 */

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //Returns the Params you defined.
        return new MyLayoutParams(getContext(), attrs);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        //Looking at the source code, we can see that the third parameter (params) of addView is the custom Params that we set in the generateLayoutParams () method.
        MyLayoutParams myLayoutParams = (MyLayoutParams) params;
        if (myLayoutParams.isHaveMyProperty()) {
            MyFrameLayout myFrameLayout = new MyFrameLayout(getContext());
            myFrameLayout.addView(child);
            myFrameLayout.setmAlphaSupport(myLayoutParams.mAlphaSupport);
            myFrameLayout.setmScaleXSupport(myLayoutParams.mScaleXSupport);
            myFrameLayout.setmScaleYSupport(myLayoutParams.mScaleYSupport);
            myFrameLayout.setmBgColorStart(myLayoutParams.mBgColorStart);
            myFrameLayout.setmBgColorEnd(myLayoutParams.mBgColorEnd);
            myFrameLayout.setmTranslationValue(myLayoutParams.mTranslationValue);
            super.addView(myFrameLayout, index, params);
        } else {
            super.addView(child, index, params);
        }

    }

    //Customize LayoutParams to store custom attributes obtained from child controls.
    public class MyLayoutParams extends LinearLayout.LayoutParams {
        //Whether transparency is supported or not;
        public boolean mAlphaSupport;
        //Whether support X-Y axis zooming;
        public boolean mScaleXSupport;
        public boolean mScaleYSupport;

        //The initial value of color change;
        public int mBgColorStart;
        public int mBgColorEnd;
        //Moving value;
        public int mTranslationValue;

        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyFrameLayout);
            mAlphaSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_alpha, false);
            mBgColorStart = typedArray.getColor(R.styleable.MyFrameLayout_bgColorStart, -1);
            mBgColorEnd = typedArray.getColor(R.styleable.MyFrameLayout_bgColorEnd, -1);
            mScaleXSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleX, false);
            mScaleYSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleY, false);
            mTranslationValue = typedArray.getInt(R.styleable.MyFrameLayout_anitation_translation, -1);
            typedArray.recycle();
        }

    }
}

In GeneeLayoutParams (Attribute Set attrs), because the return is a LayoutParams, by looking at the source code we already know that this LayoutParams is the bottom addView (View child, int index, ViewGroup.LayoutParams params).

) In order to better encapsulate the third parameter of the method, I inherited LinearLayout.LayoutParams and customized a LayoutParams. All parsing operations are implemented internally. Take a look at the code:

//Customize LayoutParams to store custom attributes obtained from child controls.
public class MyLayoutParams extends LinearLayout.LayoutParams {
    //Whether transparency is supported or not;
    public boolean mAlphaSupport;
    //Whether support X-Y axis zooming;
    public boolean mScaleXSupport;
    public boolean mScaleYSupport;

    //The initial value of color change;
    public int mBgColorStart;
    public int mBgColorEnd;
    //Moving value;
    public int mTranslationValue;

    public MyLayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyFrameLayout);
        mAlphaSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_alpha, false);
        mBgColorStart = typedArray.getColor(R.styleable.MyFrameLayout_bgColorStart, -1);
        mBgColorEnd = typedArray.getColor(R.styleable.MyFrameLayout_bgColorEnd, -1);
        mScaleXSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleX, false);
        mScaleYSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_anitation_scaleY, false);
        mTranslationValue = typedArray.getInt(R.styleable.MyFrameLayout_anitation_translation, -1);
        typedArray.recycle();
    }

    /**
     * Determine whether the current params contains custom attributes;
     *
     * @return
     */
    public boolean isHaveMyProperty() {
        if (mAlphaSupport || mScaleXSupport || mScaleYSupport || (mBgColorStart != -1 && mBgColorEnd != -1) || mTranslationValue != -1) {
            return true;
        }
        return false;
    }

}

There's nothing inside of him. It parses all the customizations and saves them by sttrs passed through the generateLayoutParams(AttributeSet attrs) method, and defines a method to include the customized attributes. Back to addView(View) in MyLinerLayout child, int index, ViewGroup.LayoutParams params) method, we know that the third parameter at this time is our custom LayoutParams, because if there are custom attributes inside it, it will be parsed, so here to determine whether there are custom attributes, if there is, create our MyFrameLayout and save the custom information to MyFrameLayout. Finally, add the system View to our MyFrameLayout, and add MyFrameLayout to our custom MyLinerLayout. This completes our original idea. If the child View does not have custom attributes, there is no need to wrap MyFrameLayout. We do this in our code. In this way, users only need to lay out the layout as follows:

<MyLinerLayout>
  <TextView Android:id="tv1" custom_anitation1=""/>
  <ImageView Android:id="iv1" custom_anitation1=""/>
  <TextView  Android:id="tv2"/>
</MyLinerLayout>

Just look at the idea, don't care about the details, then according to our practice above, our tv1, iv1 will be wrapped in a layer of <MyFrameLayout>, and Custom_animation 1 animation value is saved in MyFrameLayout. tv2 will not be wrapped. Is it much simpler than what is said above when it is used by users? Framework must be done from the user's point of view. Now that the animation is available, how to execute it? When will it be implemented? We know that when animation is performed when each View slides out of the screen, we need to customize a ScrollView to implement its onScrollChanged(int). l, int t, int oldl, int oldt) method, which implements scroll monitoring in his internal, see the code:

/**
 * @Explain: Customize ScrollView to get scroll monitor and animate according to different situations.
 * @Author:LYL
 * @Version:
 * @Time:2017/6/14
 */

public class MyScrollView extends ScrollView {
    private MyLinearLayout mMyLinearLayout;

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //Get the internal Linear Layout;
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMyLinearLayout = (MyLinearLayout) getChildAt(0);
    }

    //Set the first picture to a full screen.
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMyLinearLayout.getChildAt(0).getLayoutParams().height = getHeight();
        mMyLinearLayout.getChildAt(0).getLayoutParams().width = getWidth();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        int scrollViewHeight = getHeight();
        for (int i = 0; i < mMyLinearLayout.getChildCount(); i++) {
            //If the child control is not MyFrameLayout, the next child control is looped.
            View child = mMyLinearLayout.getChildAt(i);
            if (!(child instanceof MyFrameLayout)) {
                continue;
            }
            //The following is to execute the animation logic.
            MyFrameLayoutAnitationCallBack myCallBack = (MyFrameLayoutAnitationCallBack) child;
            //Get the height of the child View;
            int childHeight = child.getHeight();
            //The distance from the child control to the parent control;
            int childTop = child.getTop();
            //In the scrolling process, the distance between the child View and the top of the parent control;
            int childAbsluteTop = childTop - t;
            //Enter the screen
            if (childAbsluteTop <= scrollViewHeight) {
                //The height displayed by the current child control;
                int childShowHeight = scrollViewHeight - childAbsluteTop;
                float moveRadio = childShowHeight / (float) childHeight;//This must be converted to float type.
                //Carry out animation;
                myCallBack.excuteAnitation(getMiddleValue(moveRadio, 0, 1));
            } else {
                //Not in the screen, restore data;
                myCallBack.resetViewAnitation();
            }
        }
    }

    /**
     * Find the value of the middle size.
     *
     * @param radio
     * @param minValue
     * @param maxValue
     * @return
     */
    private float getMiddleValue(float radio, float minValue, float maxValue) {
        return Math.max(Math.min(maxValue, radio), minValue);
    }
}

I also rewrote onFinishInflate(), onSizeChanged(int w, int h, int oldw, int oldh). In onFinishInflate(), I got the inner MyLinear Layout. In onSizeChanged(int w, int h, int oldw, int oldh), for animation loading, I set the first View of MyLinear Layout to full screen mode, which is the picture of the sister. Slice. The key code is onScrollChanged(int) L, int, int oldl, int oldt) method, because we only wrapped MyFrameLayout with the control of custom method, so we need to judge that the main function of this method is to execute animation at the right time. The code of judging the right time is not mentioned, there are comments. I define a connection here in order to better reflect the encapsulation when calling the execution animation. There are two methods in the interface. One is to perform animation according to the current sub-View (that is, the wrapped MyFrameLayout) according to the percentage of different positions in the screen during the sliding process. This percentage is the sliding part divided by the height of the current View (view.getHeight ()). The other method is to restore the original value. So which class to implement this interface, of course, which class to execute animation and which class to implement the interface, that is, our MyFrameLayout class, which itself saves information such as what animation to execute. Focus on these methods of MyFrameLayout:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;
    mHeight = h;
}

@Override
public void excuteAnitation(float moveRadio) {
    //Set up animation;
    if (mAlphaSupport)
        setAlpha(moveRadio);
    if (mScaleXSupport)
        setScaleX(moveRadio);
    if (mScaleYSupport)
        setScaleY(moveRadio);
    //Move from left to in situ.
    if (isContainDirection(TRANSLATION_LEFT))
        setTranslationX(-mWidth * (1 - moveRadio));
    if (isContainDirection(TRANSLATION_TOP))
        setTranslationY(-mHeight * (1 - moveRadio));
    if (isContainDirection(TRANSLATION_RIGHT))
        setTranslationX(mWidth * (1 - moveRadio));
    if (isContainDirection(TRANSLATION_BOTTOM))
        setTranslationY(mHeight * (1 - moveRadio));
    if (mBgColorStart != -1 && mBgColorEnd != -1)
        setBackgroundColor((int) mArgbEvaluator.evaluate(moveRadio, mBgColorStart, mBgColorEnd));

}

//
@Override
public void resetViewAnitation() {
    if (mAlphaSupport)
        setAlpha(0);
    if (mScaleXSupport)
        setScaleX(0);
    if (mScaleYSupport)
        setScaleY(0);
    //Move from left to in situ.
    if (isContainDirection(TRANSLATION_LEFT))
        setTranslationX(-mWidth);
    if (isContainDirection(TRANSLATION_TOP))
        setTranslationY(-mHeight);
    if (isContainDirection(TRANSLATION_RIGHT))
        setTranslationX(mWidth);
    if (isContainDirection(TRANSLATION_BOTTOM))
        setTranslationY(mHeight);

}

private boolean isContainDirection(int direction) {
    if (mTranslationValue == -1)
        return false;
    return (mTranslationValue & direction) == direction;
}

On Size Changed (intw, inth, intoldw, The current width and height of MyFrameLayout are captured in the intoldh method. In the excute Anitation (float motion Radio) method, we use the percentage of execution animation passed in to determine which animation is available to perform the animation. Bit arithmetic is used to determine whether there is one of the mobile animations. When we define four directions, the code is as follows:

private static final int TRANSLATION_LEFT = 0x01;
private static final int TRANSLATION_TOP = 0x02;
private static final int TRANSLATION_RIGHT = 0x04;
private static final int TRANSLATION_BOTTOM = 0x08;

Converting 0x01 to binary representation 0001

0x02 converted to binary representation 0010

0x04 converted to binary representation 0100

0x08 converted to binary representation 1000

When users assign a value to a custom control, for example, android:layout_gravity="left|top", then we can get 0011 by parsing attributes because of yes or operation, and when I judge whether there is a left, the left which is judged in the isContainDirection(intdirection) method is processed and calculated as 0011. & The final value of 0001 is 0001, judging whether it is equal to left, in this way we can judge whether there is left value in our custom attribute. top, right and bottom are identical. This is explained here, and finally the color estimator, the ArgbEvaluator class, is used to perform the color gradient. In resetViewAnitation() method, recovery processing is performed. Okay, we've done all the work, so let's lay it out in our XML. The code is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lyl="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.jason.myscollanitationdemo.MyScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.jason.myscollanitationdemo.MyLinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="vertical">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/timg2" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/share_weixin_up"
                lyl:anitation_alpha="true"
                lyl:anitation_scaleX="true" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="In the Quiet Night\n Author: Li Bai\n The bright moonlight in front of the bed is suspected to be frost on the ground.\n Raising my head, I see the moon so bright; withdrawing my eyes, my nostalgia comes around."
                android:textColor="@android:color/white"
                android:textSize="17dp"
                lyl:anitation_alpha="true"
                lyl:anitation_translation="bottom"
                lyl:bgColorEnd="#FF00FF"
                lyl:bgColorStart="#7EB445" />

            <ImageView
                android:layout_width="200dp"
                android:layout_height="180dp"
                android:background="@drawable/discount"
                lyl:anitation_alpha="true"
                lyl:anitation_translation="left|bottom" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="#NAME?·Remembering the Ancient in Chibi\n
                //Author: Su Shi
                //To the east of the Great River, the waves wash out, and the people who have been in fashion for thousands of years. \n
                //On the west side of the base, the humanity is Zhoulang Chibi of the Three Kingdoms. \n
                //The rocks pierced through, the waves beat the shore and rolled up thousands of piles of snow. \n
                //The mountains and rivers are picturesque. How many heroes are there in a moment? \n
                //Looking back on Gong Jin, Xiao Qiao first married and became a hero. \n
                //Feather fan fiber towel, chatting and laughing, ashes fly out. \n
                //When you travel in your native country, you should laugh at me for being sentimental and have beautiful hair as early as possible. \n
                //Life is like a dream, a river moon. "
                android:textSize="17dp"
                android:gravity="center"
                lyl:anitation_alpha="true"
                lyl:anitation_translation="top|right"
                lyl:bgColorEnd="#FFFF21"
                lyl:bgColorStart="#21FF21" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/img_head_classtwo_high"
                lyl:anitation_alpha="true"
                lyl:anitation_translation="top" />
        </com.jason.myscollanitationdemo.MyLinearLayout>

    </com.jason.myscollanitationdemo.MyScrollView>
</LinearLayout>

If all the resources are ready, the result will appear. Well, an imperfect framework will be encapsulated. Of course, there are many things to be optimized. If you want to download the source code, click here . There may be omissions above, but the source code is complete, you can look at the process in a unified way.

That's all for today. It's not too early. It's time to go to bed. Take time to organize your blog. If you have any shortcomings, please give me some advice. Thank you.

Keywords: Android xml Attribute encoding

Added by gotDNS on Sat, 22 Jun 2019 02:25:31 +0300