2019-09-03 解析仿照ins視圖拖動效果

之前就一直覺得INS的視圖拖動縮放效果很酷炫,特地去網(wǎng)上搜索了下(本著不重復(fù)造輪子的想法),找到一個比較合適的。
地址是:https://github.com/okaybroda/ImageZoom

首先分析ins的視圖拖動效果,雙指放大以后,視圖View就會懸浮,跟著手指移動,進行縮放,效果是相當(dāng)?shù)目犰拧?br> 所以可以分成幾步來梳理:
1.雙指放上去以后有縮放動作,取出視圖View
2.根據(jù)雙指的操作,對視圖View進行放大縮小以及拖動
3.當(dāng)屏幕上的手指數(shù)小于2時,將視圖View放回原來的地方

基本上就是以上4個步驟,那就逐個分析。

一 取出視圖View

用過ins的同學(xué)都知道,不管是視頻還是圖片,都是可以拿出來縮放的,仔細觀察可以發(fā)現(xiàn)當(dāng)視圖View被拿出來的時候,原先的位置是一片空白的,隨后的移動縮放界面,除了把原來的位置上的View摳出來以后,并不影響其他,那么猜測應(yīng)該是新建了一個Act設(shè)置成透明的全屏背景加上原來的View進行展示或者是一個全屏的Dialog。那么如何把View摳出來呢?

ImageZoom這個項目里首先是將需要綁定的View進行setTag,回頭再根據(jù)這個tag找到對應(yīng)的View

/**
     * Finds the view that has the R.id.zoomable tag and also contains the x and y coordinations
     * of two pointers
     *
     * @param event MotionEvent that contains two pointers   帶兩個手指的手勢
     * @param view  View to find in  也就是我們需要縮放的View的ViewGroup
     * @return zoomable View      我們需要縮放的View
     */
private View findZoomableView(MotionEvent event, View view) {
        if (view instanceof ViewGroup) {
            //首先獲得viewGoup,以及他的子View的個數(shù)
            ViewGroup viewGroup = (ViewGroup) view;
            int childCount = viewGroup.getChildCount();

            //獲得兩個手指所在坐標(biāo)點的對象pointerCoords1,pointerCoords2 
            MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
            event.getPointerCoords(0, pointerCoords1);  

            MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
            event.getPointerCoords(1, pointerCoords2);

            //采取遞歸的方法尋找目標(biāo)view
            for (int i = 0; i < childCount; i++) {
                View child = viewGroup.getChildAt(i);

                if (child.getTag(R.id.unzoomable) == null) {
                    Rect visibleRect = new Rect();
                    int location[] = new int[2];
                    child.getLocationOnScreen(location);
                    visibleRect.left = location[0];
                    visibleRect.top = location[1];
                    visibleRect.right = visibleRect.left + child.getWidth();
                    visibleRect.bottom = visibleRect.top + child.getHeight();

                    if (visibleRect.contains((int) pointerCoords1.x, (int) pointerCoords1.y) &&
                            visibleRect.contains((int) pointerCoords2.x, (int) pointerCoords2.y)) {
                        if (child.getTag(R.id.zoomable) != null)
                            return child;
                        else
                            return findZoomableView(event, child);
                    }
                }
            }
        }

        return null;
    }

