Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin version #772

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
427 changes: 427 additions & 0 deletions photoview/CircleImageView.kt

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions photoview/Compat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2011, 2012 Chris Banes.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.github.chrisbanes.photoview

import android.annotation.TargetApi
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.view.View

internal object Compat {
private const val SIXTY_FPS_INTERVAL = 1000 / 60
fun postOnAnimation(view: View, runnable: Runnable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
postOnAnimationJellyBean(view, runnable)
} else {
view.postDelayed(runnable, SIXTY_FPS_INTERVAL.toLong())
}
}

@TargetApi(16)
private fun postOnAnimationJellyBean(view: View, runnable: Runnable) {
view.postOnAnimation(runnable)
}
}
186 changes: 186 additions & 0 deletions photoview/CustomGestureDetector.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
Copyright 2011, 2012 Chris Banes.
<p/>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
<p/>
http://www.apache.org/licenses/LICENSE-2.0
<p/>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.github.chrisbanes.photoview

import android.content.Context
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.ScaleGestureDetector.OnScaleGestureListener
import android.view.VelocityTracker
import android.view.ViewConfiguration

/**
* Does a whole lot of gesture detecting.
*/
internal class CustomGestureDetector() {
private var mActivePointerId = INVALID_POINTER_ID
private var mActivePointerIndex = 0
private var mDetector: ScaleGestureDetector? = null
private var mVelocityTracker: VelocityTracker? = null
var isDragging = false
private set
private var mLastTouchX = 0f
private var mLastTouchY = 0f
private var mTouchSlop: Float = 0.0f
private var mMinimumVelocity: Float = 0.0f
private lateinit var mListener: OnGestureListener
private fun getActiveX(ev: MotionEvent): Float {
return try {
ev.getX(mActivePointerIndex)
} catch (e: Exception) {
ev.x
}
}

private fun getActiveY(ev: MotionEvent): Float {
return try {
ev.getY(mActivePointerIndex)
} catch (e: Exception) {
ev.y
}
}

val isScaling: Boolean
get() = mDetector!!.isInProgress

fun onTouchEvent(ev: MotionEvent): Boolean {
return try {
mDetector!!.onTouchEvent(ev)
processTouchEvent(ev)
} catch (e: IllegalArgumentException) {
// Fix for support lib bug, happening when onDestroy is called
true
}
}

private fun processTouchEvent(ev: MotionEvent): Boolean {
val action = ev.action
when (action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
mActivePointerId = ev.getPointerId(0)
mVelocityTracker = VelocityTracker.obtain()
if (null != mVelocityTracker) {
mVelocityTracker!!.addMovement(ev)
}
mLastTouchX = getActiveX(ev)
mLastTouchY = getActiveY(ev)
isDragging = false
}
MotionEvent.ACTION_MOVE -> {
val x = getActiveX(ev)
val y = getActiveY(ev)
val dx = x - mLastTouchX
val dy = y - mLastTouchY
if (!isDragging) {
// Use Pythagoras to see if drag length is larger than
// touch slop
isDragging = Math.sqrt(dx * dx + (dy * dy).toDouble()) >= mTouchSlop
}
if (isDragging) {
mListener.onDrag(dx, dy)
mLastTouchX = x
mLastTouchY = y
if (null != mVelocityTracker) {
mVelocityTracker!!.addMovement(ev)
}
}
}
MotionEvent.ACTION_CANCEL -> {
mActivePointerId = INVALID_POINTER_ID
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker!!.recycle()
mVelocityTracker = null
}
}
MotionEvent.ACTION_UP -> {
mActivePointerId = INVALID_POINTER_ID
if (isDragging) {
if (null != mVelocityTracker) {
mLastTouchX = getActiveX(ev)
mLastTouchY = getActiveY(ev)

// Compute velocity within the last 1000ms
mVelocityTracker!!.addMovement(ev)
mVelocityTracker!!.computeCurrentVelocity(1000)
val vX = mVelocityTracker!!.xVelocity
val vY = mVelocityTracker!!
.yVelocity

// If the velocity is greater than minVelocity, call
// listener
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
mListener.onFling(mLastTouchX, mLastTouchY, -vX,
-vY)
}
}
}

// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker!!.recycle()
mVelocityTracker = null
}
}
MotionEvent.ACTION_POINTER_UP -> {
val pointerIndex = Util.getPointerIndex(ev.action)
val pointerId = ev.getPointerId(pointerIndex)
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
val newPointerIndex = if (pointerIndex == 0) 1 else 0
mActivePointerId = ev.getPointerId(newPointerIndex)
mLastTouchX = ev.getX(newPointerIndex)
mLastTouchY = ev.getY(newPointerIndex)
}
}
}
mActivePointerIndex = ev
.findPointerIndex(if (mActivePointerId != INVALID_POINTER_ID) mActivePointerId else 0)
return true
}

