*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布


1、最终效果

有木有发现还是很小清新的感觉

2、看整体效果这是一个scrollView,滑动时每个子view都有一个或多个动画效果,但是如果我们直接给每个子view加上动画去实现这个需求就太low了,而且也不利于扩展,所以这里将会设计一套框架,使别人能很方便的使用我们定义的控件。

3、首先看看我们是怎么使用自己设计的这个控件的

<scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:discrollve="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollViewContent
        android:layout_width="match_parent"
        android:layout_height="match_parent">

       ...

        <ImageView
            android:layout_width="300dp"
            android:layout_height="180dp"
            android:layout_gravity="center"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_translation="fromLeft|fromBottom"
            android:src="@drawable/cheese1" />

       ...

    </scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollViewContent>
</scrollviewgroup.lly.com.scrollviewgroup.lib.DiscrollView>

discrollve:discrollve_alpha="true"

discrollve:discrollve_translation="fromLeft|fromBottom"


这里我们给系统控件加上自定义属性,这样当别人用我们的控件,简直不要太爽。

不过大家有没有发现这是系统控件哎,你就这么随随便便的给它加个属性,它认识么 不报错你就谢天谢地了 还让它工作,想的美。

带着这个疑惑,我们先来看看系统的ViewGroup.java类是怎么做的。

一般我们在代码中给布局动态添加子控件的时候都会用到addView这个方法

这里我们就跟踪这个方法,最后发现他们会调用到ViewGroup的addview方法

public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

有没有发现这里这里最后的params是怎么来的 不就是子控件的params么。

而addView(child, index, params); 最后会调用addViewInner

下面我们看下addViewInner是怎么做的

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

        ...
        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

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

       ...
        addInArray(child, index);

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

          ...
        onViewAdded(child);

        ...
    }

代码还是比较多的,只关注对我们有用的片段

首先它会调用checkLayoutParams(params)

 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }

如果不等于空就会调用就调用generateLayoutParams

protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p;
    }

继续执行

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

看到上面的checkLayoutParams和generateLayoutParams方法都比较简单而且是protected的 所以应该是给子类实现的,我们看一个viewgroup的子类 LinearLayout是怎么做的

@Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LinearLayout.LayoutParams;
    }
        @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }

看到这里就在想我们是不是也可以这么做呢,那当然是可以的 系统都可以了还有什么问题,

接下来我们的大波代码来袭了

public class DiscrollViewContent extends LinearLayout {

    public DiscrollViewContent(Context context) {
        super(context);
        setOrientation(VERTICAL);
    }

    public DiscrollViewContent(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
    }

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

    /**
     * 重写addView
     * @param child
     * @param index
     * @param params
     */
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(asDiscrollvable(child,(MyLayoutParams)params), index, params);
    }

    private View asDiscrollvable(View child, MyLayoutParams params) {
        if(!isDiscrollvable(params)){
            return child;
        }
        DiscrollvableView discrollvableChild = new DiscrollvableView(getContext());
        discrollvableChild.setDiscrollveAlpha(params.mDiscrollveAlpha);
        discrollvableChild.setDiscrollveTranslation(params.mDiscrollveTranslation);
        discrollvableChild.setDiscrollveScaleX(params.mDiscrollveScaleX);
        discrollvableChild.setDiscrollveScaleY(params.mDiscrollveScaleY);
        discrollvableChild.setDiscrollveThreshold(params.mDiscrollveThreshold);
        discrollvableChild.setDiscrollveFromBgColor(params.mDiscrollveFromBgColor);
        discrollvableChild.setDiscrollveToBgColor(params.mDiscrollveToBgColor);
        discrollvableChild.addView(child);
        return discrollvableChild;
    }

    /**
     * 判断是否是我们定义的LayoutParams
     * @param lp
     * @return
     */
    private boolean isDiscrollvable(MyLayoutParams lp) {
        return lp.mDiscrollveAlpha ||
                lp.mDiscrollveTranslation != -1 ||
                lp.mDiscrollveScaleX ||
                lp.mDiscrollveScaleY ||
                (lp.mDiscrollveFromBgColor != -1 && lp.mDiscrollveToBgColor != -1);
    }

    /**
     * 重写checkLayoutParams
     * @param p
     * @return
     */
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof MyLayoutParams;
    }

    /**
     * 重写generateDefaultLayoutParams
     * @return
     */
    @Override
    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
        return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    /**
     * 重写generateLayoutParams
     * @param attrs
     * @return
     */
    @Override
    public LinearLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MyLayoutParams(getContext(), attrs);
    }

    /**
     * 重写generateLayoutParams
     * @param p
     * @return
     */
    @Override
    protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new MyLayoutParams(p.width, p.height);
    }

    /**
     * 自定义LinearLayout.LayoutParams
     */
    class MyLayoutParams extends LinearLayout.LayoutParams {

        private int mDiscrollveFromBgColor;
        private int mDiscrollveToBgColor;
        private float mDiscrollveThreshold;
        public boolean mDiscrollveAlpha;
        public boolean mDiscrollveScaleX;
        public boolean mDiscrollveScaleY;
        private int mDiscrollveTranslation;
        public MyLayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            try {
                mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);
                mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
                mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
                mDiscrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
                mDiscrollveThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollve_threshold, 0.0f);
                mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
                mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
            } finally {
                a.recycle();
            }
        }

        public MyLayoutParams(int width, int height) {
            super(width, height);
        }

    }
}

