微信朋友圈大圖預覽及大圖關閉,縮放歸位效果實現

jgkh0615 8年前發布 | 46K 次閱讀 安卓開發 Android開發 移動開發

微信朋友圈的圖片預覽和預覽關閉時,圖片的縮放歸位效果很贊,一直都比較喜歡,剛好app要用到,于是打算實現這個效果。一開始也是沒有思路的,還好網上已經有類似的demo,于是就結合了網上的資料實現了這個效果。

一、效果圖

gif看起來可能不是很流暢,實際運行demo效果還是棒棒的

gif5新文件 (1).gif

二、實現思路分析

1.從微信android版看,點擊預覽大圖時,應該是從當前界面將一個隱藏的View進行顯示,然后再進行動畫處理。這只是一種猜測,當然也無須關注它具體是不是這樣實現的,我們要做的就是按照這個方案,實現這個效果即可。

2.大圖預覽的顯示動畫:仔細觀察,你會發現圖片是從某一張圖片的位置開始進行縮放動畫顯示的,所以我們需要知道縮略圖在屏幕中的位置。

3.大圖預覽關閉動畫:現在我們看下gif,首先我們進入的是一個圖片列表,當我們點擊大圖預覽,然后再單擊關閉預覽時,圖片會縮放到列表中對應的圖片位置。可能出現的情況是,我點擊的是列表的第一張圖片進行查看大圖,在大圖滑動到最后一張時進行關閉大圖預覽界面。那么問題來了,如果列表的圖片很多,列表的最后一張圖片可能還沒有顯示到屏幕,這種情況我們是沒有辦法得到縮略圖的位置的。怎么辦呢?列表是根據手指的滑動而顯示的,所以我們只需要在查看大圖時,同時將列表中的縮略圖滑動到可見的位置即可。當然這里不是用手指去滑動,因為查看大圖時,縮略圖列表是不可見的。大圖滑動時,我們只需要調用列表控件對應的方法滑動列表即可。

4.縮放比例計算:列表中的大圖,不可能所有的圖片大小、高度都一致,所以為了實現比較好的效果,我們需要去動態計算縮放比例。而在查看大圖時,我們在未加載出圖片時,我們無法拿到大圖的實際大小,直接拿屏幕或者控件的高度去計算縮放比,可能會導致圖片縮放變形,所以我們需要在加載圖片成功后,拿到bitmap寬高,并緩存用于計算。為了提高加載速度及內存優化使用到了LruCache。關于圖片加載優化,可以參考我之前的一篇文章 圖片加載優化,拒絕OOM

三、代碼

具體實現思路已經分析,廢話不多說,直接上代碼

1、xml布局代碼

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </LinearLayout>

        <FrameLayout
            android:id="@+id/fly_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clickable="true"
            android:orientation="vertical"
            android:visibility="invisible">

            <View
                android:id="@+id/view_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@android:color/black" />

            <com.leo.example.widght.HackyViewPager
                android:id="@+id/vp_pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clickable="true" />
        </FrameLayout>
    </FrameLayout>

2、主要代碼(代碼太多,只貼主要代碼了,詳情可查看demo)

無論是圖片預覽和是關閉預覽都需要獲得縮略圖Image的位置坐標,所以需要用到View中的getGlobalVisibleRect方法去測量對應縮略圖在屏幕中的位置,以實現縮放歸位的動畫效果。

/**
     * 添加需要顯示的圖片
     *
     * @param photoData
     * @param position
     * @param rect
     */
    public void showPhotoView(List<ItemSubjectsInfoViewModel> photoData, int
     position, Rect rect) {
        zoomImagePagerAdapter = new ZoomImagePagerAdapter(this);
        zoomImagePagerAdapter.addAll(PhotoZoomViewModel.toViewModel(photoData,
         actionHide));
        binding.vpPager.setAdapter(zoomImagePagerAdapter);
        binding.vpPager.addOnPageChangeListener(changeAdapter);
        binding.vpPager.setCurrentItem(mCurrentItem = position);
        binding.vpPager.setLocked(zoomImagePagerAdapter.getCount() == 1);
        binding.flyLayout.getGlobalVisibleRect(finalBounds, globalOffset);
        showPhotoAnimator(rect);
    }

    /**
     * 隱藏圖片預覽界面
     *
     * @return
     */
    private Action1<View> actionHide = new Action1<View>() {
        @Override
        public void call(View view) {
            Rect originBound =
            ViewUtils.getViewGlobalRect(shadowListAdapter.getItemView(mCurrentItem));
            hidePhotoAnimator(originBound);
        }
    };

3、顯示與隱藏動畫