找到目標(biāo)View以后,

                    // 計算出view在界面上原來的坐標(biāo)
                    originalXY = new int[2];
                    view.getLocationInWindow(originalXY);

                    // 新建一個FrameLayout用來作為view的父布局
                    FrameLayout frameLayout = new FrameLayout(view.getContext());

                    // 這個視圖是用來當(dāng)用戶縮放的時候,控制背景的透明度的
                    darkView = new View(view.getContext());
                    darkView.setBackgroundColor(Color.BLACK);
                    darkView.setAlpha(0f);

                    // 將darkView添加到父布局當(dāng)中
                    frameLayout.addView(darkView, new FrameLayout.LayoutParams(
                            FrameLayout.LayoutParams.MATCH_PARENT,
                            FrameLayout.LayoutParams.MATCH_PARENT));

                    // 創(chuàng)建一個dialog,用來展示縮放布局
                    dialog = new Dialog(activity,
                            android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
                    dialog.addContentView(frameLayout,
                            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                    ViewGroup.LayoutParams.MATCH_PARENT));
                    dialog.show();

                    // 獲取zoomView的父布局,并且得到zoomview在布局中的index,以及zoomview的    
                    // layoutparams
                    parentOfZoomableView = (ViewGroup) zoomableView.getParent();
                    viewIndex = parentOfZoomableView.indexOfChild(zoomableView);
                    this.zoomableViewLP = zoomableView.getLayoutParams();

                    // 將之前得到的參數(shù)設(shè)置到zoomview里面
                    zoomableViewFrameLP = new FrameLayout.LayoutParams(
                            view.getWidth(), view.getHeight());
                    zoomableViewFrameLP.leftMargin = originalXY[0];
                    zoomableViewFrameLP.topMargin = originalXY[1];

                    // 創(chuàng)建一個臨時的view,用來代替zoomview原來的位置
                    placeholderView = new View(activity);

                    // 把placeholdeView的背景設(shè)置成zoomview的緩存視圖
                    // 這樣可以避免在添加和刪除視圖時,造成閃爍
                    zoomableView.setDrawingCacheEnabled(true);

                    BitmapDrawable placeholderDrawable = new BitmapDrawable(
                            activity.getResources(),
                            Bitmap.createBitmap(zoomableView.getDrawingCache()));
                    if (Build.VERSION.SDK_INT >= 16) {
                        placeholderView.setBackground(placeholderDrawable);
                    } else {
                        placeholderView.setBackgroundDrawable(placeholderDrawable);
                    }

                    // placeholderView暫時替代zoomview
                    parentOfZoomableView.addView(placeholderView, zoomableViewLP);

                    // 在添加到Framelayout之前,要先把zoomview從父布局移除
                    parentOfZoomableView.removeView(zoomableView);
                    frameLayout.addView(zoomableView, zoomableViewFrameLP);

                    // 利用zoomview的post方法移除placeholderview的緩存視圖
                    zoomableView.post(new Runnable() {
                        @Override
                        public void run() {
                            if (dialog != null) {
                                if (Build.VERSION.SDK_INT >= 16) {
                                    placeholderView.setBackground(null);
                                } else {
                                    placeholderView.setBackgroundDrawable(null);
                                }

                                zoomableView.setDrawingCacheEnabled(false);
                            }
                        }
                    });

                    // 創(chuàng)建對象pointerCoords1,pointerCoords2,得到兩個手指的坐標(biāo)位置
                    MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
                    ev.getPointerCoords(0, pointerCoords1);

                    MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
                    ev.getPointerCoords(1, pointerCoords2);

                    // 計算出兩個坐標(biāo)點之間的距離,后面可以用來計算縮放大小
                    originalDistance = (int) getDistance(pointerCoords1.x, pointerCoords2.x,
                            pointerCoords1.y, pointerCoords2.y);

                    // 計算出兩個坐標(biāo)對應(yīng)的矩形的中心點位,后面可以根據(jù)這個點位來移動視圖
                    twoPointCenter = new int[]{
                            (int) ((pointerCoords2.x + pointerCoords1.x) / 2),
                            (int) ((pointerCoords2.y + pointerCoords1.y) / 2)
                    };

                    //計算出手指移動的距離,用來縮放移動視圖
                    pivotX = (int) ev.getRawX() - originalXY[0];
                    pivotY = (int) ev.getRawY() - originalXY[1];

                    sendZoomEventToListeners(zoomableView, true);
                    return true;

上面的注釋已經(jīng)寫得很詳細了,當(dāng)我們的兩個手指放在屏幕上時,第一次尋找zoomview時,簡單來說,就是摳出zommview,放到新的布局里面,然后創(chuàng)建一個view,臨時代替zoomview原來的位置.下面我們看看zoomview找到以后,具體又是怎么操作進行縮放移動的。

                //首先還是先拿到兩個手指的位置,后面用來算出兩個手指的中心點位newCenter
                MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
                ev.getPointerCoords(0, pointerCoords1);

                MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
                ev.getPointerCoords(1, pointerCoords2);

                int[] newCenter = new int[]{
                        (int) ((pointerCoords2.x + pointerCoords1.x) / 2),
                        (int) ((pointerCoords2.y + pointerCoords1.y) / 2)
                };

                //通過跟剛才初始距離的比較,計算出現(xiàn)在的縮放百分比
                int currentDistance = (int) getDistance(pointerCoords1.x, pointerCoords2.x,
                        pointerCoords1.y, pointerCoords2.y);
                double pctIncrease = (currentDistance - originalDistance) / originalDistance;

                //設(shè)置zoomview的縮放中心點為剛才手指頭剛放上去的中心點
                zoomableView.setPivotX(pivotX);
                zoomableView.setPivotY(pivotY);

                //設(shè)置zoomview的縮放比例
                zoomableView.setScaleX((float) (1 + pctIncrease));
                zoomableView.setScaleY((float) (1 + pctIncrease));

                //更新zoomview的邊距
                updateZoomableViewMargins(newCenter[0] - twoPointCenter[0] + originalXY[0],
                        newCenter[1] - twoPointCenter[1] + originalXY[1]);

                //設(shè)置darkview的透明度
                darkView.setAlpha((float) (pctIncrease / 8));

