1. 程式人生 > >仿QQ個人資訊詳情介面中背景圖的下拉擴充套件放大功能

仿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;
    }
  頭部檢視的回彈實現就是通過給頭部控制元件新增一個動畫實現的,內容比較簡單,但是需要注意當圖片只是擴充套件時不需要給檢視新增放大回彈效果

  這樣主要的內容就分析完成,實現起來還是比較簡單的,有興趣可以看下原始碼

  參考部落格地址:http://blog.csdn.net/anyfive/article/details/52575262