companion object {
private const val INVALID_POINTER_ID = -1
}

constructor(context: Context?, listener: OnGestureListener) : this() {
val configuration = ViewConfiguration.get(context)
mMinimumVelocity = configuration.scaledMinimumFlingVelocity.toFloat()
mTouchSlop = configuration.scaledTouchSlop.toFloat()
mListener = listener
val mScaleListener: OnScaleGestureListener = object : OnScaleGestureListener {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scaleFactor = detector.scaleFactor
if (java.lang.Float.isNaN(scaleFactor) || java.lang.Float.isInfinite(scaleFactor)) return false
if (scaleFactor >= 0) {
mListener.onScale(scaleFactor,
detector.focusX, detector.focusY)
}
return true
}

override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
return true
}

override fun onScaleEnd(detector: ScaleGestureDetector) {
// NO-OP
}
}
mDetector = ScaleGestureDetector(context, mScaleListener)
}
}
24 changes: 24 additions & 0 deletions photoview/OnGestureListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Copyright 2011, 2012 Chris Banes.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.github.chrisbanes.photoview

internal interface OnGestureListener {
fun onDrag(dx: Float, dy: Float)
fun onFling(startX: Float, startY: Float, velocityX: Float,
velocityY: Float)

fun onScale(scaleFactor: Float, focusX: Float, focusY: Float)
}
17 changes: 17 additions & 0 deletions photoview/OnMatrixChangedListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.chrisbanes.photoview

import android.graphics.RectF

/**
* Interface definition for a callback to be invoked when the internal Matrix has changed for
* this View.
*/
interface OnMatrixChangedListener {
/**
* Callback for when the Matrix displaying the Drawable has changed. This could be because
* the View's bounds have changed, or the user has zoomed.
*
* @param rect - Rectangle displaying the Drawable's new bounds.
*/
fun onMatrixChanged(rect: RectF)
}
13 changes: 13 additions & 0 deletions photoview/OnOutsidePhotoTapListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.chrisbanes.photoview

import android.widget.ImageView

/**
* Callback when the user tapped outside of the photo
*/
interface OnOutsidePhotoTapListener {
/**
* The outside of the photo has been tapped
*/
fun onOutsidePhotoTap(imageView: ImageView?)
}
21 changes: 21 additions & 0 deletions photoview/OnPhotoTapListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.chrisbanes.photoview

import android.widget.ImageView

/**
* A callback to be invoked when the Photo is tapped with a single
* tap.
*/
interface OnPhotoTapListener {
/**
* A callback to receive where the user taps on a photo. You will only receive a callback if
* the user taps on the actual photo, tapping on 'whitespace' will be ignored.
*
* @param view ImageView the user tapped.
* @param x where the user tapped from the of the Drawable, as percentage of the
* Drawable width.
* @param y where the user tapped from the top of the Drawable, as percentage of the
* Drawable height.
*/
fun onPhotoTap(view: ImageView?, x: Float, y: Float)
}
15 changes: 15 additions & 0 deletions photoview/OnScaleChangedListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.chrisbanes.photoview

/**
* Interface definition for callback to be invoked when attached ImageView scale changes
*/
interface OnScaleChangedListener {
/**
* Callback for when the scale changes
*
* @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in)
* @param focusX focal point X position
* @param focusY focal point Y position
*/
fun onScaleChange(scaleFactor: Float, focusX: Float, focusY: Float)
}
20 changes: 20 additions & 0 deletions photoview/OnSingleFlingListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.chrisbanes.photoview

