高仿微信查看大圖 放大縮小

高仿微信朋友圈,點擊查看大圖,放大 縮小,可自定義java

前言

相信你們都用過微信,在微信盆友圈中,點點擊一個小圖片的時候,會很天然的切換到大圖模式,而後再點擊一下就縮回去,這個動畫效果很是好看,爲了到達這個效果首先確認微信的動畫效果哪些android

實現目的

  • 點擊小圖 漸變切換到大圖,背景漸變成黑色
  • 點擊大圖 漸變到小圖,背景變成原來的樣式
  • 往下拖拽,背景的顏色根據往下拖拽的進度改變,越往下 顏色越淺
  • 圖片能夠放大,縮小
  • 能夠滑動
  • 小圖到大圖的過程當中有一個圖片加載進度

目前這個使咱們須要完成的需求,eumm 仍是有點煩人的git

演示效果

最終的項目效果以下圖所示github

實現講解

滑動分析

一會兒讓拿到這個需求,其實仍是比較懵逼的,不過飯要一口一口吃,既然這個是仿造微信的,他的最基本的需求就是 點擊小圖 而後顯示一個大圖,而且可以切換,分析可最基本的需求,一個activity + viewpager 搞定 打完收工,動畫什麼的都是浮雲~~緩存

滑動 viewpager + activity ,點擊跳轉到新的activity 中bash

完成了第一個基本的需求微信

產品說:"不行,要有動畫"app

項目中使用的是viewpager2 算嚐個鮮,實際使用中也沒發現問題框架

點擊小圖切換到大圖的過程 狸貓換代太子

仔細的看一下上面的動畫,一個小圖的activityA 跳轉到 大圖的 activityB ,我想到的一個思路是 當跳轉到activityB 之後,首先在B 的位置上畫一個 和A上如出一轍的imageview 而後 縮放的 大圖的位置maven

那麼問題來了,如何在 activityB 上顯示一個和activityA 上相同的imageview 呢

我想到的辦法是 將 activityA 的ImageView 記錄下來,而後在 activityB 中 將 imageview 的 寬 高 和 圖片的 scaleType 設置的和他相同 這樣圖片的大小 和樣式就和 activityA 中同樣了

如何將 activityB 的圖片位置肯定下來

咱們一開始獲得的 activityA 中的 imageview 咱們是知道他的xy 座標的 只須要將這個座標給到 activityB 中就好了

代碼以下

override fun beforeTransition( photoId: Int, fullView: ImageView, thumbnailView: ImageView ) {
        fullView.scaleType = thumbnailView.scaleType
        fullView.layoutParams = fullView.layoutParams.apply {
            width = thumbnailView.width
            height = thumbnailView.height
            val location = getLocationOnScreen(thumbnailView)
            when (fullView.parent) {
                is ConstraintLayout -> {
                    val constraintSet = ConstraintSet().apply {
                        clone(fullView.parent as ConstraintLayout)
                        clear(photoId, ConstraintSet.START)
                        clear(photoId, ConstraintSet.TOP)
                        clear(photoId, ConstraintSet.BOTTOM)
                        clear(photoId, ConstraintSet.RIGHT)
                        //從新創建約束
                        connect(
                            photoId, ConstraintSet.TOP, ConstraintSet.PARENT_ID,
                            ConstraintSet.TOP, location[1]
                        )
                        connect(
                            photoId, ConstraintSet.START, ConstraintSet.PARENT_ID,
                            ConstraintSet.START, location[0]
                        )
                    }
                    constraintSet.applyTo(fullView.parent as ConstraintLayout)
                }
                else -> {
                    if (this is ViewGroup.MarginLayoutParams) {
                        marginStart = location[0]
                        topMargin = location[1]
                    }
                }
            }


        }

    }

複製代碼

activityB 中的圖片 變大

上面的 beforeTransition 方法 咱們已經能夠 從 activityA 跳轉到 activityB 而且在activityB 中繪製了一個和activityA 中相同大小,位置,樣式的圖片,接着 咱們要把這個圖片變大。。既然是變大,確定少不了動畫,分析一下這個由小到大,

圖片的寬高不在是原來小圖的 位置也再也不是小圖的 ,樣式也再也不是小圖的,真正的變成了太子。。

override fun startTransition(fullView: ImageView, thumbnailView: ImageView) {
        fullView.scaleType = ImageView.ScaleType.FIT_CENTER
        fullView.layoutParams = fullView.layoutParams.apply {
            width = ViewGroup.LayoutParams.MATCH_PARENT
            height = ViewGroup.LayoutParams.MATCH_PARENT if (this is ViewGroup.MarginLayoutParams) {
                marginStart = 0
                topMargin = 0
            }
        }
    }

    override fun transitionSet(durationTime: Long): Transition {
        return TransitionSet().apply {
            addTransition(ChangeBounds())
            addTransition(ChangeImageTransform())
            duration = durationTime
            interpolator = DecelerateInterpolator()
        }
    }
