仿頭條、微信大圖預覽檢視

cedear發表於2018-03-26

圖片大圖預覽

在我現在的專案當中,也存在大圖預覽的功能,但其實現過於繁重,採用一個Activity實現,並且在圖片展示的過程中會產生卡頓感,整體感覺很是不好,正巧專案也在重構過程中,所以決定將這一功能寫成一個成型的控制元件。話不多說,先上圖看下效果。

image

整體實現思路

圖片展示:PhotoView(大圖支援雙擊放大)
圖片載入:Glide(載入網路圖片、本地圖片、資原始檔)
小圖變大圖時的實現:動畫
圖片的下載:插入系統相簿

該控制元件採用自定義View的方式,通過一些基本的控制元件的組合,來形成一個具有大圖預覽的控制元件。上程式碼

使用方法

(1)在佈局檔案中引用該view

<com.demo.gallery.view.GalleryView
        android:id="@+id/photo_gallery_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        app:animDuration="300"
        app:saveText="儲存至相簿"
        app:saveTextColor="#987622"/>  
複製程式碼

(2)具體使用方法
GalleryView galleryView = findViewById(R.id.photo_gallery_view);
galleryView.showPhotoGallery(index, List, ImageView);

到這裡就結束了,就是這麼簡單!

具體實現

(1)先從showPhotoGallery(index, List, ImageView)這個方法講起

int index:我們想要展示的一個圖片列表中的第幾個
List list: 我們要傳入的要展示的圖片型別list(支援網路圖片、資源圖片、本地圖片(本地圖片與網路圖片其實都是一個字串地址))

public class GalleryPhotoModel {

    public Object photoSource;

    public GalleryPhotoModel(@DrawableRes int drawableRes) {
        this.photoSource = drawableRes;
    }

    public GalleryPhotoModel(String path) {
        this.photoSource = path;
    }

}
複製程式碼

ImageView:即你點選想要展示的那個圖片

(2)對傳入GalleryView的資料進行處理

/**
     * @param index             想要展示的圖片的索引值
     * @param photoList         圖片集合(URL、Drawable、Bitmap)
     * @param clickImageView    點選的第一個圖片
     */
    public void showPhotoGallery(int index, List<GalleryPhotoModel> photoList, ImageView clickImageView) {
        GalleryPhotoParameterModel photoParameter = new GalleryPhotoParameterModel();
        //圖片
        photoParameter.photoObj = photoList.get(index).photoSource;
        //圖片在list中的索引
        photoParameter.index = index;
        int[] locationOnScreen = new int[2];
        //圖片位置引數
        clickImageView.getLocationOnScreen(locationOnScreen);
        photoParameter.locOnScreen = locationOnScreen;
        //圖片的寬高
        int width = clickImageView.getDrawable().getBounds().width();
        int height = clickImageView.getDrawable().getBounds().height();
        photoParameter.imageWidth = clickImageView.getWidth();
        photoParameter.imageHeight = clickImageView.getHeight();
        photoParameter.photoHeight = height;
        photoParameter.photoWidth = width;
        //scaleType
        photoParameter.scaleType = clickImageView.getScaleType();
        //將第一個點選的圖片引數連同整個圖片列表傳入
        this.setVisibility(View.VISIBLE);
        post(new Runnable() {
            @Override
            public void run() {
                requestFocus();
            }
        });
        setGalleryPhotoList(photoList, photoParameter);
    }
複製程式碼

通過傳遞進來的ImageView,獲取被點選View引數,並拼裝成引數model,再進行資料的相關處理。

(3)GalleryView的實現機制

該View的實現思路主要是:最外層是一個RelativeLayout,內部有一個充滿父佈局的ImageView和ViewPager。ImageView用來進行圖片的動畫縮放,ViewPager用來進行最後的圖片的展示。其實該View最主要的地方就是通過點選ImageView到最後ViewPager的展示的動畫。接下來主要是講解一下這個地方。先看一下被點選ImageView的引數Model。GalleryPhotoParameterModel

public class GalleryPhotoParameterModel {

    //索引
    public int index;
    // 圖片的型別
    public Object photoObj;
    // 在螢幕上的位置
    public int[] locOnScreen = new int[]{-1, -1};
    // 圖片的寬
    public int photoWidth = 0;
    // 圖片的高
    public int photoHeight = 0;
    // ImageView的寬
    public int imageWidth = 0;
    // ImageView的高
    public int imageHeight = 0;
    // ImageView的縮放型別
    public ImageView.ScaleType scaleType;

}
複製程式碼

3.1圖片放大操作

private void handleZoomAnimation() {
        // 螢幕的寬高
        this.mScreenRect = GalleryScreenUtil.getDisplayPixes(getContext());
        //將被縮放的圖片放在一個單獨的ImageView上進行單獨的動畫處理。
        Glide.with(getContext()).load(firstClickItemParameterModel.photoObj).into(mScaleImageView);
        //開啟動畫
        mScaleImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //開始放大操作
                calculateScaleAndStartZoomInAnim(firstClickItemParameterModel);
                //
                mScaleImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
    }