import android.view.MotionEvent

/**
* A callback to be invoked when the ImageView is flung with a single
* touch
*/
interface OnSingleFlingListener {
/**
* A callback to receive where the user flings on a ImageView. You will receive a callback if
* the user flings anywhere on the view.
*
* @param e1 MotionEvent the user first touch.
* @param e2 MotionEvent the user last touch.
* @param velocityX distance of user's horizontal fling.
* @param velocityY distance of user's vertical fling.
*/
fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean
}
15 changes: 15 additions & 0 deletions photoview/OnViewDragListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.chrisbanes.photoview

/**
* Interface definition for a callback to be invoked when the photo is experiencing a drag event
*/
interface OnViewDragListener {
/**
* Callback for when the photo is experiencing a drag event. This cannot be invoked when the
* user is scaling.
*
* @param dx The change of the coordinates in the x-direction
* @param dy The change of the coordinates in the y-direction
*/
fun onDrag(dx: Float, dy: Float)
}
15 changes: 15 additions & 0 deletions photoview/OnViewTapListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.chrisbanes.photoview

import android.view.View

interface OnViewTapListener {
/**
* A callback to receive where the user taps on a ImageView. You will receive a callback if
* the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.
*
* @param view - View the user tapped.
* @param x - where the user tapped from the left of the View.
* @param y - where the user tapped from the top of the View.
*/
fun onViewTap(view: View?, x: Float, y: Float)
}
228 changes: 228 additions & 0 deletions photoview/PhotoView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
Copyright 2011, 2012 Chris Banes.
<p>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
<p>
http://www.apache.org/licenses/LICENSE-2.0
<p>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.github.chrisbanes.photoview

import android.content.Context
import android.graphics.Matrix
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.view.GestureDetector.OnDoubleTapListener
import androidx.appcompat.widget.AppCompatImageView


class PhotoView : AppCompatImageView {
/**
* Get the current [PhotoViewAttacher] for this view. Be wary of holding on to references
* to this attacher, as it has a reference to this view, which, if a reference is held in the
* wrong place, can cause memory leaks.
*
* @return the attacher.
*/
var attacher: PhotoViewAttacher? = null
private set
private var pendingScaleType: ScaleType? = null


constructor(context: Context) : super(context) {

init()
}

@JvmOverloads
constructor(context: Context, attrs: AttributeSet, defStyle: Int = 0) : super(context, attrs, defStyle) {
init()
}

private fun init() {
attacher = PhotoViewAttacher(this)
//We always pose as a Matrix scale type, though we can change to another scale type
//via the attacher
super.setScaleType(ScaleType.MATRIX)
//apply the previously applied scale type
if (pendingScaleType != null) {
scaleType = pendingScaleType!!
pendingScaleType = null
}
}


override fun getScaleType(): ScaleType {
return attacher!!.scaleType
}

override fun getImageMatrix(): Matrix {
return attacher!!.imageMatrix
}

override fun setOnLongClickListener(l: OnLongClickListener?) {
attacher!!.setOnLongClickListener(l)
}

override fun setOnClickListener(l: OnClickListener?) {
attacher!!.setOnClickListener(l)
}

override fun setScaleType(scaleType: ScaleType) {
if (attacher == null) {
pendingScaleType = scaleType
} else {
attacher!!.scaleType = scaleType
}
}

override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
// setImageBitmap calls through to this method
if (attacher != null) {
attacher!!.update()
}
}

override fun setImageResource(resId: Int) {
super.setImageResource(resId)
if (attacher != null) {
attacher!!.update()
}
}

override fun setImageURI(uri: Uri?) {
super.setImageURI(uri)
if (attacher != null) {
attacher!!.update()
}
}

override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
val changed = super.setFrame(l, t, r, b)
if (changed) {
attacher!!.update()
}
return changed
}

fun setRotationTo(rotationDegree: Float) {
attacher!!.setRotationTo(rotationDegree)
}

fun setRotationBy(rotationDegree: Float) {
attacher!!.setRotationBy(rotationDegree)
}