找到zoomview以后的工作量已經(jīng)不多了,就是通過計算出兩個手指頭最新的距離,跟原始距離進行比較,算出縮放的百分比以后,利用view的自帶方法,進行縮放,并且設(shè)置darkview的透明度。此時手指拖動摳出目標(biāo)view,并且對view進行縮放已經(jīng)分析完成了。接下來就分析手指頭釋放以后,將view設(shè)置回原來的位置,并且有一個回彈的動畫效果。

//首先肯定是zoomview不為空,并且動畫需要展示的情況,才允許接下來的操作
if (zoomableView != null && !isAnimatingDismiss) {
                isAnimatingDismiss = true;

                //先拿到zoomview的最終縮放大小,邊距,以及darkview的最終透明度
                final float scaleYStart = zoomableView.getScaleY();
                final float scaleXStart = zoomableView.getScaleX();
                final int leftMarginStart = zoomableViewFrameLP.leftMargin;
                final int topMarginStart = zoomableViewFrameLP.topMargin;
                final float alphaStart = darkView.getAlpha();

                //下面就是沒有進行縮放之前的參數(shù),包括縮放大小,邊距,darkview的透明度。
                final float scaleYEnd = 1f;
                final float scaleXEnd = 1f;
                final int leftMarginEnd = originalXY[0];
                final int topMarginEnd = originalXY[1];
                final float alphaEnd = 0f;

                //利用valueAnimator展示動畫,就是根據(jù)動畫的進行的百分比,進行百分比的縮放等回彈
                //相信有一點基礎(chǔ)的Android開發(fā)同學(xué)都是懂得的,就不過多分析了
                final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
                          
                valueAnimator.setDuration(activity.getResources().getInteger
                                                                                   (android.R.integer.config_shortAnimTime));
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        float animatedFraction = valueAnimator.getAnimatedFraction();
                        if (zoomableView != null) {
                            updateZoomableView(animatedFraction, scaleYStart, scaleXStart,
                                    leftMarginStart, topMarginStart,
                                    scaleXEnd, scaleYEnd, leftMarginEnd, topMarginEnd);
                        }

                        if (darkView != null) {
                            darkView.setAlpha(((alphaEnd - alphaStart) * animatedFraction) +
                                    alphaStart);
                        }
                    }
                });
                valueAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationCancel(Animator animation) {
                        super.onAnimationCancel(animation);
                        end();
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        end();
                    }

                    //當(dāng)前view已經(jīng)縮放結(jié)束,恢復(fù)初始的一些狀態(tài),并且
                    void end() {
                        if (zoomableView != null) {
                            updateZoomableView(1f, scaleYStart, scaleXStart,
                                    leftMarginStart, topMarginStart,
                                    scaleXEnd, scaleYEnd, leftMarginEnd, topMarginEnd);
                        }
                        dismissDialogAndViews();

                        valueAnimator.removeAllListeners();
                        valueAnimator.removeAllUpdateListeners();
                    }
                });
                valueAnimator.start();

                return true;
            }

到這里基本就已經(jīng)分析完了,還有最后一個方法要分析dismissDialogAndViews(),隱藏dialog并且恢復(fù)zoomview在原來act上面的位置。

        //回調(diào)通知縮放結(jié)束了
        sendZoomEventToListeners(zoomableView, false);

        if (zoomableView != null) {
            //將zoomview從framelayout中摳出來,重新添加到之前的父布局當(dāng)中去
            zoomableView.setVisibility(View.VISIBLE);
            zoomableView.setDrawingCacheEnabled(true);

            BitmapDrawable placeholderDrawable = new BitmapDrawable(
                    zoomableView.getResources(),
                    Bitmap.createBitmap(zoomableView.getDrawingCache()));
            if (Build.VERSION.SDK_INT >= 16) {
                placeholderView.setBackground(placeholderDrawable);
            } else {
                placeholderView.setBackgroundDrawable(placeholderDrawable);
            }

            ViewGroup parent = (ViewGroup) zoomableView.getParent();
            parent.removeView(zoomableView);
            //利用剛才拿到的viewIndex,準(zhǔn)確的添加回原來的位置
            this.parentOfZoomableView.addView(zoomableView, viewIndex, zoomableViewLP);
            this.parentOfZoomableView.removeView(placeholderView);
            final View finalZoomView = zoomableView;
            zoomableView.setDrawingCacheEnabled(false);
            dismissDialog();
            finalZoomView.invalidate();
        } else {
            dismissDialog();
        }

        isAnimatingDismiss = false;

終于分析完了,其他的一些相信都能看的懂,有疑問的地方的再留言,大家一起討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容