複製程式碼
/**
     * 計算放大比例,開啟放大動畫
     *
     * @param photoData
     */
    private void calculateScaleAndStartZoomInAnim(final GalleryPhotoParameterModel photoData) {
        mScaleImageView.setVisibility(View.VISIBLE);

        // 放大動畫引數
        int translationX = (photoData.locOnScreen[0] + photoData.imageWidth / 2) - (int) (mScreenRect.width() / 2);
        int translationY = (photoData.locOnScreen[1] + photoData.imageHeight / 2) - (int) ((mScreenRect.height() + GalleryScreenUtil.getStatusBarHeight(getContext())) / 2);
        float scale = getImageViewScale(photoData);
        // 開啟放大動畫
        executeZoom(mScaleImageView, translationX, translationY, scale, true, new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {}

            @Override
            public void onAnimationEnd(Animator animation) {
                showOtherViews();
                tvPhotoSize.setText(String.format("%d/%d", viewPager.getCurrentItem() + 1, photoList.size()));
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }
複製程式碼

3.2 圖片縮小操作

/**
     * 計算縮小比例,開啟縮小動畫
     */
    private void calculateScaleAndStartZoomOutAnim() {
        hiedOtherViews();

        // 縮小動畫引數
        int translationX = (firstClickItemParameterModel.locOnScreen[0] + firstClickItemParameterModel.imageWidth / 2) - (int) (mScreenRect.width() / 2);
        int translationY = (firstClickItemParameterModel.locOnScreen[1] + firstClickItemParameterModel.imageHeight / 2) - (int) ((mScreenRect.height() + GalleryScreenUtil.getStatusBarHeight(getContext())) / 2);
        float scale = getImageViewScale(firstClickItemParameterModel);
        // 開啟縮小動畫
        executeZoom(mScaleImageView, translationX, translationY, scale, false, new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {}

            @Override
            public void onAnimationEnd(Animator animation) {
                mScaleImageView.setImageDrawable(null);
                mScaleImageView.setVisibility(GONE);
                setVisibility(GONE);
            }

            @Override
            public void onAnimationCancel(Animator animation) {}

            @Override
            public void onAnimationRepeat(Animator animation) {}
        });
    }
複製程式碼

3.3 計算圖片縮放的比例

private float getImageViewScale(GalleryPhotoParameterModel photoData) {
        float scale;
        float scaleX = photoData.imageWidth / mScreenRect.width();
        float scaleY = photoData.photoHeight * 1.0f / mScaleImageView.getHeight();

        // 橫向圖片
        if (photoData.photoWidth > photoData.photoHeight) {
            // 圖片的寬高比
            float photoScale = photoData.photoWidth * 1.0f / photoData.photoHeight;
            // 執行動畫的ImageView寬高比
            float animationImageScale = mScaleImageView.getWidth() * 1.0f / mScaleImageView.getHeight();

            if (animationImageScale > photoScale) {
                // 動畫ImageView寬高比大於圖片寬高比的時候,需要用圖片的高度除以動畫ImageView高度的比例尺
                scale = scaleY;
            }
            else {
                scale = scaleX;
            }
        }
        // 正方形圖片
        else if (photoData.photoWidth == photoData.photoHeight) {
            if (mScaleImageView.getWidth() > mScaleImageView.getHeight()) {
                scale = scaleY;
            }
            else {
                scale = scaleX;
            }
        }
        // 縱向圖片
        else {
            scale = scaleY;
        }
        return scale;
    }
複製程式碼

3.4 執行動畫的縮放

 /**
     * 執行縮放動畫
     * @param scaleImageView
     * @param translationX
     * @param translationY
     * @param scale
     * @param isEnlarge
     */
    private void executeZoom(final ImageView scaleImageView, int translationX, int translationY, float scale, boolean isEnlarge, Animator.AnimatorListener listener) {
        float startTranslationX, startTranslationY, endTranslationX, endTranslationY;
        float startScale, endScale, startAlpha, endAlpha;

        // 放大
        if (isEnlarge) {
            startTranslationX = translationX;
            endTranslationX = 0;
            startTranslationY = translationY;
            endTranslationY = 0;
            startScale = scale;
            endScale = 1;
            startAlpha = 0f;
            endAlpha = 0.75f;
        }
        // 縮小
        else {
            startTranslationX = 0;
            endTranslationX = translationX;
            startTranslationY = 0;
            endTranslationY = translationY;
            startScale = 1;
            endScale = scale;
            startAlpha = 0.75f;
            endAlpha = 0f;
        }

        //-------縮小動畫--------
        AnimatorSet set = new AnimatorSet();
        set.play(
                ObjectAnimator.ofFloat(scaleImageView, "translationX", startTranslationX, endTranslationX))
                .with(ObjectAnimator.ofFloat(scaleImageView, "translationY", startTranslationY, endTranslationY))
                .with(ObjectAnimator.ofFloat(scaleImageView, "scaleX", startScale, endScale))
                .with(ObjectAnimator.ofFloat(scaleImageView, "scaleY", startScale, endScale))
                // ---Alpha動畫---
                // mMaskView伴隨著一個Alpha減小動畫
                .with(ObjectAnimator.ofFloat(maskView, "alpha", startAlpha, endAlpha));
        set.setDuration(animDuration);
        if (listener != null) {
            set.addListener(listener);
        }
        set.setInterpolator(new DecelerateInterpolator());
        set.start();
    }
複製程式碼

改View的主要實現如上,在圖片進行縮放的時候,要考慮的情況:短邊適配、圖片原尺寸的寬高、展示圖片的ImageView的寬高比、橫豎屏時螢幕的尺寸。在此非常感謝震哥的幫助、抱拳了!老鐵。如有更多想法的小夥伴。請移步我的github GalleryView地址

相關文章