/**
     * 顯示照片查看界面
     *
     * @param startBounds
     */
    private void showPhotoAnimator(Rect startBounds) {
        startBounds.offset(-globalOffset.x, -globalOffset.y);
        finalBounds.offset(-globalOffset.x, -globalOffset.y);
        ViewHelper.setAlpha(binding.viewBg, 1);
        binding.flyLayout.setVisibility(View.VISIBLE);
        float ratio = RatioUtils.getShowRatio(startBounds, finalBounds);
        calculateLocationRect(startBounds, finalBounds, ratio);
        binding.vpPager.setPivotX(0);
        binding.vpPager.setPivotY(0);
        animator = new AnimatorSet();
        animator.play(ObjectAnimator.ofFloat(binding.vpPager, View.X, 
        startBounds.left, finalBounds.left))
                .with(ObjectAnimator.ofFloat(binding.vpPager, View.Y, 
                startBounds.top, finalBounds.top))
                .with(ObjectAnimator.ofFloat(binding.vpPager, View.SCALE_X, ratio, 
                1f))
                .with(ObjectAnimator.ofFloat(binding.vpPager, View.SCALE_Y, ratio,
                 1f));
        animator.setDuration(mDuration);
        animator.setInterpolator(interpolator);
        animator.addListener(showAnimatorListener);
        animator.start();
    }


    /**
     * 隱藏照片查看界面
     *
     * @param originBound
     */
    private void hidePhotoAnimator(Rect originBound) {
        //如果展開動畫沒有展示完全就關閉,執行退出動畫
        if (animator != null) {
            animator.cancel();
        }
        //將背景設為透明
        ViewHelper.setAlpha(binding.viewBg, 0);
        //測量View位置,獲取坐標參數
        binding.flyLayout.getGlobalVisibleRect(finalBounds, globalOffset);
        originBound.offset(-globalOffset.x, -globalOffset.y);
        finalBounds.offset(-globalOffset.x, -globalOffset.y);
        //將ViewPager的位置移動到屏幕中心
        binding.vpPager.setPivotX(0);
        binding.vpPager.setPivotY(0);
        animator = new AnimatorSet();
        float ratio = getHideRatio(originBound, 
        zoomImagePagerAdapter.getImageSizeInfo(mCurrentItem));
        calculateLocationRect(originBound, finalBounds, ratio);
        animator.play(ObjectAnimator.ofFloat(binding.vpPager, View.X, 
        originBound.left))
                .with(ObjectAnimator.ofFloat(binding.vpPager, View.Y, 
                originBound.top))
                .with(ObjectAnimator.ofFloat(binding.vpPager, View.SCALE_X, 1f, 
                ratio))
                .with(ObjectAnimator.ofFloat(binding.vpPager, View.SCALE_Y, 1f, 
                ratio));
        animator.setDuration(mDuration);
        animator.setInterpolator(interpolator);
        animator.addListener(hideAnimatorListener);
        animator.start();
    }

4、圖片位置以及圖片縮放比例計算方法

/**
     * 根據View的寬高,計算動畫及顯示位置的偏移量
     *
     * @param startBounds
     * @param finalBounds
     * @param ratio
     */
    private void calculateLocationRect(Rect startBounds, Rect finalBounds, float 
    ratio) {
        if ((float) finalBounds.width() / finalBounds.height() > (float) 
        startBounds.width() / startBounds.height()) {
            float startWidth = ratio * finalBounds.width();
            float deltaWidth = (startWidth - startBounds.width()) / 2;
            startBounds.left -= deltaWidth;
            startBounds.right += deltaWidth;
            return;
        }
        float startHeight = ratio * finalBounds.height();
        float deltaHeight = (startHeight - startBounds.height()) / 2;
        startBounds.top -= deltaHeight;
        startBounds.bottom += deltaHeight;
        float startWidth = ratio * finalBounds.width();
        float deltaWidth = (startWidth - startBounds.width()) / 2;
        //根據圖片所在位置計算位置偏移量
        if (mRow > 1 && LocationUtils.isRight(mCurrentItem, mRow)) {
            startBounds.left -= startWidth - startBounds.width();
        } else if (!LocationUtils.isRight(mCurrentItem, mRow) && !
        LocationUtils.isLeft(mCurrentItem, mRow) || 3 == 1) {
            startBounds.left -= deltaWidth;
            startBounds.right += deltaWidth;
        }
    }


    /**
     * 獲取隱藏縮放比
     *
     * @param info
     * @param endBounds
     */
    private float getHideRatio(Rect endBounds, ImageSizeInfo info) {
        float ratio;
        //可能出現圖片未加載完畢,拿不到圖片寬高數據的情況,此時默認拿外層ViewGroup的寬高,保證基本
        的效果即可
        if (info == null) {
            info = new ImageSizeInfo();
        }
        if (info.getWidth() == 0 || info.getHeight() == 0) {
            info.setWidth(binding.flyLayout.getWidth());
            info.setHeight(binding.flyLayout.getHeight());
        }
        //根據圖片顯示的尺寸計算,隱藏時的縮放比例
        //為保證縮放效果,以圖片寬、高中,以參數小的為計算基準
        float scale = (float) screenWidth / info.getWidth();
        if (info.getWidth() > info.getHeight()) {
            ratio = RatioUtils.getHeightRatio(info, endBounds, scale);
            //如果計算的縮放的最終寬度,要小于RecyclerView中item寬度
            //則重新以圖片高度為基準,進行計算
            if (ratio * info.getWidth() * scale < endBounds.width()) {
                ratio = RatioUtils.getWidthRatio(info, endBounds, scale);
            }
            return ratio;
        }
        ratio = RatioUtils.getWidthRatio(info, endBounds, scale);
        //如果計算的縮放的最終高度,要小于RecyclerView中item高度
        //則重新以圖片寬度為基準,進行計算
        if (ratio * info.getHeight() * scale < endBounds.height()) {
            ratio = RatioUtils.getHeightRatio(info, endBounds, scale);
        }
        return ratio;
    }

