仿QQ个人信息详情界面中背景图的下拉扩展放大功能

    一般一些项目中都少不了一些头部背景图,但是如果背景图静态的现实并不能呈现出与用户操作的交互感,所以要想办法让背景图动起来,qq的一些交互感我很喜欢,比如他的个人详情界面的背景图就是可以下拉扩展,并在扩展到一定程度中可以放大图片。其设计原理就是先隐藏头部和底部的一些视图,然后在下拉过程中慢慢把隐藏的部分显示出来,到完整显示后就可以放大图片,这样设计的好处就是:1、节省一些屏幕空间,不影响正常的操作内容显示。2、增加了趣味性,能更好的提升界面与用户的互动性。

   既然知道了原理就让我们自己来动手撸一个这样的控件出来吧。在此之前我也了解了一些别人实现的头部图片方法,一般是使用重写ScrollView实现的,但是其中的滑动冲突并没有解决,这自然满足不了对其他项目的兼容性,所以我实现的方法是重写NestedScrollView,使用原因是这个控件已经处理好了与子控件的滑动冲突。

  先上图:

   

  仔细看效果,这个背景图是先扩展然后再放大的。和qq的背景图的效果差不多。现在我们来分析一下怎么实现的

  第一步就是怎么一开始把图片的顶部和底部隐藏

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取头部视图的原始宽高
        if (viewWidth <= 0 || viewHeight <=0) {
            viewWidth = headView.getMeasuredWidth();
            viewHeight = headView.getMeasuredHeight();
        }
        //绘制视图时隐藏头部View的顶部和底部
        if (hideHeight==0){
            hideHeight=viewHeight/hideRatio;
            ViewGroup.LayoutParams layoutParams = headView.getLayoutParams();
            ((MarginLayoutParams) layoutParams).setMargins(0, -hideHeight, 0,-hideHeight);
            headView.setLayoutParams(layoutParams);
        }
    }
  这里我采取的方式是通过在绘制时调整其顶部与底部的边距实现的。就这几行代码,就简单的实现了头部图片部分的隐藏

  但是在此之前还要注意一下头部View的获取:

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
//        不可过度滚动,否则上移后下拉会出现部分空白的情况
        setOverScrollMode(OVER_SCROLL_NEVER);
//        获得默认第一个view
        if (getChildAt(0) != null && getChildAt(0) instanceof ViewGroup && headView == null) {
            ViewGroup mViewGroup = (ViewGroup) getChildAt(0);
            if (mViewGroup.getChildCount() > 0) {
                headView = mViewGroup.getChildAt(0);
            }
        }
    }
  实现的方法很简单,就是获取其内容中的第一个子视图,因为滑动视图的特性,其最近的子视图是ViewGroup,所以则获取这个ViewGroup的第一个子View

  隐藏部分实现后接下来就是实现其下拉操作的部分:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (viewWidth <= 0 || viewHeight <=0) {
            viewWidth = headView.getMeasuredWidth();
            viewHeight = headView.getMeasuredHeight();
        }
        if (headView == null || viewWidth <= 0 || viewHeight <= 0) {
            return super.onTouchEvent(ev);
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (!isUnfolding) {
                    if (getScrollY() == 0) {
                        pullY = ev.getY();//滑动到顶部时,记录位置
                    } else {
                        break;
                    }
                }
                int distance = (int) ((ev.getY() - pullY)*zoomRatio);
                if (distance < 0) break;//若往下滑动
                isUnfolding = true;
                if (hideHeight>distance){
                    unfoldImage(distance);
                    unfoldHeight=distance;
                    return true;
                }
                setZoom(distance-hideHeight);

                return true;
            case MotionEvent.ACTION_UP:
                isUnfolding = false;
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (!isUnfolding){
                            replyView();
                        }
                    }
                }, 100);

                break;
        }
        return super.onTouchEvent(ev);
    }
  如果其子视图没有初始化宽高则初始化宽高,获取头部视图失败则过滤其滑动事件,只需要重写滑动事件即可,当滑动到顶部的时候记录下滑动的第一个位置,否则继续执行其滑动事件,当到达顶部并往上滑的时候传递滑动事件,正常使用滑动视图的功能,当到达顶部并下拉的时候开始进行扩展放大。首先是进行扩展,当图片完全展示出来后进行放大处理。当手指放开便进行视图返回操作,这里我加了个延时,目的是操作时更自然点。这个主要是介绍一下处理的逻辑,接下来分析一下扩展、放大、回弹的实现内容

  

    /**
     * 扩展头部视图
     * @param distance 顶部和底部扩展的距离
     */
    private void unfoldImage(float distance) {
        ViewGroup.LayoutParams layoutParams = headView.getLayoutParams();
        //下拉时保持居中,设置顶部和底部的边距让隐藏的部分视图显示出来,达到扩展目的
        ((MarginLayoutParams) layoutParams).setMargins(-(layoutParams.width
                - viewWidth) / 2>0?0:-(layoutParams.width - viewWidth) / 2,
                (int)(distance-hideHeight), 0,(int)(distance-hideHeight));
        headView.setLayoutParams(layoutParams);
    }

  扩展头部视图的实现就是根据下拉的距离来改变顶部和底部的边距,达到隐藏部分慢慢扩展显示的效果

    /**
     * 放大头部View
     * @param distance  放大的距离
     */
    private void setZoom(float distance) {
        float scaleTimes = (float) ((viewWidth+distance)/(viewWidth*1.0));
//        如超过最大放大倍数,直接返回
        if (scaleTimes > maxZoomRatio) return;

        ViewGroup.LayoutParams layoutParams = headView.getLayoutParams();
        layoutParams.width = (int) (viewWidth + distance);
        layoutParams.height = (int)(viewHeight*((viewWidth+distance)/viewWidth));
//        设置控件水平居中
        ((MarginLayoutParams) layoutParams).setMargins(-(layoutParams.width - viewWidth) / 2, 0, 0, 0);
        headView.setLayoutParams(layoutParams);
        isZoom=true;
    }

  放大的效果即调整头部视图的宽度和高度来实现的,在放大的同时要注意保持头部空间的居中特性。因为在拉伸图片放大时高度会完全显示,但是宽度会超过屏幕,要是不居中,放大的焦点就会处于左上角,居中后放大的焦点将处于控件中心。

    /**
     * 头部视图还原
     */
    private void replyView() {
        /**
         * 如果头部视图被放大,添加动画还原放大的头部视图
         */
        if (isZoom){
            final float distance = headView.getMeasuredWidth() - viewWidth;
            // 设置动画
            ValueAnimator anim = ObjectAnimator.ofFloat(distance, 0.0F).setDuration((long) (distance * replyTimeRatio));
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    setZoom((Float) animation.getAnimatedValue());
                }
            });
            anim.start();
        }
        /**
         * 将扩展出来的头部视图还原
         */
        ValueAnimator unfold = ObjectAnimator.ofFloat(unfoldHeight, 0.0F).setDuration((long) (unfoldHeight));
        unfold.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                unfoldImage((Float) animation.getAnimatedValue());
            }
        });
        unfold.start();
        unfoldHeight=0;
    }
  头部视图的回弹实现就是通过给头部控件添加一个动画实现的,内容比较简单,但是需要注意当图片只是扩展时不需要给视图添加放大回弹效果

  这样主要的内容就分析完成,实现起来还是比较简单的,有兴趣可以看下源码

  GitHub地址

  参考博客地址:http://blog.csdn.net/anyfive/article/details/52575262




评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值