Files
assistant-android/app/src/main/java/com/gh/gamecenter/ImageViewerActivity.kt

1032 lines
44 KiB
Kotlin

package com.gh.gamecenter
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.PointF
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.util.Base64
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.Window
import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.transition.*
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipeline
import com.facebook.imagepipeline.request.ImageRequest
import com.gh.common.constant.Config
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.ImageUtils.getTransformedUrl
import com.gh.gamecenter.common.view.DraggableBigImageView
import com.gh.gamecenter.common.view.Gh_RelativeLayout
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.ActivityViewimageBinding
import com.gh.gamecenter.entity.ImageInfoEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.github.piasy.biv.view.BigImageView
import com.github.piasy.biv.view.FrescoImageViewFactory
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.math.abs
import kotlin.math.roundToInt
/**
* 查看游戏截图页面
*
* @author 黄壮华
*
*/
class ImageViewerActivity : BaseActivity(), OnPageChangeListener {
private var mScale = 0F
private var mAnimating = false
private var mImageRatio = 1F
private var mViewRatio = 1F
lateinit var mViewPager: ViewPager
lateinit var mProgressHint: TextView
lateinit var mIndicatorMask: View
lateinit var mIndicatorTv: TextView
lateinit var mBackgroundView: View
lateinit var mSavePicBtn: View
lateinit var mArticleDetailBtn: View
private lateinit var mBinding: ActivityViewimageBinding
private var adapter: ViewImageAdapter? = null
private var mImagePipeline: ImagePipeline? = null
private var mShowBase64Image = false
private var mUrlList: ArrayList<String>? = null
private var mViewedSet: HashSet<Int>? = null // 让调用者知道该图片是否被看过了
private var mImageInfoMap: MutableMap<String, ImageInfoEntity>? = null
private var mBigImageView: BigImageView? = null
private var mContainer: ViewGroup? = null
private var mContainerMap = SparseArray<ViewGroup>()
private var mFinalUrl = ""
private var mInitialPosition = 0
private var mUseEnterAndExitAnimation = false
private var mShowSaveBtn = false
private var mAnswerEntity: AnswerEntity? = null
private var mIsFromImageContainerView = false
private var mLimitWidth = 0
private var mOriginLeftList: ArrayList<Int>? = null
private var mOriginTopList: ArrayList<Int>? = null
private var mOriginHeightList: ArrayList<Int>? = null
private var mOriginWidthList: ArrayList<Int>? = null
private var mOriginLeft = 0
private var mOriginTop = 0
private var mOriginHeight = 0
private var mOriginWidth = 0
private var mOriginCenterX = 0
private var mOriginCenterY = 0
private var mOriginHeightWidthRatio = 0f
private var mTargetHeight = 0f
private var mTargetWidth = 0f
private var mScaleX = 0f
private var mScaleY = 0f
private var mTranslationX = 0f
private var mTranslationY = 0f
private var mTargetCenterX = 0f
private var mTargetCenterY = 0f
override fun getLayoutId() = R.layout.activity_viewimage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityViewimageBinding.bind(mContentView)
mViewPager = mBinding.imageDetailPage
mProgressHint = mBinding.imageDetailProgress
mIndicatorMask = mBinding.imageMask
mIndicatorTv = mBinding.imageIndicatorTv
mBackgroundView = mBinding.backgroundView
mSavePicBtn = mBinding.btnSavePic
mArticleDetailBtn = mBinding.btnArticleDetail
mViewedSet = HashSet()
mImageInfoMap = HashMap()
mImagePipeline = Fresco.getImagePipeline()
mInitialPosition = savedInstanceState?.getInt(EntranceConsts.KEY_CURRENTITEM, 0) ?: 0
intent.extras?.let {
if (it.getBoolean(KEY_BASE64)) {
mShowBase64Image = true
mUrlList = ArrayList()
mUrlList?.add(base64Image)
} else {
mUrlList = it.getStringArrayList(KEY_URL_LIST) ?: arrayListOf()
mInitialPosition = it.getInt(KEY_CURRENT, 0)
}
mShowSaveBtn = it.getBoolean(KEY_SHOW_SAVE)
mUseEnterAndExitAnimation = it.getBoolean(KEY_USE_ENTER_AND_EXIT_ANIMATION)
mAnswerEntity = it.getParcelable(AnswerEntity::class.java.name)
mOriginLeftList = it.getIntegerArrayList(KEY_LEFT)
mOriginTopList = it.getIntegerArrayList(KEY_TOP)
mOriginHeightList = it.getIntegerArrayList(KEY_HEIGHT)
mOriginWidthList = it.getIntegerArrayList(KEY_WIDTH)
mIsFromImageContainerView = it.getBoolean(KEY_IS_FROM_IMAGE_CONTAINER_VIEW)
}
if (mUrlList == null || mUrlList?.isEmpty() == true) {
ToastUtils.toast("无法查看大图")
finish()
}
mSavePicBtn.visibleIf(mShowSaveBtn)
mArticleDetailBtn.visibleIf(mAnswerEntity != null)
mIndicatorMask.goneIf(mUrlList?.size == 1)
mIndicatorTv.text = String.format("%d/%d", mInitialPosition + 1, mUrlList!!.size)
// init slide
val widthPixels = DisplayUtils.getScreenWidth()
mLimitWidth = if (NetworkUtils.isWifiOr4GConnected(this)) {
widthPixels * 2
} else {
widthPixels
}
// init viewPage
adapter = ViewImageAdapter()
mViewPager.adapter = adapter
mViewPager.currentItem = mInitialPosition
mViewPager.addOnPageChangeListener(this)
mProgressHint.setOnClickListener {
val position = mViewPager.currentItem
val `object`: Any? = mViewPager.findViewWithTag(position)
if (`object` != null) {
val view = `object` as RelativeLayout
val imageView: BigImageView = view.findViewById(R.id.viewimage_iv_show)
val url = mUrlList!![position]
imageView.showImage(Uri.parse(url))
imageView.setImageLoaderCallback(object : SimpleImageLoader() {
@SuppressLint("SetTextI18n")
override fun onProgress(progress: Int) {
if (position == mViewPager.currentItem) { // 防止下载过程中切换图片
if (progress < 100) {
mProgressHint.text = "$progress%"
} else {
mProgressHint.text = "已完成"
mBaseHandler.postDelayed({
if (position == mViewPager.currentItem) { // 防止等待过程中切换图片
mProgressHint.visibility = View.GONE
}
}, 500)
}
}
}
})
}
}
mSavePicBtn.setOnClickListener {
checkStoragePermissionBeforeAction {
mBigImageView?.currentImageFile?.run {
ImageUtils.saveImageToFile(this, mFinalUrl)
}
}
}
mArticleDetailBtn.setOnClickListener {
val intent = ArticleDetailActivity.getIntent(
this,
CommunityEntity(
if (!mAnswerEntity?.communityId.isNullOrEmpty()) mAnswerEntity?.communityId
?: "" else mAnswerEntity?.articleCommunityId ?: "",
mAnswerEntity?.communityName ?: ""
),
mAnswerEntity?.id
?: "",
mEntrance,
""
)
startActivity(intent)
finish()
}
initEnterAnimation()
DisplayUtils.transparentStatusAndNavigation(this)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(EntranceConsts.KEY_CURRENTITEM, mViewPager.currentItem)
}
override fun onDestroy() {
super.onDestroy()
if (mShowBase64Image) {
mUrlList?.clear()
base64Image = ""
}
mContainerMap.clear()
}
@SuppressLint("SetTextI18n")
override fun onPageScrolled(
position: Int, positionOffset: Float,
positionOffsetPixels: Int
) {
if (positionOffset != 0f) {
mProgressHint.visibility = View.GONE
} else {
val url = mUrlList!![position]
val imageInfoEntity = mImageInfoMap!![url]
if (imageInfoEntity?.fileSize != null &&
!mImagePipeline!!.isInBitmapMemoryCache(ImageRequest.fromUri(url)) &&
!mImagePipeline!!.isInDiskCacheSync(ImageRequest.fromUri(url))
) {
val size = String.format(
Locale.CHINA, "%.1fM",
Integer.valueOf(imageInfoEntity.fileSize.value) / 1024f / 1024f
)
mProgressHint.visibility = View.VISIBLE
mProgressHint.text = "查看原图($size)"
val layoutParams = mProgressHint.layoutParams
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
mProgressHint.layoutParams = layoutParams
}
}
mViewedSet?.add(position)
setResult(Activity.RESULT_OK, Intent().putExtra(VIEWED_IMAGE, mViewedSet))
}
private fun updateOriginPosition(position: Int) {
if (position >= mOriginLeftList?.size ?: 0 || position >= mOriginTopList?.size ?: 0 || position >= mOriginHeightList?.size ?: 0 || position >= mOriginWidthList?.size ?: 0) return
mContainer = mContainerMap[position]
if (mContainer != null) {
mBigImageView = mContainerMap[position].findViewById(R.id.viewimage_iv_show)
}
mOriginLeft = mOriginLeftList?.safelyGetInRelease(position) ?: 0
mOriginTop = mOriginTopList?.safelyGetInRelease(position) ?: 0
mOriginHeight = mOriginHeightList?.safelyGetInRelease(position) ?: 0
mOriginWidth = mOriginWidthList?.safelyGetInRelease(position) ?: 0
resizeImage(position)
mOriginCenterX = mOriginLeft + mOriginWidth / 2
mOriginCenterY = mOriginTop + mOriginHeight / 2
mOriginHeightWidthRatio = mOriginHeight.toFloat() / mOriginWidth
mScaleX = mOriginWidth.toFloat() / mTargetWidth
mScaleY = mScaleX
mTranslationX = mOriginCenterX - mTargetCenterX
mTranslationY = mOriginCenterY - mTargetCenterY
}
// 处理非等比例缩放的图片
private fun resizeImage(position: Int) {
mBigImageView?.ssiv?.run {
mScale = scale
mImageRatio = sWidth / sHeight.toFloat()
mViewRatio = mOriginWidth / mOriginHeight.toFloat()
if (!shouldResize()) return
if (mViewRatio < mImageRatio) {
mOriginWidth = (mOriginHeight * mImageRatio).round2Int()
mOriginLeft -= (mOriginWidth - (mOriginWidthList?.safelyGetInRelease(position) ?: 0)) / 2
} else {
mOriginHeight = (mOriginWidth / mImageRatio).round2Int()
if (justChangeBoundsAndTransform()) return
mOriginTop -= (mOriginHeight - (mOriginHeightList?.safelyGetInRelease(position) ?: 0)) / 2
}
}
}
private fun Float.round2Int() = if (!isNaN()) roundToInt() else 0
override fun onPageSelected(position: Int) {
/*var ghRelativeLayout: Gh_RelativeLayout?
for (i in 0 until mViewPager.childCount) {
if (mViewPager.getChildAt(i).tag != null) {
ghRelativeLayout = mViewPager.getChildAt(i) as? Gh_RelativeLayout
if (ghRelativeLayout == null) {
return
}
val imageView: BigImageView = ghRelativeLayout.findViewById(R.id.viewimage_iv_show)
val ssiv = imageView.ssiv
ssiv?.resetScaleAndCenter()
}
}*/
mIndicatorTv.text = String.format("%d/%d", position + 1, mUrlList!!.size)
}
override fun onPageScrollStateChanged(newState: Int) {}
private fun initEnterAnimation() {
mViewPager.viewTreeObserver
.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
mViewPager.viewTreeObserver.removeOnGlobalLayoutListener(this)
mOriginLeft = mOriginLeftList?.safelyGetInRelease(mInitialPosition) ?: 0
mOriginTop = mOriginTopList?.safelyGetInRelease(mInitialPosition) ?: 0
mOriginHeight = mOriginHeightList?.safelyGetInRelease(mInitialPosition) ?: 0
mOriginWidth = mOriginWidthList?.safelyGetInRelease(mInitialPosition) ?: 0
mOriginCenterX = mOriginLeft + mOriginWidth / 2
mOriginCenterY = mOriginTop + mOriginHeight / 2
mOriginHeightWidthRatio = mOriginHeight.toFloat() / mOriginWidth
val location = IntArray(2)
mBigImageView?.getLocationOnScreen(location)
mTargetHeight = mBigImageView?.height?.toFloat() ?: 0F
mTargetWidth = mBigImageView?.width?.toFloat() ?: 0F
mScaleX = if (mOriginWidth == 0 && mTargetWidth == 0F) 1F else mOriginWidth.toFloat() / mTargetWidth
mScaleY = mScaleX
mTargetCenterX = location[0] + mTargetWidth / 2
mTargetCenterY = location[1] + mTargetHeight / 2
mTranslationX = mOriginCenterX - mTargetCenterX
mTranslationY = mOriginCenterY - mTargetCenterY
if (mUseEnterAndExitAnimation) {
mBigImageView?.translationX = mTranslationX
mBigImageView?.translationY = mTranslationY
if (!mScaleX.isNaN() && mScaleX < Float.MAX_VALUE) {
mBigImageView?.scaleX = mScaleX
mBigImageView?.scaleY = mScaleY
}
}
performEnterAnimation()
}
})
}
private fun loadImageInfo(position: Int, width: Int) {
val url = mUrlList!![position]
RetrofitManager.getInstance()
.api.getImageInfo("$url?x-oss-process=image/info")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<ImageInfoEntity?>() {
override fun onResponse(response: ImageInfoEntity?) {
if (response?.imageWidth != null && Integer.valueOf(response.imageWidth.value) > width) {
mImageInfoMap!![url] = response
if (position == mViewPager.currentItem) {
onPageScrolled(position, 0f, 0) // 刷新下载原图提示按钮
}
}
}
})
}
private fun loadImage(
thumbnailUrl: String,
compressedUrl: String?,
imageView: BigImageView
) {
if (TextUtils.isEmpty(thumbnailUrl)) return
if (thumbnailUrl.startsWith("data:image/png;base64")) {
runOnIoThread {
val base64String = thumbnailUrl.replace("data:image/png;base64", "")
try {
val imageFile = File(cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".png")
val decodedString = Base64.decode(base64String, Base64.DEFAULT)
val bos = BufferedOutputStream(FileOutputStream(imageFile))
bos.write(decodedString)
bos.flush()
bos.close()
runOnUiThread {
imageView.setImageViewFactory(FrescoImageViewFactory())
imageView.showImage(Uri.fromFile(imageFile))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
} else {
// 添加GIF支持
imageView.setImageViewFactory(FrescoImageViewFactory())
imageView.setThumbnailScaleType(ImageView.ScaleType.FIT_CENTER)
imageView.showImage(Uri.parse(thumbnailUrl), Uri.parse(compressedUrl))
}
}
private fun finishWithAnimation(fadeOnly: Boolean) {
val animatorSet = AnimatorSet()
val translateXAnimator = ValueAnimator.ofFloat(0F, mTranslationX).apply {
addUpdateListener { va -> mBigImageView?.x = (va.animatedValue as Float) }
}
val translateYAnimator = ValueAnimator.ofFloat(0F, mTranslationY).apply {
addUpdateListener { va -> mBigImageView?.y = (va.animatedValue as Float) }
}
val scaleYAnimator = ValueAnimator.ofFloat(1F, mScaleY).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
mBigImageView?.scaleY = (va.animatedValue as Float)
}
}
}
val scaleXAnimator = ValueAnimator.ofFloat(1F, mScaleX).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
mBigImageView?.scaleX = (va.animatedValue as Float)
}
}
}
val backgroundAlphaAnimation = ValueAnimator.ofFloat(1F, 0F).apply {
addUpdateListener { va -> mBackgroundView.alpha = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(1F, 0F).apply {
addUpdateListener { va -> mViewPager.alpha = (va.animatedValue as Float) }
}
if (mUseEnterAndExitAnimation && !fadeOnly) {
if (mFinalUrl.contains(".gif") || mUrlList!![mViewPager.currentItem].contains(".gif")) {
animatorSet.apply {
playTogether(
translateXAnimator,
translateYAnimator,
scaleXAnimator,
scaleYAnimator,
backgroundAlphaAnimation
)
duration = ANIMATION_DURATION
doOnStart { mIndicatorMask.visibility = View.GONE }
doOnEnd {
it.removeAllListeners()
finish()
overridePendingTransition(0, 0)
}
}.start()
} else {
startEndTransition()
}
} else {
animatorSet.apply {
playTogether(alphaAnimator, backgroundAlphaAnimation)
duration = ANIMATION_DURATION
doOnStart { mIndicatorMask.visibility = View.GONE }
doOnEnd {
it.removeAllListeners()
finish()
overridePendingTransition(0, 0)
}
}.start()
}
}
private fun shouldResize() = abs(mImageRatio - mViewRatio) > RATIO_DIFF
private fun justChangeBoundsAndTransform() =
mImageRatio < mViewRatio && mIsFromImageContainerView && adapter?.count == 1
private fun startEndTransition() {
if (mAnimating) return
val doResizeTransition = Runnable {
TransitionManager.beginDelayedTransition(mContainer as ViewGroup, TransitionSet().apply {
addTransition(ChangeBounds())
addTransition(ChangeTransform())
if (!justChangeBoundsAndTransform()) addTransition(ResizeTransition())
duration = RESIZE_DURATION
interpolator = DecelerateInterpolator()
addListener(object : TransitionListenerAdapter() {
override fun onTransitionStart(transition: Transition) {
mAnimating = true
}
override fun onTransitionEnd(transition: Transition) {
if (!mAnimating) return
mAnimating = false
this@ImageViewerActivity.finish()
overridePendingTransition(0, 0)
}
})
})
mBigImageView?.ssiv?.run {
layoutParams = layoutParams.apply {
if (mViewRatio < mImageRatio) {
width = mOriginWidthList?.safelyGetInRelease(mViewPager.currentItem) ?: 0
} else {
height = mOriginHeightList?.safelyGetInRelease(mViewPager.currentItem) ?: 0
}
if (this is ViewGroup.MarginLayoutParams) {
if (mViewRatio < mImageRatio) {
leftMargin = mOriginLeftList?.safelyGetInRelease(mViewPager.currentItem) ?: 0
} else {
topMargin = mOriginTopList?.safelyGetInRelease(mViewPager.currentItem) ?: 0
}
}
}
}
}
val doTransition = Runnable {
TransitionManager.beginDelayedTransition(mContainer as ViewGroup, TransitionSet().apply {
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(SsivTransition())
duration = ANIMATION_DURATION
interpolator = DecelerateInterpolator()
addListener(object : TransitionListenerAdapter() {
override fun onTransitionStart(transition: Transition) {
mAnimating = true
mIndicatorMask.visibility = View.GONE
}
override fun onTransitionEnd(transition: Transition) {
if (!mAnimating) return
mAnimating = false
if (shouldResize()) {
mContainer?.post(doResizeTransition)
} else {
this@ImageViewerActivity.finish()
overridePendingTransition(0, 0)
}
}
})
})
mBackgroundView.animate().setDuration(ANIMATION_DURATION).alpha(0F).start()
mBigImageView?.run {
scaleX = 1F
scaleY = 1F
translationX = 0F
translationY = 0F
}
mBigImageView?.ssiv?.run {
layoutParams = layoutParams.apply {
width = mOriginWidth
height = mOriginHeight
if (this is ViewGroup.MarginLayoutParams) {
leftMargin = mOriginLeft
topMargin = mOriginTop
}
}
}
}
mContainer?.post(doTransition)
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
lifecycle.removeObserver(this)
mAnimating = false
mContainer?.removeCallbacks(doTransition)
if (shouldResize()) mContainer?.removeCallbacks(doResizeTransition)
TransitionManager.endTransitions(mContainer as ViewGroup)
}
}
})
}
private fun performEnterAnimation() {
val animatorSet = AnimatorSet()
val translateXAnimator = ValueAnimator.ofFloat(mBigImageView!!.x, 0F).apply {
addUpdateListener { va -> mBigImageView?.x = (va.animatedValue as Float) }
}
val translateYAnimator = ValueAnimator.ofFloat(mBigImageView!!.y, 0F).apply {
addUpdateListener { va -> mBigImageView?.y = (va.animatedValue as Float) }
}
val scaleYAnimator = ValueAnimator.ofFloat(mScaleY, 1F).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
mBigImageView?.scaleY = (va.animatedValue as Float)
}
}
}
val scaleXAnimator = ValueAnimator.ofFloat(mScaleX, 1F).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
mBigImageView?.scaleX = (va.animatedValue as Float)
}
}
}
val backgroundAlphaAnimator = ValueAnimator.ofFloat(0F, 1F).apply {
addUpdateListener { va -> mBackgroundView.alpha = (va.animatedValue as Float) }
}
animatorSet.apply {
if (mUseEnterAndExitAnimation) {
playTogether(
translateXAnimator,
translateYAnimator,
scaleXAnimator,
scaleYAnimator,
backgroundAlphaAnimator
)
} else {
playTogether(backgroundAlphaAnimator)
}
duration = ANIMATION_DURATION
doOnStart { mIndicatorMask.visibility = View.GONE }
doOnEnd {
if (mUrlList?.size != 1 || mAnswerEntity != null) {
mIndicatorMask.visibility = View.VISIBLE
}
}
}.start()
}
private fun performExitAnimation(view: DraggableBigImageView, scale: Float, fadeOnly: Boolean) {
val finalScale = mOriginWidth / mTargetWidth
val finalTranslationX = mOriginLeft - (1 - finalScale) * mTargetWidth / 2
val finalTranslationY =
mOriginTop - ((1 - finalScale) * mTargetHeight + (mTargetHeight * finalScale - mOriginHeight)) / 2
val animatorSet = AnimatorSet()
val scaleAnimator = ValueAnimator.ofFloat(scale, finalScale).apply {
addUpdateListener { va ->
if (va.animatedValue is Float && !(va.animatedValue as Float).isNaN() && (va.animatedValue as Float) < Float.MAX_VALUE) {
view.scaleX = (va.animatedValue as Float)
view.scaleY = (va.animatedValue as Float)
}
}
}
val translateXAnimator = ValueAnimator.ofFloat(view.x, finalTranslationX).apply {
addUpdateListener { va -> view.translationX = (va.animatedValue as Float) }
}
val translateYAnimator = ValueAnimator.ofFloat(view.y, finalTranslationY).apply {
addUpdateListener { va -> view.translationY = (va.animatedValue as Float) }
}
val backgroundAlphaAnimator = ValueAnimator.ofFloat(mBackgroundView.alpha, 0F).apply {
addUpdateListener { va -> mBackgroundView.alpha = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(1F, 0F).apply {
addUpdateListener { va -> view.alpha = (va.animatedValue as Float) }
}
if (mUseEnterAndExitAnimation && !fadeOnly) {
if (mFinalUrl.contains(".gif") || mUrlList!![mViewPager.currentItem].contains(".gif")) {
animatorSet.apply {
playTogether(scaleAnimator, translateXAnimator, translateYAnimator, backgroundAlphaAnimator)
doOnEnd {
it.removeAllListeners()
finish()
overridePendingTransition(0, 0)
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
}.start()
} else {
startEndTransition()
}
} else {
animatorSet.apply {
playTogether(backgroundAlphaAnimator, alphaAnimator)
doOnEnd {
it.removeAllListeners()
finish()
overridePendingTransition(0, 0)
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
}.start()
}
}
private inner class ResizeTransition : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) {
transitionValues.values["width"] = transitionValues.view.width
transitionValues.values["height"] = transitionValues.view.height
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
val startWidth = (startValues?.values?.get("width") as Int?) ?: 0
val endWidth = (endValues?.values?.get("width") as Int?) ?: 0
val startHeight = (startValues?.values?.get("height") as Int?) ?: 0
val endHeight = (endValues?.values?.get("height") as Int?) ?: 0
val firstValue = if (mViewRatio < mImageRatio) startWidth.toFloat() else startHeight.toFloat()
val secondValue = if (mViewRatio < mImageRatio) endWidth.toFloat() else endHeight.toFloat()
return ObjectAnimator.ofFloat(firstValue, secondValue).apply {
addUpdateListener {
sceneRoot.findViewById<BigImageView>(R.id.viewimage_iv_show)?.ssiv?.run {
maxScale = scale
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP)
resetScaleAndCenter()
}
}
}
}
}
private inner class SsivTransition : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) {
transitionValues.values["width"] = transitionValues.view.width
transitionValues.values["height"] = transitionValues.view.height
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
val startHeight = (startValues?.values?.get("height") as Int?) ?: 0
val endHeight = (endValues?.values?.get("height") as Int?) ?: 0
return ObjectAnimator.ofFloat(startHeight.toFloat(), endHeight.toFloat()).apply {
addUpdateListener {
sceneRoot.findViewById<BigImageView>(R.id.viewimage_iv_show)?.ssiv?.run {
setScaleAndCenter(minScale, PointF(sWidth / 2F, sHeight / 2F))
}
}
}
}
}
private inner class ViewImageAdapter : PagerAdapter() {
override fun getCount() = mUrlList?.size ?: 0
@SuppressLint("MissingPermission")
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val rawUrl = mUrlList!![position]
var thumbnailImageUrl = if (!mShowBase64Image) {
ImageUtils.getCachedUrl(rawUrl)
} else {
rawUrl
}
var compressedStandardImageUrl: String? = if (!mShowBase64Image && rawUrl.contains("ghzs")) {
// 用 oss.jpeg 加上 limit 以后会出现双指放大的情况
rawUrl + Config.getSettings()?.image?.oss?.gif
} else {
rawUrl
}
val imageRequest = ImageRequest.fromUri(thumbnailImageUrl)
val isInMemoryCache = mImagePipeline!!.isInBitmapMemoryCache(imageRequest)
val isInDiskCache = imageRequest != null && mImagePipeline!!.isInDiskCacheSync(imageRequest)
val view = View.inflate(container.context, R.layout.viewimage_normal_item, null) as Gh_RelativeLayout
val imageView: DraggableBigImageView = view.findViewById(R.id.viewimage_iv_show)
if (mUseEnterAndExitAnimation) {
mContainerMap.put(position, view)
}
if (mBigImageView == null) {
mBigImageView = imageView
}
imageView.setDragListener(object : DraggableBigImageView.DragListener {
override fun onRelease(draggableBigImageView: DraggableBigImageView, scale: Float) {
updateOriginPosition(mViewPager.currentItem)
performExitAnimation(draggableBigImageView, scale, isFadeOnly())
}
override fun onDrag(draggableBigImageView: DraggableBigImageView, fraction: Float) {
mBackgroundView.alpha = 1 - fraction
mIndicatorMask.visibility = View.GONE
}
override fun onRestore(draggableBigImageView: DraggableBigImageView, fraction: Float) {
mBackgroundView.alpha = 1F
// mIndicatorMask.goneIf(mUrlList?.size == 1)
if (mUrlList?.size != 1 || mAnswerEntity != null) {
mIndicatorMask.visibility = View.VISIBLE
}
}
})
// 弱网环境加载低质量图片
if (!isInMemoryCache
&& !isInDiskCache
&& !NetworkUtils.isWifiOr4GConnected(this@ImageViewerActivity)
&& !thumbnailImageUrl.contains(".gif")
) {
compressedStandardImageUrl = getTransformedUrl(thumbnailImageUrl, mLimitWidth)
thumbnailImageUrl = compressedStandardImageUrl ?: ""
}
val finalUrl = compressedStandardImageUrl
mFinalUrl = finalUrl ?: ""
imageView.setImageLoaderCallback(object : SimpleImageLoader() {
override fun onSuccess(image: File) {
if (finalUrl != mUrlList!![position]) {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(File(image.path).absolutePath, options)
loadImageInfo(position, options.outWidth) // 加载图片参数,目的是用户显示原文按钮
}
val ssiv = imageView.ssiv
if (ssiv != null) {
ssiv.maxScale = 10f // 这个缩放倍数最好很具宽高自动调节
// ssiv.setOnImageEventListener(object : DefaultOnImageEventListener() {
// override fun onReady() {
// ssiv.resetScaleAndCenter()
// }
// })
}
imageView.setOnClickListener {
onBackPressed()
}
}
})
loadImage(thumbnailImageUrl, compressedStandardImageUrl, imageView)
//长按
imageView.setOnLongClickListener {
// 下滑的时候不弹
if (imageView.isDragging()) {
return@setOnLongClickListener false
}
val dialog = Dialog(this@ImageViewerActivity)
val container1 = LinearLayout(this@ImageViewerActivity)
container1.orientation = LinearLayout.VERTICAL
container1.setBackgroundColor(Color.WHITE)
val reportTv = TextView(this@ImageViewerActivity)
reportTv.setPadding(
DisplayUtils.dip2px(this@ImageViewerActivity, 20f),
DisplayUtils.dip2px(this@ImageViewerActivity, 12f),
0,
DisplayUtils.dip2px(this@ImageViewerActivity, 12f)
)
reportTv.setText(R.string.save_pic)
reportTv.textSize = 17f
reportTv.setTextColor(ContextCompat.getColor(applicationContext, R.color.title))
reportTv.setBackgroundResource(R.drawable.textview_white_style)
val widthPixels = resources.displayMetrics.widthPixels
reportTv.layoutParams = LinearLayout.LayoutParams(
widthPixels * 9 / 10,
LinearLayout.LayoutParams.WRAP_CONTENT
)
container1.addView(reportTv)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setContentView(container1)
if (!isFinishing) {
dialog.show()
}
reportTv.setOnClickListener {
checkStoragePermissionBeforeAction {
ImageUtils.saveImageToFile(imageView.currentImageFile, finalUrl, mShowBase64Image)
dialog.cancel()
}
dialog.cancel()
}
false
}
view.tag = position
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view === `object`
}
}
private fun isFadeOnly() =
mViewPager.currentItem >= mOriginLeftList?.size ?: 0 || mOriginLeft == 0 || mOriginTop == 0
override fun onBackPressed() {
updateOriginPosition(mViewPager.currentItem)
finishWithAnimation(isFadeOnly())
}
companion object {
const val REQUEST_FOR_VIEWED_IMAGE = 921
const val VIEWED_IMAGE = "viewed_image"
const val RATIO_DIFF = 0.01F
@JvmField
var base64Image: String = ""
private const val KEY_BASE64 = "base64"
private const val KEY_URL_LIST = "urls"
private const val KEY_CURRENT = "current"
private const val KEY_SHOW_SAVE = "showSave"
private const val KEY_USE_ENTER_AND_EXIT_ANIMATION = "use_enter_and_exit_animation"
private const val KEY_IS_FROM_IMAGE_CONTAINER_VIEW = "is_from_image_container_view"
private const val KEY_LEFT = "left"
private const val KEY_TOP = "top"
private const val KEY_HEIGHT = "height"
private const val KEY_WIDTH = "width"
private const val ANIMATION_DURATION = 350L
private const val RESIZE_DURATION = 100L
@JvmStatic
fun getBase64Intent(context: Context?, showSingleBase64Image: Boolean): Intent {
val checkIntent = Intent(context, ImageViewerActivity::class.java)
checkIntent.putExtra(KEY_BASE64, showSingleBase64Image)
return checkIntent
}
@JvmStatic
fun getIntent(context: Context, list: ArrayList<String>, position: Int = 0, entrance: String?): Intent {
return getIntent(context, list, position, null, entrance, false)
}
@JvmStatic
fun getIntent(
context: Context,
list: ArrayList<String>,
position: Int = 0,
originalViewList: List<View>? = null,
entrance: String?
): Intent {
return getIntent(context, list, position, originalViewList, entrance, false, null)
}
@JvmStatic
fun getIntent(
context: Context,
list: ArrayList<String>,
position: Int = 0,
originalViewList: List<View>? = null,
answerEntity: AnswerEntity? = null,
entrance: String?,
isFromICV: Boolean = false
): Intent {
return getIntent(context, list, position, originalViewList, entrance, false, answerEntity, isFromICV)
}
/**
* 传入 viewList 代表使用渐入渐出动画
*/
@JvmStatic
fun getIntent(
context: Context,
list: ArrayList<String>,
position: Int = 0,
originalViewList: List<View>? = null,
entrance: String?,
isShowSaveBtn: Boolean,
answerEntity: AnswerEntity? = null,
isFromICV: Boolean = false
): Intent {
val intent = Intent(context, ImageViewerActivity::class.java)
intent.putExtra(KEY_URL_LIST, list)
intent.putExtra(KEY_CURRENT, position)
intent.putExtra(KEY_SHOW_SAVE, isShowSaveBtn)
intent.putExtra(AnswerEntity::class.java.name, answerEntity)
intent.putExtra(EntranceConsts.KEY_ENTRANCE, entrance)
intent.putExtra(KEY_IS_FROM_IMAGE_CONTAINER_VIEW, isFromICV)
if (originalViewList != null) {
val leftList = arrayListOf<Int>()
val topList = arrayListOf<Int>()
val heightList = arrayListOf<Int>()
val widthList = arrayListOf<Int>()
for (originalView in originalViewList) {
val location = IntArray(2)
originalView.getLocationOnScreen(location)
leftList.add(location[0])
topList.add(location[1])
heightList.add(originalView.height)
widthList.add(originalView.width)
}
intent.putExtra(KEY_LEFT, leftList)
intent.putExtra(KEY_TOP, topList)
intent.putExtra(KEY_HEIGHT, heightList)
intent.putExtra(KEY_WIDTH, widthList)
intent.putExtra(KEY_USE_ENTER_AND_EXIT_ANIMATION, true)
}
if (context is Activity) {
context.overridePendingTransition(0, 0)
}
return intent
}
}
}