上面这大段代码主要就做了我们上面分析的系统空间

首先继承LinearLayout,

重写了addView,generateLayoutParams,checkLayoutParams

并自定义了一个MyLayoutParams继承自LinearLayout.LayoutParams

在addview的时候我们首先对child进行下处理,判断子view中是否有我们定义属性,没有的话,就用它自己,有的话,我们在外层包一个FrameLayout,让他执行动画,他的子view也将跟着执行。

好了 框架的设计部分完成了,

下面就是动画的实现了

首先看我们的scrollView是怎么做的

public class DiscrollView extends ScrollView {

    private DiscrollViewContent mContent;

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

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

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

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if(getChildCount() != 1) {
            throw new IllegalStateException("Discrollview must host one child.");
        }
        View content = getChildAt(0);
        if(!(content instanceof DiscrollViewContent)) {
            throw new IllegalStateException("Discrollview must host a DiscrollViewContent.");
        }
        mContent = (DiscrollViewContent) content;
        if(mContent.getChildCount() < 2) {
            throw new IllegalStateException("Discrollview must have at least 2 children.");
        }
    }

    private void setupFirstView() {
        View first = mContent.getChildAt(0);
        if(first!=null){
            first.getLayoutParams().height = getHeight();
        }
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        onScrollChanged(t);
    }

    private void onScrollChanged(int top) {
        int scrollViewHeight = getHeight();
        int scrollViewBottom = getAbsoluteBottom();
        int scrollViewHalfHeight = scrollViewHeight / 2;
        for(int index = 1;index<mContent.getChildCount();index++){
            View child = mContent.getChildAt(index);
            if(!(child instanceof DiscrollVable)){
                continue;
            }
            DiscrollVable discrollvable = (DiscrollVable) child;
            int discrollvableTop = child.getTop();
            int discrollvableHeight = child.getHeight();
            int discrollvableAbsoluteTop = discrollvableTop - top;
            //这个view的下半部分
            if(scrollViewBottom - child.getBottom() < discrollvableHeight+scrollViewHalfHeight){
                //子view显示的时候执行
                if(discrollvableAbsoluteTop <= scrollViewHeight){
                    int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;
                    discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));
                }else {
                    //子view还没显示的时候
                    discrollvable.onResetDiscrollve();
                }
            }else{
                if(discrollvableAbsoluteTop <= scrollViewHalfHeight){
                    int visibleGap = scrollViewHalfHeight - discrollvableAbsoluteTop;
                    discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));
                }else{
                    discrollvable.onResetDiscrollve();
                }
            }
        }
    }

    private float clamp(float value, float max, float min) {
        return Math.max(Math.min(value,min),max);
    }

    public int getAbsoluteBottom() {
        View last = getChildAt(getChildCount()-1);
        if(last == null){
            return 0;
        }
        return last.getBottom();
    }
}

主要就是在滑动的时候 把滑动的百分比传给接口 ,具体由接口的实现类来执行

而实现接口的类就是我们上面的那个FrameLayout。

 @Override
    public void onDiscrollve(float ratio) {
        if(ratio >= mDiscrollveThreshold) {
            ratio = withThreshold(ratio);
            float ratioInverse = 1 - ratio;

            if(mDiscrollveAlpha) {
                setAlpha(ratio);
            }
            if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
                setTranslationY(mHeight * ratioInverse);
            }
            if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {
                setTranslationY(-mHeight * ratioInverse);
            }
            if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {
                setTranslationX(-mWidth * ratioInverse);
            }
            if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {
                setTranslationX(mWidth * ratioInverse);
            }
            if(mDiscrollveScaleX) {
                setScaleX(ratio);
            }
            if(mDiscrollveScaleY) {
                setScaleY(ratio);
            }
            if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
                setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
            }
        }

    }

    @Override
    public void onResetDiscrollve() {
        if(mDiscrollveAlpha) {
            setAlpha(0.0f);
        }
        if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {
            setTranslationY(mHeight);
        }
        if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {
            setTranslationY(-mHeight);
        }
        if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {
            setTranslationX(-mWidth);
        }
        if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {
            setTranslationX(mWidth);
        }
        if(mDiscrollveScaleX) {
            setScaleX(0.0f);
        }
        if(mDiscrollveScaleY) {
            setScaleY(0.0f);
        }
        if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {
            setBackgroundColor(mDiscrollveFromBgColor);
        }

    }

