1032 lines
44 KiB
Kotlin
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
|
|
}
|
|
}
|
|
} |