複製代碼

背景的顏色變成黑色

拿到父類的view 改變他的背景顏色透明度 加上一個 漸變的動畫效果就好了

//修改進入的時候背景 漸變 黑色
    fun start( parent: View, originalScale: Float, duration: Long ) {
        val valueAnimator = ValueAnimator()
        valueAnimator.duration = duration
        valueAnimator.setFloatValues(originalScale, 1f)
        valueAnimator.addUpdateListener { animation ->
            parent.setBackgroundColor(
                ColorTool.getColorWithAlpha(Color.BLACK, (animation.animatedValue as Float))
            )
        }
        valueAnimator.start()
    }
複製代碼

大圖變成小圖

上面的小圖到大圖的 看官理解啦,那麼大圖到小圖 就返回來就行啦

圖片的放大,縮小

這個圖片的放大縮小,我用的是開源的框架 PhotoView

手指拖動改變大小和背景顏色

先看一下效果

分析一下 這裏有兩個動畫,

  • 下拉的位置不大,返回原來的狀態,
  • 下拉的位置很大,直接變成小圖

由於 PhotoView 不知道 圖片的 drag 因此 在 PhotoView 的基礎上進行拓展

object ImageDragHelper {


    //是否正在移動
    private var isDragIng = false

    //是否正在動畫
    var isAnimIng = false fun startDrag( image: PhotoView, holder: ViewPagerAdapter.PhotoViewHolder, listener: OnDragAnimListener ) {
        image.setOnViewDragListener(object : OnViewDragListener {
            override fun onDrag(dx: Float, dy: Float) {
            }

            override fun onScroll(x: Float, y: Float) {
                //這裏 須要判斷一下滑動的角度 防止和 viewpager 滑動衝突
                if (isDragIng) {
                    //正在播放動畫 不給滑動
                    if (isAnimIng) {
                        return
                    }
                    //圖片處於縮放狀態
                    if (image.scale != 1.0f) {
                        return
                    }
                    drag(image, x, y, listener)
                } else {
                    if (abs(x) > 30 && abs(y) > 60) {
                        //正在播放動畫 不給滑動
                        if (isAnimIng) {
                            return
                        }
                        //圖片處於縮放狀態
                        if (image.scale != 1.0f) {
                            return
                        }
                        // 一開始向上滑動無效的
                        if (y > 0) {
                            isDragIng = true drag(image, x, y, listener) } } } } override fun onScrollFinish() {
                if (isDragIng)
                    listener.onEndDrag(image,holder)
            }

            override fun onScrollStart() {
                isDragIng = false
            }
        })

    }

    private fun drag(image: PhotoView, x: Float, y: Float, listener: OnDragAnimListener) {
        listener.onStartDrag(image, x, y)
    }
}
複製代碼

知道了 x y 就好處理了

//圖片的縮放 和位置變化
            val fixedOffsetY = y - 0
            val fraction = abs(max(-1f, min(1f, fixedOffsetY / image.height)))
            val fakeScale = 1 - min(0.4f, fraction)
            image.scaleX = fakeScale
            image.scaleY = fakeScale
            image.translationY = fixedOffsetY * dampingData
            image.translationX = x / 2 * dampingData
複製代碼

判斷是縮回去 仍是 返回原來的樣子,

override fun onEndDrag( image: PhotoView, holder: ViewPagerAdapter.PhotoViewHolder ) {
            setViewPagerEnable(true)
            setZoomable(image, true)

            var fraction = setFraction()
            if (fraction > 1f) {
                fraction = 1f
            } else if (fraction < 0f) {
                fraction = 0f
            }
            if (abs(image.translationY) < image.height * fraction) {
                AnimDragHelper.start(
                    getPhotoViewId(),
                    setDuration(),
                    image,
                    getImageArrayList()[mCurrentIndex],
                    holder,
                    enterAnimListener,
                    afterTransitionListener
                )
                //背景顏色變化
                AnimBgEnterHelper.start(parentView, currentScale, setDuration())
            } else {
                exitAnim(currentScale, holder)
            }
        }
複製代碼

自定義的圖片加載器

這裏本來想着放到框架內部,又想到 郭嬸在 litepal 中說的 作減法,因此沒有放在內部,而是做爲demo 的形式放在了外部, 開發者能夠更大程度的自定義樣式

demo 用到的是 glide ,

圖片加載進度

這個功能其實不屬於框架自己的樣式,因此我放在了demno 中 供你們參考,有興趣的小夥伴能夠參考 Glide 圖片加載進度

下載

Step 1. Add the JitPack repository to your build file

Add it in your root build.gradle at the end of repositories:

allprojects {
		repositories {
			...
			maven { url 'https://www.jitpack.io' }
		}
	}
