diff --git a/lib_base/src/main/java/com/android/base/permission/AutoPermissionRequester.java b/lib_base/src/main/java/com/android/base/permission/AutoPermissionRequester.java index b9a7ab9..d48dbd2 100644 --- a/lib_base/src/main/java/com/android/base/permission/AutoPermissionRequester.java +++ b/lib_base/src/main/java/com/android/base/permission/AutoPermissionRequester.java @@ -12,6 +12,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import timber.log.Timber; @@ -128,11 +129,28 @@ public class AutoPermissionRequester { FragmentManager supportFragmentManager = mActivity.getSupportFragmentManager(); AutoPermissionFragment fragment = (AutoPermissionFragment) supportFragmentManager.findFragmentByTag(AutoPermissionFragment.class.getName()); + Lifecycle.State currentState = mActivity.getLifecycle().getCurrentState(); + + if (currentState.isAtLeast(Lifecycle.State.RESUMED)) { + startRequestInternal(supportFragmentManager, fragment); + } else { + DefaultLifecycleObserver lifecycleObserver = new DefaultLifecycleObserver() { + @Override + public void onResume(@NonNull LifecycleOwner owner) { + mActivity.getLifecycle().removeObserver(this); + startRequestInternal(supportFragmentManager, fragment); + } + }; + mActivity.getLifecycle().addObserver(lifecycleObserver); + } + } + + private void startRequestInternal(FragmentManager supportFragmentManager, AutoPermissionFragment fragment) { if (fragment == null) { fragment = AutoPermissionFragment.newInstance(); supportFragmentManager.beginTransaction() .add(fragment, AutoPermissionFragment.class.getName()) - .commitNowAllowingStateLoss(); + .commitAllowingStateLoss(); } fragment.setRequester(getCallback()); diff --git a/lib_media_selector/README.md b/lib_media_selector/README.md index ad362b8..e35cc58 100644 --- a/lib_media_selector/README.md +++ b/lib_media_selector/README.md @@ -1,6 +1,6 @@ # 多媒体文件选择库 - 目前基于boxing修改 +目前基于boxing修改 ## 1 AndroidN 在 FileProvider 的 xm l配置中加入: @@ -9,16 +9,9 @@ ``` -## 2 So库 +## 2 RotatePhotoView -``` - defaultConfig { - ...... - ndk {//只打包armeabi架构的so库 - abiFilters 'armeabi' - } - } -``` +[RotatePhotoView](https://github.com/ChenSiLiang/RotatePhotoView) ## 3 其他备选参考 @@ -29,4 +22,4 @@ -  [Album](https://github.com/yanzhenjie/Album) -  [uCrop](https://github.com/Yalantis/uCrop) -  [smartCropper](https://github.com/pqpo/SmartCropper) --  [simpleCropper](https:github.com/igreenwood/SimpleCropView) \ No newline at end of file +-  [simpleCropper](https://github.com/igreenwood/SimpleCropView) diff --git a/lib_media_selector/build.gradle b/lib_media_selector/build.gradle index 6e517eb..b4daf3a 100644 --- a/lib_media_selector/build.gradle +++ b/lib_media_selector/build.gradle @@ -22,11 +22,19 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + sourceSets { + main { + java.srcDirs += "src/github/java" + res.srcDirs += "src/github/res" + } + } + } dependencies { //support - implementation androidLibraries.compat + implementation androidLibraries.appcompat implementation androidLibraries.material /*imageLoader*/ @@ -38,17 +46,11 @@ dependencies { implementation('com.bilibili:boxing-impl:1.0.4') { exclude group: 'com.android.support' + exclude group: 'com.chensl.rotatephotoview' } - implementation('com.chensl.rotatephotoview:rotatephotoview:1.0.5') { - exclude group: 'com.android.support', module: 'appcompat' - exclude group: 'com.android.support', module: 'support-v4' - exclude module: 'recyclerview-v7' - } - - implementation('com.yalantis:ucrop:2.2.0') { - exclude group: 'com.android.support' - exclude group: 'com.squareup.okio' + implementation('com.github.yalantis:ucrop:2.2.4') { exclude group: 'com.squareup.okhttp3' } -} + +} \ No newline at end of file diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/Compat.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/Compat.java new file mode 100644 index 0000000..6faccdf --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/Compat.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview; + +import android.annotation.TargetApi; +import android.os.Build; +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(Build.VERSION_CODES.ECLAIR) + private static int getPointerIndexEclair(int action) { + return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static int getPointerIndexHoneyComb(int action) { + return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/DefaultOnDoubleTapListener.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/DefaultOnDoubleTapListener.java new file mode 100644 index 0000000..b5a8f21 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/DefaultOnDoubleTapListener.java @@ -0,0 +1,100 @@ +package uk.co.senab.photoview; + +import android.graphics.RectF; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.widget.ImageView; + +/** + * Provided default implementation of GestureDetector.OnDoubleTapListener, to be overridden with custom behavior, if needed + *

 

+ * To be used via {@link uk.co.senab.photoview.PhotoViewAttacher#setOnDoubleTapListener(android.view.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; + }else{ + photoViewAttacher.getOnPhotoTapListener().onOutsidePhotoTap(); + } + } + } + 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/lib_media_selector/src/github/java/uk/co/senab/photoview/IPhotoView.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/IPhotoView.java new file mode 100644 index 0000000..fe72202 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/IPhotoView.java @@ -0,0 +1,285 @@ +/** + * **************************************************************************** + * 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 uk.co.senab.photoview; + +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.view.GestureDetector; +import android.view.View; +import android.widget.ImageView; + + +public interface IPhotoView { + + float DEFAULT_MAX_SCALE = 3.0f; + float DEFAULT_MID_SCALE = 1.75f; + float DEFAULT_MIN_SCALE = 1.0f; + 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. + * + * @param finalMatrix target matrix to set PhotoView to + * @return - true if rectangle was applied successfully + */ + boolean setDisplayMatrix(Matrix finalMatrix); + + /** + * Copies the Display Matrix of the currently displayed Drawable. The Rectangle is considered + * relative to this View and includes all scaling and translations. + * + * @param matrix target matrix to copy to + */ + void getDisplayMatrix(Matrix matrix); + + /** + * @return The current minimum scale level. What this value represents depends on the current + * {@link android.widget.ImageView.ScaleType}. + */ + float getMinimumScale(); + + /** + * @return The current medium scale level. What this value represents depends on the current + * {@link android.widget.ImageView.ScaleType}. + */ + float getMediumScale(); + + /** + * @return The current maximum scale level. What this value represents depends on the current + * {@link android.widget.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. + * + * @return current ImageView.ScaleType + */ + 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. + * + * @param allow whether to allow intercepting by parent element or not + */ + void setAllowParentInterceptOnEdge(boolean allow); + + /** + * Sets the minimum scale level. What this value represents depends on the current {@link + * android.widget.ImageView.ScaleType}. + * + * @param minimumScale minimum allowed scale + */ + void setMinimumScale(float minimumScale); + + /** + * Sets the medium scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}. + * + * @param mediumScale medium scale preset + */ + void setMediumScale(float mediumScale); + + /** + * Sets the maximum scale level. What this value represents depends on the current {@link + * android.widget.ImageView.ScaleType}. + * + * @param maximumScale maximum allowed scale preset + */ + void setMaximumScale(float maximumScale); + + /** + * Allows to set all three scale levels at once, so you don't run into problem with setting + * medium/minimum scale before the maximum one + * + * @param minimumScale minimum allowed scale + * @param mediumScale medium allowed scale + * @param maximumScale maximum allowed scale preset + */ + void setScaleLevels(float minimumScale, float mediumScale, 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); + + /** + * Register a callback to be invoked when the View is tapped with a single tap. + * + * @param onRotateListener + */ + void setOnRotateListener(PhotoViewAttacher.OnRotateListener onRotateListener); + + /** + * Enables rotation via PhotoView internal functions. + * + * @param rotationDegree - Degree to rotate PhotoView to, should be in range 0 to 360 + */ + void setRotationTo(float rotationDegree); + + /** + * Enables rotation via PhotoView internal functions. + * + * @param rotationDegree - Degree to rotate PhotoView by, should be in range 0 to 360 + */ + void setRotationBy(float rotationDegree); + + /** + * Changes the current scale to the specified value. + * + * @param scale - Value to scale to + */ + void setScale(float scale); + + /** + * 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(); + + /** + * 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); + + /** + * 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 + * android.widget.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); + + /** + * 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); + + /** + * Will return instance of IPhotoView (eg. PhotoViewAttacher), can be used to provide better + * integration + * + * @return IPhotoView implementation instance if available, null if not + */ + IPhotoView getIPhotoViewImplementation(); + + /** + * 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 + */ + void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener); + + /** + * Will report back about scale changes + * + * @param onScaleChangeListener OnScaleChangeListener instance + */ + void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener); + + /** + * Will report back about fling(single touch) + * + * @param onSingleFlingListener OnSingleFlingListener instance + */ + void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener); +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/PhotoView.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/PhotoView.java new file mode 100644 index 0000000..5abac2e --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/PhotoView.java @@ -0,0 +1,284 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview; + +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.view.GestureDetector; + +import androidx.appcompat.widget.AppCompatImageView; +import uk.co.senab.photoview.PhotoViewAttacher.OnMatrixChangedListener; +import uk.co.senab.photoview.PhotoViewAttacher.OnPhotoTapListener; +import uk.co.senab.photoview.PhotoViewAttacher.OnViewTapListener; + +public class PhotoView extends AppCompatImageView implements IPhotoView { + + private 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); + init(); + } + + protected void init() { + if (null == mAttacher || null == mAttacher.getImageView()) { + mAttacher = new PhotoViewAttacher(this); + } + + if (null != mPendingScaleType) { + setScaleType(mPendingScaleType); + mPendingScaleType = null; + } + } + + @Override + public void setRotationTo(float rotationDegree) { + mAttacher.setRotationTo(rotationDegree); + } + + @Override + public void setRotationBy(float rotationDegree) { + mAttacher.setRotationBy(rotationDegree); + } + + @Override + public boolean canZoom() { + return mAttacher.canZoom(); + } + + @Override + public RectF getDisplayRect() { + return mAttacher.getDisplayRect(); + } + + @Override + public void getDisplayMatrix(Matrix matrix) { + mAttacher.getDisplayMatrix(matrix); + } + + @Override + public boolean setDisplayMatrix(Matrix finalRectangle) { + return mAttacher.setDisplayMatrix(finalRectangle); + } + + @Override + public float getMinimumScale() { + return mAttacher.getMinimumScale(); + } + + @Override + public float getMediumScale() { + return mAttacher.getMediumScale(); + } + + @Override + public float getMaximumScale() { + return mAttacher.getMaximumScale(); + } + + @Override + public float getScale() { + return mAttacher.getScale(); + } + + @Override + public ScaleType getScaleType() { + return mAttacher.getScaleType(); + } + + @Override + public Matrix getImageMatrix() { + return mAttacher.getImageMatrix(); + } + + @Override + public void setAllowParentInterceptOnEdge(boolean allow) { + mAttacher.setAllowParentInterceptOnEdge(allow); + } + + @Override + public void setMinimumScale(float minimumScale) { + mAttacher.setMinimumScale(minimumScale); + } + + @Override + public void setMediumScale(float mediumScale) { + mAttacher.setMediumScale(mediumScale); + } + + @Override + public void setMaximumScale(float maximumScale) { + mAttacher.setMaximumScale(maximumScale); + } + + @Override + public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) { + mAttacher.setScaleLevels(minimumScale, mediumScale, 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 + protected boolean setFrame(int l, int t, int r, int b) { + boolean changed = super.setFrame(l, t, r, b); + if (null != mAttacher) { + mAttacher.update(); + } + return changed; + } + + @Override + public void setOnMatrixChangeListener(OnMatrixChangedListener listener) { + mAttacher.setOnMatrixChangeListener(listener); + } + + @Override + public void setOnLongClickListener(OnLongClickListener l) { + mAttacher.setOnLongClickListener(l); + } + + @Override + public void setOnPhotoTapListener(OnPhotoTapListener listener) { + mAttacher.setOnPhotoTapListener(listener); + } + + @Override + public void setOnRotateListener(PhotoViewAttacher.OnRotateListener onRotateListener) { + mAttacher.setOnRotateListener(onRotateListener); + } + + @Override + public OnViewTapListener getOnViewTapListener() { + return mAttacher.getOnViewTapListener(); + } + + @Override + public void setOnViewTapListener(OnViewTapListener listener) { + mAttacher.setOnViewTapListener(listener); + } + + @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 + public IPhotoView getIPhotoViewImplementation() { + return mAttacher; + } + + @Override + public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) { + mAttacher.setOnDoubleTapListener(newOnDoubleTapListener); + } + + @Override + public void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener) { + mAttacher.setOnScaleChangeListener(onScaleChangeListener); + } + + @Override + public void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener) { + mAttacher.setOnSingleFlingListener(onSingleFlingListener); + } + + @Override + protected void onDetachedFromWindow() { + mAttacher.cleanup(); + mAttacher = null; + super.onDetachedFromWindow(); + } + + @Override + protected void onAttachedToWindow() { + init(); + super.onAttachedToWindow(); + } + +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/PhotoViewAttacher.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/PhotoViewAttacher.java new file mode 100644 index 0000000..42781e5 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/PhotoViewAttacher.java @@ -0,0 +1,1373 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview; + +import android.annotation.SuppressLint; +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 androidx.annotation.Nullable; +import androidx.core.view.MotionEventCompat; +import uk.co.senab.photoview.gestures.IRotateListener; +import uk.co.senab.photoview.gestures.OnGestureListener; +import uk.co.senab.photoview.gestures.RotateGestureDetector; +import uk.co.senab.photoview.gestures.VersionedGestureDetector; +import uk.co.senab.photoview.log.LogManager; +import uk.co.senab.photoview.scrollerproxy.ScrollerProxy; + +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); + + private Interpolator mInterpolator = new AccelerateDecelerateInterpolator(); + private int ZOOM_DURATION = DEFAULT_ZOOM_DURATION; + + private static final int EDGE_NONE = -1; + private static final int EDGE_LEFT = 0; + private static final int EDGE_RIGHT = 1; + private static final int EDGE_BOTH = 2; + + private static int SINGLE_TOUCH = 1; + + private float mMinScale = DEFAULT_MIN_SCALE; + private float mMidScale = DEFAULT_MID_SCALE; + private float mMaxScale = DEFAULT_MAX_SCALE; + + private boolean mAllowParentInterceptOnEdge = true; + private boolean mBlockParentIntercept = false; + + private static void checkZoomLevels(float minZoom, float midZoom, + float maxZoom) { + if (minZoom >= midZoom) { + throw new IllegalArgumentException( + "Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value"); + } else if (midZoom >= maxZoom) { + throw new IllegalArgumentException( + "Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value"); + } + } + + /** + * @return true if the ImageView exists, and it's Drawable exists + */ + 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 uk.co.senab.photoview.gestures.GestureDetector mScaleDragDetector; + private RotateGestureDetector mRotateGestureDetector; + + + // 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 OnScaleChangeListener mScaleChangeListener; + private OnRotateListener mOnRotateListener; + private OnSingleFlingListener mSingleFlingListener; + + private int mIvTop, mIvRight, mIvBottom, mIvLeft; + private FlingRunnable mCurrentFlingRunnable; + private int mScrollEdge = EDGE_BOTH; + private float mBaseRotation; + + private boolean mZoomEnabled; + private ScaleType mScaleType = ScaleType.FIT_CENTER; + //create by ChenSiLiang + private boolean mIsEnableRotate; + private boolean mIsToRightAngle; + private boolean mIsToRighting; + private RightAngleRunnable mRightAngleRunnable; + + public PhotoViewAttacher(ImageView imageView) { + this(imageView, true); + } + + public PhotoViewAttacher(ImageView imageView, boolean zoomable) { + 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()); + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + if (mSingleFlingListener != null) { + if (getScale() > DEFAULT_MIN_SCALE) { + return false; + } + + if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH + || MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) { + return false; + } + + return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY); + } + return false; + } + }); + //modify by ChenSiLiang + setRotateGestureDetector(); + + mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); + mBaseRotation = 0.0f; + + // Finally, update the UI so that we're zoomable + setZoomable(zoomable); + } + + /** + * set rotate + * Modify by ChenSL on 2015 / 9 / 16. + */ + private void setRotateGestureDetector() { + if (mRotateGestureDetector == null) { + mRotateGestureDetector = new RotateGestureDetector(); + mRotateGestureDetector.setRotateListener(new IRotateListener() { + @Override + public void rotate(int degree, int pivotX, int pivotY) { + if (mRightAngleRunnable != null && mIsToRighting) { + getImageView().removeCallbacks(mRightAngleRunnable); + } + mSuppMatrix.postRotate(degree, pivotX, pivotY); + if (mOnRotateListener != null) { + mOnRotateListener.onRotate(degree); + } + //Post the rotation to the image + checkAndDisplayMatrix(); + } + + @Override + public void upRotate(int pivotX, int pivotY) { + if (mIsToRightAngle) { + float[] v = new float[9]; + mSuppMatrix.getValues(v); + // calculate the degree of rotation + int angle = (int) (Math.round(Math.atan2(v[Matrix.MSKEW_X], v[Matrix.MSCALE_X]) * (180 / Math.PI))); + if (angle <= 0) { + angle = -angle; + } else { + angle = 360 - angle; + } + mRightAngleRunnable = new RightAngleRunnable(angle, pivotX, pivotY); + getImageView().post(mRightAngleRunnable); + } + } + }); + } + } + + @Override + public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) { + if (newOnDoubleTapListener != null) { + this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener); + } else { + this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); + } + } + + @Override + public void setOnScaleChangeListener(OnScaleChangeListener onScaleChangeListener) { + this.mScaleChangeListener = onScaleChangeListener; + } + + @Override + public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) { + this.mSingleFlingListener = onSingleFlingListener; + } + + /** + * set Rotatable + * Created by ChenSL on 2015/9/16. + * + * @param isRotatable true,enbale + */ + public void setRotatable(boolean isRotatable) { + mIsEnableRotate = isRotatable; + } + + /** + * set the boolean to the rotation to right angle(0,90,180,270 degree) when one finger up from the screen + * Created by ChenSL on 2015/9/16. + * + * @param toRightAngle true,recover to right angle when one finger lift;false,otherwise. + */ + public void setToRightAngle(boolean toRightAngle) { + mIsToRightAngle = toRightAngle; + } + + @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 android.view.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); + } + if (null != mRotateGestureDetector) { + mRotateGestureDetector.setRotateListener(null); + } + + // Clear listeners too + mMatrixChangeListener = null; + mPhotoTapListener = null; + mViewTapListener = null; + mOnRotateListener = 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; + } + + public void setBaseRotation(final float degrees) { + mBaseRotation = degrees % 360; + update(); + setRotationBy(mBaseRotation); + checkAndDisplayMatrix(); + } + + @Override + public void setRotationTo(float degrees) { + mSuppMatrix.setRotate(degrees % 360); + checkAndDisplayMatrix(); + } + + @Override + public void setRotationBy(float degrees) { + mSuppMatrix.postRotate(degrees % 360); + 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(); + LogManager.getLogger().i(LOG_TAG, + "ImageView no longer exists. You should not use this PhotoViewAttacher any more."); + } + + return imageView; + } + + @Override + public float getMinimumScale() { + return mMinScale; + } + + @Override + public float getMediumScale() { + return mMidScale; + } + + @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 && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) { + 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) && (getScale() > mMinScale || scaleFactor > 1f)) { + if (null != mScaleChangeListener) { + mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); + } + mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); + checkAndDisplayMatrix(); + } + } + + @SuppressLint("ClickableViewAccessibility") + @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 { + LogManager.getLogger().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; + } + + //detect the rotation + if (mIsEnableRotate && ev.getPointerCount() == 2) { + mRotateGestureDetector.onTouchEvent(ev); + } + boolean wasRotate = mRotateGestureDetector.isRotating(); + + // Try the Scale/Drag detector + if (null != mScaleDragDetector) { + boolean wasScaling = mScaleDragDetector.isScaling(); + boolean wasDragging = mScaleDragDetector.isDragging(); + + handled = mScaleDragDetector.onTouchEvent(ev); + + boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling(); + boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging(); + boolean didnttRotate = !wasRotate && !mRotateGestureDetector.isRotating(); + + mBlockParentIntercept = didntScale && didntDrag && didnttRotate; + } + + // 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 + public void setMinimumScale(float minimumScale) { + checkZoomLevels(minimumScale, mMidScale, mMaxScale); + mMinScale = minimumScale; + } + + @Override + public void setMediumScale(float mediumScale) { + checkZoomLevels(mMinScale, mediumScale, mMaxScale); + mMidScale = mediumScale; + } + + @Override + public void setMaximumScale(float maximumScale) { + checkZoomLevels(mMinScale, mMidScale, maximumScale); + mMaxScale = maximumScale; + } + + @Override + public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) { + checkZoomLevels(minimumScale, mediumScale, maximumScale); + mMinScale = minimumScale; + mMidScale = mediumScale; + 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 void setOnRotateListener(OnRotateListener onRotateListener) { + mOnRotateListener = onRotateListener; + } + + @Override + public OnViewTapListener getOnViewTapListener() { + return mViewTapListener; + } + + @Nullable + OnPhotoTapListener getOnPhotoTapListener() { + return mPhotoTapListener; + } + + @Override + public void setOnViewTapListener(OnViewTapListener listener) { + mViewTapListener = listener; + } + + @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(); + } + } + } + + /** + * Set the zoom interpolator + * @param interpolator the zoom interpolator + */ + public void setZoomInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + @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(); + } + } + } + + /** + * Get the display matrix + * @param matrix target matrix to copy to + */ + @Override + public void getDisplayMatrix(Matrix matrix) { + matrix.set(getDrawMatrix()); + } + + /** + * Get the current support matrix + */ + public void getSuppMatrix(Matrix matrix) { + matrix.set(mSuppMatrix); + } + + private Matrix getDrawMatrix() { + mDrawMatrix.set(mBaseMatrix); + mDrawMatrix.postConcat(mSuppMatrix); + return mDrawMatrix; + } + + private void cancelFling() { + if (null != mCurrentFlingRunnable) { + mCurrentFlingRunnable.cancelFling(); + mCurrentFlingRunnable = null; + } + } + + public Matrix getImageMatrix() { + return mDrawMatrix; + } + + /** + * 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. You should call setScaleType on the PhotoViewAttacher instead of on the ImageView" ); + } + } + } + + 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; + } + + @Override + public IPhotoView getIPhotoViewImplementation() { + return this; + } + + /** + * 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(); + setRotationBy(mBaseRotation); + 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); + + if ((int) mBaseRotation % 180 != 0) { + mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth); + } + + 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 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 callback to be invoked when attached ImageView scale changes + * + * @author Marek Sebera + */ + public interface OnScaleChangeListener { + /** + * Callback for when the scale changes + * + * @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in) + * @param focusX focal point X position + * @param focusY focal point Y position + */ + void onScaleChange(float scaleFactor, float focusX, float focusY); + } + + /** + * Interface definition for a callback to be invoked when the Photo is tapped with a single + * tap. + * + * @author Chris Banes + */ + public 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); + + /** + * A simple callback where out of photo happened; + * */ + void onOutsidePhotoTap(); + } + + /** + * Interface definition for a callback to be invoked when the ImageView is tapped with a single + * tap. + * + * @author Chris Banes + */ + public 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); + } + + /** + * Interface definition for a callback to be invoked when the ImageView is roateted with two finger. + * + * @author ChenSL + */ + public interface OnRotateListener { + /** + * A callBack to receive when the user rotate a ImageView.You will receive a callback + * if the user rotate the ImageView + * + * @param degree rotate mOldDegree + */ + void onRotate(int degree); + } + + /** + * Interface definition for a callback to be invoked when the ImageView is fling with a single + * touch + * + * @author tonyjs + */ + public interface OnSingleFlingListener { + + /** + * A callback to receive where the user flings on a ImageView. You will receive a callback if + * the user flings anywhere on the view. + * + * @param e1 - MotionEvent the user first touch. + * @param e2 - MotionEvent the user last touch. + * @param velocityX - distance of user's horizontal fling. + * @param velocityY - distance of user's vertical fling. + */ + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); + } + + 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(); + + onScale(deltaScale, mFocalX, mFocalY); + + // 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 = mInterpolator.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); + } + } + } + + /** + * a RightAngleRunnable that finger lift rotate to 0,90,180,270 degree + */ + private class RightAngleRunnable implements Runnable { + private static final int RECOVER_SPEED = 4; + private int mOldDegree; + private int mNeedToRotate; + private int mRoPivotX; + private int mRoPivotY; + + RightAngleRunnable(int degree, int pivotX, int pivotY) { + this.mOldDegree = degree; + this.mNeedToRotate = calDegree(degree) - mOldDegree; + this.mRoPivotX = pivotX; + this.mRoPivotY = pivotY; + } + + /** + * get right degree,when one finger lifts + * + * @param oldDegree current degree + * @return 0, 90, 180, 270 according to oldDegree + */ + private int calDegree(int oldDegree) { + int result; + float n = (float) oldDegree / 45; + if (n >= 0 && n < 1) { + result = 0; + } else if (n >= 1 && n <= 2.5) { + result = 90; + } else if (n > 2.5 && n < 5.5) { + result = 180; + } else if (n >= 5.5 && n <= 7) { + result = 270; + } else { + result = 360; + } + return result; + } + + @Override + public void run() { + if (mNeedToRotate == 0) { + mIsToRighting = false; + return; + } + ImageView imageView = getImageView(); + if (imageView == null) { + mIsToRighting = false; + return; + } + mIsToRighting = true; + if (mNeedToRotate > 0) { + //Clockwise rotation + if (mNeedToRotate >= RECOVER_SPEED) { + mSuppMatrix.postRotate(RECOVER_SPEED, mRoPivotX, mRoPivotY); + mNeedToRotate -= RECOVER_SPEED; + } else { + mSuppMatrix.postRotate(mNeedToRotate, mRoPivotX, mRoPivotY); + mNeedToRotate = 0; + } + } else if (mNeedToRotate < 0) { + //Counterclockwise rotation + if (mNeedToRotate <= -RECOVER_SPEED) { + mSuppMatrix.postRotate(-RECOVER_SPEED, mRoPivotX, mRoPivotY); + mNeedToRotate += RECOVER_SPEED; + } else { + mSuppMatrix.postRotate(mNeedToRotate, mRoPivotX, mRoPivotY); + mNeedToRotate = 0; + } + } + checkAndDisplayMatrix(); + Compat.postOnAnimation(imageView, this); + } + } +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/CupcakeGestureDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/CupcakeGestureDetector.java new file mode 100644 index 0000000..e14f463 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/CupcakeGestureDetector.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview.gestures; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +import uk.co.senab.photoview.log.LogManager; + +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(); + } + + @Override + public boolean isScaling() { + return false; + } + + @Override + public boolean isDragging() { + return mIsDragging; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + mVelocityTracker = VelocityTracker.obtain(); + if (null != mVelocityTracker) { + mVelocityTracker.addMovement(ev); + } else { + LogManager.getLogger().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/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/EclairGestureDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/EclairGestureDetector.java new file mode 100644 index 0000000..f4b4b73 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/EclairGestureDetector.java @@ -0,0 +1,92 @@ +/** + * **************************************************************************** + * 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 uk.co.senab.photoview.gestures; + +import android.annotation.TargetApi; +import android.content.Context; +import android.view.MotionEvent; + +import uk.co.senab.photoview.Compat; + +@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); + try { + return super.onTouchEvent(ev); + } catch (IllegalArgumentException e) { + // Fix for support lib bug, happening when onDestroy is + return true; + } + } +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/FroyoGestureDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/FroyoGestureDetector.java new file mode 100644 index 0000000..5331704 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/FroyoGestureDetector.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview.gestures; + +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) { + try { + mDetector.onTouchEvent(ev); + return super.onTouchEvent(ev); + } catch (IllegalArgumentException e) { + // Fix for support lib bug, happening when onDestroy is + return true; + } + } + +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/GestureDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/GestureDetector.java new file mode 100644 index 0000000..3c9177e --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/GestureDetector.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview.gestures; + +import android.view.MotionEvent; + +public interface GestureDetector { + + boolean onTouchEvent(MotionEvent ev); + + boolean isScaling(); + + boolean isDragging(); + + void setOnGestureListener(OnGestureListener listener); + +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/IRotateDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/IRotateDetector.java new file mode 100644 index 0000000..03a70eb --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/IRotateDetector.java @@ -0,0 +1,24 @@ +package uk.co.senab.photoview.gestures; + +import android.view.MotionEvent; + +/** + * Interface to detect rotation + * Created by ChenSL on 2015/9/16. + */ +public interface IRotateDetector { + /** + * handle rotation in onTouchEvent + * + * @param event The motion event. + * @return True if the event was handled, false otherwise. + */ + boolean onTouchEvent(MotionEvent event); + + /** + * is the Gesture Rotate + * + * @return true:rotating;false,otherwise + */ + boolean isRotating(); +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/IRotateListener.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/IRotateListener.java new file mode 100644 index 0000000..96f76aa --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/IRotateListener.java @@ -0,0 +1,20 @@ +package uk.co.senab.photoview.gestures; + +/** + * Interface for a callback for rotation + * Created by ChenSL on 2015/9/16. + */ +public interface IRotateListener { + /** + * callback for rotation + * + * @param degree degree of rotation + */ + void rotate(int degree, int pivotX, int pivotY); + + /** + * MotionEvent.ACTION_POINTER_UP happens when two finger minus to only one + * change the ImageView to 0,90,180,270 + */ + void upRotate(int pivotX, int pivotY); +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/OnGestureListener.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/OnGestureListener.java new file mode 100644 index 0000000..4c124d1 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/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 uk.co.senab.photoview.gestures; + +public interface OnGestureListener { + + void onDrag(float dx, float dy); + + void onFling(float startX, float startY, float velocityX, + float velocityY); + + void onScale(float scaleFactor, float focusX, float focusY); + +} \ No newline at end of file diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/RotateGestureDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/RotateGestureDetector.java new file mode 100644 index 0000000..45cc6df --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/RotateGestureDetector.java @@ -0,0 +1,113 @@ +package uk.co.senab.photoview.gestures; + +import android.view.MotionEvent; + +/** + * Handle ImageView rotate event with two fingers + * Created by ChenSL on 2015/9/16. + */ +public class RotateGestureDetector implements IRotateDetector { + private int mLastAngle = 0; + private IRotateListener mListener; + private boolean mIsRotate; + + /** + * set rotation listener for callback + * + * @param listener a rotation listener + */ + public void setRotateListener(IRotateListener listener) { + this.mListener = listener; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return doRotate(event); + } + + @Override + public boolean isRotating() { + return mIsRotate; + } + + /** + * handle rotation + * + * @param ev Motion event + * @return always true. + */ + private boolean doRotate(MotionEvent ev) { + if (ev.getPointerCount() != 2) { + return false; + } + //Calculate the angle between the two fingers + int pivotX = (int) (ev.getX(0) + ev.getX(1)) / 2; + int pivotY = (int) (ev.getY(0) + ev.getY(1)) / 2; + float deltaX = ev.getX(0) - ev.getX(1); + float deltaY = ev.getY(0) - ev.getY(1); + double radians = Math.atan(deltaY / deltaX); + //Convert to degrees + int degrees = (int) (radians * 180 / Math.PI); + /* + * Must use getActionMasked() for switching to pick up pointer events. + * These events have the pointer index encoded in them so the return + * from getAction() won't match the exact action constant. + */ + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mLastAngle = degrees; + mIsRotate = false; + break; + case MotionEvent.ACTION_UP: + mIsRotate = false; + break; + case MotionEvent.ACTION_POINTER_DOWN: + mLastAngle = degrees; + mIsRotate = false; + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_POINTER_UP: + mIsRotate = false; + upRotate(pivotX, pivotY); + mLastAngle = degrees; + break; + case MotionEvent.ACTION_MOVE: + mIsRotate = true; + int degreesValue = degrees - mLastAngle; + if (degreesValue > 45) { + //Going CCW across the boundary + rotate(-5, pivotX, pivotY); + } else if (degreesValue < -45) { + //Going CW across the boundary + rotate(5, pivotX, pivotY); + } else { + //Normal rotation, rotate the difference + rotate(degreesValue, pivotX, pivotY); + } + //Save the current angle + mLastAngle = degrees; + break; + } + return true; + } + + /** + * to invoke the callback + * + * @param degree degree to rotate + */ + private void rotate(int degree, int pivotX, int pivotY) { + if (mListener != null) { + mListener.rotate(degree, pivotX, pivotY); + } + } + + /** + * to invoke the finger up action + */ + private void upRotate(int pivotX, int pivotY) { + if (mListener != null) { + mListener.upRotate(pivotX, pivotY); + } + } +} diff --git a/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/VersionedGestureDetector.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/VersionedGestureDetector.java new file mode 100644 index 0000000..c1550ce --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/gestures/VersionedGestureDetector.java @@ -0,0 +1,42 @@ +package uk.co.senab.photoview.gestures; + +/******************************************************************************* + * 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/lib_media_selector/src/github/java/uk/co/senab/photoview/log/LogManager.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/log/LogManager.java new file mode 100644 index 0000000..eefdde3 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/log/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 uk.co.senab.photoview.log; + +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/lib_media_selector/src/github/java/uk/co/senab/photoview/log/Logger.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/log/Logger.java new file mode 100644 index 0000000..5324d75 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/log/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 uk.co.senab.photoview.log; + +/** + * 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/lib_media_selector/src/github/java/uk/co/senab/photoview/log/LoggerDefault.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/log/LoggerDefault.java new file mode 100644 index 0000000..f827f4a --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/log/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 uk.co.senab.photoview.log; + +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/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/GingerScroller.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/GingerScroller.java new file mode 100644 index 0000000..e831ce3 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/GingerScroller.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * 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 uk.co.senab.photoview.scrollerproxy; + +import android.annotation.TargetApi; +import android.content.Context; +import android.widget.OverScroller; + +@TargetApi(9) +public class GingerScroller extends ScrollerProxy { + + protected final OverScroller mScroller; + + public GingerScroller(Context context) { + mScroller = new OverScroller(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, 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/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/IcsScroller.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/IcsScroller.java new file mode 100644 index 0000000..10f9eab --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/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 uk.co.senab.photoview.scrollerproxy; + +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/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/PreGingerScroller.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/PreGingerScroller.java new file mode 100644 index 0000000..2cd91b3 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/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 uk.co.senab.photoview.scrollerproxy; + +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/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/ScrollerProxy.java b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/ScrollerProxy.java new file mode 100644 index 0000000..ce39ef1 --- /dev/null +++ b/lib_media_selector/src/github/java/uk/co/senab/photoview/scrollerproxy/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 uk.co.senab.photoview.scrollerproxy; + +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/lib_media_selector/src/github/res/values/dimens.xml b/lib_media_selector/src/github/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/lib_media_selector/src/github/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/lib_media_selector/src/github/res/values/strings.xml b/lib_media_selector/src/github/res/values/strings.xml new file mode 100644 index 0000000..b53b8b6 --- /dev/null +++ b/lib_media_selector/src/github/res/values/strings.xml @@ -0,0 +1,3 @@ + + Hello world! + diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingGlideLoader.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingGlideLoader.java index 79d1990..dc637b2 100644 --- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingGlideLoader.java +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingGlideLoader.java @@ -18,8 +18,6 @@ package com.android.sdk.mediaselector; import android.graphics.Bitmap; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.widget.ImageView; import com.bilibili.boxing.loader.IBoxingCallback; @@ -31,6 +29,9 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * use https://github.com/bumptech/glide as media loader. * @@ -78,4 +79,4 @@ final class BoxingGlideLoader implements IBoxingMediaLoader { .into(imageView); } -} +} \ No newline at end of file diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingUcrop.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingUcrop.java index 2754c2d..51558a6 100644 --- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingUcrop.java +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/BoxingUcrop.java @@ -21,15 +21,16 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; import com.bilibili.boxing.loader.IBoxingCrop; import com.bilibili.boxing.model.config.BoxingCropOption; import com.yalantis.ucrop.UCrop; import com.ztiany.mediaselector.R; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + /** * use Ucrop(https://github.com/Yalantis/uCrop) as the implement for {@link IBoxingCrop} * @@ -38,22 +39,24 @@ import com.ztiany.mediaselector.R; final class BoxingUcrop implements IBoxingCrop { @Override - public void onStartCrop(Context context, Fragment fragment, @NonNull BoxingCropOption cropConfig, - @NonNull String path, int requestCode) { + public void onStartCrop(Context context, Fragment fragment, @NonNull BoxingCropOption cropConfig, @NonNull String path, int requestCode) { Uri uri = new Uri.Builder() .scheme("file") .appendPath(path) .build(); + //参数 UCrop.Options crop = new UCrop.Options(); crop.setCompressionFormat(Bitmap.CompressFormat.JPEG); crop.withMaxResultSize(cropConfig.getMaxWidth(), cropConfig.getMaxHeight()); crop.withAspectRatio(cropConfig.getAspectRatioX(), cropConfig.getAspectRatioY()); + //颜色 int color = ContextCompat.getColor(context, R.color.boxing_colorPrimaryDark); crop.setToolbarColor(color); crop.setStatusBarColor(color); + //开始裁减 UCrop.of(uri, cropConfig.getDestination()) .withOptions(crop) @@ -71,4 +74,5 @@ final class BoxingUcrop implements IBoxingCrop { } return UCrop.getOutput(data); } -} + +} \ No newline at end of file diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/CropOptions.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/CropOptions.java index a6c7f2c..e0eb950 100644 --- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/CropOptions.java +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/CropOptions.java @@ -78,5 +78,4 @@ public class CropOptions implements Serializable { return this; } - -} +} \ No newline at end of file diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/MediaSelector.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/MediaSelector.java index f0fbacb..8c44422 100644 --- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/MediaSelector.java +++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/MediaSelector.java @@ -4,7 +4,6 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.v4.app.Fragment; import android.text.TextUtils; import com.bilibili.boxing.Boxing; @@ -22,6 +21,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import androidx.fragment.app.Fragment; + /** * 基于 Boxing 的多媒体文件选择器 *

@@ -110,12 +111,10 @@ public class MediaSelector {
 
     private void start(BoxingConfig boxingConfig, int requestCode) {
         if (mFragment != null) {
-            Boxing boxing = Boxing.of(boxingConfig)
-                    .withIntent(mFragment.getContext(), BoxingActivity.class);
+            Boxing boxing = Boxing.of(boxingConfig).withIntent(mFragment.getContext(), BoxingActivity.class);
             boxing.start(mFragment, requestCode);
         } else if (mActivity != null) {
-            Boxing boxing = Boxing.of(boxingConfig)
-                    .withIntent(mActivity, BoxingActivity.class);
+            Boxing boxing = Boxing.of(boxingConfig).withIntent(mActivity, BoxingActivity.class);
             boxing.start(mActivity, requestCode);
         }
     }
diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java
index 9ac4037..00ba5a1 100644
--- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java
+++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/SystemMediaSelector.java
@@ -4,12 +4,13 @@ import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.support.v4.app.Fragment;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
 
+import androidx.fragment.app.Fragment;
+
 /**
  * 通过系统相册或者系统相机获取照片
  * 
diff --git a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java
index 3490f06..cf9bf43 100644
--- a/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java
+++ b/lib_media_selector/src/main/java/com/android/sdk/mediaselector/Utils.java
@@ -15,7 +15,6 @@ import android.os.Build;
 import android.os.Environment;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
-import android.support.v4.content.FileProvider;
 import android.text.TextUtils;
 
 import java.io.File;
@@ -23,6 +22,8 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.List;
 
+import androidx.core.content.FileProvider;
+
 /**
  * See:
  * 
@@ -35,7 +36,6 @@ import java.util.List;
  */
 final class Utils {
 
-
     private Utils() {
         throw new UnsupportedOperationException("Utils");
     }
@@ -365,4 +365,5 @@ final class Utils {
         // 最后通知图库更新
         context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + photoPath)));
     }
-}
+
+}
\ No newline at end of file
diff --git a/lib_qrcode/README.md b/lib_qrcode/README.md
index 3ee2da5..e5598d9 100644
--- a/lib_qrcode/README.md
+++ b/lib_qrcode/README.md
@@ -1,3 +1,3 @@
-## 二维码扫描库
+# 二维码扫描库
 
 修改自   [BGAQRCode-Android](https://github.com/bingoogolapple/BGAQRCode-Android)
\ No newline at end of file
diff --git a/lib_qrcode/build.gradle b/lib_qrcode/build.gradle
index 4163ab4..e386156 100644
--- a/lib_qrcode/build.gradle
+++ b/lib_qrcode/build.gradle
@@ -35,6 +35,6 @@ android {
 dependencies {
     implementation 'com.google.zxing:core:3.3.3'
     api fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
-    /*implementation uiLibraries.fotoapparat*/
+    implementation uiLibraries.fotoapparat
     implementation kotlinLibraries.kotlinStdlib
 }
\ No newline at end of file
diff --git a/lib_qrcode/libs/fotoapparat.aar b/lib_qrcode/libs/fotoapparat.aar
deleted file mode 100644
index 17f988c..0000000
Binary files a/lib_qrcode/libs/fotoapparat.aar and /dev/null differ
diff --git a/lib_qrcode/src/main/java/com/android/sdk/qrcode/CameraUtils.java b/lib_qrcode/src/main/java/com/android/sdk/qrcode/CameraUtils.java
index 58ea035..54aa372 100644
--- a/lib_qrcode/src/main/java/com/android/sdk/qrcode/CameraUtils.java
+++ b/lib_qrcode/src/main/java/com/android/sdk/qrcode/CameraUtils.java
@@ -13,8 +13,7 @@ import java.util.List;
 
 import io.fotoapparat.parameter.Resolution;
 
-@SuppressWarnings("unused")
-final class CameraUtils {
+public class CameraUtils {
 
     private static final double MAX_ASPECT_DISTORTION = 0.15;//最大比例偏差
     private static final int MIN_PREVIEW_PIXELS = 480 * 800;//小于此预览尺寸直接移除
@@ -26,7 +25,7 @@ final class CameraUtils {
         }
     }
 
-    static Resolution findBestPictureSize(Context context, Collection collection) {
+    public static Resolution findBestPictureSize(Context context, Collection collection) {
         if (collection.isEmpty()) {
             return null;
         }
@@ -36,7 +35,7 @@ final class CameraUtils {
         return bestPictureResolution;
     }
 
-    static Resolution findBestPreviewSize(Context context, Collection collection) {
+    public static Resolution findBestPreviewSize(Context context, Collection collection) {
         if (collection.isEmpty()) {
             return null;
         }
diff --git a/lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/QRCodeDecoder.java b/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeDecoder.java
similarity index 98%
rename from lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/QRCodeDecoder.java
rename to lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeDecoder.java
index f7ea79b..af56d03 100644
--- a/lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/QRCodeDecoder.java
+++ b/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeDecoder.java
@@ -1,4 +1,4 @@
-package com.android.sdk.qrcode.zxing;
+package com.android.sdk.qrcode;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
diff --git a/lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/QRCodeEncoder.java b/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeEncoder.java
similarity index 99%
rename from lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/QRCodeEncoder.java
rename to lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeEncoder.java
index 1033ba5..dfead74 100644
--- a/lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/QRCodeEncoder.java
+++ b/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeEncoder.java
@@ -1,4 +1,4 @@
-package com.android.sdk.qrcode.zxing;
+package com.android.sdk.qrcode;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
diff --git a/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeView.kt b/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeView.kt
index 249e49d..e82ad9b 100644
--- a/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeView.kt
+++ b/lib_qrcode/src/main/java/com/android/sdk/qrcode/QRCodeView.kt
@@ -18,14 +18,14 @@ abstract class QRCodeView @JvmOverloads constructor(
         context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
 ) : FrameLayout(context, attrs, defStyleAttr), ProcessDataTask.Delegate {
 
-    private lateinit var mCameraView: CameraView
-    private lateinit var mFotoapparat: Fotoapparat
+    private lateinit var cameraView: CameraView
+    private lateinit var fotoapparat: Fotoapparat
 
-    private lateinit var mScanBoxView: ScanBoxView
-    protected var mDelegate: Delegate? = null
+    private lateinit var scanBoxView: ScanBoxView
+    private var delegate: Delegate? = null
 
-    private var mProcessDataTask: ProcessDataTask? = null
-    protected var mSpotAble = false
+    private var dataTask: ProcessDataTask? = null
+    protected var spotAble = false
 
     private val framingRect = Rect()
     private var framingRectInPreview: Rect? = null
@@ -35,16 +35,16 @@ abstract class QRCodeView @JvmOverloads constructor(
     }
 
     private fun initView(context: Context, attrs: AttributeSet?) {
-        mCameraView = CameraView(getContext())
-        mScanBoxView = ScanBoxView(getContext())
-        mScanBoxView.initCustomAttrs(context, attrs)
-        addView(mCameraView)
-        addView(mScanBoxView)
+        cameraView = CameraView(getContext())
+        scanBoxView = ScanBoxView(getContext())
+        scanBoxView.initCustomAttrs(context, attrs)
+        addView(cameraView)
+        addView(scanBoxView)
         try {
-            mFotoapparat = createFotoapparat()
+            fotoapparat = createFotoapparat()
         } catch (e: Exception) {
             e.printStackTrace()
-            mDelegate?.onScanQRCodeOpenCameraError(e)
+            delegate?.onScanQRCodeOpenCameraError(e)
         }
     }
 
@@ -65,27 +65,27 @@ abstract class QRCodeView @JvmOverloads constructor(
 
         return Fotoapparat(
                 context = this@QRCodeView.context,
-                view = mCameraView,
+                view = cameraView,
                 logger = logcat(),
                 lensPosition = back(),
                 cameraConfiguration = configuration,
                 cameraErrorCallback = {
-                    mDelegate?.onScanQRCodeOpenCameraError(it)
+                    delegate?.onScanQRCodeOpenCameraError(it)
                 }
         )
     }
 
     private fun processFrame(frame: Frame) {
-        val processDataTask = mProcessDataTask
-        if (mSpotAble && (processDataTask == null || processDataTask.isCancelled)) {
+        val processDataTask = dataTask
+        if (spotAble && (processDataTask == null || processDataTask.isCancelled)) {
 
-            mProcessDataTask = object : ProcessDataTask(frame.image, frame.size, frame.rotation, this) {
+            dataTask = object : ProcessDataTask(frame.image, frame.size, frame.rotation, this) {
                 override fun onPostExecute(result: String?) {
 
-                    if (mSpotAble) {
+                    if (spotAble) {
                         if (!result.isNullOrEmpty()) {
                             try {
-                                mDelegate?.onScanQRCodeSuccess(result)
+                                delegate?.onScanQRCodeSuccess(result)
                                 stopSpot()
                             } catch (e: Exception) {
                                 e.printStackTrace()
@@ -105,21 +105,21 @@ abstract class QRCodeView @JvmOverloads constructor(
      * @param delegate 扫描二维码的代理
      */
     fun setDelegate(delegate: Delegate) {
-        mDelegate = delegate
+        this.delegate = delegate
     }
 
     /**
      * 显示扫描框
      */
     fun showScanRect() {
-        mScanBoxView.visibility = View.VISIBLE
+        scanBoxView.visibility = View.VISIBLE
     }
 
     /**
      * 隐藏扫描框
      */
     fun hiddenScanRect() {
-        mScanBoxView.visibility = View.GONE
+        scanBoxView.visibility = View.GONE
     }
 
     /**
@@ -127,7 +127,7 @@ abstract class QRCodeView @JvmOverloads constructor(
      */
     fun startCamera() {
         try {
-            mFotoapparat.start()
+            fotoapparat.start()
         } catch (throwable: Throwable) {
             throwable.printStackTrace()
         }
@@ -140,7 +140,7 @@ abstract class QRCodeView @JvmOverloads constructor(
     fun stopCamera() {
         stopSpotAndHiddenRect()
         try {
-            mFotoapparat.stop()
+            fotoapparat.stop()
         } catch (throwable: Throwable) {
             throwable.printStackTrace()
         }
@@ -151,7 +151,7 @@ abstract class QRCodeView @JvmOverloads constructor(
      */
     fun startSpot() {
         postDelayed({
-            mSpotAble = true
+            spotAble = true
             startCamera()
         }, 100)
     }
@@ -161,7 +161,7 @@ abstract class QRCodeView @JvmOverloads constructor(
      */
     fun stopSpot() {
         cancelProcessDataTask()
-        mSpotAble = false
+        spotAble = false
     }
 
     /**
@@ -186,14 +186,14 @@ abstract class QRCodeView @JvmOverloads constructor(
      * @return
      */
     val isScanBarcodeStyle: Boolean
-        get() = mScanBoxView.isBarcode
+        get() = scanBoxView.isBarcode
 
     /**
      * 打开闪光灯
      */
     fun openFlashlight() {
         try {
-            mFotoapparat.updateConfiguration(UpdateConfiguration(flashMode = firstAvailable(torch(), off())))
+            fotoapparat.updateConfiguration(UpdateConfiguration(flashMode = firstAvailable(torch(), off())))
         } catch (e: Exception) {
             e.printStackTrace()
         }
@@ -205,7 +205,7 @@ abstract class QRCodeView @JvmOverloads constructor(
      */
     fun closeFlashlight() {
         try {
-            mFotoapparat.updateConfiguration(UpdateConfiguration(flashMode = firstAvailable(off())))
+            fotoapparat.updateConfiguration(UpdateConfiguration(flashMode = firstAvailable(off())))
         } catch (e: Exception) {
             e.printStackTrace()
         }
@@ -216,29 +216,29 @@ abstract class QRCodeView @JvmOverloads constructor(
      * 销毁二维码扫描控件
      */
     fun onDestroy() {
-        mDelegate = null
+        delegate = null
     }
 
     /**
      * 取消数据处理任务
      */
     protected fun cancelProcessDataTask() {
-        mProcessDataTask?.cancelTask()
-        mProcessDataTask = null
+        dataTask?.cancelTask()
+        dataTask = null
     }
 
     /**
      * 切换成扫描条码样式
      */
     fun changeToScanBarcodeStyle() {
-        mScanBoxView.isBarcode = true
+        scanBoxView.isBarcode = true
     }
 
     /**
      * 切换成扫描二维码样式
      */
     fun changeToScanQRCodeStyle() {
-        mScanBoxView.isBarcode = false
+        scanBoxView.isBarcode = false
     }
 
     fun setDebug(debug: Boolean) {
@@ -246,7 +246,7 @@ abstract class QRCodeView @JvmOverloads constructor(
     }
 
     protected fun getFramingRectInPreview(previewWidth: Int, previewHeight: Int): Rect? {
-        if (!mScanBoxView.getScanBoxAreaRect(framingRect)) {
+        if (!scanBoxView.getScanBoxAreaRect(framingRect)) {
             return null
         }
         if (framingRectInPreview == null) {
@@ -274,7 +274,7 @@ abstract class QRCodeView @JvmOverloads constructor(
         /**
          * 处理打开相机出错
          */
-        fun onScanQRCodeOpenCameraError(error: java.lang.Exception)
+        fun onScanQRCodeOpenCameraError(error: Exception)
 
     }
 
diff --git a/lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/ZXingView.java b/lib_qrcode/src/main/java/com/android/sdk/qrcode/ZXingView.java
similarity index 94%
rename from lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/ZXingView.java
rename to lib_qrcode/src/main/java/com/android/sdk/qrcode/ZXingView.java
index 254c02d..54d4413 100644
--- a/lib_qrcode/src/main/java/com/android/sdk/qrcode/zxing/ZXingView.java
+++ b/lib_qrcode/src/main/java/com/android/sdk/qrcode/ZXingView.java
@@ -1,11 +1,9 @@
-package com.android.sdk.qrcode.zxing;
+package com.android.sdk.qrcode;
 
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 
-import com.android.sdk.qrcode.QRCodeView;
-import com.android.sdk.qrcode.Debug;
 import com.google.zxing.BinaryBitmap;
 import com.google.zxing.MultiFormatReader;
 import com.google.zxing.PlanarYUVLuminanceSource;