仿某平台的滑动动画框架

首先看下效果:
这里写图片描述
可以看到每滑出或滑入一个view,都会执行相应的动画


闲话不说,直接上代码:
思路:
      1.最外层是一个scrollview,我们要监听scrollview的滑动,根据滑动距离来计算view的可见高度,再根据view的本身高度计算一个比例值,再根据比例值来执行相应的动画
      2.各个子View执行的动画都是不同的,这是就考虑到用自定义属性来控制view执行什么样的动画
      3.怎么样去获取我们的自定义属性,并将属性值传给执行动画的控件

1.首先定义一个接口,用来操作view的动画

public interface DiscrollInterface {
    /**
     * 当滑动的时候调用该方法,用来控制里面的控件执行相应的动画
     * @param ratio 动画执行的百分比(child view画出来的距离百分比)
     */
    public void onDiscroll(float ratio);

    /**
     * 重置动画--让view所有的属性都恢复到原来的样子
     */
    public void onResetDiscroll();
}

2 . 继承scrollview,实现对滑动的监听,来执行子view的动画

public class AnimatorScrollView extends ScrollView {
    //scrollview里的包裹布局,下面会讲到,这个是最重要的部分,
    //通过该布局来分发动画
    private AnimatorLinerLayout mContent;


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

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

    public AnimatorScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


   @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        View view = getChildAt(0);
        if (view instanceof AnimatorLinerLayout) {
            mContent = (AnimatorLinerLayout) view;
        } else {
            throw new RuntimeException("The child view must instanceof AnimatorLinerLayout");
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //为了效果,把第一个子view的高度设置满屏
        View first = mContent.getChildAt(0);
        first.getLayoutParams().height = getHeight();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
//        super.onScrollChanged(l, t, oldl, oldt);
        int mHeight = getHeight();
        for (int i = 0; i < mContent.getChildCount(); i++) {
            View view = mContent.getChildAt(i);
            if (view instanceof DiscrollInterface) {
            //子view都要实现DiscrollInterface 这个接口,执行动画
                DiscrollInterface discrollInterface = (DiscrollInterface) view;
                //view划出来的距离
                int absolutY = view.getTop() - t;
                //如果view当前已经可见
                if (absolutY < mHeight) {
                    //计算可见的高度
                    int visibleGap = mHeight - absolutY;
                    //根据可见高度来算出动画执行的比例值
                    float radio = clamp(1f, 0f, visibleGap / (float) view.getHeight());
                    discrollInterface.onDiscroll(radio);
                } else {
                    discrollInterface.onResetDiscroll();
                }
            } else {
                continue;
            }
        }

    }

//为了把比例控制在0~1之间
    public float clamp(float max, float min, float value) {
        return Math.min(Math.max(min, value), max);
    }

}

3.写一些自定义动画属性

<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="DiscrollView_LayoutParams">
   <!-- 透明度-->
    <attr name="discrollve_alpha" format="boolean"/>
    <!--X方向的缩放-->
    <attr name="discrollve_scaleX" format="boolean"/>
    <!--Y方向的缩放-->
    <attr name="discrollve_scaleY" format="boolean"/>
    <!--颜色渐变的起始颜色-->
    <attr name="discrollve_fromBgColor" format="color"/>
    <!--颜色渐变的结束颜色-->
    <attr name="discrollve_toBgColor" format="color"/>
    <!--平移-->
    <attr name="discrollve_translation"/>
</declare-styleable>

<attr name="discrollve_translation">
    <flag name="fromTop" value="0x01" />
    <flag name="fromBottom" value="0x02" />
    <flag name="fromLeft" value="0x04" />
    <flag name="fromRight" value="0x08" />
</attr>
</resources>

4.接下来继承LinearLayout,用来分发子view的动画

/**
 * scrollview外层布局控件,总控内部
 */
public class AnimatorLinerLayout extends LinearLayout {

    public AnimatorLinerLayout(Context context) {
        this(context, null);
    }

    public AnimatorLinerLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

//通过查看源码,viewgroup读取控件属性是在addview创建LayoutParams时读取的,而viewGroup设置的layoutParams是通过generateLayoutParams来返回的,所以这里我们重写generateLayoutParams返回我们自己的layoutParams(见下面的注释)
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new AnimatorLayoutParams(getContext(),attrs);
    }


 //1.系统控件不识别自定义属性,所以要考虑给控件包一层布局
    //2.这里采取用父容器(AnimatorFramelayout)组件给子容器包裹一层的方式,而真正执行动画的就是AnimatorFramelayout (步骤5)
    //3.系统是通过布局文件,然后调用VIEW的addView来进行加载的
    //4.此时进行layoutparams的转换
    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        AnimatorFramelayout framelayout = new AnimatorFramelayout(child.getContext());
        AnimatorLayoutParams layoutParams = (AnimatorLayoutParams) params;
        if (!isAnimatorFramelayout(layoutParams)) {
            super.addView(child, params);
        } else {
            framelayout.addView(child);
            framelayout.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleX);    framelayout.setmDiscrollveScaleY(layoutParams.mDiscrollveScaleY);
            framelayout.setmDiscrollveAlpha(layoutParams.mDiscrollveAlpha);
            framelayout.setmDiscrollveFromBgColor(layoutParams.mDiscrollveFromBgColor);
            framelayout.setmDiscrollveToBgColor(layoutParams.mDiscrollveToBgColor);
            framelayout.setmDisCrollveTranslation(layoutParams.mDisCrollveTranslation);
            super.addView(framelayout, params);
        }

    }

    private boolean isAnimatorFramelayout(AnimatorLayoutParams params) {
        return params.mDiscrollveAlpha || params.mDiscrollveScaleX
                || params.mDiscrollveScaleY || params.mDisCrollveTranslation != -1
                || (params.mDiscrollveFromBgColor != -1 && params.mDiscrollveToBgColor != -1);
    }


//在这里继承LinearLayout.LayoutParams,初始化时读取自定义的属性
    private class AnimatorLayoutParams extends LinearLayout.LayoutParams {
        public boolean mDiscrollveAlpha;
        public boolean mDiscrollveScaleX;
        public boolean mDiscrollveScaleY;
        public int mDisCrollveTranslation;
        public int mDiscrollveFromBgColor;
        public int mDiscrollveToBgColor;


        public AnimatorLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            init(attrs);
        }

//在这里读取自定义的属性
        private void init( AttributeSet attrs) {
            TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            mDiscrollveAlpha = array.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);
            mDiscrollveScaleX = array.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
            mDiscrollveScaleY = array.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
            mDiscrollveFromBgColor = array.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
            mDiscrollveToBgColor = array.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
            mDisCrollveTranslation = array.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
            Log.i("swl","mDisCrollveTranslation=="+mDisCrollveTranslation);
            array.recycle();
        }


    }
}

5.最后一步,写一个真正执行动画的布局AnimatorFramelayout ,用来包裹子控件

public class AnimatorFramelayout extends FrameLayout implements DiscrollInterface {

    public AnimatorFramelayout(@NonNull Context context) {
        this(context, null);
    }

    public AnimatorFramelayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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


    /**
     * <attr name="discrollve_translation">
     * <flag name="fromTop" value="0x01" />
     * <flag name="fromBottom" value="0x02" />
     * <flag name="fromLeft" value="0x04" />
     * <flag name="fromRight" value="0x08" />
     * </attr>
     * 0000000001
     * 0000000010
     * 0000000100
     * 0000001000
     * top|left
     * 0000000001 top
     * 0000000100 left 或运算 |
     * 0000000101
     * 反过来就使用& 与运算
     */
    //保存自定义属性

    private static final int TRANSLATION_FROM_TOP = 0x01;
    private static final int TRANSLATION_FROM_BOTTOM = 0x02;
    private static final int TRANSLATION_FROM_LEFT = 0x04;
    private static final int TRANSLATION_FROM_RIGHT = 0x08;

    //颜色估值器
    private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
    /**
     * 自定义属性的一些接收的变量
     */
    private int mDiscrollveFromBgColor;//背景颜色变化开始值
    private int mDiscrollveToBgColor;//背景颜色变化结束值
    private boolean mDiscrollveAlpha;//是否需要透明度动画
    private int mDisCrollveTranslation;//平移值
    private boolean mDiscrollveScaleX;//是否需要x轴方向缩放
    private boolean mDiscrollveScaleY;//是否需要y轴方向缩放
    private int mHeight;//本view的高度
    private int mWidth;//宽度