代码贴的太多了 底部将给出源码

可以看出 每个类都不是很大,当用户要用的时候只要 在xml中引用我们的控件,就可以实现这个效果,而且他要别的效果的话同样只要在xml中配置就好。

github

android 自定义ViewGroup之浪漫求婚的更多相关文章

  1. android自定义viewgroup之我也玩瀑布流

    先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...

  2. Android自定义ViewGroup

    视图分类就两类,View和ViewGroup.ViewGroup是View的子类,ViewGroup可以包含所有的View(包括ViewGroup),View只能自我描绘,不能包含其他View. 然而 ...

  3. Android自定义ViewGroup,实现自动换行

    学习<Android开发艺术探索>中自定义ViewGroup章节 自定义ViewGroup总结的知识点 一.自定义ViewGroup中,onMeasure理解 onMeasure(int ...

  4. android自定义viewgroup实现等分格子布局

    先上效果图: 实现这样的效果: 一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果 还有就是利用g ...

  5. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu

    示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动, ...

  6. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]

    http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...

  7. Android自定义ViewGroup(四、打造自己的布局容器)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51500304 本文出自:[openXu的博客] 目录: 简单实现水平排列效果 自定义Layo ...

  8. android自定义viewgroup初步之一----抽屉菜单

    转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...

  9. Android 自定义ViewGroup手把手教你实现ArcMenu

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...

随机推荐

  1. 使用 C# (.NET Core) 实现命令设计模式 (Command Pattern)

    本文的概念内容来自深入浅出设计模式一书. 项目需求 有这样一个可编程的新型遥控器, 它有7个可编程插槽, 每个插槽可连接不同的家用电器设备. 每个插槽对应两个按钮: 开, 关(ON, OFF). 此外 ...

  2. 使用REST风格完成MVC前后端分离

    一个具有REST风格项目的基本特征: 具有统一响应结构 前后台数据流转机制(HTTP消息与Java对象的互相转化机制) 统一的异常处理机制 参数验证机制 Cors跨域请求机制 鉴权机制 一:统一响应结 ...

  3. win7下ubuntu14.4双系统安装

    参考https://jingyan.baidu.com/article/f71d60379824041ab641d19d.html

  4. 【swift,oc】ios开发中巧用自动布局设置自定义cell的高度

    ios开发中,遇到自定义高度不定的cell的时候,我们通常的做法是抽取一个frame类,在frame类中预算好高度,再返回. 但是苹果出来自动布局之后...春天来了!!来看看怎么巧用自动布局设置自定义 ...

  5. Struts2--标签tag

    在Struts2框架中提供了一套标签库,可以与struts2无缝结合. 数据标签a.action.bean.date.debug.i18n.include.param.property.push.se ...

  6. Spring--bean的作用范围

    在Spring中,bean的作用范围分以下几种: singleton:spring ioc容器中仅有一个bean实例,bean以单例的方式存在 prototype:每次从容器中调用bean时,都返回一 ...

  7. 用DotTrace 来分析.NET-Core程序

    1. 前言   看园子里面讲dotTrace 的文章不多,最近也有这方面的需要,于是去搜索了一下,.NET 性能分析方面的工具.目的呢,主要是想发现我的代码中,哪些代码占用了最多时间,来进行优化.主要 ...

  8. [HNOI 2010]chorus 合唱队

    Description 题库链接 对于一个包含 \(N\) 个整数的数列 \(A\) ,我们可以把它的所有元素加入一个双头队列 \(B\) . 首先 \(A_1\) 作为队列的唯一元素,然后依次加入 ...

  9. 【BZOJ4003】【JLOI2015】城池攻占

    Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池.这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,其 ...

  10. hdu 5646DZY Loves Partition(构造)

    DZY Loves Partition  Accepts: 154  Submissions: 843  Time Limit: 4000/2000 MS (Java/Others)  Memory ...