複製代碼

Step 2. Add the dependency

dependencies {
	        implementation 'com.github.JiangHaiYang01:WeChatPhoto:Tag'
	}
複製代碼

當前最新版本

使用

step1 繼承 LargerAct

繼承 LargerAct 類型可自定義 sample 中方式的是string

class SampleAct : LargerAct<String>() {


    companion object {
        const val IMAGE = "images"
        const val INDEX = "index"
    }


    //添加數據源
    override fun getData(): ArrayList<String>? {
        return intent.getStringArrayListExtra(IMAGE)
    }

    //長按事件
    override fun onLongClickListener() {
        Toast.makeText(this, "長按圖片", Toast.LENGTH_LONG).show()
    }

    //item 佈局
    override fun getItemLayout(): Int {
        return R.layout.item_def
    }

    //必定要返回一個 PhotoView 的id  內部處理仍是須要用到的
    override fun getPhotoViewId(): Int {
        return R.id.image
    }

    //當前是第幾個圖片  index 和 image 一一對應
    override fun getIndex(): Int {
        return intent.getIntExtra(INDEX, 0)
    }

    //設置持續時間
    override fun setDuration(): Long {
        return 2000
    }

    //默認拖動時候的阻尼係數   [0.0f----1.0f] 越小越難滑動
    override fun setDamping(): Float {
        return 1.0f
    }

    //設置下拉的參數 [0.0f----1.0f] 越小越容易退出
    override fun setFraction(): Float {
        return 0.5f
    }

    //設置原來的圖片源
    override fun getImageArrayList(): ArrayList<ImageView> {
        return ImagesHelper.images
    }

    //處理本身的業務邏輯
    override fun itemBindViewHolder(
        isLoadFull: Boolean,
        itemView: View,
        position: Int,
        data: String?
    ) {
        if (data == null) {
            return
        }
        //這裏用到了本身寫的一個 進度條 可自定義
        val progressView = itemView.findViewById<CircleProgressView>(R.id.progress)
        val imageView = itemView.findViewById<PhotoView>(R.id.image)

        //Glide 加載圖片的進度 具體可參考代碼
        ProgressInterceptor.addListener(data, object : ProgressListener {
            override fun onProgress(progress: Int) {
                progressView.visibility = View.VISIBLE
                progressView.progress = progress
            }
        })

        //這裏爲了演示效果  取消了緩存  正常使用是不須要的
        val options = RequestOptions()
        if (isLoadFull)
            options
                .placeholder(imageView.drawable)
                .override(imageView.width, imageView.height)
                .skipMemoryCache(true)
                .diskCacheStrategy(DiskCacheStrategy.NONE)


        Glide.with(this)
            .load(data)
            .apply(options)
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(
                    e: GlideException?,
                    model: Any?,
                    target: Target<Drawable>?,
                    isFirstResource: Boolean
                ): Boolean {
                    Log.i(TAG, "圖片加載失敗")
                    progressView.visibility = View.GONE
                    return false
                }

                override fun onResourceReady(
                    resource: Drawable?,
                    model: Any?,
                    target: Target<Drawable>?,
                    dataSource: com.bumptech.glide.load.DataSource?,
                    isFirstResource: Boolean
                ): Boolean {
                    Log.i(TAG, "圖片加載成功")
                    progressView.visibility = View.GONE
                    return false
                }
            })
            .into(imageView)
    }


}

複製代碼

step2 添加theme

<activity android:name=".larger.SampleAct" android:screenOrientation="portrait" android:theme="@style/custom_larger" />
複製代碼

step3 跳轉

private fun startAct( index: Int, images: ArrayList<String> ) {
        val intent = Intent(this, SampleAct::class.java) //傳入圖片信息 按需求自定義 intent.putStringArrayListExtra(SampleAct.IMAGE, images) //傳入當前的 index 用於處理viewpager intent.putExtra(SampleAct.INDEX, index) startActivity(intent) } 複製代碼

更新說明

0.0.2

在0.0.1 版本 實際使用的時候發現一個問題 當圖片是 FIT_XY 或者 CENTER_CROP 等 對圖片裁剪的時候 小圖到大圖的過程當中會有問題

  • 兼容了 CENTER_CROP 等狀態的處理 (MATRIX 和 CENTER 不兼容)

未處理的問題

在 本來圖片的 style 是 MATRIX 或者 CENTER 的時候,圖片不能很好的從 原來的樣式 漸漸切換到 大圖的樣式,這裏的緣由我還不知道爲啥,知道的的小夥伴能夠 說一下

apk 下載體驗

0.0.2版本

0.0.1版本

Github

點擊查看

寫在最後

這邊博客其實以前就寫完了,直接放出效果圖 + 使用方式,我想着其實寫不是關鍵,仍是想將本身寫的時候的一些心得分享一下

相關文章
相關標籤/搜索