    public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) {
        this.mDiscrollveFromBgColor = mDiscrollveFromBgColor;
    }

    public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) {
        this.mDiscrollveToBgColor = mDiscrollveToBgColor;
    }

    public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) {
        this.mDiscrollveAlpha = mDiscrollveAlpha;
    }

    public void setmDisCrollveTranslation(int mDisCrollveTranslation) {
        this.mDisCrollveTranslation = mDisCrollveTranslation;
    }

    public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) {
        this.mDiscrollveScaleX = mDiscrollveScaleX;
    }

    public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) {
        this.mDiscrollveScaleY = mDiscrollveScaleY;
    }


    @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 onDiscroll(float ratio) {
        if (mDiscrollveAlpha) {
            setAlpha(ratio);
        }
        if (mDiscrollveScaleX) {
            setScaleX(ratio);
        }

        if (mDiscrollveScaleY) {
            setScaleY(ratio);
        }

        if (-1 != mDiscrollveFromBgColor && -1 != mDiscrollveToBgColor) {
            setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
        }

        if (isTranslation(TRANSLATION_FROM_TOP)) {
//            setTranslationY();
            setTranslationY(-(1 - ratio) * mHeight);
        }
        if (isTranslation(TRANSLATION_FROM_BOTTOM)) {
            setTranslationY((1 - ratio) * mHeight);
        }
        if (isTranslation(TRANSLATION_FROM_LEFT)) {
            setTranslationX(-(1 - ratio) * mWidth);
        }
        if (isTranslation(TRANSLATION_FROM_RIGHT)) {
            setTranslationX((1 - ratio) * mWidth);
        }
    }

    @Override
    public void onResetDiscroll() {
        if (mDiscrollveAlpha) {
            setAlpha(0);
        }
        if (mDiscrollveScaleX) {
            setScaleX(0);
        }
        if (mDiscrollveScaleY) {
            setScaleY(0);
        }
        //平移动画  int值:left,right,top,bottom    left|bottom
        if (isTranslation(TRANSLATION_FROM_BOTTOM)) {
            setTranslationY(mHeight);//height--->0(0代表恢复到原来的位置)
        }
        if (isTranslation(TRANSLATION_FROM_TOP)) {
            setTranslationY(-mHeight);//-height--->0(0代表恢复到原来的位置)
        }
        if (isTranslation(TRANSLATION_FROM_LEFT)) {
            setTranslationX(-mWidth);//mWidth--->0(0代表恢复到原来的位置)
        }
        if (isTranslation(TRANSLATION_FROM_RIGHT)) {
            setTranslationX(mWidth);//-mWidth--->0(0代表恢复到原来的位置)
        }
    }


//是否有平移动画属性
    private boolean isTranslation(int value) {
        if (mDisCrollveTranslation == -1) {
            return false;
        }
        return (mDisCrollveTranslation & value) == value;

    }
}

最后附上布局文件:

<com.swl.animfreamwork.AnimatorScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:discrollve="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.swl.animfreamwork.AnimatorLinerLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#007788"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="带上您的行李箱,准备shopping!"
            discrollve:discrollve_alpha="true"
            />

        <ImageView
            android:layout_width="200dp"
            android:layout_height="120dp"
            android:layout_gravity="top|right"
            discrollve:discrollve_translation="fromLeft|fromBottom"
            discrollve:discrollve_alpha="true"
            android:src="@mipmap/baggage" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="准备好相机,这里有你想象不到的惊喜!"
            discrollve:discrollve_fromBgColor="#ffff00"
            discrollve:discrollve_toBgColor="#88EE66"
            />

        <ImageView
            android:layout_width="220dp"
            android:layout_height="110dp"
            android:layout_gravity="right"
            android:src="@mipmap/camera"
            discrollve:discrollve_translation="fromRight" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:fontFamily="serif"
            android:gravity="center"
            android:background="#D97C1F"
            android:text="这次淘宝造物节真的来了,我们都在造,你造吗?\n
                            7月22日-7月24日\n
                            上海世博展览馆\n
                            在现场,我们造什么?"
            android:textSize="23sp"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_translation="fromBottom" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/sweet"
            discrollve:discrollve_scaleX="true"
            discrollve:discrollve_scaleY="true"  />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/shoes"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleX="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft|fromBottom" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/shoes"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromRight|fromTop" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/sweet"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/camera"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft" />

    </com.swl.animfreamwork.AnimatorLinerLayout>

