diff --git a/apng/build.gradle b/apng/build.gradle deleted file mode 100644 index 4f6538de..00000000 --- a/apng/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - id 'com.android.library' -} -android { - defaultConfig { - minSdkVersion 16 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - compileSdk 28 - - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - publishing { - singleVariant('release') { - withSourcesJar() - } - } - namespace = "com.github.penfeizhou.animation.apng" -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - api("com.github.penfeizhou.android.animation:frameanimation:${rootProject.ext.Version}") - implementation 'androidx.appcompat:appcompat:1.6.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} -apply from: rootProject.file('scripts/upload.gradle') \ No newline at end of file diff --git a/apng/build.gradle.kts b/apng/build.gradle.kts new file mode 100644 index 00000000..82b6a81d --- /dev/null +++ b/apng/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("com.android.library") +} + +android { + namespace = "com.github.penfeizhou.animation.apng" + compileSdk = 36 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + api(project(":frameanimation")) + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.3.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") +} + +apply(from = rootProject.file("scripts/upload.gradle.kts")) diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGAssetLoader.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGAssetLoader.java index 9d7d4bdd..0adfc0f7 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGAssetLoader.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGAssetLoader.java @@ -3,6 +3,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.github.penfeizhou.animation.loader.AssetStreamLoader; /** @@ -13,7 +15,7 @@ @Deprecated public class APNGAssetLoader extends AssetStreamLoader { - public APNGAssetLoader(Context context, String assetName) { + public APNGAssetLoader(@NonNull Context context, String assetName) { super(context, assetName); } } diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGDrawable.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGDrawable.java index bd48767c..755ba58b 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGDrawable.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGDrawable.java @@ -3,6 +3,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.github.penfeizhou.animation.FrameAnimationDrawable; import com.github.penfeizhou.animation.apng.decode.APNGDecoder; import com.github.penfeizhou.animation.decode.FrameSeqDecoder; @@ -17,31 +19,35 @@ * @CreateDate: 2019/3/27 */ public class APNGDrawable extends FrameAnimationDrawable { - public APNGDrawable(Loader provider) { + public APNGDrawable(@NonNull Loader provider) { super(provider); } - public APNGDrawable(APNGDecoder decoder) { + public APNGDrawable(@NonNull APNGDecoder decoder) { super(decoder); } + @NonNull @Override protected APNGDecoder createFrameSeqDecoder(Loader streamLoader, FrameSeqDecoder.RenderListener listener) { return new APNGDecoder(streamLoader, listener); } - public static APNGDrawable fromAsset(Context context, String assetPath) { + @NonNull + public static APNGDrawable fromAsset(@NonNull Context context, String assetPath) { AssetStreamLoader assetStreamLoader = new AssetStreamLoader(context, assetPath); return new APNGDrawable(assetStreamLoader); } + @NonNull public static APNGDrawable fromFile(String filePath) { FileLoader fileLoader = new FileLoader(filePath); return new APNGDrawable(fileLoader); } - public static APNGDrawable fromResource(Context context, int resId) { + @NonNull + public static APNGDrawable fromResource(@NonNull Context context, int resId) { ResourceStreamLoader resourceStreamLoader = new ResourceStreamLoader(context, resId); return new APNGDrawable(resourceStreamLoader); } diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGResourceLoader.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGResourceLoader.java index c1ca1b14..aa058cab 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGResourceLoader.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/APNGResourceLoader.java @@ -3,6 +3,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.github.penfeizhou.animation.loader.ResourceStreamLoader; /** @@ -12,7 +14,7 @@ */ @Deprecated public class APNGResourceLoader extends ResourceStreamLoader { - public APNGResourceLoader(Context context, int resId) { + public APNGResourceLoader(@NonNull Context context, int resId) { super(context, resId); } } diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGDecoder.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGDecoder.java index 2756593f..8e8b0504 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGDecoder.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGDecoder.java @@ -8,6 +8,8 @@ import android.graphics.Rect; import android.util.Log; +import androidx.annotation.NonNull; + import com.github.penfeizhou.animation.apng.io.APNGReader; import com.github.penfeizhou.animation.apng.io.APNGWriter; import com.github.penfeizhou.animation.decode.Frame; @@ -58,6 +60,7 @@ protected APNGWriter getWriter() { return apngWriter; } + @NonNull @Override protected APNGReader getReader(Reader reader) { return new APNGReader(reader); @@ -75,8 +78,9 @@ protected void release() { } + @NonNull @Override - protected Rect read(APNGReader reader) throws IOException { + protected Rect read(@NonNull APNGReader reader) throws IOException { List chunks = APNGParser.parse(reader); List otherChunks = new ArrayList<>(); @@ -89,7 +93,7 @@ protected Rect read(APNGReader reader) throws IOException { Log.e(TAG, "chunk read reach to end"); break; } - + if (chunk instanceof ACTLChunk) { mLoopCount = ((ACTLChunk) chunk).num_plays; actl = true; @@ -121,7 +125,7 @@ protected Rect read(APNGReader reader) throws IOException { canvasWidth = ((IHDRChunk) chunk).width; canvasHeight = ((IHDRChunk) chunk).height; ihdrData = ((IHDRChunk) chunk).data; - } else if (!(chunk instanceof IENDChunk)) { + } else { otherChunks.add(chunk); } } @@ -131,8 +135,8 @@ protected Rect read(APNGReader reader) throws IOException { } @Override - protected void renderFrame(Frame frame) { - if (frame == null || fullRect == null) { + protected void renderFrame(@NonNull Frame frame) { + if (fullRect == null) { return; } try { diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGFrame.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGFrame.java index 4a142986..9a2f3bc7 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGFrame.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGFrame.java @@ -4,7 +4,9 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Rect; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.github.penfeizhou.animation.apng.io.APNGReader; import com.github.penfeizhou.animation.apng.io.APNGWriter; @@ -40,7 +42,7 @@ private CRC32 getCRC32() { return crc32; } - public APNGFrame(APNGReader reader, FCTLChunk fctlChunk) { + public APNGFrame(APNGReader reader, @NonNull FCTLChunk fctlChunk) { super(reader); blend_op = fctlChunk.blend_op; dispose_op = fctlChunk.dispose_op; @@ -59,7 +61,7 @@ public APNGFrame(APNGReader reader, FCTLChunk fctlChunk) { frameY = fctlChunk.y_offset; } - private int encode(APNGWriter apngWriter) throws IOException { + private int encode(@NonNull APNGWriter apngWriter) throws IOException { int fileSize = 8 + 13 + 12; //prefixChunks @@ -130,7 +132,7 @@ private int encode(APNGWriter apngWriter) throws IOException { @Override - public Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, APNGWriter writer) { + public Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, @NonNull APNGWriter writer) { try { int length = encode(writer); BitmapFactory.Options options = new BitmapFactory.Options(); diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGParser.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGParser.java index e3836159..5c2e89c2 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGParser.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/decode/APNGParser.java @@ -2,6 +2,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.github.penfeizhou.animation.apng.io.APNGReader; import com.github.penfeizhou.animation.io.Reader; import com.github.penfeizhou.animation.io.StreamReader; @@ -24,7 +26,7 @@ static class FormatException extends IOException { } } - public static boolean isAPNG(String filePath) { + public static boolean isAPNG(@NonNull String filePath) { InputStream inputStream = null; try { inputStream = new FileInputStream(filePath); @@ -42,7 +44,7 @@ public static boolean isAPNG(String filePath) { } } - public static boolean isAPNG(Context context, String assetPath) { + public static boolean isAPNG(@NonNull Context context, @NonNull String assetPath) { InputStream inputStream = null; try { inputStream = context.getAssets().open(assetPath); @@ -60,7 +62,7 @@ public static boolean isAPNG(Context context, String assetPath) { } } - public static boolean isAPNG(Context context, int resId) { + public static boolean isAPNG(@NonNull Context context, int resId) { InputStream inputStream = null; try { inputStream = context.getResources().openRawResource(resId); @@ -98,7 +100,8 @@ public static boolean isAPNG(Reader in) { return false; } - public static List parse(APNGReader reader) throws IOException { + @NonNull + public static List parse(@NonNull APNGReader reader) throws IOException { if (!reader.matchFourCC("\u0089PNG") || !reader.matchFourCC("\r\n\u001a\n")) { throw new FormatException(); } diff --git a/apng/src/main/java/com/github/penfeizhou/animation/apng/io/APNGReader.java b/apng/src/main/java/com/github/penfeizhou/animation/apng/io/APNGReader.java index ee41d951..34609663 100644 --- a/apng/src/main/java/com/github/penfeizhou/animation/apng/io/APNGReader.java +++ b/apng/src/main/java/com/github/penfeizhou/animation/apng/io/APNGReader.java @@ -2,6 +2,9 @@ import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.github.penfeizhou.animation.io.FilterReader; import com.github.penfeizhou.animation.io.Reader; @@ -16,6 +19,7 @@ public class APNGReader extends FilterReader { private static ThreadLocal __intBytes = new ThreadLocal<>(); + @NonNull protected static byte[] ensureBytes() { byte[] bytes = __intBytes.get(); if (bytes == null) { @@ -48,7 +52,7 @@ public short readShort() throws IOException { /** * @return read FourCC and match chars */ - public boolean matchFourCC(String chars) throws IOException { + public boolean matchFourCC(@Nullable String chars) throws IOException { if (TextUtils.isEmpty(chars) || chars.length() != 4) { return false; } diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index b73c15f3..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'com.google.devtools.ksp' -} - -android { - defaultConfig { - applicationId "com.github.penfeizhou.animation.demo" - minSdkVersion 21 - compileSdk 34 - targetSdkVersion 34 - versionCode 1 - versionName "1.0" - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = '17' - } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.3" - } - - buildFeatures { - viewBinding true - compose true - } - namespace = "com.github.penfeizhou.animation.demo" -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.recyclerview:recyclerview:1.3.1' - implementation 'com.github.bumptech.glide:glide:4.16.0' - ksp 'com.github.bumptech.glide:ksp:4.16.0' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12' - - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - - implementation("com.github.penfeizhou.android.animation:awebp:${rootProject.ext.Version}") - implementation("com.github.penfeizhou.android.animation:apng:${rootProject.ext.Version}") - implementation("com.github.penfeizhou.android.animation:gif:${rootProject.ext.Version}") - implementation("com.github.penfeizhou.android.animation:avif:${rootProject.ext.Version}") - implementation("com.github.penfeizhou.android.animation:glide-plugin:${rootProject.ext.Version}") - implementation("com.github.penfeizhou.android.animation:awebpencoder:${rootProject.ext.Version}") - - implementation 'androidx.compose.ui:ui:1.5.0' - implementation 'androidx.compose.ui:ui-tooling:1.5.0' - implementation 'androidx.compose.foundation:foundation:1.5.0' - implementation 'androidx.compose.material:material:1.5.0' - implementation 'androidx.compose.material:material-icons-core:1.5.0' - implementation 'androidx.compose.material:material-icons-extended:1.5.0' - implementation 'androidx.compose.runtime:runtime-livedata:1.5.0' - implementation 'androidx.compose.runtime:runtime-rxjava2:1.5.0' - implementation 'androidx.activity:activity-compose:1.7.2' - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0-alpha01' - implementation "com.github.bumptech.glide:compose:1.0.0-alpha.6" - implementation "com.github.bumptech.glide:avif-integration:4.16.0" -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..e3f43e6c --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,71 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.plugin.compose") + id("com.google.devtools.ksp") +} + +android { + namespace = "com.github.penfeizhou.animation.demo" + compileSdk = 36 + + defaultConfig { + applicationId = "com.github.penfeizhou.animation.demo" + minSdk = 23 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + buildFeatures { + viewBinding = true + compose = true + } +} + +dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + implementation("com.google.android.material:material:1.13.0") + implementation("androidx.constraintlayout:constraintlayout:2.2.1") + implementation("androidx.recyclerview:recyclerview:1.4.0") + implementation("com.github.bumptech.glide:glide:5.0.5") + ksp("com.github.bumptech.glide:ksp:5.0.5") + debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.3.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") + + val version = rootProject.extra["Version"] as String + implementation("com.github.penfeizhou.android.animation:awebp:$version") + implementation("com.github.penfeizhou.android.animation:apng:$version") + implementation("com.github.penfeizhou.android.animation:gif:$version") + implementation("com.github.penfeizhou.android.animation:avif:$version") + implementation("com.github.penfeizhou.android.animation:glide-plugin:$version") + implementation("com.github.penfeizhou.android.animation:awebpencoder:$version") + + implementation("androidx.compose.ui:ui:1.10.4") + implementation("androidx.compose.ui:ui-tooling:1.10.4") + implementation("androidx.compose.foundation:foundation:1.10.4") + implementation("androidx.compose.material:material:1.10.4") + implementation("androidx.compose.material:material-icons-core:1.7.8") + implementation("androidx.compose.material:material-icons-extended:1.7.8") + implementation("androidx.compose.runtime:runtime-livedata:1.10.4") + implementation("androidx.compose.runtime:runtime-rxjava2:1.10.4") + implementation("androidx.activity:activity-compose:1.12.4") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0") + implementation("com.github.bumptech.glide:compose:1.0.0-beta08") + implementation("com.github.bumptech.glide:avif-integration:5.0.5") +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a1e0d3cc..ffb8d5e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,6 +34,12 @@ + + @@ -41,4 +47,4 @@ - \ No newline at end of file + diff --git a/app/src/main/assets/animated_webp_with_transparency.webp b/app/src/main/assets/animated_webp_with_transparency.webp new file mode 100644 index 00000000..f9fcb722 Binary files /dev/null and b/app/src/main/assets/animated_webp_with_transparency.webp differ diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/APNGRecyclerViewTestActivity.java b/app/src/main/java/com/github/penfeizhou/animation/demo/APNGRecyclerViewTestActivity.java deleted file mode 100644 index d4048ddb..00000000 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/APNGRecyclerViewTestActivity.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.penfeizhou.animation.demo; - -import android.os.Bundle; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -public class APNGRecyclerViewTestActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_apng_list_test); - - final RecyclerView recyclerView = findViewById(R.id.rv); - recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); - recyclerView.setAdapter(new TestAdapter(this)); - } -} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/APNGRecyclerViewTestActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/APNGRecyclerViewTestActivity.kt new file mode 100644 index 00000000..83457e8c --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/APNGRecyclerViewTestActivity.kt @@ -0,0 +1,43 @@ +package com.github.penfeizhou.animation.demo + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.recyclerview.widget.GridLayoutManager +import com.github.penfeizhou.animation.demo.databinding.ActivityApngListTestBinding + +class APNGRecyclerViewTestActivity : AppCompatActivity() { + private lateinit var binding: ActivityApngListTestBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityApngListTestBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets: WindowInsetsCompat -> + val navigationBarsWithIme = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime() + ) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.rv.setPadding( + binding.rv.paddingLeft, + binding.rv.paddingTop, + binding.rv.paddingRight, + navigationBarsWithIme.bottom + ) + insets + } + + binding.rv.layoutManager = GridLayoutManager(this, 3) + binding.rv.adapter = TestAdapter(this) + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/APNGTestActivity.java b/app/src/main/java/com/github/penfeizhou/animation/demo/APNGTestActivity.java deleted file mode 100644 index 18b8edb0..00000000 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/APNGTestActivity.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.penfeizhou.animation.demo; - -import android.app.Activity; -import android.os.Bundle; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.bumptech.glide.Glide; -import com.github.penfeizhou.animation.glide.AnimationDecoderOption; - -/** - * @Description: 作用描述 - * @Author: pengfei.zhou - * @CreateDate: 2019/3/29 - */ -public class APNGTestActivity extends Activity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_apnglib); - LinearLayout linearLayout = findViewById(R.id.layout); - String[] urls = new String[]{ - "file:///android_asset/test.avif", - "file:///android_asset/wheel.avif", - "file:///android_asset/world-cup.avif", - "file:///android_asset/apng_detail_guide.png", - "file:///android_asset/1.gif", - "file:///android_asset/2.gif", - "file:///android_asset/3.gif", - "file:///android_asset/4.gif", - "file:///android_asset/5.gif", - "file:///android_asset/1.webp", - "file:///android_asset/2.webp", - }; - for (String url : urls) { - ImageView imageView = new ImageView(this); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - layoutParams.bottomMargin = 50; - layoutParams.topMargin = 50; - linearLayout.addView(imageView, layoutParams); - Glide.with(imageView) - .load(url) -// .set(AnimationDecoderOption.NO_ANIMATION_BOUNDS_MEASURE, true) - .into(imageView); - } - } -} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/APNGTestActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/APNGTestActivity.kt new file mode 100644 index 00000000..2beed222 --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/APNGTestActivity.kt @@ -0,0 +1,75 @@ +package com.github.penfeizhou.animation.demo + +import android.os.Bundle +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.bumptech.glide.Glide +import com.github.penfeizhou.animation.demo.databinding.ActivityApnglibBinding + +/** + * @Description: 作用描述 + * @Author: pengfei.zhou + * @CreateDate: 2019/3/29 + */ +class APNGTestActivity : AppCompatActivity() { + private lateinit var binding: ActivityApnglibBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityApnglibBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets: WindowInsetsCompat -> + val navigationBarsWithIme = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime() + ) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.scrollView.setPadding( + binding.scrollView.paddingLeft, + binding.scrollView.paddingTop, + binding.scrollView.paddingRight, + navigationBarsWithIme.bottom + ) + insets + } + + val linearLayout = binding.layout + val urls = arrayOf( + "file:///android_asset/test.avif", + "file:///android_asset/wheel.avif", + "file:///android_asset/world-cup.avif", + "file:///android_asset/apng_detail_guide.png", + "file:///android_asset/1.gif", + "file:///android_asset/2.gif", + "file:///android_asset/3.gif", + "file:///android_asset/4.gif", + "file:///android_asset/5.gif", + "file:///android_asset/1.webp", + "file:///android_asset/2.webp" + ) + for (url in urls) { + val imageView = ImageView(this) + val layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + layoutParams.bottomMargin = 50 + layoutParams.topMargin = 50 + linearLayout.addView(imageView, layoutParams) + Glide.with(imageView) + .load(url) + .into(imageView) + } + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/AnimationTestActivity.java b/app/src/main/java/com/github/penfeizhou/animation/demo/AnimationTestActivity.java deleted file mode 100644 index 168aeb15..00000000 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/AnimationTestActivity.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.github.penfeizhou.animation.demo; - -import android.app.Activity; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.github.penfeizhou.animation.apng.APNGDrawable; -import com.github.penfeizhou.animation.avif.AVIFDrawable; -import com.github.penfeizhou.animation.gif.GifDrawable; -import com.github.penfeizhou.animation.webp.WebPDrawable; - - -/** - * @Description: 作用描述 - * @Author: pengfei.zhou - * @CreateDate: 2019/3/29 - */ -public class AnimationTestActivity extends Activity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_apnglib); - LinearLayout linearLayout = findViewById(R.id.layout); - String[] files = getIntent().getStringArrayExtra("files"); - for (String assetFile : files) { - ImageView imageView = new ImageView(this); - Drawable drawable = null; - if (assetFile.endsWith("png")) { - drawable = APNGDrawable.fromAsset(this, assetFile); - } - if (assetFile.endsWith("webp")) { - drawable = WebPDrawable.fromAsset(this, assetFile); - } - if (assetFile.endsWith("gif")) { - drawable = GifDrawable.fromAsset(this, assetFile); - } - if (assetFile.endsWith("avif")) { - drawable = AVIFDrawable.fromAsset(this, assetFile); - } - imageView.setImageDrawable(drawable); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - layoutParams.bottomMargin = 50; - layoutParams.topMargin = 50; - linearLayout.addView(imageView, layoutParams); - } - } -} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/AnimationTestActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/AnimationTestActivity.kt new file mode 100644 index 00000000..fc82b112 --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/AnimationTestActivity.kt @@ -0,0 +1,81 @@ +package com.github.penfeizhou.animation.demo + +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.github.penfeizhou.animation.apng.APNGDrawable +import com.github.penfeizhou.animation.avif.AVIFDrawable +import com.github.penfeizhou.animation.demo.databinding.ActivityApnglibBinding +import com.github.penfeizhou.animation.gif.GifDrawable +import com.github.penfeizhou.animation.webp.WebPDrawable + +/** + * @Description: 作用描述 + * @Author: pengfei.zhou + * @CreateDate: 2019/3/29 + */ +class AnimationTestActivity : AppCompatActivity() { + private lateinit var binding: ActivityApnglibBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityApnglibBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets: WindowInsetsCompat -> + val navigationBarsWithIme = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime() + ) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.scrollView.setPadding( + binding.scrollView.paddingLeft, + binding.scrollView.paddingTop, + binding.scrollView.paddingRight, + navigationBarsWithIme.bottom + ) + insets + } + + val linearLayout = binding.layout + val files = intent.getStringArrayExtra("files") + if (files != null) { + for (assetFile in files) { + val imageView = ImageView(this) + var drawable: Drawable? = null + if (assetFile.endsWith("png")) { + drawable = APNGDrawable.fromAsset(this, assetFile) + } + if (assetFile.endsWith("webp")) { + drawable = WebPDrawable.fromAsset(this, assetFile) + } + if (assetFile.endsWith("gif")) { + drawable = GifDrawable.fromAsset(this, assetFile) + } + if (assetFile.endsWith("avif")) { + drawable = AVIFDrawable.fromAsset(this, assetFile) + } + imageView.setImageDrawable(drawable) + val layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + layoutParams.bottomMargin = 50 + layoutParams.topMargin = 50 + linearLayout.addView(imageView, layoutParams) + } + } + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/ComposeActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/ComposeActivity.kt index 42e512e3..76dcd8da 100644 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/ComposeActivity.kt +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/ComposeActivity.kt @@ -1,8 +1,7 @@ package com.github.penfeizhou.animation.demo import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth @@ -12,8 +11,12 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage +import com.github.penfeizhou.animation.demo.databinding.ActivityComposeBinding /** @@ -22,10 +25,30 @@ import com.bumptech.glide.integration.compose.GlideImage * @Author: pengfei.zhou * @CreateDate: 2023/9/6 */ -class ComposeActivity : ComponentActivity() { +class ComposeActivity : AppCompatActivity() { + private lateinit var binding: ActivityComposeBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { + binding = ActivityComposeBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val navigationBarsWithIme = + insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime()) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.composeView.updatePadding(bottom = navigationBarsWithIme.bottom) + insets + } + + binding.composeView.setContent { AnimationDemo() } } @@ -58,4 +81,4 @@ fun AnimationDemo() { contentDescription = "Test", ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/EncoderTestActivity.java b/app/src/main/java/com/github/penfeizhou/animation/demo/EncoderTestActivity.java deleted file mode 100644 index 07d76e46..00000000 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/EncoderTestActivity.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.penfeizhou.animation.demo; - -import android.app.Activity; -import android.os.Bundle; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.bumptech.glide.Glide; -import com.github.penfeizhou.animation.apng.APNGDrawable; -import com.github.penfeizhou.animation.awebpencoder.WebPEncoder; - -/** - * @Description: 作用描述 - * @Author: pengfei.zhou - * @CreateDate: 2019/3/29 - */ -public class EncoderTestActivity extends Activity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_apnglib); - LinearLayout linearLayout = findViewById(R.id.layout); - final ImageView imageView = new ImageView(this); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - layoutParams.bottomMargin = 50; - layoutParams.topMargin = 50; - linearLayout.addView(imageView, layoutParams); - - new Thread(new Runnable() { - @Override - public void run() { - - final byte[] ret = WebPEncoder.fromDecoder( - APNGDrawable.fromAsset(EncoderTestActivity.this, - "test2.png").getFrameSeqDecoder()).build(); - imageView.post(new Runnable() { - @Override - public void run() { - Glide.with(imageView) - .load(ret) - .into(imageView); - } - }); - } - }).start(); - } -} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/EncoderTestActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/EncoderTestActivity.kt new file mode 100644 index 00000000..8c31a2c3 --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/EncoderTestActivity.kt @@ -0,0 +1,74 @@ +package com.github.penfeizhou.animation.demo + +import android.os.Bundle +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.bumptech.glide.Glide +import com.github.penfeizhou.animation.apng.APNGDrawable +import com.github.penfeizhou.animation.awebpencoder.WebPEncoder +import com.github.penfeizhou.animation.demo.databinding.ActivityApnglibBinding +import kotlin.concurrent.thread + +/** + * @Description: 作用描述 + * @Author: pengfei.zhou + * @CreateDate: 2019/3/29 + */ +class EncoderTestActivity : AppCompatActivity() { + private lateinit var binding: ActivityApnglibBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityApnglibBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets: WindowInsetsCompat -> + val navigationBarsWithIme = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime() + ) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.scrollView.setPadding( + binding.scrollView.paddingLeft, + binding.scrollView.paddingTop, + binding.scrollView.paddingRight, + navigationBarsWithIme.bottom + ) + insets + } + + val linearLayout = binding.layout + val imageView = ImageView(this) + val layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + layoutParams.bottomMargin = 50 + layoutParams.topMargin = 50 + linearLayout.addView(imageView, layoutParams) + + thread { + val ret = WebPEncoder.fromDecoder( + APNGDrawable.fromAsset( + this@EncoderTestActivity, + "test2.png" + ).frameSeqDecoder + ).build() + imageView.post { + Glide.with(imageView) + .load(ret) + .into(imageView) + } + } + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/MainActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/MainActivity.kt index 256eb6b8..d7975431 100644 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/MainActivity.kt +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/MainActivity.kt @@ -6,7 +6,11 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import com.github.penfeizhou.animation.demo.databinding.ActivityMainBinding +import com.github.penfeizhou.animation.demo.frame_sequence_test.FrameSequenceTestActivity /** * @@ -25,6 +29,20 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { ) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + setSupportActionBar(binding.toolbar) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val navigationBarsWithIme = + insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime()) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.contentContainer.updatePadding(bottom = navigationBarsWithIme.bottom) + insets + } binding.tv0.setOnClickListener(this) binding.tvAvif.setOnClickListener(this) binding.tv1.setOnClickListener(this) @@ -35,14 +53,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { binding.tv6.setOnClickListener(this) binding.tv7.setOnClickListener(this) binding.tv8.setOnClickListener(this) - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) + binding.tv9.setOnClickListener(this) + binding.tv10.setOnClickListener(this) } override fun onClick(v: View) { @@ -137,6 +149,16 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { ) startActivity(intent) } + + R.id.tv_9 -> { + val intent = Intent(this, TransparencyTestActivity::class.java) + startActivity(intent) + } + + R.id.tv_10 -> { + val intent = Intent(this, FrameSequenceTestActivity::class.java) + startActivity(intent) + } } } } diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/TestAdapter.java b/app/src/main/java/com/github/penfeizhou/animation/demo/TestAdapter.java deleted file mode 100644 index 9bf9f0ee..00000000 --- a/app/src/main/java/com/github/penfeizhou/animation/demo/TestAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.github.penfeizhou.animation.demo; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; - -public class TestAdapter extends RecyclerView.Adapter { - - private final Context mContext; - - public TestAdapter(Context context) { - mContext = context; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - Glide.with(mContext) - .load("https://raw.githubusercontent.com/penfeizhou/APNG4Android/master/app/src/main/assets/test2.png") - .into(holder.iv); - } - - - @Override - public int getItemCount() { - return 50; - } - - - static class ViewHolder extends RecyclerView.ViewHolder { - ImageView iv; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - iv = itemView.findViewById(R.id.iv); - } - } -} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/TestAdapter.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/TestAdapter.kt new file mode 100644 index 00000000..1a89aef5 --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/TestAdapter.kt @@ -0,0 +1,30 @@ +package com.github.penfeizhou.animation.demo + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide + +class TestAdapter(private val context: Context) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + Glide.with(context) + .load("https://raw.githubusercontent.com/penfeizhou/APNG4Android/master/app/src/main/assets/test2.png") + .into(holder.iv) + } + + override fun getItemCount(): Int { + return 50 + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val iv: ImageView = itemView.findViewById(R.id.iv) + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/TransparencyTestActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/TransparencyTestActivity.kt new file mode 100644 index 00000000..3123a309 --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/TransparencyTestActivity.kt @@ -0,0 +1,44 @@ +package com.github.penfeizhou.animation.demo + +import android.graphics.Color +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.bumptech.glide.Glide +import com.github.penfeizhou.animation.demo.databinding.ActivityTransparencyTestBinding +import kotlin.random.Random + +class TransparencyTestActivity : AppCompatActivity() { + private lateinit var binding: ActivityTransparencyTestBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityTransparencyTestBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val navigationBarsWithIme = + insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime()) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.scrollView.setPadding(0, 0, 0, navigationBarsWithIme.bottom) + insets + } + + Glide.with(this) + .load("file:///android_asset/animated_webp_with_transparency.webp") + .into(binding.imageView) + + binding.btnChangeBackground.setOnClickListener { + val color = Color.rgb(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256)) + binding.backgroundContainer.setBackgroundColor(color) + } + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/frame_sequence_test/FrameSequenceTestActivity.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/frame_sequence_test/FrameSequenceTestActivity.kt new file mode 100644 index 00000000..3a29105d --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/frame_sequence_test/FrameSequenceTestActivity.kt @@ -0,0 +1,120 @@ +package com.github.penfeizhou.animation.demo.frame_sequence_test + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.MotionEvent +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.github.penfeizhou.animation.demo.databinding.ActivityFrameSequenceTestBinding + +class FrameSequenceTestActivity : AppCompatActivity() { + private lateinit var binding: ActivityFrameSequenceTestBinding + private val viewModel: FrameSequenceViewModel by viewModels() + + @SuppressLint("ClickableViewAccessibility") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityFrameSequenceTestBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets: WindowInsetsCompat -> + val navigationBarsWithIme = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime() + ) + binding.appBarLayout.setPadding( + navigationBarsWithIme.left, + navigationBarsWithIme.top, + navigationBarsWithIme.right, + 0 + ) + binding.root.setPadding(navigationBarsWithIme.left, 0, navigationBarsWithIme.right, 0) + binding.scrollView.setPadding( + binding.scrollView.paddingLeft, + binding.scrollView.paddingTop, + binding.scrollView.paddingRight, + navigationBarsWithIme.bottom + ) + insets + } + + // List only files that have animation (based on common demo files) + val animationFiles = listOf( + "wheel.avif", "world-cup.avif", + "apng_detail_guide.png", "test2.png", + "1.gif", "2.gif", "3.gif", "4.gif", "5.gif", "6.gif", "world-cup.gif", + "1.webp", "2.webp", "animated_webp_with_transparency.webp" + ) + + val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, animationFiles) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.spinnerFiles.adapter = adapter + + binding.spinnerFiles.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + viewModel.selectFile(animationFiles[position]) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + + binding.btnNextFrame.setOnClickListener { + viewModel.nextFrame() + } + + binding.btnNextFrameLoop.setOnTouchListener { _, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + viewModel.startLoop() + true + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + viewModel.stopLoop() + true + } + + else -> false + } + } + + binding.btnRunPerformance.setOnClickListener { + viewModel.runPerformanceTest() + } + + // Observers + viewModel.frameBitmap.observe(this) { bitmap -> + binding.imageView.setImageBitmap(bitmap) + } + + viewModel.frameInfo.observe(this) { info -> + binding.tvFrameInfo.text = "Frame Info: $info" + } + + viewModel.fileInfo.observe(this) { info -> + binding.tvFileInfo.text = info + } + + viewModel.performanceProgress.observe(this) { progress -> + binding.pbPerformance.progress = progress + } + + viewModel.performanceInfo.observe(this) { info -> + binding.tvPerformanceInfo.text = "Performance Info: $info" + } + + viewModel.isDecodingAll.observe(this) { isDecoding -> + binding.btnRunPerformance.isEnabled = !isDecoding + binding.btnRunPerformance.text = if (isDecoding) "Decoding..." else "Run Decode All Frames" + binding.pbPerformance.visibility = if (isDecoding) View.VISIBLE else View.GONE + binding.spinnerFiles.isEnabled = !isDecoding + binding.btnNextFrame.isEnabled = !isDecoding + binding.btnNextFrameLoop.isEnabled = !isDecoding + } + } +} diff --git a/app/src/main/java/com/github/penfeizhou/animation/demo/frame_sequence_test/FrameSequenceViewModel.kt b/app/src/main/java/com/github/penfeizhou/animation/demo/frame_sequence_test/FrameSequenceViewModel.kt new file mode 100644 index 00000000..9cd7bd50 --- /dev/null +++ b/app/src/main/java/com/github/penfeizhou/animation/demo/frame_sequence_test/FrameSequenceViewModel.kt @@ -0,0 +1,166 @@ +package com.github.penfeizhou.animation.demo.frame_sequence_test + +import android.app.Application +import android.graphics.Bitmap +import androidx.lifecycle.* +import com.github.penfeizhou.animation.apng.decode.APNGDecoder +import com.github.penfeizhou.animation.apng.decode.APNGParser +import com.github.penfeizhou.animation.avif.decode.AVIFDecoder +import com.github.penfeizhou.animation.avif.decode.AVIFParser +import com.github.penfeizhou.animation.decode.FrameSeqDecoder +import com.github.penfeizhou.animation.gif.decode.GifDecoder +import com.github.penfeizhou.animation.gif.decode.GifParser +import com.github.penfeizhou.animation.loader.AssetStreamLoader +import com.github.penfeizhou.animation.loader.Loader +import com.github.penfeizhou.animation.webp.decode.WebPDecoder +import com.github.penfeizhou.animation.webp.decode.WebPParser +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.io.IOException + +class FrameSequenceViewModel(application: Application) : AndroidViewModel(application) { + private var decoder: FrameSeqDecoder<*, *>? = null + private var currentBitmap: Bitmap? = null + + private val _frameBitmap = MutableLiveData() + val frameBitmap: LiveData = _frameBitmap + + private val _frameInfo = MutableLiveData() + val frameInfo: LiveData = _frameInfo + + private val _fileInfo = MutableLiveData() + val fileInfo: LiveData = _fileInfo + + private val _performanceProgress = MutableLiveData() + val performanceProgress: LiveData = _performanceProgress + + private val _performanceInfo = MutableLiveData() + val performanceInfo: LiveData = _performanceInfo + + private val _isDecodingAll = MutableLiveData(false) + val isDecodingAll: LiveData = _isDecodingAll + + private var loopJob: Job? = null + + fun selectFile(assetName: String) { + stopDecoding() + decoder?.stop() + val context = getApplication() + val loader = AssetStreamLoader(context, assetName) + decoder = createDecoder(loader) + + viewModelScope.launch(Dispatchers.IO) { + decoder?.let { decoder -> + try { + decoder.prepareSequentialDecode() + val bounds = decoder.bounds + val frameCount = decoder.frameCount + _fileInfo.postValue("File: $assetName\nSize: ${bounds.width()}x${bounds.height()}\nFrames: $frameCount") + + currentBitmap?.recycle() + currentBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888) + nextFrame() + } catch (e: IOException) { + _fileInfo.postValue("Error: ${e.message}") + } + } + } + } + + private fun createDecoder(loader: Loader): FrameSeqDecoder<*, *>? { + val reader = loader.obtain() + return try { + when { + WebPParser.isAWebP(reader) -> WebPDecoder(loader, null) + APNGParser.isAPNG(reader.also { it.reset() }) -> APNGDecoder(loader, null) + GifParser.isGif(reader.also { it.reset() }) -> GifDecoder(loader, null) + AVIFParser.isAVIF(reader.also { it.reset() }) -> AVIFDecoder(loader, null) + else -> null + } + } finally { + reader.close() + } + } + + fun nextFrame() { + viewModelScope.launch(Dispatchers.IO) { + val d = decoder ?: return@launch + val b = currentBitmap ?: return@launch + val duration = d.nextFrame(b) + if (duration >= 0) { + _frameBitmap.postValue(b) + _frameInfo.postValue("Index: ${d.frameIndex}, Duration: ${duration}ms") + } else { + // Wrap around + d.prepareSequentialDecode() + val firstDuration = d.nextFrame(b) + _frameBitmap.postValue(b) + _frameInfo.postValue("Index: ${d.frameIndex}, Duration: ${firstDuration}ms") + } + } + } + + fun startLoop() { + if (loopJob?.isActive == true) return + loopJob = viewModelScope.launch(Dispatchers.IO) { + while (true) { + val d = decoder ?: break + val b = currentBitmap ?: break + val duration = d.nextFrame(b) + if (duration >= 0) { + _frameBitmap.postValue(b) + _frameInfo.postValue("Index: ${d.frameIndex}, Duration: ${duration}ms") + delay(duration.toLong().coerceAtLeast(16L)) + } else { + d.prepareSequentialDecode() + } + } + } + } + + fun stopLoop() { + loopJob?.cancel() + } + + fun runPerformanceTest() { + if (_isDecodingAll.value == true) return + _isDecodingAll.value = true + _performanceInfo.value = "Starting..." + + viewModelScope.launch(Dispatchers.IO) { + val d = decoder ?: run { + _isDecodingAll.postValue(false) + return@launch + } + val start = System.currentTimeMillis() + val totalFrames = d.frameCount + + d.decodeAllFrames(object : FrameSeqDecoder.FrameVisitor { + override fun onFrame(index: Int, bitmap: Bitmap, duration: Int): Boolean { + _performanceProgress.postValue((index + 1) * 100 / totalFrames) + return true + } + + override fun onException(t: Throwable) { + _performanceInfo.postValue("Error: ${t.message}") + } + }) + + val end = System.currentTimeMillis() + _performanceInfo.postValue("Decoded $totalFrames frames in ${end - start}ms") + _isDecodingAll.postValue(false) + } + } + + private fun stopDecoding() { + stopLoop() + } + + override fun onCleared() { + super.onCleared() + decoder?.stop() + currentBitmap?.recycle() + } +} diff --git a/app/src/main/res/layout/activity_apng_list_test.xml b/app/src/main/res/layout/activity_apng_list_test.xml index edd56137..f2e110a9 100644 --- a/app/src/main/res/layout/activity_apng_list_test.xml +++ b/app/src/main/res/layout/activity_apng_list_test.xml @@ -1,10 +1,29 @@ - + android:layout_height="match_parent" + tools:context=".APNGRecyclerViewTestActivity"> + + + + + + - + android:layout_height="match_parent" + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + + diff --git a/app/src/main/res/layout/activity_apnglib.xml b/app/src/main/res/layout/activity_apnglib.xml index 8724980e..71a9a637 100644 --- a/app/src/main/res/layout/activity_apnglib.xml +++ b/app/src/main/res/layout/activity_apnglib.xml @@ -1,13 +1,29 @@ - + android:layout_height="match_parent"> - + + + + + + + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + - - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml new file mode 100644 index 00000000..63a5c0f3 --- /dev/null +++ b/app/src/main/res/layout/activity_compose.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_frame_sequence_test.xml b/app/src/main/res/layout/activity_frame_sequence_test.xml new file mode 100644 index 00000000..fcddfd8c --- /dev/null +++ b/app/src/main/res/layout/activity_frame_sequence_test.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + +