From 9028ff6b9c86dba1c034a8849866a5f215229dcc Mon Sep 17 00:00:00 2001
From: HolyshitOvO <39211450+HolyshitOvO@users.noreply.github.com>
Date: Mon, 4 Nov 2024 18:04:32 +0800
Subject: [PATCH] 1.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
使用旧版本Glide,增加流畅度
改用另一个图片查看器库,增加流畅度
更新地图
---
PictureViewer/.gitignore | 1 +
PictureViewer/build.gradle | 30 +
PictureViewer/proguard-rules.pro | 25 +
PictureViewer/src/main/AndroidManifest.xml | 14 +
.../com/SuperKotlin/pictureviewer/Compat.java | 59 +
.../pictureviewer/CupcakeGestureDetector.java | 142 +++
.../DefaultOnDoubleTapListener.java | 98 ++
.../pictureviewer/EclairGestureDetector.java | 84 ++
.../pictureviewer/FroyoGestureDetector.java | 68 ++
.../pictureviewer/GestureDetector.java | 28 +
.../pictureviewer/GingerScroller.java | 68 ++
.../pictureviewer/HackyViewPager.java | 50 +
.../SuperKotlin/pictureviewer/IPhotoView.java | 283 +++++
.../pictureviewer/IcsScroller.java | 33 +
.../pictureviewer/ImageDetailFragment.java | 105 ++
.../pictureviewer/ImagePagerActivity.java | 115 ++
.../SuperKotlin/pictureviewer/ImageUtil.java | 100 ++
.../SuperKotlin/pictureviewer/LogManager.java | 35 +
.../com/SuperKotlin/pictureviewer/Logger.java | 116 ++
.../pictureviewer/LoggerDefault.java | 76 ++
.../pictureviewer/OnGestureListener.java | 27 +
.../SuperKotlin/pictureviewer/PhotoView.java | 258 ++++
.../pictureviewer/PhotoViewAttacher.java | 1079 +++++++++++++++++
.../pictureviewer/PictureConfig.java | 77 ++
.../pictureviewer/PreGingerScroller.java | 58 +
.../pictureviewer/ScrollerProxy.java | 48 +
.../com/SuperKotlin/pictureviewer/Tools.java | 21 +
.../VersionedGestureDetector.java | 42 +
.../src/main/res/drawable-nodpi/subway.png | Bin 0 -> 3371859 bytes
.../layout/activity_image_detail_pager.xml | 24 +
.../main/res/layout/fragment_image_detail.xml | 15 +
PictureViewer/src/main/res/values/colors.xml | 5 +
PictureViewer/src/main/res/values/strings.xml | 4 +
app/build.gradle | 23 +-
app/src/main/AndroidManifest.xml | 11 +
.../com/xiaoming/gzmetro/MainActivity.java | 9 +-
.../com/xiaoming/gzmetro/MainActivity2.java | 17 +
app/src/main/res/drawable-nodpi/pic.webp | Bin 1721216 -> 0 bytes
app/src/main/res/layout/activity_main.xml | 12 +-
settings.gradle | 1 +
40 files changed, 3241 insertions(+), 20 deletions(-)
create mode 100644 PictureViewer/.gitignore
create mode 100644 PictureViewer/build.gradle
create mode 100644 PictureViewer/proguard-rules.pro
create mode 100644 PictureViewer/src/main/AndroidManifest.xml
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Compat.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/CupcakeGestureDetector.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/DefaultOnDoubleTapListener.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/EclairGestureDetector.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/FroyoGestureDetector.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GestureDetector.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GingerScroller.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/HackyViewPager.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IPhotoView.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IcsScroller.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageDetailFragment.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImagePagerActivity.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageUtil.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LogManager.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Logger.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LoggerDefault.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/OnGestureListener.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoView.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoViewAttacher.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PictureConfig.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PreGingerScroller.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ScrollerProxy.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Tools.java
create mode 100644 PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/VersionedGestureDetector.java
create mode 100644 PictureViewer/src/main/res/drawable-nodpi/subway.png
create mode 100644 PictureViewer/src/main/res/layout/activity_image_detail_pager.xml
create mode 100644 PictureViewer/src/main/res/layout/fragment_image_detail.xml
create mode 100644 PictureViewer/src/main/res/values/colors.xml
create mode 100644 PictureViewer/src/main/res/values/strings.xml
create mode 100644 app/src/main/java/com/xiaoming/gzmetro/MainActivity2.java
delete mode 100644 app/src/main/res/drawable-nodpi/pic.webp
diff --git a/PictureViewer/.gitignore b/PictureViewer/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/PictureViewer/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/PictureViewer/build.gradle b/PictureViewer/build.gradle
new file mode 100644
index 0000000..0b1c088
--- /dev/null
+++ b/PictureViewer/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdk 34
+
+ namespace 'com.SuperKotlin.pictureviewer'
+ defaultConfig {
+ minSdkVersion 18
+ targetSdkVersion 21
+ versionCode 1
+ versionName "2.0.1"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ //implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.github.bumptech.glide:glide:3.7.0'
+ implementation 'com.android.support:support-v4:28.0.0'
+
+}
diff --git a/PictureViewer/proguard-rules.pro b/PictureViewer/proguard-rules.pro
new file mode 100644
index 0000000..d32ebfe
--- /dev/null
+++ b/PictureViewer/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\zhuyong\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/PictureViewer/src/main/AndroidManifest.xml b/PictureViewer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c810229
--- /dev/null
+++ b/PictureViewer/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Compat.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Compat.java
new file mode 100644
index 0000000..656eddb
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Compat.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.annotation.TargetApi;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class Compat {
+
+ private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
+
+ public static void postOnAnimation(View view, Runnable runnable) {
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ postOnAnimationJellyBean(view, runnable);
+ } else {
+ view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
+ }
+ }
+
+ @TargetApi(16)
+ private static void postOnAnimationJellyBean(View view, Runnable runnable) {
+ view.postOnAnimation(runnable);
+ }
+
+ public static int getPointerIndex(int action) {
+ if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
+ return getPointerIndexHoneyComb(action);
+ else
+ return getPointerIndexEclair(action);
+ }
+
+ @SuppressWarnings("deprecation")
+ @TargetApi(VERSION_CODES.ECLAIR)
+ private static int getPointerIndexEclair(int action) {
+ return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+ }
+
+ @TargetApi(VERSION_CODES.HONEYCOMB)
+ private static int getPointerIndexHoneyComb(int action) {
+ return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/CupcakeGestureDetector.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/CupcakeGestureDetector.java
new file mode 100644
index 0000000..4fe6d2f
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/CupcakeGestureDetector.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+public class CupcakeGestureDetector implements GestureDetector {
+
+ protected OnGestureListener mListener;
+ private static final String LOG_TAG = "CupcakeGestureDetector";
+ float mLastTouchX;
+ float mLastTouchY;
+ final float mTouchSlop;
+ final float mMinimumVelocity;
+
+ @Override
+ public void setOnGestureListener(OnGestureListener listener) {
+ this.mListener = listener;
+ }
+
+ public CupcakeGestureDetector(Context context) {
+ final ViewConfiguration configuration = ViewConfiguration
+ .get(context);
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mTouchSlop = configuration.getScaledTouchSlop();
+ }
+
+ private VelocityTracker mVelocityTracker;
+ private boolean mIsDragging;
+
+ float getActiveX(MotionEvent ev) {
+ return ev.getX();
+ }
+
+ float getActiveY(MotionEvent ev) {
+ return ev.getY();
+ }
+
+ public boolean isScaling() {
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ mVelocityTracker = VelocityTracker.obtain();
+ if (null != mVelocityTracker) {
+ mVelocityTracker.addMovement(ev);
+ } else {
+ Log.i(LOG_TAG, "Velocity tracker is null");
+ }
+
+ mLastTouchX = getActiveX(ev);
+ mLastTouchY = getActiveY(ev);
+ mIsDragging = false;
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ final float x = getActiveX(ev);
+ final float y = getActiveY(ev);
+ final float dx = x - mLastTouchX, dy = y - mLastTouchY;
+
+ if (!mIsDragging) {
+ // Use Pythagoras to see if drag length is larger than
+ // touch slop
+ mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
+ }
+
+ if (mIsDragging) {
+ mListener.onDrag(dx, dy);
+ mLastTouchX = x;
+ mLastTouchY = y;
+
+ if (null != mVelocityTracker) {
+ mVelocityTracker.addMovement(ev);
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL: {
+ // Recycle Velocity Tracker
+ if (null != mVelocityTracker) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP: {
+ if (mIsDragging) {
+ if (null != mVelocityTracker) {
+ mLastTouchX = getActiveX(ev);
+ mLastTouchY = getActiveY(ev);
+
+ // Compute velocity within the last 1000ms
+ mVelocityTracker.addMovement(ev);
+ mVelocityTracker.computeCurrentVelocity(1000);
+
+ final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
+ .getYVelocity();
+
+ // 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;
+ }
+ break;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/DefaultOnDoubleTapListener.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/DefaultOnDoubleTapListener.java
new file mode 100644
index 0000000..31ed9c5
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/DefaultOnDoubleTapListener.java
@@ -0,0 +1,98 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.graphics.RectF;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+
+/**
+ * Provided default implementation of GestureDetector.OnDoubleTapListener, to be overriden with custom behavior, if needed
+ *
+ * To be used via {@link uk.co.senab.photoview.PhotoViewAttacher#setOnDoubleTapListener(GestureDetector.OnDoubleTapListener)}
+ */
+public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener {
+
+ private PhotoViewAttacher photoViewAttacher;
+
+ /**
+ * Default constructor
+ *
+ * @param photoViewAttacher PhotoViewAttacher to bind to
+ */
+ public DefaultOnDoubleTapListener(PhotoViewAttacher photoViewAttacher) {
+ setPhotoViewAttacher(photoViewAttacher);
+ }
+
+ /**
+ * Allows to change PhotoViewAttacher within range of single instance
+ *
+ * @param newPhotoViewAttacher PhotoViewAttacher to bind to
+ */
+ public void setPhotoViewAttacher(PhotoViewAttacher newPhotoViewAttacher) {
+ this.photoViewAttacher = newPhotoViewAttacher;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (this.photoViewAttacher == null)
+ return false;
+
+ ImageView imageView = photoViewAttacher.getImageView();
+
+ if (null != photoViewAttacher.getOnPhotoTapListener()) {
+ final RectF displayRect = photoViewAttacher.getDisplayRect();
+
+ if (null != displayRect) {
+ final float x = e.getX(), y = e.getY();
+
+ // Check to see if the user tapped on the photo
+ if (displayRect.contains(x, y)) {
+
+ float xResult = (x - displayRect.left)
+ / displayRect.width();
+ float yResult = (y - displayRect.top)
+ / displayRect.height();
+
+ photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult);
+ return true;
+ }
+ }
+ }
+ if (null != photoViewAttacher.getOnViewTapListener()) {
+ photoViewAttacher.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY());
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent ev) {
+ if (photoViewAttacher == null)
+ return false;
+
+ try {
+ float scale = photoViewAttacher.getScale();
+ float x = ev.getX();
+ float y = ev.getY();
+
+ if (scale < photoViewAttacher.getMediumScale()) {
+ photoViewAttacher.setScale(photoViewAttacher.getMediumScale(), x, y, true);
+ } else if (scale >= photoViewAttacher.getMediumScale() && scale < photoViewAttacher.getMaximumScale()) {
+ photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(), x, y, true);
+ } else {
+ photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(), x, y, true);
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Can sometimes happen when getX() and getY() is called
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ // Wait for the confirmed onDoubleTap() instead
+ return false;
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/EclairGestureDetector.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/EclairGestureDetector.java
new file mode 100644
index 0000000..a8503bf
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/EclairGestureDetector.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.view.MotionEvent;
+
+@TargetApi(5)
+public class EclairGestureDetector extends CupcakeGestureDetector {
+
+ private static final int INVALID_POINTER_ID = -1;
+ private int mActivePointerId = INVALID_POINTER_ID;
+ private int mActivePointerIndex = 0;
+
+ public EclairGestureDetector(Context context) {
+ super(context);
+ }
+
+ @Override
+ float getActiveX(MotionEvent ev) {
+ try {
+ return ev.getX(mActivePointerIndex);
+ } catch (Exception e) {
+ return ev.getX();
+ }
+ }
+
+ @Override
+ float getActiveY(MotionEvent ev) {
+ try {
+ return ev.getY(mActivePointerIndex);
+ } catch (Exception e) {
+ return ev.getY();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mActivePointerId = INVALID_POINTER_ID;
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ // Ignore deprecation, ACTION_POINTER_ID_MASK and
+ // ACTION_POINTER_ID_SHIFT has same value and are deprecated
+ // You can have either deprecation or lint target api warning
+ final int pointerIndex = Compat.getPointerIndex(ev.getAction());
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ mLastTouchX = ev.getX(newPointerIndex);
+ mLastTouchY = ev.getY(newPointerIndex);
+ }
+ break;
+ }
+
+ mActivePointerIndex = ev
+ .findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
+ : 0);
+ return super.onTouchEvent(ev);
+ }
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/FroyoGestureDetector.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/FroyoGestureDetector.java
new file mode 100644
index 0000000..bb5bbcd
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/FroyoGestureDetector.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+@TargetApi(8)
+public class FroyoGestureDetector extends EclairGestureDetector {
+
+ protected final ScaleGestureDetector mDetector;
+
+ public FroyoGestureDetector(Context context) {
+ super(context);
+ ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ float scaleFactor = detector.getScaleFactor();
+
+ if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
+ return false;
+
+ mListener.onScale(scaleFactor,
+ detector.getFocusX(), detector.getFocusY());
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ // NO-OP
+ }
+ };
+ mDetector = new ScaleGestureDetector(context, mScaleListener);
+ }
+
+ @Override
+ public boolean isScaling() {
+ return mDetector.isInProgress();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mDetector.onTouchEvent(ev);
+ return super.onTouchEvent(ev);
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GestureDetector.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GestureDetector.java
new file mode 100644
index 0000000..a642cb1
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GestureDetector.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.view.MotionEvent;
+
+public interface GestureDetector {
+
+ public boolean onTouchEvent(MotionEvent ev);
+
+ public boolean isScaling();
+
+ public void setOnGestureListener(OnGestureListener listener);
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GingerScroller.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GingerScroller.java
new file mode 100644
index 0000000..d5242a1
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/GingerScroller.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.widget.OverScroller;
+
+@TargetApi(9)
+public class GingerScroller extends ScrollerProxy {
+
+ protected final OverScroller mScroller;
+ private boolean mFirstScroll = false;
+
+ public GingerScroller(Context context) {
+ mScroller = new OverScroller(context);
+ }
+
+ @Override
+ public boolean computeScrollOffset() {
+ // Workaround for first scroll returning 0 for the direction of the edge it hits.
+ // Simply recompute values.
+ if (mFirstScroll) {
+ mScroller.computeScrollOffset();
+ mFirstScroll = false;
+ }
+ return mScroller.computeScrollOffset();
+ }
+
+ @Override
+ public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
+ int overX, int overY) {
+ mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, overY);
+ }
+
+ @Override
+ public void forceFinished(boolean finished) {
+ mScroller.forceFinished(finished);
+ }
+
+ @Override
+ public boolean isFinished() {
+ return mScroller.isFinished();
+ }
+
+ @Override
+ public int getCurrX() {
+ return mScroller.getCurrX();
+ }
+
+ @Override
+ public int getCurrY() {
+ return mScroller.getCurrY();
+ }
+}
\ No newline at end of file
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/HackyViewPager.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/HackyViewPager.java
new file mode 100644
index 0000000..22ff1e0
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/HackyViewPager.java
@@ -0,0 +1,50 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+/**
+ * Hacky fix for Issue #4 and
+ * http://code.google.com/p/android/issues/detail?id=18990
+ *
+ * ScaleGestureDetector seems to mess up the touch events, which means that
+ * ViewGroups which make use of onInterceptTouchEvent throw a lot of
+ * IllegalArgumentException: pointerIndex out of range.
+ *
+ * There's not much I can do in my code for now, but we can mask the result by
+ * just catching the problem and ignoring it.
+ *
+ * @author Chris Banes
+ */
+public class HackyViewPager extends ViewPager {
+
+
+ private static final String TAG = "HackyViewPager";
+
+ public HackyViewPager(Context context) {
+ super(context);
+ }
+
+ public HackyViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ try {
+ return super.onInterceptTouchEvent(ev);
+ } catch (IllegalArgumentException e) {
+ //不理会
+ Log.e(TAG,"hacky viewpager error1");
+ return false;
+ }catch(ArrayIndexOutOfBoundsException e ){
+ //不理会
+ Log.e(TAG,"hacky viewpager error2");
+ return false;
+ }
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IPhotoView.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IPhotoView.java
new file mode 100644
index 0000000..de2a714
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IPhotoView.java
@@ -0,0 +1,283 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.View;
+import android.widget.ImageView;
+
+
+public interface IPhotoView {
+
+ public static final float DEFAULT_MAX_SCALE = 10.0f;
+ public static final float DEFAULT_MID_SCALE = 10f;
+ public static final float DEFAULT_MIN_SCALE = 1.0f;
+ public static final int DEFAULT_ZOOM_DURATION = 200;
+
+ /**
+ * Returns true if the PhotoView is set to allow zooming of Photos.
+ *
+ * @return true if the PhotoView allows zooming.
+ */
+ boolean canZoom();
+
+ /**
+ * Gets the Display Rectangle of the currently displayed Drawable. The Rectangle is relative to
+ * this View and includes all scaling and translations.
+ *
+ * @return - RectF of Displayed Drawable
+ */
+ RectF getDisplayRect();
+
+ /**
+ * Sets the Display Matrix of the currently displayed Drawable. The Rectangle is considered
+ * relative to this View and includes all scaling and translations.
+ *
+ * @return - true if rectangle was applied successfully
+ */
+ boolean setDisplayMatrix(Matrix finalMatrix);
+
+ /**
+ * Gets the Display Matrix of the currently displayed Drawable. The Rectangle is considered
+ * relative to this View and includes all scaling and translations.
+ *
+ * @return - true if rectangle was applied successfully
+ */
+ Matrix getDisplayMatrix();
+
+ /**
+ * Use {@link #getMinimumScale()} instead, this will be removed in future release
+ *
+ * @return The current minimum scale level. What this value represents depends on the current
+ * {@link ImageView.ScaleType}.
+ */
+ @Deprecated
+ float getMinScale();
+
+ /**
+ * @return The current minimum scale level. What this value represents depends on the current
+ * {@link ImageView.ScaleType}.
+ */
+ float getMinimumScale();
+
+ /**
+ * Use {@link #getMediumScale()} instead, this will be removed in future release
+ *
+ * @return The current middle scale level. What this value represents depends on the current
+ * {@link ImageView.ScaleType}.
+ */
+ @Deprecated
+ float getMidScale();
+
+ /**
+ * @return The current medium scale level. What this value represents depends on the current
+ * {@link ImageView.ScaleType}.
+ */
+ float getMediumScale();
+
+ /**
+ * Use {@link #getMaximumScale()} instead, this will be removed in future release
+ *
+ * @return The current maximum scale level. What this value represents depends on the current
+ * {@link ImageView.ScaleType}.
+ */
+ @Deprecated
+ float getMaxScale();
+
+ /**
+ * @return The current maximum scale level. What this value represents depends on the current
+ * {@link ImageView.ScaleType}.
+ */
+ float getMaximumScale();
+
+ /**
+ * Returns the current scale value
+ *
+ * @return float - current scale value
+ */
+ float getScale();
+
+ /**
+ * Return the current scale type in use by the ImageView.
+ */
+ ImageView.ScaleType getScaleType();
+
+ /**
+ * Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll
+ * to it's horizontal edge.
+ */
+ void setAllowParentInterceptOnEdge(boolean allow);
+
+ /**
+ * Use {@link #setMinimumScale(float minimumScale)} instead, this will be removed in future
+ * release
+ *
+ * Sets the minimum scale level. What this value represents depends on the current {@link
+ * ImageView.ScaleType}.
+ */
+ @Deprecated
+ void setMinScale(float minScale);
+
+ /**
+ * Sets the minimum scale level. What this value represents depends on the current {@link
+ * ImageView.ScaleType}.
+ */
+ void setMinimumScale(float minimumScale);
+
+ /**
+ * Use {@link #setMediumScale(float mediumScale)} instead, this will be removed in future
+ * release
+ *
+ * Sets the middle scale level. What this value represents depends on the current {@link
+ * ImageView.ScaleType}.
+ */
+ @Deprecated
+ void setMidScale(float midScale);
+
+ /*
+ * Sets the medium scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
+ */
+ void setMediumScale(float mediumScale);
+
+ /**
+ * Use {@link #setMaximumScale(float maximumScale)} instead, this will be removed in future
+ * release
+ *
+ * Sets the maximum scale level. What this value represents depends on the current {@link
+ * ImageView.ScaleType}.
+ */
+ @Deprecated
+ void setMaxScale(float maxScale);
+
+ /**
+ * Sets the maximum scale level. What this value represents depends on the current {@link
+ * ImageView.ScaleType}.
+ */
+ void setMaximumScale(float maximumScale);
+
+ /**
+ * Register a callback to be invoked when the Photo displayed by this view is long-pressed.
+ *
+ * @param listener - Listener to be registered.
+ */
+ void setOnLongClickListener(View.OnLongClickListener listener);
+
+ /**
+ * Register a callback to be invoked when the Matrix has changed for this View. An example would
+ * be the user panning or scaling the Photo.
+ *
+ * @param listener - Listener to be registered.
+ */
+ void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener);
+
+ /**
+ * Register a callback to be invoked when the Photo displayed by this View is tapped with a
+ * single tap.
+ *
+ * @param listener - Listener to be registered.
+ */
+ void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);
+
+ /**
+ * Returns a listener to be invoked when the Photo displayed by this View is tapped with a
+ * single tap.
+ *
+ * @return PhotoViewAttacher.OnPhotoTapListener currently set, may be null
+ */
+ PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener();
+
+ /**
+ * Register a callback to be invoked when the View is tapped with a single tap.
+ *
+ * @param listener - Listener to be registered.
+ */
+ void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);
+
+ /**
+ * Returns a callback listener to be invoked when the View is tapped with a single tap.
+ *
+ * @return PhotoViewAttacher.OnViewTapListener currently set, may be null
+ */
+ PhotoViewAttacher.OnViewTapListener getOnViewTapListener();
+
+ /**
+ * Changes the current scale to the specified value.
+ *
+ * @param scale - Value to scale to
+ */
+ void setScale(float scale);
+
+ /**
+ * Changes the current scale to the specified value.
+ *
+ * @param scale - Value to scale to
+ * @param animate - Whether to animate the scale
+ */
+ void setScale(float scale, boolean animate);
+
+ /**
+ * Changes the current scale to the specified value, around the given focal point.
+ *
+ * @param scale - Value to scale to
+ * @param focalX - X Focus Point
+ * @param focalY - Y Focus Point
+ * @param animate - Whether to animate the scale
+ */
+ void setScale(float scale, float focalX, float focalY, boolean animate);
+
+ /**
+ * Controls how the image should be resized or moved to match the size of the ImageView. Any
+ * scaling or panning will happen within the confines of this {@link
+ * ImageView.ScaleType}.
+ *
+ * @param scaleType - The desired scaling mode.
+ */
+ void setScaleType(ImageView.ScaleType scaleType);
+
+ /**
+ * Allows you to enable/disable the zoom functionality on the ImageView. When disable the
+ * ImageView reverts to using the FIT_CENTER matrix.
+ *
+ * @param zoomable - Whether the zoom functionality is enabled.
+ */
+ void setZoomable(boolean zoomable);
+
+ /**
+ * Enables rotation via PhotoView internal functions. Name is chosen so it won't collide with
+ * View.setRotation(float) in API since 11
+ *
+ * @param rotationDegree - Degree to rotate PhotoView by, should be in range 0 to 360
+ */
+ void setPhotoViewRotation(float rotationDegree);
+
+ /**
+ * Extracts currently visible area to Bitmap object, if there is no image loaded yet or the
+ * ImageView is already destroyed, returns {@code null}
+ *
+ * @return currently visible area as bitmap or null
+ */
+ Bitmap getVisibleRectangleBitmap();
+
+ /**
+ * Allows to change zoom transition speed, default value is 200 (PhotoViewAttacher.DEFAULT_ZOOM_DURATION).
+ * Will default to 200 if provided negative value
+ *
+ * @param milliseconds duration of zoom interpolation
+ */
+ void setZoomTransitionDuration(int milliseconds);
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IcsScroller.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IcsScroller.java
new file mode 100644
index 0000000..aa09e09
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/IcsScroller.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+
+@TargetApi(14)
+public class IcsScroller extends GingerScroller {
+
+ public IcsScroller(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean computeScrollOffset() {
+ return mScroller.computeScrollOffset();
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageDetailFragment.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageDetailFragment.java
new file mode 100644
index 0000000..613d0ef
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageDetailFragment.java
@@ -0,0 +1,105 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+// import com.bumptech.glide.request.transition.Transition;
+
+public class ImageDetailFragment extends Fragment {
+ public static int mImageLoading;//占位符图片
+ public static boolean mNeedDownload = false;//默认不支持下载
+ private String mImageUrl;
+ private ImageView mImageView;
+ private PhotoViewAttacher mAttacher;
+ private Bitmap mBitmap;
+
+ public static ImageDetailFragment newInstance(String imageUrl) {
+ final ImageDetailFragment imageDetailFragment = new ImageDetailFragment();
+
+ final Bundle args = new Bundle();
+ args.putString("url",imageUrl);
+ imageDetailFragment.setArguments(args);
+
+ return imageDetailFragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageUrl = getArguments() != null ? getArguments().getString("url") : null;
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
+ final View v = inflater.inflate(R.layout.fragment_image_detail,container,false);
+ mImageView = (ImageView) v.findViewById(R.id.image);
+ mAttacher = new PhotoViewAttacher(mImageView);
+ // mAttacher.setOnLongClickListener(new View.OnLongClickListener() {
+ // @Override
+ // public boolean onLongClick(View v) {
+ // if (mNeedDownload) {
+ // AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ // builder.setMessage("保存图片");
+ // builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
+ //
+ // @Override
+ // public void onClick(DialogInterface dialog, int which) {
+ //
+ // }
+ // });
+ // builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
+ //
+ // @Override
+ // public void onClick(DialogInterface dialog, int which) {
+ // ImageUtil.saveImage(getActivity(), mImageUrl, mBitmap);
+ // }
+ // });
+ // builder.create().show();
+ // }
+ // return false;
+ // }
+ // });
+ mAttacher.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() {
+
+ @Override
+ public void onPhotoTap(View arg0,float arg1,float arg2) {
+ getActivity().finish();
+ }
+ });
+ return v;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // if (!TextUtils.isEmpty(mImageUrl)) {
+ Glide.with(getActivity())
+ // .load(mImageUrl)
+ .load(R.drawable.subway)
+ .asBitmap()
+ .placeholder(mImageLoading)
+ .error(mImageLoading)
+ .into(new SimpleTarget() {
+ @Override
+ public void onResourceReady(Bitmap bitmap,GlideAnimation super Bitmap> glideAnimation) {
+ mBitmap = bitmap;
+ mImageView.setImageBitmap(mBitmap);
+ mAttacher.update();
+ }
+ });
+ // } else {
+ // mImageView.setImageResource(mImageLoading);
+ // }
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImagePagerActivity.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImagePagerActivity.java
new file mode 100644
index 0000000..9393a65
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImagePagerActivity.java
@@ -0,0 +1,115 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * 图片查看器
+ * Created by zhuyong on 2017/5/12.
+ */
+public class ImagePagerActivity extends FragmentActivity {
+ private static final String TAG = "ImagePagerActivity";
+ private static final String STATE_POSITION = "STATE_POSITION";
+ public static final String EXTRA_IMAGE_INDEX = "image_index";
+ public static final String EXTRA_IMAGE_URLS = "image_urls";
+
+ private HackyViewPager mPager;
+ private int pagerPosition;
+ private TextView indicator;
+ private static boolean mIsShowNumber = false;//是否显示数字下标
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //透明状态栏
+ }
+ setContentView(R.layout.activity_image_detail_pager);
+ pagerPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0);
+ List urls = getIntent().getStringArrayListExtra(EXTRA_IMAGE_URLS);
+ mPager = (HackyViewPager) findViewById(R.id.pager);
+ ImagePagerAdapter mAdapter = new ImagePagerAdapter(
+ getSupportFragmentManager(), urls);
+ mPager.setAdapter(mAdapter);
+ indicator = (TextView) findViewById(R.id.indicator);
+
+ CharSequence text = getString(R.string.viewpager_indicator, 1, mPager.getAdapter().getCount());
+ indicator.setText(text);
+ // 更新下标
+ mPager.setOnPageChangeListener(new OnPageChangeListener() {
+
+ @Override
+ public void onPageScrollStateChanged(int arg0) {
+ }
+
+ @Override
+ public void onPageScrolled(int arg0, float arg1, int arg2) {
+ }
+
+ @Override
+ public void onPageSelected(int arg0) {
+ CharSequence text = getString(R.string.viewpager_indicator,
+ arg0 + 1, mPager.getAdapter().getCount());
+ indicator.setText(text);
+ }
+
+ });
+ if (savedInstanceState != null) {
+ pagerPosition = savedInstanceState.getInt(STATE_POSITION);
+ }
+
+ mPager.setCurrentItem(pagerPosition);
+ // indicator.setVisibility(mIsShowNumber ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_POSITION, mPager.getCurrentItem());
+ }
+
+ private class ImagePagerAdapter extends FragmentStatePagerAdapter {
+
+ public List fileList;
+
+ public ImagePagerAdapter(FragmentManager fm, List fileList) {
+ super(fm);
+ this.fileList = fileList;
+ }
+
+ @Override
+ public int getCount() {
+ return fileList == null ? 0 : fileList.size();
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ String url = fileList.get(position).toString();
+ return ImageDetailFragment.newInstance(url);
+ }
+
+ }
+
+ public static void startActivity(Context context, PictureConfig config) {
+ Intent intent = new Intent(context, ImagePagerActivity.class);
+ intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_URLS, config.list);
+ intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_INDEX, config.position);
+ ImageDetailFragment.mImageLoading = config.resId;
+ ImageDetailFragment.mNeedDownload = config.needDownload;
+ mIsShowNumber = config.mIsShowNumber;
+ ImageUtil.path = config.path;
+ context.startActivity(intent);
+ }
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageUtil.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageUtil.java
new file mode 100644
index 0000000..1bbcaee
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ImageUtil.java
@@ -0,0 +1,100 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * 图片下载类,因为这里都是无压缩下载,为了避免ANR所以开启子线程下载
+ */
+public class ImageUtil {
+
+ public static String path = "pictureviewer";//下载路径
+ private static final int DOWNLOAD_SUCCESS = 0X11;
+ private static final int DOWNLOAD_FAILD = 0X12;
+ @SuppressLint("StaticFieldLeak")
+ private static Context mContext;
+
+ private static Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == DOWNLOAD_SUCCESS) {
+ Toast.makeText(mContext, (String) msg.obj, Toast.LENGTH_SHORT).show();
+ } else if (msg.what == DOWNLOAD_FAILD) {
+ Toast.makeText(mContext, "保存失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+ };
+
+ public static void saveImage(final Context context, String url, final Bitmap bitmap) {
+ mContext = context;
+ //保存路径
+ String imgDir = "";
+ if (checkSDCard()) {
+ imgDir = Environment.getExternalStorageDirectory().getPath() + "/" + path;
+ } else {
+ imgDir = Environment.getDataDirectory().getPath() + "/" + path;
+ }
+ //图片名称处理
+ String[] fileNameArr = url.substring(url.lastIndexOf("/") + 1).split("\\.");
+ final String fileName = fileNameArr[0] + ".png";
+ //创建文件路径
+ File fileDir = new File(imgDir);
+ if (!fileDir.exists()) {
+ fileDir.mkdir();
+ }
+ //创建文件
+ final File imageFile = new File(fileDir, fileName);
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FileOutputStream fos = new FileOutputStream(imageFile);
+ boolean compress = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
+ if (compress) {
+ Message message = new Message();
+ message.what = DOWNLOAD_SUCCESS;
+ message.obj = "保存成功,路径/sd卡/" + path + "/" + fileName;
+ mHandler.sendMessage(message);
+ } else {
+ mHandler.sendEmptyMessage(DOWNLOAD_FAILD);
+ }
+ fos.flush();
+ fos.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ mHandler.sendEmptyMessage(DOWNLOAD_FAILD);
+ } catch (IOException e) {
+ e.printStackTrace();
+ mHandler.sendEmptyMessage(DOWNLOAD_FAILD);
+ } catch (Exception e) {
+ e.printStackTrace();
+ mHandler.sendEmptyMessage(DOWNLOAD_FAILD);
+ }
+ }
+ }).start();
+
+ Uri uri = Uri.fromFile(imageFile);
+ //发送广播,通知图库更新
+ context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
+ }
+
+ private static boolean checkSDCard() {
+ return Environment.getExternalStorageState().equals(
+ Environment.MEDIA_MOUNTED);
+ }
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LogManager.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LogManager.java
new file mode 100644
index 0000000..5211ae8
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LogManager.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.util.Log;
+
+/**
+ * class that holds the {@link Logger} for this library, defaults to {@link LoggerDefault} to send logs to android {@link Log}
+ */
+public final class LogManager {
+
+ private static Logger logger = new LoggerDefault();
+
+ public static void setLogger(Logger newLogger) {
+ logger = newLogger;
+ }
+
+ public static Logger getLogger() {
+ return logger;
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Logger.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Logger.java
new file mode 100644
index 0000000..c3f8b83
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Logger.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+/**
+ * interface for a logger class to replace the static calls to {@link android.util.Log}
+ */
+public interface Logger {
+ /**
+ * Send a {@link android.util.Log#VERBOSE} log message.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ int v(String tag, String msg);
+
+ /**
+ * Send a {@link android.util.Log#VERBOSE} log message and log the exception.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ int v(String tag, String msg, Throwable tr);
+
+ /**
+ * Send a {@link android.util.Log#DEBUG} log message.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ int d(String tag, String msg);
+
+ /**
+ * Send a {@link android.util.Log#DEBUG} log message and log the exception.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ int d(String tag, String msg, Throwable tr);
+
+ /**
+ * Send an {@link android.util.Log#INFO} log message.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ int i(String tag, String msg);
+
+ /**
+ * Send a {@link android.util.Log#INFO} log message and log the exception.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ int i(String tag, String msg, Throwable tr);
+
+ /**
+ * Send a {@link android.util.Log#WARN} log message.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ int w(String tag, String msg);
+
+ /**
+ * Send a {@link android.util.Log#WARN} log message and log the exception.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ int w(String tag, String msg, Throwable tr);
+
+ /**
+ * Send an {@link android.util.Log#ERROR} log message.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ int e(String tag, String msg);
+
+ /**
+ * Send a {@link android.util.Log#ERROR} log message and log the exception.
+ *
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ int e(String tag, String msg, Throwable tr);
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LoggerDefault.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LoggerDefault.java
new file mode 100644
index 0000000..7c9f1c9
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/LoggerDefault.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.util.Log;
+
+/**
+ * Helper class to redirect {@link LogManager#logger} to {@link Log}
+ */
+public class LoggerDefault implements Logger {
+
+ @Override
+ public int v(String tag, String msg) {
+ return Log.v(tag, msg);
+ }
+
+ @Override
+ public int v(String tag, String msg, Throwable tr) {
+ return Log.v(tag, msg, tr);
+ }
+
+ @Override
+ public int d(String tag, String msg) {
+ return Log.d(tag, msg);
+ }
+
+ @Override
+ public int d(String tag, String msg, Throwable tr) {
+ return Log.d(tag, msg, tr);
+ }
+
+ @Override
+ public int i(String tag, String msg) {
+ return Log.i(tag, msg);
+ }
+
+ @Override
+ public int i(String tag, String msg, Throwable tr) {
+ return Log.i(tag, msg, tr);
+ }
+
+ @Override
+ public int w(String tag, String msg) {
+ return Log.w(tag, msg);
+ }
+
+ @Override
+ public int w(String tag, String msg, Throwable tr) {
+ return Log.w(tag, msg, tr);
+ }
+
+ @Override
+ public int e(String tag, String msg) {
+ return Log.e(tag, msg);
+ }
+
+ @Override
+ public int e(String tag, String msg, Throwable tr) {
+ return Log.e(tag, msg, tr);
+ }
+
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/OnGestureListener.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/OnGestureListener.java
new file mode 100644
index 0000000..8a71d8f
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/OnGestureListener.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+public interface OnGestureListener {
+
+ public void onDrag(float dx, float dy);
+
+ public void onFling(float startX, float startY, float velocityX,
+ float velocityY);
+
+ public void onScale(float scaleFactor, float focusX, float focusY);
+
+}
\ No newline at end of file
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoView.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoView.java
new file mode 100644
index 0000000..5aea6c1
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoView.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class PhotoView extends ImageView implements IPhotoView {
+
+ private final PhotoViewAttacher mAttacher;
+
+ private ScaleType mPendingScaleType;
+
+ public PhotoView(Context context) {
+ this(context, null);
+ }
+
+ public PhotoView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public PhotoView(Context context, AttributeSet attr, int defStyle) {
+ super(context, attr, defStyle);
+ super.setScaleType(ScaleType.MATRIX);
+ mAttacher = new PhotoViewAttacher(this);
+
+ if (null != mPendingScaleType) {
+ setScaleType(mPendingScaleType);
+ mPendingScaleType = null;
+ }
+ }
+
+ @Override
+ public void setPhotoViewRotation(float rotationDegree) {
+ mAttacher.setPhotoViewRotation(rotationDegree);
+ }
+
+ @Override
+ public boolean canZoom() {
+ return mAttacher.canZoom();
+ }
+
+ @Override
+ public RectF getDisplayRect() {
+ return mAttacher.getDisplayRect();
+ }
+
+ @Override
+ public Matrix getDisplayMatrix() {
+ return mAttacher.getDrawMatrix();
+ }
+
+ @Override
+ public boolean setDisplayMatrix(Matrix finalRectangle) {
+ return mAttacher.setDisplayMatrix(finalRectangle);
+ }
+
+ @Override
+ @Deprecated
+ public float getMinScale() {
+ return getMinimumScale();
+ }
+
+ @Override
+ public float getMinimumScale() {
+ return mAttacher.getMinimumScale();
+ }
+
+ @Override
+ @Deprecated
+ public float getMidScale() {
+ return getMediumScale();
+ }
+
+ @Override
+ public float getMediumScale() {
+ return mAttacher.getMediumScale();
+ }
+
+ @Override
+ @Deprecated
+ public float getMaxScale() {
+ return getMaximumScale();
+ }
+
+ @Override
+ public float getMaximumScale() {
+ return mAttacher.getMaximumScale();
+ }
+
+ @Override
+ public float getScale() {
+ return mAttacher.getScale();
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return mAttacher.getScaleType();
+ }
+
+ @Override
+ public void setAllowParentInterceptOnEdge(boolean allow) {
+ mAttacher.setAllowParentInterceptOnEdge(allow);
+ }
+
+ @Override
+ @Deprecated
+ public void setMinScale(float minScale) {
+ setMinimumScale(minScale);
+ }
+
+ @Override
+ public void setMinimumScale(float minimumScale) {
+ mAttacher.setMinimumScale(minimumScale);
+ }
+
+ @Override
+ @Deprecated
+ public void setMidScale(float midScale) {
+ setMediumScale(midScale);
+ }
+
+ @Override
+ public void setMediumScale(float mediumScale) {
+ mAttacher.setMediumScale(mediumScale);
+ }
+
+ @Override
+ @Deprecated
+ public void setMaxScale(float maxScale) {
+ setMaximumScale(maxScale);
+ }
+
+ @Override
+ public void setMaximumScale(float maximumScale) {
+ mAttacher.setMaximumScale(maximumScale);
+ }
+
+ @Override
+ // setImageBitmap calls through to this method
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ if (null != mAttacher) {
+ mAttacher.update();
+ }
+ }
+
+ @Override
+ public void setImageResource(int resId) {
+ super.setImageResource(resId);
+ if (null != mAttacher) {
+ mAttacher.update();
+ }
+ }
+
+ @Override
+ public void setImageURI(Uri uri) {
+ super.setImageURI(uri);
+ if (null != mAttacher) {
+ mAttacher.update();
+ }
+ }
+
+ @Override
+ public void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener) {
+ mAttacher.setOnMatrixChangeListener(listener);
+ }
+
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mAttacher.setOnLongClickListener(l);
+ }
+
+ @Override
+ public void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener) {
+ mAttacher.setOnPhotoTapListener(listener);
+ }
+
+ @Override
+ public PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener() {
+ return mAttacher.getOnPhotoTapListener();
+ }
+
+ @Override
+ public void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener) {
+ mAttacher.setOnViewTapListener(listener);
+ }
+
+ @Override
+ public PhotoViewAttacher.OnViewTapListener getOnViewTapListener() {
+ return mAttacher.getOnViewTapListener();
+ }
+
+ @Override
+ public void setScale(float scale) {
+ mAttacher.setScale(scale);
+ }
+
+ @Override
+ public void setScale(float scale, boolean animate) {
+ mAttacher.setScale(scale, animate);
+ }
+
+ @Override
+ public void setScale(float scale, float focalX, float focalY, boolean animate) {
+ mAttacher.setScale(scale, focalX, focalY, animate);
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if (null != mAttacher) {
+ mAttacher.setScaleType(scaleType);
+ } else {
+ mPendingScaleType = scaleType;
+ }
+ }
+
+ @Override
+ public void setZoomable(boolean zoomable) {
+ mAttacher.setZoomable(zoomable);
+ }
+
+ @Override
+ public Bitmap getVisibleRectangleBitmap() {
+ return mAttacher.getVisibleRectangleBitmap();
+ }
+
+ @Override
+ public void setZoomTransitionDuration(int milliseconds) {
+ mAttacher.setZoomTransitionDuration(milliseconds);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mAttacher.cleanup();
+ super.onDetachedFromWindow();
+ }
+
+}
\ No newline at end of file
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoViewAttacher.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoViewAttacher.java
new file mode 100644
index 0000000..f17989b
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PhotoViewAttacher.java
@@ -0,0 +1,1079 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+
+import java.lang.ref.WeakReference;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener,
+ OnGestureListener,
+ ViewTreeObserver.OnGlobalLayoutListener {
+
+ private static final String LOG_TAG = "PhotoViewAttacher";
+
+ // let debug flag be dynamic, but still Proguard can be used to remove from
+ // release builds
+ private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
+
+ static final Interpolator sInterpolator = new AccelerateDecelerateInterpolator();
+ int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;
+
+ static final int EDGE_NONE = -1;
+ static final int EDGE_LEFT = 0;
+ static final int EDGE_RIGHT = 1;
+ static final int EDGE_BOTH = 2;
+
+ private float mMinScale = DEFAULT_MIN_SCALE;
+ private float mMidScale = DEFAULT_MID_SCALE;
+ private float mMaxScale = DEFAULT_MAX_SCALE;
+
+ private boolean mAllowParentInterceptOnEdge = true;
+
+ private static void checkZoomLevels(float minZoom, float midZoom,
+ float maxZoom) {
+ if (minZoom >= midZoom) {
+ throw new IllegalArgumentException(
+ "MinZoom has to be less than MidZoom");
+ } else if (midZoom >= maxZoom) {
+ throw new IllegalArgumentException(
+ "MidZoom has to be less than MaxZoom");
+ }
+ }
+
+ /**
+ * @return true if the ImageView exists, and it's Drawable existss
+ */
+ private static boolean hasDrawable(ImageView imageView) {
+ return null != imageView && null != imageView.getDrawable();
+ }
+
+ /**
+ * @return true if the ScaleType is supported.
+ */
+ private static boolean isSupportedScaleType(final ScaleType scaleType) {
+ if (null == scaleType) {
+ return false;
+ }
+
+ switch (scaleType) {
+ case MATRIX:
+ throw new IllegalArgumentException(scaleType.name()
+ + " is not supported in PhotoView");
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Set's the ImageView's ScaleType to Matrix.
+ */
+ private static void setImageViewScaleTypeMatrix(ImageView imageView) {
+ /**
+ * PhotoView sets it's own ScaleType to Matrix, then diverts all calls
+ * setScaleType to this.setScaleType automatically.
+ */
+ if (null != imageView && !(imageView instanceof IPhotoView)) {
+ if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
+ imageView.setScaleType(ScaleType.MATRIX);
+ }
+ }
+ }
+
+ private WeakReference mImageView;
+
+ // Gesture Detectors
+ private GestureDetector mGestureDetector;
+ private com.SuperKotlin.pictureviewer.GestureDetector mScaleDragDetector;
+
+ // These are set so we don't keep allocating them on the heap
+ private final Matrix mBaseMatrix = new Matrix();
+ private final Matrix mDrawMatrix = new Matrix();
+ private final Matrix mSuppMatrix = new Matrix();
+ private final RectF mDisplayRect = new RectF();
+ private final float[] mMatrixValues = new float[9];
+
+ // Listeners
+ private OnMatrixChangedListener mMatrixChangeListener;
+ private OnPhotoTapListener mPhotoTapListener;
+ private OnViewTapListener mViewTapListener;
+ private OnLongClickListener mLongClickListener;
+
+ private int mIvTop, mIvRight, mIvBottom, mIvLeft;
+ private FlingRunnable mCurrentFlingRunnable;
+ private int mScrollEdge = EDGE_BOTH;
+
+ private boolean mRotationDetectionEnabled = false;
+ private boolean mZoomEnabled;
+ private ScaleType mScaleType = ScaleType.FIT_CENTER;
+
+ public PhotoViewAttacher(ImageView imageView) {
+ mImageView = new WeakReference(imageView);
+
+ imageView.setDrawingCacheEnabled(true);
+ imageView.setOnTouchListener(this);
+
+ ViewTreeObserver observer = imageView.getViewTreeObserver();
+ if (null != observer)
+ observer.addOnGlobalLayoutListener(this);
+
+ // Make sure we using MATRIX Scale Type
+ setImageViewScaleTypeMatrix(imageView);
+
+ if (imageView.isInEditMode()) {
+ return;
+ }
+ // Create Gesture Detectors...
+ mScaleDragDetector = VersionedGestureDetector.newInstance(
+ imageView.getContext(), this);
+
+ mGestureDetector = new GestureDetector(imageView.getContext(),
+ new GestureDetector.SimpleOnGestureListener() {
+
+ // forward long click listener
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if (null != mLongClickListener) {
+ mLongClickListener.onLongClick(getImageView());
+ }
+ }
+ });
+
+ mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
+
+ // Finally, update the UI so that we're zoomable
+ setZoomable(true);
+ }
+
+ /**
+ * Sets custom double tap listener, to intercept default given functions. To reset behavior to
+ * default, you can just pass in "null" or public field of PhotoViewAttacher.defaultOnDoubleTapListener
+ *
+ * @param newOnDoubleTapListener custom OnDoubleTapListener to be set on ImageView
+ */
+ public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
+ if (newOnDoubleTapListener != null)
+ this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
+ else
+ this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
+ }
+
+ @Override
+ public boolean canZoom() {
+ return mZoomEnabled;
+ }
+
+ /**
+ * Clean-up the resources attached to this object. This needs to be called when the ImageView is
+ * no longer used. A good example is from {@link View#onDetachedFromWindow()} or
+ * from {@link android.app.Activity#onDestroy()}. This is automatically called if you are using
+ * {@link uk.co.senab.photoview.PhotoView}.
+ */
+ @SuppressWarnings("deprecation")
+ public void cleanup() {
+ if (null == mImageView) {
+ return; // cleanup already done
+ }
+
+ final ImageView imageView = mImageView.get();
+
+ if (null != imageView) {
+ // Remove this as a global layout listener
+ ViewTreeObserver observer = imageView.getViewTreeObserver();
+ if (null != observer && observer.isAlive()) {
+ observer.removeGlobalOnLayoutListener(this);
+ }
+
+ // Remove the ImageView's reference to this
+ imageView.setOnTouchListener(null);
+
+ // make sure a pending fling runnable won't be run
+ cancelFling();
+ }
+
+ if (null != mGestureDetector) {
+ mGestureDetector.setOnDoubleTapListener(null);
+ }
+
+ // Clear listeners too
+ mMatrixChangeListener = null;
+ mPhotoTapListener = null;
+ mViewTapListener = null;
+
+ // Finally, clear ImageView
+ mImageView = null;
+ }
+
+ @Override
+ public RectF getDisplayRect() {
+ checkMatrixBounds();
+ return getDisplayRect(getDrawMatrix());
+ }
+
+ @Override
+ public boolean setDisplayMatrix(Matrix finalMatrix) {
+ if (finalMatrix == null)
+ throw new IllegalArgumentException("Matrix cannot be null");
+
+ ImageView imageView = getImageView();
+ if (null == imageView)
+ return false;
+
+ if (null == imageView.getDrawable())
+ return false;
+
+ mSuppMatrix.set(finalMatrix);
+ setImageViewMatrix(getDrawMatrix());
+ checkMatrixBounds();
+
+ return true;
+ }
+
+ private float mLastRotation = 0;
+
+ @Override
+ public void setPhotoViewRotation(float degrees) {
+ degrees %= 360;
+ mSuppMatrix.postRotate(mLastRotation - degrees);
+ mLastRotation = degrees;
+ checkAndDisplayMatrix();
+ }
+
+ public ImageView getImageView() {
+ ImageView imageView = null;
+
+ if (null != mImageView) {
+ imageView = mImageView.get();
+ }
+
+ // If we don't have an ImageView, call cleanup()
+ if (null == imageView) {
+ cleanup();
+ Log.i(LOG_TAG,
+ "ImageView no longer exists. You should not use this PhotoViewAttacher any more.");
+ }
+
+ return imageView;
+ }
+
+ @Override
+ @Deprecated
+ public float getMinScale() {
+ return getMinimumScale();
+ }
+
+ @Override
+ public float getMinimumScale() {
+ return mMinScale;
+ }
+
+ @Override
+ @Deprecated
+ public float getMidScale() {
+ return getMediumScale();
+ }
+
+ @Override
+ public float getMediumScale() {
+ return mMidScale;
+ }
+
+ @Override
+ @Deprecated
+ public float getMaxScale() {
+ return getMaximumScale();
+ }
+
+ @Override
+ public float getMaximumScale() {
+ return mMaxScale;
+ }
+
+ @Override
+ public float getScale() {
+ return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return mScaleType;
+ }
+
+ @Override
+ public void onDrag(float dx, float dy) {
+ if (mScaleDragDetector.isScaling()) {
+ return; // Do not drag if we are already scaling
+ }
+
+ if (DEBUG) {
+ LogManager.getLogger().d(LOG_TAG,
+ String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
+ }
+
+ ImageView imageView = getImageView();
+ mSuppMatrix.postTranslate(dx, dy);
+ checkAndDisplayMatrix();
+
+ /**
+ * Here we decide whether to let the ImageView's parent to start taking
+ * over the touch event.
+ *
+ * First we check whether this function is enabled. We never want the
+ * parent to take over if we're scaling. We then check the edge we're
+ * on, and the direction of the scroll (i.e. if we're pulling against
+ * the edge, aka 'overscrolling', let the parent take over).
+ */
+ ViewParent parent = imageView.getParent();
+ if (mAllowParentInterceptOnEdge) {
+ if (mScrollEdge == EDGE_BOTH
+ || (mScrollEdge == EDGE_LEFT && dx >= 1f)
+ || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
+ if (null != parent)
+ parent.requestDisallowInterceptTouchEvent(false);
+ }
+ } else {
+ if (null != parent) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ }
+
+ @Override
+ public void onFling(float startX, float startY, float velocityX,
+ float velocityY) {
+ if (DEBUG) {
+ LogManager.getLogger().d(
+ LOG_TAG,
+ "onFling. sX: " + startX + " sY: " + startY + " Vx: "
+ + velocityX + " Vy: " + velocityY);
+ }
+ ImageView imageView = getImageView();
+ mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
+ mCurrentFlingRunnable.fling(getImageViewWidth(imageView),
+ getImageViewHeight(imageView), (int) velocityX, (int) velocityY);
+ imageView.post(mCurrentFlingRunnable);
+ }
+
+ @Override
+ public void onGlobalLayout() {
+ ImageView imageView = getImageView();
+
+ if (null != imageView) {
+ if (mZoomEnabled) {
+ final int top = imageView.getTop();
+ final int right = imageView.getRight();
+ final int bottom = imageView.getBottom();
+ final int left = imageView.getLeft();
+
+ /**
+ * We need to check whether the ImageView's bounds have changed.
+ * This would be easier if we targeted API 11+ as we could just use
+ * View.OnLayoutChangeListener. Instead we have to replicate the
+ * work, keeping track of the ImageView's bounds and then checking
+ * if the values change.
+ */
+ if (top != mIvTop || bottom != mIvBottom || left != mIvLeft
+ || right != mIvRight) {
+ // Update our base matrix, as the bounds have changed
+ updateBaseMatrix(imageView.getDrawable());
+
+ // Update values as something has changed
+ mIvTop = top;
+ mIvRight = right;
+ mIvBottom = bottom;
+ mIvLeft = left;
+ }
+ } else {
+ updateBaseMatrix(imageView.getDrawable());
+ }
+ }
+ }
+
+ @Override
+ public void onScale(float scaleFactor, float focusX, float focusY) {
+ if (DEBUG) {
+ LogManager.getLogger().d(
+ LOG_TAG,
+ String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f",
+ scaleFactor, focusX, focusY));
+ }
+
+ if (getScale() < mMaxScale || scaleFactor < 1f) {
+ mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
+ checkAndDisplayMatrix();
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ boolean handled = false;
+
+ if (mZoomEnabled && hasDrawable((ImageView) v)) {
+ ViewParent parent = v.getParent();
+ switch (ev.getAction()) {
+ case ACTION_DOWN:
+ // First, disable the Parent from intercepting the touch
+ // event
+ if (null != parent)
+ parent.requestDisallowInterceptTouchEvent(true);
+ else
+ Log.i(LOG_TAG, "onTouch getParent() returned null");
+
+ // If we're flinging, and the user presses down, cancel
+ // fling
+ cancelFling();
+ break;
+
+ case ACTION_CANCEL:
+ case ACTION_UP:
+ // If the user has zoomed less than min scale, zoom back
+ // to min scale
+ if (getScale() < mMinScale) {
+ RectF rect = getDisplayRect();
+ if (null != rect) {
+ v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
+ rect.centerX(), rect.centerY()));
+ handled = true;
+ }
+ }
+ break;
+ }
+
+ // Try the Scale/Drag detector
+ if (null != mScaleDragDetector
+ && mScaleDragDetector.onTouchEvent(ev)) {
+ handled = true;
+ }
+
+ // Check to see if the user double tapped
+ if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
+ handled = true;
+ }
+ }
+
+ return handled;
+ }
+
+ @Override
+ public void setAllowParentInterceptOnEdge(boolean allow) {
+ mAllowParentInterceptOnEdge = allow;
+ }
+
+ @Override
+ @Deprecated
+ public void setMinScale(float minScale) {
+ setMinimumScale(minScale);
+ }
+
+ @Override
+ public void setMinimumScale(float minimumScale) {
+ checkZoomLevels(minimumScale, mMidScale, mMaxScale);
+ mMinScale = minimumScale;
+ }
+
+ @Override
+ @Deprecated
+ public void setMidScale(float midScale) {
+ setMediumScale(midScale);
+ }
+
+ @Override
+ public void setMediumScale(float mediumScale) {
+ checkZoomLevels(mMinScale, mediumScale, mMaxScale);
+ mMidScale = mediumScale;
+ }
+
+ @Override
+ @Deprecated
+ public void setMaxScale(float maxScale) {
+ setMaximumScale(maxScale);
+ }
+
+ @Override
+ public void setMaximumScale(float maximumScale) {
+ checkZoomLevels(mMinScale, mMidScale, maximumScale);
+ mMaxScale = maximumScale;
+ }
+
+ @Override
+ public void setOnLongClickListener(OnLongClickListener listener) {
+ mLongClickListener = listener;
+ }
+
+ @Override
+ public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
+ mMatrixChangeListener = listener;
+ }
+
+ @Override
+ public void setOnPhotoTapListener(OnPhotoTapListener listener) {
+ mPhotoTapListener = listener;
+ }
+
+ @Override
+ public OnPhotoTapListener getOnPhotoTapListener() {
+ return mPhotoTapListener;
+ }
+
+ @Override
+ public void setOnViewTapListener(OnViewTapListener listener) {
+ mViewTapListener = listener;
+ }
+
+ @Override
+ public OnViewTapListener getOnViewTapListener() {
+ return mViewTapListener;
+ }
+
+ @Override
+ public void setScale(float scale) {
+ setScale(scale, false);
+ }
+
+ @Override
+ public void setScale(float scale, boolean animate) {
+ ImageView imageView = getImageView();
+
+ if (null != imageView) {
+ setScale(scale,
+ (imageView.getRight()) / 2,
+ (imageView.getBottom()) / 2,
+ animate);
+ }
+ }
+
+ @Override
+ public void setScale(float scale, float focalX, float focalY,
+ boolean animate) {
+ ImageView imageView = getImageView();
+
+ if (null != imageView) {
+ // Check to see if the scale is within bounds
+ if (scale < mMinScale || scale > mMaxScale) {
+ LogManager
+ .getLogger()
+ .i(LOG_TAG,
+ "Scale must be within the range of minScale and maxScale");
+ return;
+ }
+
+ if (animate) {
+ imageView.post(new AnimatedZoomRunnable(getScale(), scale,
+ focalX, focalY));
+ } else {
+ mSuppMatrix.setScale(scale, scale, focalX, focalY);
+ checkAndDisplayMatrix();
+ }
+ }
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
+ mScaleType = scaleType;
+
+ // Finally update
+ update();
+ }
+ }
+
+ @Override
+ public void setZoomable(boolean zoomable) {
+ mZoomEnabled = zoomable;
+ update();
+ }
+
+ public void update() {
+ ImageView imageView = getImageView();
+
+ if (null != imageView) {
+ if (mZoomEnabled) {
+ // Make sure we using MATRIX Scale Type
+ setImageViewScaleTypeMatrix(imageView);
+
+ // Update the base matrix using the current drawable
+ updateBaseMatrix(imageView.getDrawable());
+ } else {
+ // Reset the Matrix...
+ resetMatrix();
+ }
+ }
+ }
+
+ @Override
+ public Matrix getDisplayMatrix() {
+ return new Matrix(getDrawMatrix());
+ }
+
+ public Matrix getDrawMatrix() {
+ mDrawMatrix.set(mBaseMatrix);
+ mDrawMatrix.postConcat(mSuppMatrix);
+ return mDrawMatrix;
+ }
+
+ private void cancelFling() {
+ if (null != mCurrentFlingRunnable) {
+ mCurrentFlingRunnable.cancelFling();
+ mCurrentFlingRunnable = null;
+ }
+ }
+
+ /**
+ * Helper method that simply checks the Matrix, and then displays the result
+ */
+ private void checkAndDisplayMatrix() {
+ if (checkMatrixBounds()) {
+ setImageViewMatrix(getDrawMatrix());
+ }
+ }
+
+ private void checkImageViewScaleType() {
+ ImageView imageView = getImageView();
+
+ /**
+ * PhotoView's getScaleType() will just divert to this.getScaleType() so
+ * only call if we're not attached to a PhotoView.
+ */
+ if (null != imageView && !(imageView instanceof IPhotoView)) {
+ if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
+ throw new IllegalStateException(
+ "The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher");
+ }
+ }
+ }
+
+ private boolean checkMatrixBounds() {
+ final ImageView imageView = getImageView();
+ if (null == imageView) {
+ return false;
+ }
+
+ final RectF rect = getDisplayRect(getDrawMatrix());
+ if (null == rect) {
+ return false;
+ }
+
+ final float height = rect.height(), width = rect.width();
+ float deltaX = 0, deltaY = 0;
+
+ final int viewHeight = getImageViewHeight(imageView);
+ if (height <= viewHeight) {
+ switch (mScaleType) {
+ case FIT_START:
+ deltaY = -rect.top;
+ break;
+ case FIT_END:
+ deltaY = viewHeight - height - rect.top;
+ break;
+ default:
+ deltaY = (viewHeight - height) / 2 - rect.top;
+ break;
+ }
+ } else if (rect.top > 0) {
+ deltaY = -rect.top;
+ } else if (rect.bottom < viewHeight) {
+ deltaY = viewHeight - rect.bottom;
+ }
+
+ final int viewWidth = getImageViewWidth(imageView);
+ if (width <= viewWidth) {
+ switch (mScaleType) {
+ case FIT_START:
+ deltaX = -rect.left;
+ break;
+ case FIT_END:
+ deltaX = viewWidth - width - rect.left;
+ break;
+ default:
+ deltaX = (viewWidth - width) / 2 - rect.left;
+ break;
+ }
+ mScrollEdge = EDGE_BOTH;
+ } else if (rect.left > 0) {
+ mScrollEdge = EDGE_LEFT;
+ deltaX = -rect.left;
+ } else if (rect.right < viewWidth) {
+ deltaX = viewWidth - rect.right;
+ mScrollEdge = EDGE_RIGHT;
+ } else {
+ mScrollEdge = EDGE_NONE;
+ }
+
+ // Finally actually translate the matrix
+ mSuppMatrix.postTranslate(deltaX, deltaY);
+ return true;
+ }
+
+ /**
+ * Helper method that maps the supplied Matrix to the current Drawable
+ *
+ * @param matrix - Matrix to map Drawable against
+ * @return RectF - Displayed Rectangle
+ */
+ private RectF getDisplayRect(Matrix matrix) {
+ ImageView imageView = getImageView();
+
+ if (null != imageView) {
+ Drawable d = imageView.getDrawable();
+ if (null != d) {
+ mDisplayRect.set(0, 0, d.getIntrinsicWidth(),
+ d.getIntrinsicHeight());
+ matrix.mapRect(mDisplayRect);
+ return mDisplayRect;
+ }
+ }
+ return null;
+ }
+
+ public Bitmap getVisibleRectangleBitmap() {
+ ImageView imageView = getImageView();
+ return imageView == null ? null : imageView.getDrawingCache();
+ }
+
+ @Override
+ public void setZoomTransitionDuration(int milliseconds) {
+ if (milliseconds < 0)
+ milliseconds = DEFAULT_ZOOM_DURATION;
+ this.ZOOM_DURATION = milliseconds;
+ }
+
+ /**
+ * Helper method that 'unpacks' a Matrix and returns the required value
+ *
+ * @param matrix - Matrix to unpack
+ * @param whichValue - Which value from Matrix.M* to return
+ * @return float - returned value
+ */
+ private float getValue(Matrix matrix, int whichValue) {
+ matrix.getValues(mMatrixValues);
+ return mMatrixValues[whichValue];
+ }
+
+ /**
+ * Resets the Matrix back to FIT_CENTER, and then displays it.s
+ */
+ private void resetMatrix() {
+ mSuppMatrix.reset();
+ setImageViewMatrix(getDrawMatrix());
+ checkMatrixBounds();
+ }
+
+ private void setImageViewMatrix(Matrix matrix) {
+ ImageView imageView = getImageView();
+ if (null != imageView) {
+
+ checkImageViewScaleType();
+ imageView.setImageMatrix(matrix);
+
+ // Call MatrixChangedListener if needed
+ if (null != mMatrixChangeListener) {
+ RectF displayRect = getDisplayRect(matrix);
+ if (null != displayRect) {
+ mMatrixChangeListener.onMatrixChanged(displayRect);
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculate Matrix for FIT_CENTER
+ *
+ * @param d - Drawable being displayed
+ */
+ private void updateBaseMatrix(Drawable d) {
+ ImageView imageView = getImageView();
+ if (null == imageView || null == d) {
+ return;
+ }
+
+ final float viewWidth = getImageViewWidth(imageView);
+ final float viewHeight = getImageViewHeight(imageView);
+ final int drawableWidth = d.getIntrinsicWidth();
+ final int drawableHeight = d.getIntrinsicHeight();
+
+ mBaseMatrix.reset();
+
+ final float widthScale = viewWidth / drawableWidth;
+ final float heightScale = viewHeight / drawableHeight;
+
+ if (mScaleType == ScaleType.CENTER) {
+ mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
+ (viewHeight - drawableHeight) / 2F);
+
+ } else if (mScaleType == ScaleType.CENTER_CROP) {
+ float scale = Math.max(widthScale, heightScale);
+ mBaseMatrix.postScale(scale, scale);
+ mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
+ (viewHeight - drawableHeight * scale) / 2F);
+
+ } else if (mScaleType == ScaleType.CENTER_INSIDE) {
+ float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
+ mBaseMatrix.postScale(scale, scale);
+ mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
+ (viewHeight - drawableHeight * scale) / 2F);
+
+ } else {
+ RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
+ RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
+
+ switch (mScaleType) {
+ case FIT_CENTER:
+ mBaseMatrix
+ .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
+ break;
+
+ case FIT_START:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
+ break;
+
+ case FIT_END:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
+ break;
+
+ case FIT_XY:
+ mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ resetMatrix();
+ }
+
+ private int getImageViewWidth(ImageView imageView) {
+ if (null == imageView)
+ return 0;
+ return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
+ }
+
+ private int getImageViewHeight(ImageView imageView) {
+ if (null == imageView)
+ return 0;
+ return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the internal Matrix has changed for
+ * this View.
+ *
+ * @author Chris Banes
+ */
+ public static 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.
+ */
+ void onMatrixChanged(RectF rect);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the Photo is tapped with a single
+ * tap.
+ *
+ * @author Chris Banes
+ */
+ public static 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 - View 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.
+ */
+ void onPhotoTap(View view, float x, float y);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the ImageView is tapped with a single
+ * tap.
+ *
+ * @author Chris Banes
+ */
+ public static 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.
+ */
+ void onViewTap(View view, float x, float y);
+ }
+
+ private class AnimatedZoomRunnable implements Runnable {
+
+ private final float mFocalX, mFocalY;
+ private final long mStartTime;
+ private final float mZoomStart, mZoomEnd;
+
+ public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
+ final float focalX, final float focalY) {
+ mFocalX = focalX;
+ mFocalY = focalY;
+ mStartTime = System.currentTimeMillis();
+ mZoomStart = currentZoom;
+ mZoomEnd = targetZoom;
+ }
+
+ @Override
+ public void run() {
+ ImageView imageView = getImageView();
+ if (imageView == null) {
+ return;
+ }
+
+ float t = interpolate();
+ float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
+ float deltaScale = scale / getScale();
+
+ mSuppMatrix.postScale(deltaScale, deltaScale, mFocalX, mFocalY);
+ checkAndDisplayMatrix();
+
+ // We haven't hit our target scale yet, so post ourselves again
+ if (t < 1f) {
+ Compat.postOnAnimation(imageView, this);
+ }
+ }
+
+ private float interpolate() {
+ float t = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION;
+ t = Math.min(1f, t);
+ t = sInterpolator.getInterpolation(t);
+ return t;
+ }
+ }
+
+ private class FlingRunnable implements Runnable {
+
+ private final ScrollerProxy mScroller;
+ private int mCurrentX, mCurrentY;
+
+ public FlingRunnable(Context context) {
+ mScroller = ScrollerProxy.getScroller(context);
+ }
+
+ public void cancelFling() {
+ if (DEBUG) {
+ LogManager.getLogger().d(LOG_TAG, "Cancel Fling");
+ }
+ mScroller.forceFinished(true);
+ }
+
+ public void fling(int viewWidth, int viewHeight, int velocityX,
+ int velocityY) {
+ final RectF rect = getDisplayRect();
+ if (null == rect) {
+ return;
+ }
+
+ final int startX = Math.round(-rect.left);
+ final int minX, maxX, minY, maxY;
+
+ if (viewWidth < rect.width()) {
+ minX = 0;
+ maxX = Math.round(rect.width() - viewWidth);
+ } else {
+ minX = maxX = startX;
+ }
+
+ final int startY = Math.round(-rect.top);
+ if (viewHeight < rect.height()) {
+ minY = 0;
+ maxY = Math.round(rect.height() - viewHeight);
+ } else {
+ minY = maxY = startY;
+ }
+
+ mCurrentX = startX;
+ mCurrentY = startY;
+
+ if (DEBUG) {
+ LogManager.getLogger().d(
+ LOG_TAG,
+ "fling. StartX:" + startX + " StartY:" + startY
+ + " MaxX:" + maxX + " MaxY:" + maxY);
+ }
+
+ // If we actually can move, fling the scroller
+ if (startX != maxX || startY != maxY) {
+ mScroller.fling(startX, startY, velocityX, velocityY, minX,
+ maxX, minY, maxY, 0, 0);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mScroller.isFinished()) {
+ return; // remaining post that should not be handled
+ }
+
+ ImageView imageView = getImageView();
+ if (null != imageView && mScroller.computeScrollOffset()) {
+
+ final int newX = mScroller.getCurrX();
+ final int newY = mScroller.getCurrY();
+
+ if (DEBUG) {
+ LogManager.getLogger().d(
+ LOG_TAG,
+ "fling run(). CurrentX:" + mCurrentX + " CurrentY:"
+ + mCurrentY + " NewX:" + newX + " NewY:"
+ + newY);
+ }
+
+ mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
+ setImageViewMatrix(getDrawMatrix());
+
+ mCurrentX = newX;
+ mCurrentY = newY;
+
+ // Post On animation
+ Compat.postOnAnimation(imageView, this);
+ }
+ }
+ }
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PictureConfig.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PictureConfig.java
new file mode 100644
index 0000000..2c8b37f
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PictureConfig.java
@@ -0,0 +1,77 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.support.annotation.DrawableRes;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * Created by zhuyong on 2017/6/8.
+ */
+
+public class PictureConfig {
+ public static boolean mIsShowNumber = true;//是否显示数字下标
+ public static boolean needDownload = false;
+ public static String path = "pictureviewer";
+ public static int resId = 0;//占位符资源图片
+ public static int position = 0;//下标
+ public static ArrayList list;
+
+ public PictureConfig(Builder builder) {
+ this.mIsShowNumber = builder.mIsShowNumber;
+ this.needDownload = builder.needDownload;
+ this.path = builder.path;
+ this.resId = builder.resId;
+ this.position = builder.position;
+ this.list = builder.list;
+ }
+
+ public static class Builder implements Serializable {
+
+ private boolean mIsShowNumber = true;//是否显示数字下标
+ private boolean needDownload = false;
+ private int resId = 0;
+ private String path = "pictureviewer";
+ private int position = 0;
+ private ArrayList list;
+
+ public Builder() {
+ super();
+ }
+
+ public Builder setListData(ArrayList list) {
+ this.list = list;
+ return this;
+ }
+
+ public Builder setPosition(int position) {
+ this.position = position;
+ return this;
+ }
+
+ public Builder setIsShowNumber(boolean mIsShowNumber) {
+ this.mIsShowNumber = mIsShowNumber;
+ return this;
+ }
+
+ public Builder needDownload(boolean needDownload) {
+ this.needDownload = needDownload;
+ return this;
+ }
+
+ public Builder setDownloadPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public Builder setPlacrHolder(@DrawableRes int resId) {
+ this.resId = resId;
+ return this;
+ }
+
+ public PictureConfig build() {
+ return new PictureConfig(this);
+ }
+ }
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PreGingerScroller.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PreGingerScroller.java
new file mode 100644
index 0000000..de5f9a9
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/PreGingerScroller.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.widget.Scroller;
+
+public class PreGingerScroller extends ScrollerProxy {
+
+ private final Scroller mScroller;
+
+ public PreGingerScroller(Context context) {
+ mScroller = new Scroller(context);
+ }
+
+ @Override
+ public boolean computeScrollOffset() {
+ return mScroller.computeScrollOffset();
+ }
+
+ @Override
+ public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
+ int overX, int overY) {
+ mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
+ }
+
+ @Override
+ public void forceFinished(boolean finished) {
+ mScroller.forceFinished(finished);
+ }
+
+ public boolean isFinished() {
+ return mScroller.isFinished();
+ }
+
+ @Override
+ public int getCurrX() {
+ return mScroller.getCurrX();
+ }
+
+ @Override
+ public int getCurrY() {
+ return mScroller.getCurrY();
+ }
+}
\ No newline at end of file
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ScrollerProxy.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ScrollerProxy.java
new file mode 100644
index 0000000..93708ba
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/ScrollerProxy.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+
+public abstract class ScrollerProxy {
+
+ public static ScrollerProxy getScroller(Context context) {
+ if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
+ return new PreGingerScroller(context);
+ } else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return new GingerScroller(context);
+ } else {
+ return new IcsScroller(context);
+ }
+ }
+
+ public abstract boolean computeScrollOffset();
+
+ public abstract void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY,
+ int maxY, int overX, int overY);
+
+ public abstract void forceFinished(boolean finished);
+
+ public abstract boolean isFinished();
+
+ public abstract int getCurrX();
+
+ public abstract int getCurrY();
+
+
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Tools.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Tools.java
new file mode 100644
index 0000000..e3dd72d
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/Tools.java
@@ -0,0 +1,21 @@
+package com.SuperKotlin.pictureviewer;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+public class Tools {
+
+ public static void openSubwayMap(Context context){
+ ArrayList list = new ArrayList<>();
+ list.add("");
+ PictureConfig config = new PictureConfig.Builder()
+ .setListData(list)//图片数据List list
+ .setPosition(0)//图片下标(从第position张图片开始浏览)
+ .setDownloadPath("pictureviewer")//图片下载文件夹地址
+ .setIsShowNumber(true)//是否显示数字下标
+ .needDownload(true)//是否支持图片下载
+ .build();
+ ImagePagerActivity.startActivity(context,config);
+ }
+}
diff --git a/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/VersionedGestureDetector.java b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/VersionedGestureDetector.java
new file mode 100644
index 0000000..e1c820d
--- /dev/null
+++ b/PictureViewer/src/main/java/com/SuperKotlin/pictureviewer/VersionedGestureDetector.java
@@ -0,0 +1,42 @@
+package com.SuperKotlin.pictureviewer;
+
+/*******************************************************************************
+ * 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.
+ *******************************************************************************/
+
+import android.content.Context;
+import android.os.Build;
+
+public final class VersionedGestureDetector {
+
+ public static GestureDetector newInstance(Context context,
+ OnGestureListener listener) {
+ final int sdkVersion = Build.VERSION.SDK_INT;
+ GestureDetector detector;
+
+ if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
+ detector = new CupcakeGestureDetector(context);
+ } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
+ detector = new EclairGestureDetector(context);
+ } else {
+ detector = new FroyoGestureDetector(context);
+ }
+
+ detector.setOnGestureListener(listener);
+
+ return detector;
+ }
+
+}
\ No newline at end of file
diff --git a/PictureViewer/src/main/res/drawable-nodpi/subway.png b/PictureViewer/src/main/res/drawable-nodpi/subway.png
new file mode 100644
index 0000000000000000000000000000000000000000..dce5bf8aa296e8da9b256dc932612a55f557ae0c
GIT binary patch
literal 3371859
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