</com.swl.animfreamwork.AnimatorScrollView>

至此,整个动画框架的代码就完成了。


注释:ViewGroup的源码查看分析

 /**
     * <p>Adds a child view. If no layout parameters are already set on the child, the
     * default parameters for this ViewGroup are set on the child.</p>
     *
     * <p><strong>Note:</strong> do not invoke this method from
     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
     *
     * @param child the child view to add
     *
     * @see #generateDefaultLayoutParams()
     */
    public void addView(View child) {
        addView(child, -1);
    }

接着 addView(child, -1)

  /**
     * Adds a child view. If no layout parameters are already set on the child, the
     * default parameters for this ViewGroup are set on the child.
     *
     * <p><strong>Note:</strong> do not invoke this method from
     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
     *
     * @param child the child view to add
     * @param index the position at which to add the child
     *
     * @see #generateDefaultLayoutParams()
     */
    public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

再接着 addView(child, index, params)

/**
     * Adds a child view with the specified layout parameters.
     *
     * <p><strong>Note:</strong> do not invoke this method from
     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
     *
     * @param child the child view to add
     * @param index the position at which to add the child or -1 to add last
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }

再查看addViewInner(child, index, params, false)

 private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (mTransition != null) {
            mTransition.addChild(this, child);
        }

        if (!checkLayoutParams(params)) {

/*----------------------------------------------
    关键代码,这里就是layoutParams真正创建的地方
---------------------------------------------------*/
            params = generateLayoutParams(params);
        }

        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

        if (index < 0) {
            index = mChildrenCount;
        }

        addInArray(child, index);

        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }

        final boolean childHasFocus = child.hasFocus();
        if (childHasFocus) {
            requestChildFocus(child, child.findFocus());
        }

        AttachInfo ai = mAttachInfo;
        if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
            boolean lastKeepOn = ai.mKeepScreenOn;
            ai.mKeepScreenOn = false;
            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
            if (ai.mKeepScreenOn) {
                needGlobalAttributesUpdate(true);
            }
            ai.mKeepScreenOn = lastKeepOn;
        }

        if (child.isLayoutDirectionInherited()) {
            child.resetRtlProperties();
        }

        dispatchViewAdded(child);

        if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
        }

        if (child.hasTransientState()) {
            childHasTransientStateChanged(child, true);
        }

        if (child.getVisibility() != View.GONE) {
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }

        if (mTransientIndices != null) {
            final int transientCount = mTransientIndices.size();
            for (int i = 0; i < transientCount; ++i) {
                final int oldIndex = mTransientIndices.get(i);
                if (index <= oldIndex) {
                    mTransientIndices.set(i, oldIndex + 1);
                }
            }
        }

        if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
            notifyChildOfDragStart(child);
        }

        if (child.hasDefaultFocus()) {
            // When adding a child that contains default focus, either during inflation or while
            // manually assembling the hierarchy, update the ancestor default-focus chain.
            setDefaultFocus(child);
        }
    }

再接着我们去查看ViewGroup.LayoutParams的源码,我们可以找到下面这个构造方法,
通过看注释就是在这里映射 XML属性(The XML attributes mapped to this set of layout parameters are)

/**
         * Creates a new set of layout parameters. The values are extracted from
         * the supplied attributes set and context. The XML attributes mapped
         * to this set of layout parameters are:
         *
         * <ul>
         *   <li><code>layout_width</code>: the width, either an exact value,
         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
         *   {@link #MATCH_PARENT} in API Level 8)</li>
         *   <li><code>layout_height</code>: the height, either an exact value,
         *   {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
         *   {@link #MATCH_PARENT} in API Level 8)</li>
         * </ul>
         *
         * @param c the application environment
         * @param attrs the set of attributes from which to extract the layout
         *              parameters' values
         */
        public LayoutParams(Context c, AttributeSet attrs) {

            //真正读取属性的地方就是在这里,所以我们就可以依葫芦画瓢,替换成我们自己的LayoutParams来获取我们的自定义属性
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

总结:

学着去查看分析android的源码,理解其中原理后,很多时候我们一些的需求就会迅速找到思路,阅读源码非常重要,非常重要,非常重要!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值