diff --git a/.gitignore b/.gitignore index 5664a63..fe3b002 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ -/build -gen -.DS_Store -/obj -/ffmpeg_rtsp -.vscode -local.properties -gradle.properties -.gradle -.idea *.iml -/exo-library +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.kotlin \ No newline at end of file diff --git a/EasyPlayerPro/build.gradle b/EasyPlayerPro/build.gradle index e3f84c9..dc96c16 100644 --- a/EasyPlayerPro/build.gradle +++ b/EasyPlayerPro/build.gradle @@ -1,16 +1,22 @@ -apply plugin: 'com.android.application' +plugins { + alias(libs.plugins.android.application) +} android { - compileSdkVersion 26 - buildToolsVersion '28.0.3' + namespace "org.easydarwin.easyplayer" + compileSdk { + version = release(36) + } defaultConfig { - applicationId "org.easydarwin.easyplayer" - minSdkVersion 19 - targetSdkVersion 26 + applicationId "org.easydarwin.easyplayer.pro" + minSdk 28 + targetSdk 36 versionCode 14210703 versionName "1.4.21.0703" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + if (project.hasProperty('PLAYER_KEY')) { buildConfigField 'String', 'PLAYER_KEY', PLAYER_KEY } else { @@ -19,44 +25,27 @@ android { } ndk { - //设置支持的SO库架构 -// abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' - abiFilters 'x86', 'armeabi-v7a' + abiFilters 'armeabi-v7a'/*, 'arm64-v8a'*/ } } buildTypes { release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug } } - dataBinding { - enabled = true + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - flavorDimensions "prod" - productFlavors { - pro { - applicationId "org.easydarwin.easyplayer.pro" - dimension "prod" - } - - /*fastPro { - applicationId "org.easydarwin.easyplayer.pro" - dimension "prod" - }*/ - - /*njjl { - applicationId "org.easydarwin.easyplayer.pro" - dimension "prod" - }*/ - } - - //签名配置 - signingConfigs { - + buildFeatures { + buildConfig true + viewBinding true + dataBinding true } android.applicationVariants.all { variant -> @@ -66,32 +55,19 @@ android { } } -repositories { - flatDir { - dirs 'libs' - } - - mavenCentral() -} - dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - - implementation files('libs/gson-2.1.jar') - - implementation "com.android.support:appcompat-v7:${support_version}" - implementation "com.android.support:support-v4:${support_version}" - implementation "com.android.support:preference-v7:${support_version}" - implementation "com.android.support:design:${support_version}" - implementation "com.android.support:cardview-v7:${support_version}" - -// implementation 'com.writingminds:FFmpegAndroid:0.3.2' - implementation 'com.github.bumptech.glide:glide:3.7.0' - implementation 'com.github.chrisbanes:PhotoView:1.3.0' - implementation 'com.squareup.okhttp3:okhttp:3.4.1' - implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.budiyev.android:code-scanner:1.9.4' - - testImplementation 'junit:junit:4.12' + implementation libs.androidx.core.ktx + implementation libs.androidx.appcompat + implementation libs.androidx.constraintlayout + implementation libs.androidx.recyclerview + implementation libs.androidx.swiperefreshlayout + implementation libs.material.components + implementation libs.glide + implementation libs.code.scanner implementation project(':ijkplayer-java') + implementation project(':photoview') + + testImplementation libs.junit + androidTestImplementation libs.androidx.junit + androidTestImplementation libs.androidx.espresso.core } diff --git a/EasyPlayerPro/libs/gson-2.1.jar b/EasyPlayerPro/libs/gson-2.1.jar deleted file mode 100644 index b85f091..0000000 Binary files a/EasyPlayerPro/libs/gson-2.1.jar and /dev/null differ diff --git a/EasyPlayerPro/proguard-rules.pro b/EasyPlayerPro/proguard-rules.pro index 6b1dc55..481bb43 100644 --- a/EasyPlayerPro/proguard-rules.pro +++ b/EasyPlayerPro/proguard-rules.pro @@ -1,30 +1,21 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in D:\software\adt-bundle-windows-x86_64-20140702\sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# Add any project specific keep options here: - # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} -# 忽略警告 --ignorewarning --keepclassmembers class org.easydarwin.video.EasyRTSPClient { - public *; -} +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable --keepclassmembers class org.easydarwin.video.RTSPClient { - private void onRTSPSourceCallBack(int, int, int, byte[], byte[]); -} --keepclassmembers class org.easydarwin.video.RTSPClient$FrameInfo{ - *; -} \ No newline at end of file +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/ApplicationTest.java b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/ApplicationTest.java deleted file mode 100644 index 0d8aa35..0000000 --- a/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.esaydarwin.rtsp.player; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java index 16089c7..3740e4f 100644 --- a/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java +++ b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java @@ -33,10 +33,14 @@ import android.opengl.GLES20; import android.opengl.Matrix; import android.os.Build; import android.os.Environment; -import android.test.AndroidTestCase; import android.util.Log; import android.view.Surface; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -66,8 +70,8 @@ import java.nio.FloatBuffer; * (This was derived from bits and pieces of CTS tests, and is packaged as such, but is not * currently part of CTS.) */ -@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) -public class CameraToMpegTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class CameraToMpegTest { private static final String TAG = "CameraToMpegTest"; private static final boolean VERBOSE = false; // lots of logging @@ -104,45 +108,41 @@ public class CameraToMpegTest extends AndroidTestCase { // allocate one of these up front so we don't need to do it every time private MediaCodec.BufferInfo mBufferInfo; - /** test entry point */ - public void testEncodeCameraToMp4() throws Throwable { - CameraToMpegWrapper.runTest(this); + /** + * Attempts to find a preview size that matches the provided width and height (which + * specify the dimensions of the encoded video). If it fails to find a match it just + * uses the default preview size. + *

+ * TODO: should do a best-fit match. + */ + private static void choosePreviewSize(Camera.Parameters parms, int width, int height) { + // We should make sure that the requested MPEG size is less than the preferred + // size, and has the same aspect ratio. + Camera.Size ppsfv = parms.getPreferredPreviewSizeForVideo(); + if (VERBOSE && ppsfv != null) { + Log.d(TAG, "Camera preferred preview size for video is " + + ppsfv.width + "x" + ppsfv.height); + } + + for (Camera.Size size : parms.getSupportedPreviewSizes()) { + if (size.width == width && size.height == height) { + parms.setPreviewSize(width, height); + return; + } + } + + Log.w(TAG, "Unable to set preview size to " + width + "x" + height); + if (ppsfv != null) { + parms.setPreviewSize(ppsfv.width, ppsfv.height); + } } /** - * Wraps encodeCameraToMpeg(). This is necessary because SurfaceTexture will try to use - * the looper in the current thread if one exists, and the CTS tests create one on the - * test thread. - * - * The wrapper propagates exceptions thrown by the worker thread back to the caller. + * test entry point */ - private static class CameraToMpegWrapper implements Runnable { - private Throwable mThrowable; - private CameraToMpegTest mTest; - - private CameraToMpegWrapper(CameraToMpegTest test) { - mTest = test; - } - - @Override - public void run() { - try { - mTest.encodeCameraToMpeg(); - } catch (Throwable th) { - mThrowable = th; - } - } - - /** Entry point. */ - public static void runTest(CameraToMpegTest obj) throws Throwable { - CameraToMpegWrapper wrapper = new CameraToMpegWrapper(obj); - Thread th = new Thread(wrapper, "codec test"); - th.start(); - th.join(); - if (wrapper.mThrowable != null) { - throw wrapper.mThrowable; - } - } + @Test + public void testEncodeCameraToMp4() throws Throwable { + CameraToMpegWrapper.runTest(this); } /** @@ -261,35 +261,6 @@ public class CameraToMpegTest extends AndroidTestCase { Log.d(TAG, "Camera preview size is " + size.width + "x" + size.height); } - /** - * Attempts to find a preview size that matches the provided width and height (which - * specify the dimensions of the encoded video). If it fails to find a match it just - * uses the default preview size. - *

- * TODO: should do a best-fit match. - */ - private static void choosePreviewSize(Camera.Parameters parms, int width, int height) { - // We should make sure that the requested MPEG size is less than the preferred - // size, and has the same aspect ratio. - Camera.Size ppsfv = parms.getPreferredPreviewSizeForVideo(); - if (VERBOSE && ppsfv != null) { - Log.d(TAG, "Camera preferred preview size for video is " + - ppsfv.width + "x" + ppsfv.height); - } - - for (Camera.Size size : parms.getSupportedPreviewSizes()) { - if (size.width == width && size.height == height) { - parms.setPreviewSize(width, height); - return; - } - } - - Log.w(TAG, "Unable to set preview size to " + width + "x" + height); - if (ppsfv != null) { - parms.setPreviewSize(ppsfv.width, ppsfv.height); - } - } - /** * Stops camera preview, and releases the camera to the system. */ @@ -497,6 +468,43 @@ public class CameraToMpegTest extends AndroidTestCase { } } + /** + * Wraps encodeCameraToMpeg(). This is necessary because SurfaceTexture will try to use + * the looper in the current thread if one exists, and the CTS tests create one on the + * test thread. + *

+ * The wrapper propagates exceptions thrown by the worker thread back to the caller. + */ + private static class CameraToMpegWrapper implements Runnable { + private Throwable mThrowable; + private CameraToMpegTest mTest; + + private CameraToMpegWrapper(CameraToMpegTest test) { + mTest = test; + } + + /** + * Entry point. + */ + public static void runTest(CameraToMpegTest obj) throws Throwable { + CameraToMpegWrapper wrapper = new CameraToMpegWrapper(obj); + Thread th = new Thread(wrapper, "codec test"); + th.start(); + th.join(); + if (wrapper.mThrowable != null) { + throw wrapper.mThrowable; + } + } + + @Override + public void run() { + try { + mTest.encodeCameraToMpeg(); + } catch (Throwable th) { + mThrowable = th; + } + } + } /** * Holds state associated with a Surface used for MediaCodec encoder input. @@ -754,16 +762,6 @@ public class CameraToMpegTest extends AndroidTestCase { private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; - private final float[] mTriangleVerticesData = { - // X, Y, Z, U, V - -1.0f, -1.0f, 0, 0.f, 0.f, - 1.0f, -1.0f, 0, 1.f, 0.f, - -1.0f, 1.0f, 0, 0.f, 1.f, - 1.0f, 1.0f, 0, 1.f, 1.f, - }; - - private FloatBuffer mTriangleVertices; - private static final String VERTEX_SHADER = "uniform mat4 uMVPMatrix;\n" + "uniform mat4 uSTMatrix;\n" + @@ -774,7 +772,6 @@ public class CameraToMpegTest extends AndroidTestCase { " gl_Position = uMVPMatrix * aPosition;\n" + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + "}\n"; - private static final String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + // highp here doesn't seem to matter @@ -783,7 +780,14 @@ public class CameraToMpegTest extends AndroidTestCase { "void main() {\n" + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + "}\n"; - + private final float[] mTriangleVerticesData = { + // X, Y, Z, U, V + -1.0f, -1.0f, 0, 0.f, 0.f, + 1.0f, -1.0f, 0, 1.f, 0.f, + -1.0f, 1.0f, 0, 0.f, 1.f, + 1.0f, 1.0f, 0, 1.f, 1.f, + }; + private FloatBuffer mTriangleVertices; private float[] mMVPMatrix = new float[16]; private float[] mSTMatrix = new float[16]; @@ -796,13 +800,19 @@ public class CameraToMpegTest extends AndroidTestCase { public STextureRender() { mTriangleVertices = ByteBuffer.allocateDirect( - mTriangleVerticesData.length * FLOAT_SIZE_BYTES) + mTriangleVerticesData.length * FLOAT_SIZE_BYTES) .order(ByteOrder.nativeOrder()).asFloatBuffer(); mTriangleVertices.put(mTriangleVerticesData).position(0); Matrix.setIdentityM(mSTMatrix, 0); } + public static void checkLocation(int location, String label) { + if (location < 0) { + throw new RuntimeException("Unable to locate '" + label + "' in program"); + } + } + public int getTextureId() { return mTextureID; } @@ -952,11 +962,5 @@ public class CameraToMpegTest extends AndroidTestCase { throw new RuntimeException(op + ": glError " + error); } } - - public static void checkLocation(int location, String label) { - if (location < 0) { - throw new RuntimeException("Unable to locate '" + label + "' in program"); - } - } } } \ No newline at end of file diff --git a/EasyPlayerPro/src/main/AndroidManifest.xml b/EasyPlayerPro/src/main/AndroidManifest.xml index ed4da98..b030871 100644 --- a/EasyPlayerPro/src/main/AndroidManifest.xml +++ b/EasyPlayerPro/src/main/AndroidManifest.xml @@ -1,18 +1,25 @@ + xmlns:tools="http://schemas.android.com/tools"> + + - + - + @@ -50,15 +59,18 @@ android:configChanges="orientation|keyboardHidden|screenSize" android:theme="@style/FullscreenTheme" /> - + - - - + + + diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java index 1a25e86..c757989 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java @@ -1,17 +1,18 @@ package org.easydarwin.easyplayer; import android.content.Intent; -import android.databinding.DataBindingUtil; import android.graphics.Color; import android.net.Uri; import android.os.Build; -import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.text.SpannableString; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.view.View; +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; + import org.easydarwin.easyplayer.databinding.ActivityAboutBinding; import org.easydarwin.easyplayer.views.ProVideoView; @@ -43,7 +44,7 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList binding.version.setText("EasyPlayer Pro播放器"); binding.version.append("("); - long activeDays = ProVideoView.getActiveDays(this,BuildConfig.PLAYER_KEY); + long activeDays = ProVideoView.getActiveDays(this, BuildConfig.PLAYER_KEY); SpannableString ss; if (activeDays >= 9999) { @@ -76,20 +77,17 @@ public class AboutActivity extends AppCompatActivity implements View.OnClickList @Override public void onClick(View v) { - Intent intent= new Intent(); + Intent intent = new Intent(); intent.setAction("android.intent.action.VIEW"); Uri content_url = Uri.parse("http://www.easydarwin.org"); - switch (v.getId()) { - case R.id.darwin_content_tv: - content_url = Uri.parse("http://www.easydarwin.org"); - break; - case R.id.dss_content_tv: - content_url = Uri.parse("http://www.easydss.com"); - break; - case R.id.nvr_content_tv: - content_url = Uri.parse("http://www.easynvr.com"); - break; + int id = v.getId(); + if (id == R.id.darwin_content_tv) { + content_url = Uri.parse("http://www.easydarwin.org"); + } else if (id == R.id.dss_content_tv) { + content_url = Uri.parse("http://www.easydss.com"); + } else if (id == R.id.nvr_content_tv) { + content_url = Uri.parse("http://www.easynvr.com"); } intent.setData(content_url); diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java index 839bbb9..234d20b 100755 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java @@ -1,13 +1,8 @@ package org.easydarwin.easyplayer; -import android.databinding.DataBindingUtil; import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; @@ -15,12 +10,19 @@ import android.text.style.RelativeSizeSpan; import android.view.MenuItem; import android.view.View; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentPagerAdapter; + import org.easydarwin.easyplayer.databinding.ActivityMediaFilesBinding; import org.easydarwin.easyplayer.fragments.LocalFileFragment; /** * 录像和截图 - * */ + * + */ public class MediaFilesActivity extends AppCompatActivity { private ActivityMediaFilesBinding mDataBinding; diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java index 0e8a460..099cffb 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java @@ -1,22 +1,17 @@ package org.easydarwin.easyplayer; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.Manifest; +import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; import android.database.Cursor; -import android.databinding.DataBindingUtil; import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; @@ -27,8 +22,15 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.bumptech.glide.Glide; -import com.bumptech.glide.signature.StringSignature; import org.easydarwin.easyplayer.data.VideoSource; import org.easydarwin.easyplayer.databinding.ActivityPlayListBinding; @@ -40,21 +42,18 @@ import org.easydarwin.easyplayer.views.ProVideoView; import java.io.File; import java.util.ArrayList; import java.util.List; -import java.util.UUID; - -import static android.content.pm.PackageManager.PERMISSION_GRANTED; /** * 视频广场 - * */ + * + */ +@SuppressLint("Range") public class PlayListActivity extends AppCompatActivity implements View.OnClickListener, View.OnLongClickListener { - private static final int REQUEST_PLAY = 1000; public static final int REQUEST_CAMERA_PERMISSION = 1001; - private static final int REQUEST_SCAN_TEXT_URL = 1003; // 扫描二维码 - public static final String EXTRA_BOOLEAN_SELECT_ITEM_TO_PLAY = "extra-boolean-select-item-to-play"; - + private static final int REQUEST_PLAY = 1000; + private static final int REQUEST_SCAN_TEXT_URL = 1003; // 扫描二维码 private int mPos; private ActivityPlayListBinding mBinding; private RecyclerView mRecyclerView; @@ -83,9 +82,7 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL mCursor = TheApp.sDB.query(VideoSource.TABLE_NAME, null, null, null, null, null, null); if (!mCursor.moveToFirst()) { List urls = new ArrayList<>(); - urls.add("rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"); - urls.add("rtmp://live.hkstv.hk.lxdns.com/live/hks2"); - urls.add("http://www.easydarwin.org/public/video/3/video.m3u8"); + urls.add("rtsp://77.110.228.219/axis-media/media.amp"); urls.add("http://m4.pptvyun.com/pvod/e11a0/ijblO6coKRX6a8NEQgg8LDZcqPY/eyJkbCI6MTUxNjYyNTM3NSwiZXMiOjYwNDgwMCwiaWQiOiIwYTJkbnEtWG82S2VvcTZMNEsyZG9hZmhvNkNjbTY2WXB3IiwidiI6IjEuMCJ9/0a2dnq-Xo6Keoq6L4K2doafho6Ccm66Ypw.mp4"); for (String url : urls) { @@ -127,7 +124,6 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL Glide.with(PlayListActivity.this) .load(file) - .signature(new StringSignature(UUID.randomUUID().toString())) .placeholder(R.drawable.placeholder) .centerCrop() .into(plvh.mImageView); @@ -192,9 +188,10 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case REQUEST_CAMERA_PERMISSION: { - if (grantResults.length > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.length > 1 && grantResults[0] == PERMISSION_GRANTED && grantResults[1] == PERMISSION_GRANTED) { toScanQRActivity(); } @@ -310,9 +307,9 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL } if (url.toLowerCase().indexOf("rtsp://") != 0 && url.toLowerCase().indexOf("rtmp://") != 0 && - url.toLowerCase().indexOf("http://") != 0 && url.toLowerCase().indexOf("https://") != 0 && + url.toLowerCase().indexOf("http://") != 0 && url.toLowerCase().indexOf("https://") != 0 && url.toLowerCase().indexOf("hls://") != 0) { - Toast.makeText(PlayListActivity.this,"不是合法的地址,请重新添加.",Toast.LENGTH_SHORT).show(); + Toast.makeText(PlayListActivity.this, "不是合法的地址,请重新添加.", Toast.LENGTH_SHORT).show(); return; } @@ -361,7 +358,7 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL private void notifyAboutColorChange() { //// !!!! important to set KEY !!!! ProVideoView.setKey(BuildConfig.PLAYER_KEY); - long activeDays = ProVideoView.getActiveDays(this,BuildConfig.PLAYER_KEY); + long activeDays = ProVideoView.getActiveDays(this, BuildConfig.PLAYER_KEY); ImageView iv = findViewById(R.id.toolbar_about); @@ -379,9 +376,25 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL startActivity(i); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_SCAN_TEXT_URL) { + if (resultCode == RESULT_OK) { + String url = data.getStringExtra("text"); + edit.setText(url); + } + } else { +// mRecyclerView.getAdapter().notifyItemChanged(mPos); + mRecyclerView.getAdapter().notifyDataSetChanged(); + } + } + /** * 视频源的item - * */ + * + */ class PlayListViewHolder extends RecyclerView.ViewHolder { private final TextView mTextView; private final TextView mAudienceNumber; @@ -399,19 +412,4 @@ public class PlayListActivity extends AppCompatActivity implements View.OnClickL itemView.setTag(this); } } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == REQUEST_SCAN_TEXT_URL) { - if (resultCode == RESULT_OK) { - String url = data.getStringExtra("text"); - edit.setText(url); - } - } else { -// mRecyclerView.getAdapter().notifyItemChanged(mPos); - mRecyclerView.getAdapter().notifyDataSetChanged(); - } - } } diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java index 0253435..5c1e227 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java @@ -16,24 +16,19 @@ package org.easydarwin.easyplayer; * limitations under the License. */ +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + import android.Manifest; import android.content.ContentResolver; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.databinding.DataBindingUtil; import android.graphics.Color; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewConfigurationCompat; -import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.GestureDetector; @@ -45,6 +40,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewConfigurationCompat; +import androidx.databinding.DataBindingUtil; + import com.bumptech.glide.Glide; import org.easydarwin.easyplayer.databinding.ActivityMainProBinding; @@ -60,13 +62,9 @@ import java.util.Date; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; - public class ProVideoActivity extends AppCompatActivity { - private static final String TAG = "ProVideoActivity"; - public static final int REQUEST_WRITE_STORAGE = 111; - + private static final String TAG = "ProVideoActivity"; private String mVideoPath; private Uri mVideoUri; @@ -315,8 +313,8 @@ public class ProVideoActivity extends AppCompatActivity { TextView view = (TextView) findViewById(R.id.loading_speed); view.setText(String.format("%3.01fKB/s", received * 1.0f / 1024)); - if (findViewById(android.R.id.progress).getVisibility() == View.VISIBLE){ - mVideoView.postDelayed(this,1000); + if (findViewById(android.R.id.progress).getVisibility() == View.VISIBLE) { + mVideoView.postDelayed(this, 1000); } } }; @@ -368,7 +366,7 @@ public class ProVideoActivity extends AppCompatActivity { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (REQUEST_WRITE_STORAGE == requestCode){ + if (REQUEST_WRITE_STORAGE == requestCode) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { doTakePicture(); } @@ -426,11 +424,11 @@ public class ProVideoActivity extends AppCompatActivity { } /* - * 截图 - * */ + * 截图 + * */ public void onTakePicture(View view) { if (mVideoView.isInPlaybackState()) { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE); } else { doTakePicture(); @@ -446,7 +444,7 @@ public class ProVideoActivity extends AppCompatActivity { final String picture = mVideoView.takePicture(file.getPath()); if (!TextUtils.isEmpty(picture)) { - Toast.makeText(ProVideoActivity.this,"图片已保存", Toast.LENGTH_SHORT).show(); + Toast.makeText(ProVideoActivity.this, "图片已保存", Toast.LENGTH_SHORT).show(); if (mScanner == null) { MediaScannerConnection connection = new MediaScannerConnection(ProVideoActivity.this, new MediaScannerConnection.MediaScannerConnectionClient() { diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java index 0f33f2c..ad342e9 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java @@ -2,11 +2,12 @@ package org.easydarwin.easyplayer; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.view.View; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + import com.budiyev.android.codescanner.CodeScanner; import com.budiyev.android.codescanner.CodeScannerView; import com.budiyev.android.codescanner.DecodeCallback; diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java index 6a657f2..0dfd57a 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java @@ -1,13 +1,14 @@ package org.easydarwin.easyplayer; -import android.databinding.DataBindingUtil; import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.CompoundButton; +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; + import org.easydarwin.easyplayer.databinding.ActivitySettingBinding; import org.easydarwin.easyplayer.util.SPUtil; diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java index 94cdb7b..42c4bf1 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java @@ -4,12 +4,13 @@ import android.annotation.SuppressLint; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; + /** * 启动页 */ @@ -53,7 +54,6 @@ public class SplashActivity extends AppCompatActivity { } } }; - private boolean mVisible; private final Runnable mHideRunnable = new Runnable() { @Override public void run() { @@ -74,6 +74,7 @@ public class SplashActivity extends AppCompatActivity { return false; } }; + private boolean mVisible; @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java index 2a0988c..afe24bb 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java @@ -2,14 +2,8 @@ package org.easydarwin.easyplayer.fragments; import android.content.ActivityNotFoundException; import android.content.Intent; -import android.databinding.DataBindingUtil; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.SparseArray; import android.view.LayoutInflater; @@ -20,9 +14,15 @@ import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.bumptech.glide.Glide; -import org.easydarwin.easyplayer.PlayListActivity; import org.easydarwin.easyplayer.ProVideoActivity; import org.easydarwin.easyplayer.R; import org.easydarwin.easyplayer.databinding.FragmentMediaFileBinding; @@ -34,16 +34,13 @@ import java.io.FilenameFilter; public class LocalFileFragment extends Fragment implements CompoundButton.OnCheckedChangeListener, View.OnClickListener { public static final String KEY_IS_RECORD = "key_last_selection"; - - private boolean mShowMp4File; - private FragmentMediaFileBinding mBinding; - SparseArray mImageChecked; - - private String mSuffix; File mRoot = null; File[] mSubFiles; int mImgHeight; + private boolean mShowMp4File; + private FragmentMediaFileBinding mBinding; + private String mSuffix; @Override public void onCreate(Bundle savedInstanceState) { diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java index 7cf2d8d..e505ad9 100644 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java @@ -1,13 +1,16 @@ package org.easydarwin.easyplayer.views; +import static tv.danmaku.ijk.media.player.IjkMediaPlayer.native_active_days; + import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.net.Uri; -import android.support.v4.app.ActivityCompat; import android.text.TextUtils; import android.util.AttributeSet; +import androidx.core.app.ActivityCompat; + import org.easydarwin.easyplayer.ProVideoActivity; import org.easydarwin.easyplayer.util.FileUtil; @@ -18,11 +21,9 @@ import java.util.Date; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import tv.danmaku.ijk.media.widget.media.IjkVideoView; -import static tv.danmaku.ijk.media.player.IjkMediaPlayer.native_active_days; - /** * 播放器 - * + *

* Created by apple on 2017/2/11. */ public class ProVideoView extends IjkVideoView implements VideoControllerView.FullScreenAbleMediaPlayerControl { @@ -53,19 +54,21 @@ public class ProVideoView extends IjkVideoView implements VideoControllerView.Fu return native_active_days(context, key); } - /** ================ super override ================ */ + /** + * ================ super override ================ + */ public void startRecord(String path, int seconds) { if (mMediaPlayer == null) { return; } - super.startRecord(path,seconds); + super.startRecord(path, seconds); mRecordPath = path; } public void stopRecord() { - if (mMediaPlayer == null){ + if (mMediaPlayer == null) { return; } @@ -73,7 +76,9 @@ public class ProVideoView extends IjkVideoView implements VideoControllerView.Fu mRecordPath = null; } - /** ================ FullScreenAbleMediaPlayerControl ================ */ + /** + * ================ FullScreenAbleMediaPlayerControl ================ + */ @Override public boolean isFullScreen() { @@ -131,7 +136,7 @@ public class ProVideoView extends IjkVideoView implements VideoControllerView.Fu @Override public void reStart() { super.reStart(); - if (mRecordPath != null){ + if (mRecordPath != null) { toggleRecord(); toggleRecord(); } @@ -142,8 +147,8 @@ public class ProVideoView extends IjkVideoView implements VideoControllerView.Fu if (getContext() instanceof ProVideoActivity) { ProVideoActivity pro = (ProVideoActivity) getContext(); - if (ActivityCompat.checkSelfPermission(pro, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ - ActivityCompat.requestPermissions(pro, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ProVideoActivity.REQUEST_WRITE_STORAGE +1); + if (ActivityCompat.checkSelfPermission(pro, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(pro, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ProVideoActivity.REQUEST_WRITE_STORAGE + 1); return; } } @@ -184,8 +189,8 @@ public class ProVideoView extends IjkVideoView implements VideoControllerView.Fu @Override public void setSpeed(float speed) { - if (mMediaPlayer == null ) { - return ; + if (mMediaPlayer == null) { + return; } if (mMediaPlayer instanceof IjkMediaPlayer) { @@ -196,7 +201,7 @@ public class ProVideoView extends IjkVideoView implements VideoControllerView.Fu @Override public void takePicture() { - if (getContext() instanceof ProVideoActivity){ + if (getContext() instanceof ProVideoActivity) { ProVideoActivity pro = (ProVideoActivity) getContext(); pro.onTakePicture(null); } diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java index a30dbae..ad320dd 100755 --- a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java @@ -3,12 +3,11 @@ package org.easydarwin.easyplayer.views; import android.content.Context; import android.os.Build; import android.util.AttributeSet; -import android.widget.ImageView; /** * Created by John on 2014/11/11. */ -public class SquareImageView extends ImageView { +public class SquareImageView extends androidx.appcompat.widget.AppCompatImageView { public SquareImageView(Context context) { super(context); diff --git a/EasyPlayerPro/src/main/res/layout/activity_about.xml b/EasyPlayerPro/src/main/res/layout/activity_about.xml index ae76471..3b57487 100644 --- a/EasyPlayerPro/src/main/res/layout/activity_about.xml +++ b/EasyPlayerPro/src/main/res/layout/activity_about.xml @@ -1,17 +1,15 @@ - + + android:background="@color/colorWhite" + android:orientation="vertical"> - + android:layout_centerHorizontal="true" + android:layout_marginBottom="12dp" + android:gravity="center" + android:text="版本信息" + android:textColor="#ffffff" + android:textSize="18sp" /> + android:layout_marginBottom="0dp" + android:src="@drawable/new_nav_back" /> - + - @@ -69,17 +67,17 @@ android:id="@+id/desc" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@string/about_info" - android:textSize="15sp" - android:lineSpacingExtra="5dp" - android:layout_marginTop="8dp" - app:layout_constraintTop_toBottomOf="@+id/version" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" + android:layout_marginTop="8dp" android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" /> + android:layout_marginRight="16dp" + android:lineSpacingExtra="5dp" + android:text="@string/about_info" + android:textSize="15sp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/version" /> @@ -129,8 +127,8 @@ android:layout_marginTop="5dp" android:layout_marginRight="16dp" android:text="http://www.easydss.com" - android:textSize="14sp" android:textColor="@color/colorTheme2" + android:textSize="14sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/dss_tv" /> @@ -173,18 +171,18 @@ app:layout_constraintTop_toBottomOf="@+id/nvr_content_tv" /> - + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imageView" /> + diff --git a/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml b/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml index e34b62b..d6164d3 100644 --- a/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml +++ b/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml @@ -9,24 +9,24 @@ tools:context="org.easydarwin.easyplayer.PlayActivity"> + android:visibility="gone" /> + android:layout_height="match_parent" + android:keepScreenOn="true" /> + android:text="加载中" + android:textColor="@color/colorTheme" /> @@ -77,16 +77,16 @@ android:id="@+id/player_container" android:layout_width="match_parent" android:layout_height="200dp" - android:background="#fff" android:layout_gravity="center" + android:background="#fff" android:gravity="center" - android:visibility="gone" - android:orientation="vertical"> + android:orientation="vertical" + android:visibility="gone"> + android:src="@drawable/new_lost" /> diff --git a/EasyPlayerPro/src/main/res/layout/activity_media_files.xml b/EasyPlayerPro/src/main/res/layout/activity_media_files.xml index bf33680..1ce81bf 100755 --- a/EasyPlayerPro/src/main/res/layout/activity_media_files.xml +++ b/EasyPlayerPro/src/main/res/layout/activity_media_files.xml @@ -6,10 +6,10 @@ + android:background="@color/colorWhite" + android:orientation="vertical"> - + android:layout_centerHorizontal="true" + android:layout_marginBottom="12dp" + android:gravity="center" + android:text="文件夹" + android:textColor="#ffffff" + android:textSize="18sp" /> + android:layout_marginBottom="0dp" + android:src="@drawable/new_nav_back" /> - + - - - + diff --git a/EasyPlayerPro/src/main/res/layout/activity_play_list.xml b/EasyPlayerPro/src/main/res/layout/activity_play_list.xml index 43b9e7b..38e26d9 100644 --- a/EasyPlayerPro/src/main/res/layout/activity_play_list.xml +++ b/EasyPlayerPro/src/main/res/layout/activity_play_list.xml @@ -1,17 +1,15 @@ - - + android:layout_centerHorizontal="true" + android:layout_marginBottom="12dp" + android:gravity="center" + android:text="EasyPlayer Pro" + android:textColor="#ffffff" + android:textSize="18sp" /> + android:layout_marginBottom="0dp" + android:src="@drawable/new_version1" /> - + + android:text="地址" + android:textColor="@drawable/new_color_text" + android:textSize="13sp" /> + android:textSize="13sp" /> + android:text="设置" + android:textColor="@drawable/new_color_text" + android:textSize="13sp" /> - + android:layout_above="@id/bottom_ll" + android:layout_below="@id/toolbar"> - - + diff --git a/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml b/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml index 298d711..a5b8d11 100644 --- a/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml +++ b/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml @@ -1,6 +1,5 @@ - @@ -13,19 +12,19 @@ app:autoFocusButtonVisible="false" app:flashButtonColor="@android:color/white" app:flashButtonVisible="true" + app:frameAspectRatioHeight="1" + app:frameAspectRatioWidth="1" app:frameColor="@android:color/white" app:frameCornersSize="50dp" - app:frameAspectRatioWidth="1" - app:frameAspectRatioHeight="1" app:frameSize="0.75" app:frameThickness="2dp" - app:maskColor="#66000000"/> + app:maskColor="#66000000" /> + android:padding="18dp" + android:src="@drawable/new_nav_back" /> \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/activity_setting.xml b/EasyPlayerPro/src/main/res/layout/activity_setting.xml index ce05fa7..8e32a96 100644 --- a/EasyPlayerPro/src/main/res/layout/activity_setting.xml +++ b/EasyPlayerPro/src/main/res/layout/activity_setting.xml @@ -9,7 +9,7 @@ android:background="@color/colorWhite" android:orientation="vertical"> - - + + android:textSize="20sp" /> + android:textSize="12sp" /> - - + android:textSize="12sp" /> \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml b/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml index c5e856d..00a814c 100755 --- a/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml +++ b/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml @@ -1,7 +1,7 @@ - diff --git a/EasyPlayerPro/src/main/res/layout/image_picker_item.xml b/EasyPlayerPro/src/main/res/layout/image_picker_item.xml index 5f89470..4ebf1ec 100755 --- a/EasyPlayerPro/src/main/res/layout/image_picker_item.xml +++ b/EasyPlayerPro/src/main/res/layout/image_picker_item.xml @@ -10,8 +10,8 @@ android:layout_width="match_parent" android:layout_height="128dp" android:layout_margin="1dp" - android:background="#666" android:adjustViewBounds="true" + android:background="#666" android:scaleType="centerCrop" /> + android:src="@drawable/new_player" /> - @@ -17,26 +16,26 @@ android:id="@+id/tv_speed" style="@style/Widget.AppCompat.ActionButton" android:layout_width="0dp" - android:layout_weight="1" android:layout_height="wrap_content" - android:visibility="gone" - android:textColor="@color/white" /> + android:layout_weight="1" + android:textColor="@color/white" + android:visibility="gone" /> + android:layout_weight="1" + android:textColor="@color/white" + android:visibility="gone" /> @@ -49,38 +48,38 @@ + android:layout_weight="1" + android:contentDescription="@string/description" + android:src="@drawable/new_slow_btn" /> + android:layout_weight="1" + android:contentDescription="@string/description" + android:src="@drawable/new_moveback_btn" /> + android:layout_weight="1" + android:contentDescription="@string/description" + android:src="@drawable/new_stop_white" /> + android:layout_weight="1" + android:contentDescription="@string/description" + android:src="@drawable/new_forward_btn" /> + android:layout_weight="1" + android:contentDescription="@string/description" + android:src="@drawable/new_fast_btn" /> @@ -95,52 +94,52 @@ android:id="@+id/action_take_picture" style="@style/Widget.AppCompat.ActionButton" android:layout_width="0dp" - android:layout_weight="1" android:layout_height="wrap_content" + android:layout_weight="1" android:src="@drawable/new_snapshot_btn" /> + android:drawablePadding="5dp" + android:gravity="center" + android:text="00:00" + android:textColor="#ff0000" + android:visibility="gone" /> @@ -150,20 +149,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:paddingLeft="4dip" - android:paddingRight="4dip" android:paddingTop="4dip" - android:textSize="14sp" + android:paddingRight="4dip" android:textColor="@color/white" + android:textSize="14sp" android:textStyle="bold" /> + android:layout_weight="1" + android:progress="10" + android:secondaryProgress="60" /> diff --git a/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml b/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml index c99db64..5f13d40 100644 --- a/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml +++ b/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml @@ -1,8 +1,6 @@ - @@ -11,12 +9,12 @@ android:id="@+id/new_media_scan" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:src="@drawable/new_scan_btn" - android:background="@null" - app:layout_constraintRight_toRightOf="parent" android:layout_marginRight="8dp" - app:layout_constraintTop_toTopOf="@id/new_media_source_url" - app:layout_constraintBottom_toBottomOf="@id/new_media_source_url" /> + android:background="@null" + android:src="@drawable/new_scan_btn" + app:layout_constraintBottom_toBottomOf="@id/new_media_source_url" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="@id/new_media_source_url" /> - \ No newline at end of file + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/video_source_item.xml b/EasyPlayerPro/src/main/res/layout/video_source_item.xml index 0a4edf3..1ea8045 100644 --- a/EasyPlayerPro/src/main/res/layout/video_source_item.xml +++ b/EasyPlayerPro/src/main/res/layout/video_source_item.xml @@ -1,13 +1,12 @@ - + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin"> - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index a56062d..9c5ba57 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,4 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - jcenter() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' - } +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false } - -allprojects { - repositories { - jcenter() - maven { url "https://jitpack.io" } - google() - } -} - -ext{ - support_version='26.1.0' -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..20e2a01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..115386a --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,30 @@ +[versions] +agp = "8.13.1" +coreKtx = "1.17.0" +junit = "4.13.2" +junitVersion = "1.3.0" +espressoCore = "3.7.0" +appcompat = "1.7.1" +constraintlayout = "2.2.1" +recyclerview = "1.4.0" +swiperefreshlayout = "1.2.0" +materialComponents = "1.13.0" +glide = "5.0.5" +code-scanner = "2.3.0" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" } +androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } +material-components = { group = "com.google.android.material", name = "material", version.ref = "materialComponents" } +glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } +code-scanner = { group = "com.github.yuriy-budiyev", name = "code-scanner", version.ref = "code-scanner" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372ae..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6b319b5..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Sat Apr 06 13:56:34 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew index 9d82f78..23d15a9 100644 --- a/gradlew +++ b/gradlew @@ -1,74 +1,129 @@ -#!/usr/bin/env bash +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null +CLASSPATH="\\\"\\\"" -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -77,84 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,26 +26,30 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,54 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/ijkplayer-java/build.gradle b/ijkplayer-java/build.gradle index 7bd1f43..6f687af 100644 --- a/ijkplayer-java/build.gradle +++ b/ijkplayer-java/build.gradle @@ -1,27 +1,28 @@ -apply plugin: 'com.android.library' +plugins { + alias(libs.plugins.android.library) +} android { - compileSdkVersion 26 - lintOptions { - abortOnError false + namespace "tv.danmaku.ijk.media.player" + compileSdk { + version = release(36) } + defaultConfig { - minSdkVersion 16 - targetSdkVersion 26 - consumerProguardFiles 'proguard-rules.pro' - versionCode 38 - versionName "1.1.17.1124" + minSdk 28 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } + buildTypes { release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - buildToolsVersion '28.0.3' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } - -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') -} - diff --git a/ijkplayer-java/consumer-rules.pro b/ijkplayer-java/consumer-rules.pro new file mode 100644 index 0000000..07b3994 --- /dev/null +++ b/ijkplayer-java/consumer-rules.pro @@ -0,0 +1,11 @@ +-keepparameternames +-keepattributes Exceptions,InnerClasses,Signature,Deprecated,EnclosingMethod +-keep class tv.danmaku.ijk.media.widget.media.IjkVideoView { + public protected *; +} +-keep class tv.danmaku.ijk.media.widget.media.IMediaController { + public protected *; +} +-keep class tv.danmaku.ijk.media.player.** { + *; +} \ No newline at end of file diff --git a/ijkplayer-java/proguard-rules.pro b/ijkplayer-java/proguard-rules.pro index 2d96c93..b278fc6 100644 --- a/ijkplayer-java/proguard-rules.pro +++ b/ijkplayer-java/proguard-rules.pro @@ -1,20 +1,24 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /opt/android/ADK/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# Add any project specific keep options here: - # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile -keepparameternames -renamesourcefileattribute SourceFile -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod diff --git a/ijkplayer-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java b/ijkplayer-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java deleted file mode 100644 index 60d1d73..0000000 --- a/ijkplayer-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package tv.danmaku.ijk.media.player; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/ijkplayer-java/src/main/.classpath b/ijkplayer-java/src/main/.classpath deleted file mode 100644 index b3caa8c..0000000 --- a/ijkplayer-java/src/main/.classpath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/ijkplayer-java/src/main/.project b/ijkplayer-java/src/main/.project deleted file mode 100644 index 31663eb..0000000 --- a/ijkplayer-java/src/main/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - ijkplayer-java - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/ijkplayer-java/src/main/.settings/org.eclipse.jdt.core.prefs b/ijkplayer-java/src/main/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index b080d2d..0000000 --- a/ijkplayer-java/src/main/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/ijkplayer-java/src/main/AndroidManifest.xml b/ijkplayer-java/src/main/AndroidManifest.xml index e45a047..a5918e6 100644 --- a/ijkplayer-java/src/main/AndroidManifest.xml +++ b/ijkplayer-java/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + + \ No newline at end of file diff --git a/ijkplayer-java/src/main/project.properties b/ijkplayer-java/src/main/project.properties deleted file mode 100644 index 362a0a3..0000000 --- a/ijkplayer-java/src/main/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-22 -android.library=true diff --git a/import-summary.txt b/import-summary.txt deleted file mode 100644 index f83e3ca..0000000 --- a/import-summary.txt +++ /dev/null @@ -1,203 +0,0 @@ -ECLIPSE ANDROID PROJECT IMPORT SUMMARY -====================================== - -Manifest Merging: ------------------ -Your project uses libraries that provide manifests, and your Eclipse -project did not explicitly turn on manifest merging. In Android Gradle -projects, manifests are always merged (meaning that contents from your -libraries' manifests will be merged into the app manifest. If you had -manually copied contents from library manifests into your app manifest -you may need to remove these for the app to build correctly. - -Ignored Files: --------------- -The following files were *not* copied into the new Gradle project; you -should evaluate whether these are still needed in your project and if -so manually move them: - -From MediaUploader: -* .idea\ -* .idea\.name -* .idea\MediaUploader.iml -* .idea\compiler.xml -* .idea\copyright\ -* .idea\copyright\profiles_settings.xml -* .idea\encodings.xml -* .idea\misc.xml -* .idea\modules.xml -* .idea\vcs.xml -* .idea\workspace.xml -* ic_launcher-web.png -* proguard-project.txt -From libstreaming: -* .gitignore -* LICENSE -* README.md -* build.xml -* doc\ -* doc\allclasses-frame.html -* doc\allclasses-noframe.html -* doc\constant-values.html -* doc\deprecated-list.html -* doc\help-doc.html -* doc\index-all.html -* doc\index.html -* doc\net\ -* doc\net\majorkernelpanic\ -* doc\net\majorkernelpanic\streaming\ -* doc\net\majorkernelpanic\streaming\MediaStream.html -* doc\net\majorkernelpanic\streaming\Session.Callback.html -* doc\net\majorkernelpanic\streaming\Session.html -* doc\net\majorkernelpanic\streaming\SessionBuilder.html -* doc\net\majorkernelpanic\streaming\Stream.html -* doc\net\majorkernelpanic\streaming\audio\ -* doc\net\majorkernelpanic\streaming\audio\AACStream.html -* doc\net\majorkernelpanic\streaming\audio\AMRNBStream.html -* doc\net\majorkernelpanic\streaming\audio\AudioQuality.html -* doc\net\majorkernelpanic\streaming\audio\AudioStream.html -* doc\net\majorkernelpanic\streaming\audio\package-frame.html -* doc\net\majorkernelpanic\streaming\audio\package-summary.html -* doc\net\majorkernelpanic\streaming\audio\package-tree.html -* doc\net\majorkernelpanic\streaming\exceptions\ -* doc\net\majorkernelpanic\streaming\exceptions\CameraInUseException.html -* doc\net\majorkernelpanic\streaming\exceptions\ConfNotSupportedException.html -* doc\net\majorkernelpanic\streaming\exceptions\InvalidSurfaceException.html -* doc\net\majorkernelpanic\streaming\exceptions\StorageUnavailableException.html -* doc\net\majorkernelpanic\streaming\exceptions\package-frame.html -* doc\net\majorkernelpanic\streaming\exceptions\package-summary.html -* doc\net\majorkernelpanic\streaming\exceptions\package-tree.html -* doc\net\majorkernelpanic\streaming\gl\ -* doc\net\majorkernelpanic\streaming\gl\SurfaceManager.html -* doc\net\majorkernelpanic\streaming\gl\SurfaceView.ViewAspectRatioMeasurer.html -* doc\net\majorkernelpanic\streaming\gl\SurfaceView.html -* doc\net\majorkernelpanic\streaming\gl\TextureManager.html -* doc\net\majorkernelpanic\streaming\gl\package-frame.html -* doc\net\majorkernelpanic\streaming\gl\package-summary.html -* doc\net\majorkernelpanic\streaming\gl\package-tree.html -* doc\net\majorkernelpanic\streaming\hw\ -* doc\net\majorkernelpanic\streaming\hw\CodecManager.html -* doc\net\majorkernelpanic\streaming\hw\EncoderDebugger.html -* doc\net\majorkernelpanic\streaming\hw\NV21Convertor.html -* doc\net\majorkernelpanic\streaming\hw\package-frame.html -* doc\net\majorkernelpanic\streaming\hw\package-summary.html -* doc\net\majorkernelpanic\streaming\hw\package-tree.html -* doc\net\majorkernelpanic\streaming\mp4\ -* doc\net\majorkernelpanic\streaming\mp4\MP4Config.html -* doc\net\majorkernelpanic\streaming\mp4\MP4Parser.html -* doc\net\majorkernelpanic\streaming\mp4\package-frame.html -* doc\net\majorkernelpanic\streaming\mp4\package-summary.html -* doc\net\majorkernelpanic\streaming\mp4\package-tree.html -* doc\net\majorkernelpanic\streaming\package-frame.html -* doc\net\majorkernelpanic\streaming\package-summary.html -* doc\net\majorkernelpanic\streaming\package-tree.html -* doc\net\majorkernelpanic\streaming\rtcp\ -* doc\net\majorkernelpanic\streaming\rtcp\SenderReport.html -* doc\net\majorkernelpanic\streaming\rtcp\package-frame.html -* doc\net\majorkernelpanic\streaming\rtcp\package-summary.html -* doc\net\majorkernelpanic\streaming\rtcp\package-tree.html -* doc\net\majorkernelpanic\streaming\rtp\ -* doc\net\majorkernelpanic\streaming\rtp\AACADTSPacketizer.html -* doc\net\majorkernelpanic\streaming\rtp\AACLATMPacketizer.html -* doc\net\majorkernelpanic\streaming\rtp\AMRNBPacketizer.html -* doc\net\majorkernelpanic\streaming\rtp\AbstractPacketizer.html -* doc\net\majorkernelpanic\streaming\rtp\H263Packetizer.html -* doc\net\majorkernelpanic\streaming\rtp\H264Packetizer.html -* doc\net\majorkernelpanic\streaming\rtp\MediaCodecInputStream.html -* doc\net\majorkernelpanic\streaming\rtp\RtpSocket.html -* doc\net\majorkernelpanic\streaming\rtp\package-frame.html -* doc\net\majorkernelpanic\streaming\rtp\package-summary.html -* doc\net\majorkernelpanic\streaming\rtp\package-tree.html -* doc\net\majorkernelpanic\streaming\rtsp\ -* doc\net\majorkernelpanic\streaming\rtsp\RtspClient.Callback.html -* doc\net\majorkernelpanic\streaming\rtsp\RtspClient.html -* doc\net\majorkernelpanic\streaming\rtsp\RtspServer.CallbackListener.html -* doc\net\majorkernelpanic\streaming\rtsp\RtspServer.LocalBinder.html -* doc\net\majorkernelpanic\streaming\rtsp\RtspServer.html -* doc\net\majorkernelpanic\streaming\rtsp\UriParser.html -* doc\net\majorkernelpanic\streaming\rtsp\package-frame.html -* doc\net\majorkernelpanic\streaming\rtsp\package-summary.html -* doc\net\majorkernelpanic\streaming\rtsp\package-tree.html -* doc\net\majorkernelpanic\streaming\video\ -* doc\net\majorkernelpanic\streaming\video\CodecManager.html -* doc\net\majorkernelpanic\streaming\video\H263Stream.html -* doc\net\majorkernelpanic\streaming\video\H264Stream.html -* doc\net\majorkernelpanic\streaming\video\VideoQuality.html -* doc\net\majorkernelpanic\streaming\video\VideoStream.html -* doc\net\majorkernelpanic\streaming\video\package-frame.html -* doc\net\majorkernelpanic\streaming\video\package-summary.html -* doc\net\majorkernelpanic\streaming\video\package-tree.html -* doc\overview-frame.html -* doc\overview-summary.html -* doc\overview-tree.html -* doc\package-list -* doc\resources\ -* doc\resources\background.gif -* doc\resources\tab.gif -* doc\resources\titlebar.gif -* doc\resources\titlebar_end.gif -* doc\serialized-form.html -* doc\stylesheet.css -* pom.xml -* proguard-project.txt - -Replaced Jars with Dependencies: --------------------------------- -The importer recognized the following .jar files as third party -libraries and replaced them with Gradle dependencies instead. This has -the advantage that more explicit version information is known, and the -libraries can be updated automatically. However, it is possible that -the .jar file in your project was of an older version than the -dependency we picked, which could render the project not compileable. -You can disable the jar replacement in the import wizard and try again: - -android-support-v4.jar => com.android.support:support-v4:19.1.0 -android-support-v7-appcompat.jar => com.android.support:appcompat-v7:19.1.0 -gson-2.2.2_android.jar => com.google.code.gson:gson:2.3.1 - -Replaced Libraries with Dependencies: -------------------------------------- -The importer recognized the following library projects as third party -libraries and replaced them with Gradle dependencies instead. This has -the advantage that more explicit version information is known, and the -libraries can be updated automatically. However, it is possible that -the source files in your project were of an older version than the -dependency we picked, which could render the project not compileable. -You can disable the library replacement in the import wizard and try -again: - -android-support-v7-appcompat => [com.android.support:appcompat-v7:19.1.0] - -Moved Files: ------------- -Android Gradle projects use a different directory structure than ADT -Eclipse projects. Here's how the projects were restructured: - -In libstreaming: -* AndroidManifest.xml => libstreaming\src\main\AndroidManifest.xml -* assets\ => libstreaming\src\main\assets -* libs\android-logging-log4j-1.0.3.jar => libstreaming\libs\android-logging-log4j-1.0.3.jar -* libs\log4j-1.2.17.jar => libstreaming\libs\log4j-1.2.17.jar -* res\ => libstreaming\src\main\res -* src\ => libstreaming\src\main\java\ -In MediaUploader: -* AndroidManifest.xml => mediaUploader\src\main\AndroidManifest.xml -* assets\ => mediaUploader\src\main\assets -* libs\armeabi\libUtils.so => mediaUploader\src\main\jniLibs\armeabi\libUtils.so -* res\ => mediaUploader\src\main\res\ -* src\ => mediaUploader\src\main\java\ - -Next Steps: ------------ -You can now build the project. The Gradle project needs network -connectivity to download dependencies. - -Bugs: ------ -If for some reason your project does not build, and you determine that -it is due to a bug or limitation of the Eclipse to Gradle importer, -please file a bug at http://b.android.com with category -Component-Tools. - -(This import summary is for your information only, and can be deleted -after import once you are satisfied with the results.) diff --git a/photoview/.gitignore b/photoview/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/photoview/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/photoview/build.gradle b/photoview/build.gradle new file mode 100644 index 0000000..7d1a6a8 --- /dev/null +++ b/photoview/build.gradle @@ -0,0 +1,36 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace 'com.github.chrisbanes.photoview' + compileSdk { + version = release(36) + } + + defaultConfig { + minSdk 28 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation libs.androidx.appcompat + implementation libs.material.components + testImplementation libs.junit + androidTestImplementation libs.androidx.junit + androidTestImplementation libs.androidx.espresso.core +} \ No newline at end of file diff --git a/photoview/consumer-rules.pro b/photoview/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/photoview/proguard-rules.pro b/photoview/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/photoview/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/photoview/src/androidTest/java/com/github/chrisbanes/photoview/ExampleInstrumentedTest.java b/photoview/src/androidTest/java/com/github/chrisbanes/photoview/ExampleInstrumentedTest.java new file mode 100644 index 0000000..3d7592f --- /dev/null +++ b/photoview/src/androidTest/java/com/github/chrisbanes/photoview/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.github.chrisbanes.photoview; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.github.chrisbanes.photoview.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/photoview/src/main/AndroidManifest.xml b/photoview/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/photoview/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/Compat.java b/photoview/src/main/java/com/github/chrisbanes/photoview/Compat.java new file mode 100644 index 0000000..d33c31c --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/Compat.java @@ -0,0 +1,39 @@ +/* + Copyright 2011, 2012 Chris Banes. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.github.chrisbanes.photoview; + +import android.annotation.TargetApi; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.view.View; + +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); + } +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java b/photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java new file mode 100644 index 0000000..aea97a7 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/CustomGestureDetector.java @@ -0,0 +1,214 @@ +/* + Copyright 2011, 2012 Chris Banes. +

+ Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at +

+ http://www.apache.org/licenses/LICENSE-2.0 +

+ Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.github.chrisbanes.photoview; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +/** + * Does a whole lot of gesture detecting. + */ +class CustomGestureDetector { + + private static final int INVALID_POINTER_ID = -1; + + private int mActivePointerId = INVALID_POINTER_ID; + private int mActivePointerIndex = 0; + private final ScaleGestureDetector mDetector; + + private VelocityTracker mVelocityTracker; + private boolean mIsDragging; + private float mLastTouchX; + private float mLastTouchY; + private final float mTouchSlop; + private final float mMinimumVelocity; + private OnGestureListener mListener; + + CustomGestureDetector(Context context, OnGestureListener listener) { + final ViewConfiguration configuration = ViewConfiguration + .get(context); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mTouchSlop = configuration.getScaledTouchSlop(); + + mListener = listener; + ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() { + private float lastFocusX, lastFocusY = 0; + + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scaleFactor = detector.getScaleFactor(); + + if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) + return false; + + if (scaleFactor >= 0) { + mListener.onScale(scaleFactor, + detector.getFocusX(), + detector.getFocusY(), + detector.getFocusX() - lastFocusX, + detector.getFocusY() - lastFocusY + ); + lastFocusX = detector.getFocusX(); + lastFocusY = detector.getFocusY(); + } + return true; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + lastFocusX = detector.getFocusX(); + lastFocusY = detector.getFocusY(); + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + // NO-OP + } + }; + mDetector = new ScaleGestureDetector(context, mScaleListener); + } + + private float getActiveX(MotionEvent ev) { + try { + return ev.getX(mActivePointerIndex); + } catch (Exception e) { + return ev.getX(); + } + } + + private float getActiveY(MotionEvent ev) { + try { + return ev.getY(mActivePointerIndex); + } catch (Exception e) { + return ev.getY(); + } + } + + public boolean isScaling() { + return mDetector.isInProgress(); + } + + public boolean isDragging() { + return mIsDragging; + } + + public boolean onTouchEvent(MotionEvent ev) { + try { + mDetector.onTouchEvent(ev); + return processTouchEvent(ev); + } catch (IllegalArgumentException e) { + // Fix for support lib bug, happening when onDestroy is called + return true; + } + } + + private boolean processTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + + mVelocityTracker = VelocityTracker.obtain(); + if (null != mVelocityTracker) { + mVelocityTracker.addMovement(ev); + } + + 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: + mActivePointerId = INVALID_POINTER_ID; + // Recycle Velocity Tracker + if (null != mVelocityTracker) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + case MotionEvent.ACTION_UP: + mActivePointerId = INVALID_POINTER_ID; + 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; + case MotionEvent.ACTION_POINTER_UP: + final int pointerIndex = Util.getPointerIndex(ev.getAction()); + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + mLastTouchX = ev.getX(newPointerIndex); + mLastTouchY = ev.getY(newPointerIndex); + } + break; + } + + mActivePointerIndex = ev + .findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId + : 0); + return true; + } +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java new file mode 100644 index 0000000..37caf68 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnGestureListener.java @@ -0,0 +1,28 @@ +/* + Copyright 2011, 2012 Chris Banes. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.github.chrisbanes.photoview; + +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); + + void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy); +} \ No newline at end of file diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnMatrixChangedListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnMatrixChangedListener.java new file mode 100644 index 0000000..f3031d6 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnMatrixChangedListener.java @@ -0,0 +1,18 @@ +package com.github.chrisbanes.photoview; + +import android.graphics.RectF; + +/** + * Interface definition for a callback to be invoked when the internal Matrix has changed for + * this View. + */ +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); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnOutsidePhotoTapListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnOutsidePhotoTapListener.java new file mode 100644 index 0000000..99fc7b4 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnOutsidePhotoTapListener.java @@ -0,0 +1,14 @@ +package com.github.chrisbanes.photoview; + +import android.widget.ImageView; + +/** + * Callback when the user tapped outside of the photo + */ +public interface OnOutsidePhotoTapListener { + + /** + * The outside of the photo has been tapped + */ + void onOutsidePhotoTap(ImageView imageView); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnPhotoTapListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnPhotoTapListener.java new file mode 100644 index 0000000..5f6f070 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnPhotoTapListener.java @@ -0,0 +1,22 @@ +package com.github.chrisbanes.photoview; + +import android.widget.ImageView; + +/** + * A callback to be invoked when the Photo is tapped with a single + * tap. + */ +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 ImageView the user tapped. + * @param x where the user tapped from the of the Drawable, as percentage of the + * Drawable width. + * @param y where the user tapped from the top of the Drawable, as percentage of the + * Drawable height. + */ + void onPhotoTap(ImageView view, float x, float y); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnScaleChangedListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnScaleChangedListener.java new file mode 100644 index 0000000..c6bb7a6 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnScaleChangedListener.java @@ -0,0 +1,17 @@ +package com.github.chrisbanes.photoview; + + +/** + * Interface definition for callback to be invoked when attached ImageView scale changes + */ +public interface OnScaleChangedListener { + + /** + * Callback for when the scale changes + * + * @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in) + * @param focusX focal point X position + * @param focusY focal point Y position + */ + void onScaleChange(float scaleFactor, float focusX, float focusY); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnSingleFlingListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnSingleFlingListener.java new file mode 100644 index 0000000..f5dab92 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnSingleFlingListener.java @@ -0,0 +1,21 @@ +package com.github.chrisbanes.photoview; + +import android.view.MotionEvent; + +/** + * A callback to be invoked when the ImageView is flung with a single + * touch + */ +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); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnViewDragListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnViewDragListener.java new file mode 100644 index 0000000..66999a5 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnViewDragListener.java @@ -0,0 +1,16 @@ +package com.github.chrisbanes.photoview; + +/** + * Interface definition for a callback to be invoked when the photo is experiencing a drag event + */ +public interface OnViewDragListener { + + /** + * Callback for when the photo is experiencing a drag event. This cannot be invoked when the + * user is scaling. + * + * @param dx The change of the coordinates in the x-direction + * @param dy The change of the coordinates in the y-direction + */ + void onDrag(float dx, float dy); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/OnViewTapListener.java b/photoview/src/main/java/com/github/chrisbanes/photoview/OnViewTapListener.java new file mode 100644 index 0000000..6856255 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/OnViewTapListener.java @@ -0,0 +1,16 @@ +package com.github.chrisbanes.photoview; + +import android.view.View; + +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); +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java b/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java new file mode 100644 index 0000000..8a8ba0a --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoView.java @@ -0,0 +1,256 @@ +/* + Copyright 2011, 2012 Chris Banes. +

+ Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at +

+ http://www.apache.org/licenses/LICENSE-2.0 +

+ Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.github.chrisbanes.photoview; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.AttributeSet; +import android.view.GestureDetector; + +import androidx.appcompat.widget.AppCompatImageView; + +/** + * A zoomable ImageView. See {@link PhotoViewAttacher} for most of the details on how the zooming + * is accomplished + */ +@SuppressWarnings("unused") +public class PhotoView extends AppCompatImageView { + + private PhotoViewAttacher attacher; + private ScaleType pendingScaleType; + + 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); + init(); + } + + private void init() { + attacher = new PhotoViewAttacher(this); + //We always pose as a Matrix scale type, though we can change to another scale type + //via the attacher + super.setScaleType(ScaleType.MATRIX); + //apply the previously applied scale type + if (pendingScaleType != null) { + setScaleType(pendingScaleType); + pendingScaleType = null; + } + } + + /** + * Get the current {@link PhotoViewAttacher} for this view. Be wary of holding on to references + * to this attacher, as it has a reference to this view, which, if a reference is held in the + * wrong place, can cause memory leaks. + * + * @return the attacher. + */ + public PhotoViewAttacher getAttacher() { + return attacher; + } + + @Override + public ScaleType getScaleType() { + return attacher.getScaleType(); + } + + @Override + public Matrix getImageMatrix() { + return attacher.getImageMatrix(); + } + + @Override + public void setOnLongClickListener(OnLongClickListener l) { + attacher.setOnLongClickListener(l); + } + + @Override + public void setOnClickListener(OnClickListener l) { + attacher.setOnClickListener(l); + } + + @Override + public void setScaleType(ScaleType scaleType) { + if (attacher == null) { + pendingScaleType = scaleType; + } else { + attacher.setScaleType(scaleType); + } + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + // setImageBitmap calls through to this method + if (attacher != null) { + attacher.update(); + } + } + + @Override + public void setImageResource(int resId) { + super.setImageResource(resId); + if (attacher != null) { + attacher.update(); + } + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + if (attacher != null) { + attacher.update(); + } + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + boolean changed = super.setFrame(l, t, r, b); + if (changed) { + attacher.update(); + } + return changed; + } + + public void setRotationTo(float rotationDegree) { + attacher.setRotationTo(rotationDegree); + } + + public void setRotationBy(float rotationDegree) { + attacher.setRotationBy(rotationDegree); + } + + public boolean isZoomable() { + return attacher.isZoomable(); + } + + public void setZoomable(boolean zoomable) { + attacher.setZoomable(zoomable); + } + + public RectF getDisplayRect() { + return attacher.getDisplayRect(); + } + + public void getDisplayMatrix(Matrix matrix) { + attacher.getDisplayMatrix(matrix); + } + + @SuppressWarnings("UnusedReturnValue") public boolean setDisplayMatrix(Matrix finalRectangle) { + return attacher.setDisplayMatrix(finalRectangle); + } + + public void getSuppMatrix(Matrix matrix) { + attacher.getSuppMatrix(matrix); + } + + public boolean setSuppMatrix(Matrix matrix) { + return attacher.setDisplayMatrix(matrix); + } + + public float getMinimumScale() { + return attacher.getMinimumScale(); + } + + public float getMediumScale() { + return attacher.getMediumScale(); + } + + public float getMaximumScale() { + return attacher.getMaximumScale(); + } + + public float getScale() { + return attacher.getScale(); + } + + public void setAllowParentInterceptOnEdge(boolean allow) { + attacher.setAllowParentInterceptOnEdge(allow); + } + + public void setMinimumScale(float minimumScale) { + attacher.setMinimumScale(minimumScale); + } + + public void setMediumScale(float mediumScale) { + attacher.setMediumScale(mediumScale); + } + + public void setMaximumScale(float maximumScale) { + attacher.setMaximumScale(maximumScale); + } + + public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) { + attacher.setScaleLevels(minimumScale, mediumScale, maximumScale); + } + + public void setOnMatrixChangeListener(OnMatrixChangedListener listener) { + attacher.setOnMatrixChangeListener(listener); + } + + public void setOnPhotoTapListener(OnPhotoTapListener listener) { + attacher.setOnPhotoTapListener(listener); + } + + public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener listener) { + attacher.setOnOutsidePhotoTapListener(listener); + } + + public void setOnViewTapListener(OnViewTapListener listener) { + attacher.setOnViewTapListener(listener); + } + + public void setOnViewDragListener(OnViewDragListener listener) { + attacher.setOnViewDragListener(listener); + } + + public void setScale(float scale) { + attacher.setScale(scale); + } + + public void setScale(float scale, boolean animate) { + attacher.setScale(scale, animate); + } + + public void setScale(float scale, float focalX, float focalY, boolean animate) { + attacher.setScale(scale, focalX, focalY, animate); + } + + public void setZoomTransitionDuration(int milliseconds) { + attacher.setZoomTransitionDuration(milliseconds); + } + + public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) { + attacher.setOnDoubleTapListener(onDoubleTapListener); + } + + public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangedListener) { + attacher.setOnScaleChangeListener(onScaleChangedListener); + } + + public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) { + attacher.setOnSingleFlingListener(onSingleFlingListener); + } +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java b/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java new file mode 100644 index 0000000..55965b8 --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java @@ -0,0 +1,823 @@ +/* + Copyright 2011, 2012 Chris Banes. +

+ Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at +

+ http://www.apache.org/licenses/LICENSE-2.0 +

+ Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.github.chrisbanes.photoview; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Matrix.ScaleToFit; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnLongClickListener; +import android.view.ViewParent; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.OverScroller; + +/** + * The component of {@link PhotoView} which does the work allowing for zooming, scaling, panning, etc. + * It is made public in case you need to subclass something other than AppCompatImageView and still + * gain the functionality that {@link PhotoView} offers + */ +public class PhotoViewAttacher implements View.OnTouchListener, + View.OnLayoutChangeListener { + + private static float DEFAULT_MAX_SCALE = 3.0f; + private static float DEFAULT_MID_SCALE = 1.75f; + private static float DEFAULT_MIN_SCALE = 1.0f; + private static int DEFAULT_ZOOM_DURATION = 200; + + private static final int HORIZONTAL_EDGE_NONE = -1; + private static final int HORIZONTAL_EDGE_LEFT = 0; + private static final int HORIZONTAL_EDGE_RIGHT = 1; + private static final int HORIZONTAL_EDGE_BOTH = 2; + private static final int VERTICAL_EDGE_NONE = -1; + private static final int VERTICAL_EDGE_TOP = 0; + private static final int VERTICAL_EDGE_BOTTOM = 1; + private static final int VERTICAL_EDGE_BOTH = 2; + private static int SINGLE_TOUCH = 1; + + private Interpolator mInterpolator = new AccelerateDecelerateInterpolator(); + private int mZoomDuration = DEFAULT_ZOOM_DURATION; + 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 ImageView mImageView; + + // Gesture Detectors + private GestureDetector mGestureDetector; + private CustomGestureDetector mScaleDragDetector; + + // These are set so we don't keep allocating them on the heap + private final Matrix mBaseMatrix = new Matrix(); + private final Matrix mDrawMatrix = new Matrix(); + private final Matrix mSuppMatrix = new Matrix(); + private final RectF mDisplayRect = new RectF(); + private final float[] mMatrixValues = new float[9]; + + // Listeners + private OnMatrixChangedListener mMatrixChangeListener; + private OnPhotoTapListener mPhotoTapListener; + private OnOutsidePhotoTapListener mOutsidePhotoTapListener; + private OnViewTapListener mViewTapListener; + private View.OnClickListener mOnClickListener; + private OnLongClickListener mLongClickListener; + private OnScaleChangedListener mScaleChangeListener; + private OnSingleFlingListener mSingleFlingListener; + private OnViewDragListener mOnViewDragListener; + + private FlingRunnable mCurrentFlingRunnable; + private int mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH; + private int mVerticalScrollEdge = VERTICAL_EDGE_BOTH; + private float mBaseRotation; + + private boolean mZoomEnabled = true; + private ScaleType mScaleType = ScaleType.FIT_CENTER; + + private OnGestureListener onGestureListener = new OnGestureListener() { + @Override + public void onDrag(float dx, float dy) { + if (mScaleDragDetector.isScaling()) { + return; // Do not drag if we are already scaling + } + if (mOnViewDragListener != null) { + mOnViewDragListener.onDrag(dx, dy); + } + 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 = mImageView.getParent(); + if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) { + if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH + || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f) + || (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f) + || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f) + || (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) { + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(false); + } + } + } else { + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + } + + @Override + public void onFling(float startX, float startY, float velocityX, float velocityY) { + mCurrentFlingRunnable = new FlingRunnable(mImageView.getContext()); + mCurrentFlingRunnable.fling(getImageViewWidth(mImageView), + getImageViewHeight(mImageView), (int) velocityX, (int) velocityY); + mImageView.post(mCurrentFlingRunnable); + } + + @Override + public void onScale(float scaleFactor, float focusX, float focusY) { + onScale(scaleFactor, focusX, focusY, 0, 0); + } + + @Override + public void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy) { + if (getScale() < mMaxScale || scaleFactor < 1f) { + if (mScaleChangeListener != null) { + mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); + } + mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); + mSuppMatrix.postTranslate(dx, dy); + checkAndDisplayMatrix(); + } + } + }; + + public PhotoViewAttacher(ImageView imageView) { + mImageView = imageView; + imageView.setOnTouchListener(this); + imageView.addOnLayoutChangeListener(this); + if (imageView.isInEditMode()) { + return; + } + mBaseRotation = 0.0f; + // Create Gesture Detectors... + mScaleDragDetector = new CustomGestureDetector(imageView.getContext(), onGestureListener); + mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() { + + // forward long click listener + @Override + public void onLongPress(MotionEvent e) { + if (mLongClickListener != null) { + mLongClickListener.onLongClick(mImageView); + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + if (mSingleFlingListener != null) { + if (getScale() > DEFAULT_MIN_SCALE) { + return false; + } + if (e1.getPointerCount() > SINGLE_TOUCH + || e2.getPointerCount() > SINGLE_TOUCH) { + return false; + } + return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY); + } + return false; + } + }); + mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (mOnClickListener != null) { + mOnClickListener.onClick(mImageView); + } + final RectF displayRect = getDisplayRect(); + final float x = e.getX(), y = e.getY(); + if (mViewTapListener != null) { + mViewTapListener.onViewTap(mImageView, x, y); + } + if (displayRect != null) { + // 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(); + if (mPhotoTapListener != null) { + mPhotoTapListener.onPhotoTap(mImageView, xResult, yResult); + } + return true; + } else { + if (mOutsidePhotoTapListener != null) { + mOutsidePhotoTapListener.onOutsidePhotoTap(mImageView); + } + } + } + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent ev) { + try { + float scale = getScale(); + float x = ev.getX(); + float y = ev.getY(); + if (scale < getMediumScale()) { + setScale(getMediumScale(), x, y, true); + } else if (scale >= getMediumScale() && scale < getMaximumScale()) { + setScale(getMaximumScale(), x, y, true); + } else { + setScale(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; + } + }); + } + + public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) { + this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener); + } + + public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangeListener) { + this.mScaleChangeListener = onScaleChangeListener; + } + + public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) { + this.mSingleFlingListener = onSingleFlingListener; + } + + @Deprecated + public boolean isZoomEnabled() { + return mZoomEnabled; + } + + public RectF getDisplayRect() { + checkMatrixBounds(); + return getDisplayRect(getDrawMatrix()); + } + + public boolean setDisplayMatrix(Matrix finalMatrix) { + if (finalMatrix == null) { + throw new IllegalArgumentException("Matrix cannot be null"); + } + if (mImageView.getDrawable() == null) { + return false; + } + mSuppMatrix.set(finalMatrix); + checkAndDisplayMatrix(); + return true; + } + + public void setBaseRotation(final float degrees) { + mBaseRotation = degrees % 360; + update(); + setRotationBy(mBaseRotation); + checkAndDisplayMatrix(); + } + + public void setRotationTo(float degrees) { + mSuppMatrix.setRotate(degrees % 360); + checkAndDisplayMatrix(); + } + + public void setRotationBy(float degrees) { + mSuppMatrix.postRotate(degrees % 360); + checkAndDisplayMatrix(); + } + + public float getMinimumScale() { + return mMinScale; + } + + public float getMediumScale() { + return mMidScale; + } + + public float getMaximumScale() { + return mMaxScale; + } + + 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)); + } + + public ScaleType getScaleType() { + return mScaleType; + } + + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int + oldRight, int oldBottom) { + // Update our base matrix, as the bounds have changed + if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { + updateBaseMatrix(mImageView.getDrawable()); + } + } + + @Override + public boolean onTouch(View v, MotionEvent ev) { + boolean handled = false; + if (mZoomEnabled && Util.hasDrawable((ImageView) v)) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + ViewParent parent = v.getParent(); + // First, disable the Parent from intercepting the touch + // event + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + // If we're flinging, and the user presses down, cancel + // fling + cancelFling(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // If the user has zoomed less than min scale, zoom back + // to min scale + if (getScale() < mMinScale) { + RectF rect = getDisplayRect(); + if (rect != null) { + v.post(new AnimatedZoomRunnable(getScale(), mMinScale, + rect.centerX(), rect.centerY())); + handled = true; + } + } else if (getScale() > mMaxScale) { + RectF rect = getDisplayRect(); + if (rect != null) { + v.post(new AnimatedZoomRunnable(getScale(), mMaxScale, + rect.centerX(), rect.centerY())); + handled = true; + } + } + break; + } + // Try the Scale/Drag detector + if (mScaleDragDetector != null) { + boolean wasScaling = mScaleDragDetector.isScaling(); + boolean wasDragging = mScaleDragDetector.isDragging(); + handled = mScaleDragDetector.onTouchEvent(ev); + boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling(); + boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging(); + mBlockParentIntercept = didntScale && didntDrag; + } + // Check to see if the user double tapped + if (mGestureDetector != null && mGestureDetector.onTouchEvent(ev)) { + handled = true; + } + + } + return handled; + } + + public void setAllowParentInterceptOnEdge(boolean allow) { + mAllowParentInterceptOnEdge = allow; + } + + public void setMinimumScale(float minimumScale) { + Util.checkZoomLevels(minimumScale, mMidScale, mMaxScale); + mMinScale = minimumScale; + } + + public void setMediumScale(float mediumScale) { + Util.checkZoomLevels(mMinScale, mediumScale, mMaxScale); + mMidScale = mediumScale; + } + + public void setMaximumScale(float maximumScale) { + Util.checkZoomLevels(mMinScale, mMidScale, maximumScale); + mMaxScale = maximumScale; + } + + public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) { + Util.checkZoomLevels(minimumScale, mediumScale, maximumScale); + mMinScale = minimumScale; + mMidScale = mediumScale; + mMaxScale = maximumScale; + } + + public void setOnLongClickListener(OnLongClickListener listener) { + mLongClickListener = listener; + } + + public void setOnClickListener(View.OnClickListener listener) { + mOnClickListener = listener; + } + + public void setOnMatrixChangeListener(OnMatrixChangedListener listener) { + mMatrixChangeListener = listener; + } + + public void setOnPhotoTapListener(OnPhotoTapListener listener) { + mPhotoTapListener = listener; + } + + public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener mOutsidePhotoTapListener) { + this.mOutsidePhotoTapListener = mOutsidePhotoTapListener; + } + + public void setOnViewTapListener(OnViewTapListener listener) { + mViewTapListener = listener; + } + + public void setOnViewDragListener(OnViewDragListener listener) { + mOnViewDragListener = listener; + } + + public void setScale(float scale) { + setScale(scale, false); + } + + public void setScale(float scale, boolean animate) { + setScale(scale, + (mImageView.getRight()) / 2, + (mImageView.getBottom()) / 2, + animate); + } + + public void setScale(float scale, float focalX, float focalY, + boolean animate) { + // Check to see if the scale is within bounds + if (scale < mMinScale || scale > mMaxScale) { + throw new IllegalArgumentException("Scale must be within the range of minScale and maxScale"); + } + if (animate) { + mImageView.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; + } + + public void setScaleType(ScaleType scaleType) { + if (Util.isSupportedScaleType(scaleType) && scaleType != mScaleType) { + mScaleType = scaleType; + update(); + } + } + + public boolean isZoomable() { + return mZoomEnabled; + } + + public void setZoomable(boolean zoomable) { + mZoomEnabled = zoomable; + update(); + } + + public void update() { + if (mZoomEnabled) { + // Update the base matrix using the current drawable + updateBaseMatrix(mImageView.getDrawable()); + } else { + // Reset the Matrix... + resetMatrix(); + } + } + + /** + * Get the display matrix + * + * @param matrix target matrix to copy to + */ + 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; + } + + public Matrix getImageMatrix() { + return mDrawMatrix; + } + + public void setZoomTransitionDuration(int milliseconds) { + this.mZoomDuration = milliseconds; + } + + /** + * Helper method that 'unpacks' a Matrix and returns the required value + * + * @param matrix Matrix to unpack + * @param whichValue Which value from Matrix.M* to return + * @return 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 its contents + */ + private void resetMatrix() { + mSuppMatrix.reset(); + setRotationBy(mBaseRotation); + setImageViewMatrix(getDrawMatrix()); + checkMatrixBounds(); + } + + private void setImageViewMatrix(Matrix matrix) { + mImageView.setImageMatrix(matrix); + // Call MatrixChangedListener if needed + if (mMatrixChangeListener != null) { + RectF displayRect = getDisplayRect(matrix); + if (displayRect != null) { + mMatrixChangeListener.onMatrixChanged(displayRect); + } + } + } + + /** + * Helper method that simply checks the Matrix, and then displays the result + */ + private void checkAndDisplayMatrix() { + if (checkMatrixBounds()) { + setImageViewMatrix(getDrawMatrix()); + } + } + + /** + * 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) { + Drawable d = mImageView.getDrawable(); + if (d != null) { + mDisplayRect.set(0, 0, d.getIntrinsicWidth(), + d.getIntrinsicHeight()); + matrix.mapRect(mDisplayRect); + return mDisplayRect; + } + return null; + } + + /** + * Calculate Matrix for FIT_CENTER + * + * @param drawable - Drawable being displayed + */ + private void updateBaseMatrix(Drawable drawable) { + if (drawable == null) { + return; + } + final float viewWidth = getImageViewWidth(mImageView); + final float viewHeight = getImageViewHeight(mImageView); + final int drawableWidth = drawable.getIntrinsicWidth(); + final int drawableHeight = drawable.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 boolean checkMatrixBounds() { + final RectF rect = getDisplayRect(getDrawMatrix()); + if (rect == null) { + return false; + } + final float height = rect.height(), width = rect.width(); + float deltaX = 0, deltaY = 0; + final int viewHeight = getImageViewHeight(mImageView); + 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; + } + mVerticalScrollEdge = VERTICAL_EDGE_BOTH; + } else if (rect.top > 0) { + mVerticalScrollEdge = VERTICAL_EDGE_TOP; + deltaY = -rect.top; + } else if (rect.bottom < viewHeight) { + mVerticalScrollEdge = VERTICAL_EDGE_BOTTOM; + deltaY = viewHeight - rect.bottom; + } else { + mVerticalScrollEdge = VERTICAL_EDGE_NONE; + } + final int viewWidth = getImageViewWidth(mImageView); + 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; + } + mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH; + } else if (rect.left > 0) { + mHorizontalScrollEdge = HORIZONTAL_EDGE_LEFT; + deltaX = -rect.left; + } else if (rect.right < viewWidth) { + deltaX = viewWidth - rect.right; + mHorizontalScrollEdge = HORIZONTAL_EDGE_RIGHT; + } else { + mHorizontalScrollEdge = HORIZONTAL_EDGE_NONE; + } + // Finally actually translate the matrix + mSuppMatrix.postTranslate(deltaX, deltaY); + return true; + } + + private int getImageViewWidth(ImageView imageView) { + return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight(); + } + + private int getImageViewHeight(ImageView imageView) { + return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom(); + } + + private void cancelFling() { + if (mCurrentFlingRunnable != null) { + mCurrentFlingRunnable.cancelFling(); + mCurrentFlingRunnable = null; + } + } + + 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() { + float t = interpolate(); + float scale = mZoomStart + t * (mZoomEnd - mZoomStart); + float deltaScale = scale / getScale(); + onGestureListener.onScale(deltaScale, mFocalX, mFocalY); + // We haven't hit our target scale yet, so post ourselves again + if (t < 1f) { + Compat.postOnAnimation(mImageView, this); + } + } + + private float interpolate() { + float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration; + t = Math.min(1f, t); + t = mInterpolator.getInterpolation(t); + return t; + } + } + + private class FlingRunnable implements Runnable { + + private final OverScroller mScroller; + private int mCurrentX, mCurrentY; + + public FlingRunnable(Context context) { + mScroller = new OverScroller(context); + } + + public void cancelFling() { + mScroller.forceFinished(true); + } + + public void fling(int viewWidth, int viewHeight, int velocityX, + int velocityY) { + final RectF rect = getDisplayRect(); + if (rect == null) { + 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 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 + } + if (mScroller.computeScrollOffset()) { + final int newX = mScroller.getCurrX(); + final int newY = mScroller.getCurrY(); + mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY); + checkAndDisplayMatrix(); + mCurrentX = newX; + mCurrentY = newY; + // Post On animation + Compat.postOnAnimation(mImageView, this); + } + } + } +} diff --git a/photoview/src/main/java/com/github/chrisbanes/photoview/Util.java b/photoview/src/main/java/com/github/chrisbanes/photoview/Util.java new file mode 100644 index 0000000..2e3e5ad --- /dev/null +++ b/photoview/src/main/java/com/github/chrisbanes/photoview/Util.java @@ -0,0 +1,37 @@ +package com.github.chrisbanes.photoview; + +import android.view.MotionEvent; +import android.widget.ImageView; + +class Util { + + 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"); + } + } + + static boolean hasDrawable(ImageView imageView) { + return imageView.getDrawable() != null; + } + + static boolean isSupportedScaleType(final ImageView.ScaleType scaleType) { + if (scaleType == null) { + return false; + } + switch (scaleType) { + case MATRIX: + throw new IllegalStateException("Matrix scale type is not supported"); + } + return true; + } + + static int getPointerIndex(int action) { + return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } +} diff --git a/photoview/src/test/java/com/github/chrisbanes/photoview/ExampleUnitTest.java b/photoview/src/test/java/com/github/chrisbanes/photoview/ExampleUnitTest.java new file mode 100644 index 0000000..0dc410f --- /dev/null +++ b/photoview/src/test/java/com/github/chrisbanes/photoview/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.github.chrisbanes.photoview; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index c5a1f8a..2d6ba43 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,25 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} + include ':EasyPlayerPro' include ':ijkplayer-java' +include ':photoview'