var isZoomable: Boolean
get() = attacher!!.isZoomable
set(zoomable) {
attacher!!.isZoomable = zoomable
}

val displayRect: RectF?
get() = attacher!!.displayRect

fun getDisplayMatrix(matrix: Matrix?) {
attacher!!.getDisplayMatrix(matrix!!)
}

fun setDisplayMatrix(finalRectangle: Matrix?): Boolean {
return attacher!!.setDisplayMatrix(finalRectangle)
}

fun getSuppMatrix(matrix: Matrix?) {
attacher!!.getSuppMatrix(matrix!!)
}

fun setSuppMatrix(matrix: Matrix?): Boolean {
return attacher!!.setDisplayMatrix(matrix)
}

var minimumScale: Float
get() = attacher!!.minimumScale
set(minimumScale) {
attacher!!.minimumScale = minimumScale
}

var mediumScale: Float
get() = attacher!!.mediumScale
set(mediumScale) {
attacher!!.mediumScale = mediumScale
}

var maximumScale: Float
get() = attacher!!.maximumScale
set(maximumScale) {
attacher!!.maximumScale = maximumScale
}

var scale: Float
get() = attacher!!.scale.toFloat()
set(scale) {
attacher!!.scale = scale.toDouble()
}

fun setAllowParentInterceptOnEdge(allow: Boolean) {
attacher!!.setAllowParentInterceptOnEdge(allow)
}

fun setScaleLevels(minimumScale: Float, mediumScale: Float, maximumScale: Float) {
attacher!!.setScaleLevels(minimumScale, mediumScale, maximumScale)
}

fun setOnMatrixChangeListener(listener: OnMatrixChangedListener?) {
attacher!!.setOnMatrixChangeListener(listener)
}

fun setOnPhotoTapListener(listener: OnPhotoTapListener?) {
attacher!!.setOnPhotoTapListener(listener)
}

fun setOnOutsidePhotoTapListener(listener: OnOutsidePhotoTapListener?) {
attacher!!.setOnOutsidePhotoTapListener(listener)
}

fun setOnViewTapListener(listener: OnViewTapListener?) {
attacher!!.setOnViewTapListener(listener)
}

fun setOnViewDragListener(listener: OnViewDragListener?) {
attacher!!.setOnViewDragListener(listener)
}

fun setScale(scale: Float, animate: Boolean) {
attacher!!.setScale(scale.toDouble(), animate)
}

fun setScale(scale: Float, focalX: Float, focalY: Float, animate: Boolean) {
attacher!!.setScale(scale.toDouble(), focalX, focalY, animate)
}

fun setZoomTransitionDuration(milliseconds: Int) {
attacher!!.setZoomTransitionDuration(milliseconds)
}

fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener?) {
attacher!!.setOnDoubleTapListener(onDoubleTapListener)
}

fun setOnScaleChangeListener(onScaleChangedListener: OnScaleChangedListener?) {
attacher!!.setOnScaleChangeListener(onScaleChangedListener)
}

fun setOnSingleFlingListener(onSingleFlingListener: OnSingleFlingListener?) {
attacher!!.setOnSingleFlingListener(onSingleFlingListener)
}


}
738 changes: 738 additions & 0 deletions photoview/PhotoViewAttacher.kt

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions photoview/Util.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.github.chrisbanes.photoview

import android.view.MotionEvent
import android.widget.ImageView
import android.widget.ImageView.ScaleType

internal object Util {
fun checkZoomLevels(minZoom: Float, midZoom: Float,
maxZoom: Float) {
require(minZoom < midZoom) { "Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value" }
require(midZoom < maxZoom) { "Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value" }
}

fun hasDrawable(imageView: ImageView): Boolean {
return imageView.drawable != null
}

fun isSupportedScaleType(scaleType: ScaleType?): Boolean {
if (scaleType == null) {
return false
}
when (scaleType) {
ScaleType.MATRIX -> throw IllegalStateException("Matrix scale type is not supported")
}
return true
}

fun getPointerIndex(action: Int): Int {
return action and MotionEvent.ACTION_POINTER_INDEX_MASK shr MotionEvent.ACTION_POINTER_INDEX_SHIFT
}
}