5、圖片列表位置滑動處理

大圖查看控件使用的是ViewPager,所以我把列表滑動處理放在了,ViewPager滑動回調的方法中,項目中使用的是RecyclerView,所以直接調用GridLayoutManager的滑動位置方法即可實現列表滑動

/**
     * ViewPage滑動監聽
     */
    private ViewPager.OnPageChangeListener changeAdapter = new
     ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            if (position >= shadowListAdapter.size() || position == mCurrentItem) {
                return;
            }
            mCurrentItem = position;
            //因實現圖片歸位的動畫效果,需要先測量View在屏幕中的坐標,所以需要保證RecyclerView
            中的item必須顯示在屏幕中
            //所以圖片預覽切換圖片時,需要同時滑動RecyclerView
            GridLayoutManager layoutManager = (GridLayoutManager) 
            binding.rvView.getLayoutManager();
            layoutManager.scrollToPositionWithOffset(position, 0);
        }
    };

6、圖片加載優化及bitmap寬高獲取就直接放在了PagerAdapter中,具體看代碼

public class ZoomImagePagerAdapter extends PagerViewModelAdapter<ItemShowPhotoImageBinding, PhotoZoomViewModel> {
    private SparseArray<ImageSizeInfo> infoSparseArray = new SparseArray<>();
    private SparseArray<PhotoView> viewSparseArray = new SparseArray<>();

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

    @Override
    public void onBindDataToView(BaseDataViewHodler<ItemShowPhotoImageBinding> 
    holder, int position, PhotoZoomViewModel photoViewModel) {
        String url = photoViewModel.getImageUrl().get();
        Bitmap bitmap = LruCacheUtils.getInstance().getBitmapFromMemCache(url);
        PhotoView photoView = holder.getBinding().showPhoto;
        viewSparseArray.put(position, photoView);
        if (bitmap != null) {
            cacheImageData(bitmap, url, position);
            photoView.setImageBitmap(bitmap);
        } else {
            PhotoLoader.displayImageLruCaches(photoView, url, getTarget(photoView,
             url, position));
        }
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    public ImageSizeInfo getImageSizeInfo(int position) {
        return infoSparseArray.get(position);
    }


    /**
     * 將View對象置為null
     */
    public void onDestoryPhotoView() {
        for (int i = 0; i < viewSparseArray.size(); i++) {
            PhotoView view = viewSparseArray.get(i);
            if (view != null) {
                view = null;
            }
        }
        infoSparseArray.clear();
        viewSparseArray.clear();
    }


    /**
     * 獲取BitmapImageViewTarget
     */
    private BitmapImageViewTarget getTarget(ImageView imageView, final String url,
     final int position) {
        return new BitmapImageViewTarget(imageView) {
            @Override
            protected void setResource(Bitmap resource) {
                super.setResource(resource);
                cacheImageData(resource, url, position);
            }
        };
    }


    /**
     * 緩存圖片數據
     */
    private void cacheImageData(Bitmap resource, String url, int position) {
        ImageSizeInfo info = infoSparseArray.get(position);
        if (info == null) {
            info = new ImageSizeInfo();
        }
        if (resource != null) {
            info.setWidth(resource.getWidth());
            info.setHeight(resource.getHeight());
            infoSparseArray.put(position, info);
            LruCacheUtils.getInstance().addBitmapToMemoryCache(url, resource);
        }
    }
}

 

 

來自:http://www.jianshu.com/p/78d9ad3f0e4c

 

 本文由用戶 jgkh0615 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!