Work in creator activity and improvements in app
Working on apng decoder
This commit is contained in:
parent
4863dfb2e8
commit
acd55f6d5a
|
@ -17,6 +17,8 @@ import oupson.apng.utils.Utils.Companion.to4Bytes
|
|||
import java.io.File
|
||||
import java.util.zip.CRC32
|
||||
|
||||
|
||||
// TODO CREATE A BETTER CLASS
|
||||
/**
|
||||
* Create an APNG file
|
||||
*/
|
||||
|
|
|
@ -92,7 +92,7 @@ fun ImageView.loadApng(@RawRes res : Int, speed : Float? = null, apngAnimatorOpt
|
|||
|
||||
/**
|
||||
* Class to play APNG
|
||||
* For better performance but lesser features use [ExperimentalApngDecoder] instead
|
||||
* For better performance but lesser features use [ApngDecoder] instead
|
||||
*/
|
||||
class ApngAnimator(private val context: Context?) {
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
|
@ -112,6 +112,7 @@ class ApngAnimator(private val context: Context?) {
|
|||
}
|
||||
private var imageView: ImageView? = null
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
var anim: CustomAnimationDrawable? = null
|
||||
private var activeAnimation: CustomAnimationDrawable? = null
|
||||
|
||||
|
|
|
@ -24,16 +24,14 @@ import java.io.InputStream
|
|||
import java.net.URL
|
||||
import java.util.zip.CRC32
|
||||
|
||||
// TODO DOC CODE
|
||||
class ExperimentalApngDecoder {
|
||||
class ApngDecoder {
|
||||
interface Callback {
|
||||
fun onSuccess(drawable : Drawable)
|
||||
fun onError(error : java.lang.Exception)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// TODO Change TAG
|
||||
private const val TAG = "ExperimentalApngDecoder"
|
||||
private const val TAG = "ApngDecoder"
|
||||
|
||||
private val clearPaint : Paint by lazy {
|
||||
Paint().apply {
|
||||
|
@ -378,7 +376,7 @@ class ExperimentalApngDecoder {
|
|||
* @param file File to decode.
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
|
@ -406,7 +404,7 @@ class ExperimentalApngDecoder {
|
|||
* @param uri Uri to load
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
|
@ -435,7 +433,7 @@ class ExperimentalApngDecoder {
|
|||
* @param res Raw resource to load
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
|
@ -464,7 +462,7 @@ class ExperimentalApngDecoder {
|
|||
* @param url URL to load
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
|
@ -492,7 +490,7 @@ class ExperimentalApngDecoder {
|
|||
* @param string URL to load
|
||||
* @param imageView Image View.
|
||||
* @param speed Optional parameter.
|
||||
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error
|
||||
* @param callback [ApngDecoder.Callback] to handle success and error
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
|
@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
class Loader {
|
||||
|
@ -15,7 +16,7 @@ class Loader {
|
|||
* @param url Url of the file to download
|
||||
* @return [ByteArray] of the file
|
||||
*/
|
||||
// @Throws(IOException::class, java.io.FileNotFoundException::class, java.lang.Exception::class)
|
||||
@Throws(IOException::class, java.io.FileNotFoundException::class)
|
||||
suspend fun load(context: Context, url: URL) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val currentDir = context.filesDir
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".MainActivity">
|
||||
<activity android:name=".activities.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ViewerActivity"
|
||||
android:name=".activities.ViewerActivity"
|
||||
android:label="APNG Viewer">
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
@ -36,8 +36,23 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".CreatorActivity"
|
||||
android:exported="true"/>
|
||||
android:name=".activities.CreatorActivity"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".activities.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activities.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,183 +0,0 @@
|
|||
package oupson.apngcreator
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import org.jetbrains.anko.*
|
||||
import org.jetbrains.anko.design.appBarLayout
|
||||
import org.jetbrains.anko.design.floatingActionButton
|
||||
import org.jetbrains.anko.sdk27.coroutines.onClick
|
||||
import oupson.apng.Apng
|
||||
import oupson.apng.ApngAnimator
|
||||
import oupson.apngcreator.adapter.AnkoAdapter
|
||||
import java.io.File
|
||||
|
||||
// TODO
|
||||
class CreatorActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val PICK_IMAGE = 999
|
||||
}
|
||||
private var items : ArrayList<Bitmap> = ArrayList()
|
||||
private var bitmapAdapter : AnkoAdapter<Bitmap>? = null
|
||||
|
||||
private var view = CreatorActivityLayout()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
view.setContentView(this)
|
||||
view.addFrameButton.onClick {
|
||||
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
getIntent.type = "image/*"
|
||||
|
||||
val pickIntent = Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||
pickIntent.type = "image/*"
|
||||
|
||||
val chooserIntent = Intent.createChooser(getIntent, "Select Image")
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pickIntent))
|
||||
|
||||
startActivityForResult(chooserIntent, PICK_IMAGE)
|
||||
}
|
||||
view.createButton.onClick {
|
||||
val apngCreated = Apng()
|
||||
|
||||
items.forEach { bitmap ->
|
||||
apngCreated.addFrames(bitmap)
|
||||
}
|
||||
|
||||
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "apn0.png").writeBytes(apngCreated.toByteArray())
|
||||
|
||||
apngCreated.apply {
|
||||
if (view.optimiseCheckBox.isChecked)
|
||||
apngCreated.optimiseFrame()
|
||||
}
|
||||
val a = ApngAnimator(applicationContext)
|
||||
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "apn.png").writeBytes(apngCreated.toByteArray())
|
||||
a.load(apngCreated.toByteArray())
|
||||
a.onLoaded { anim ->
|
||||
alert {
|
||||
customView {
|
||||
imageView {
|
||||
this.setImageDrawable(anim.anim)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
bitmapAdapter = AnkoAdapter({items}) { index, items, _ ->
|
||||
with(items[index]) {
|
||||
verticalLayout {
|
||||
lparams {
|
||||
width = matchParent
|
||||
height = matchParent
|
||||
}
|
||||
imageView {
|
||||
setImageBitmap(this@with)
|
||||
}.lparams {
|
||||
width = matchParent
|
||||
height = matchParent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* frameListViewAdapter(this, items) */
|
||||
view.listView.adapter = bitmapAdapter
|
||||
setSupportActionBar(view.toolbar)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when(requestCode) {
|
||||
PICK_IMAGE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (data?.data != null) {
|
||||
contentResolver.openInputStream(data.data!!)?.readBytes()?.apply {
|
||||
items.add(BitmapFactory.decodeByteArray(this, 0, this.size))
|
||||
bitmapAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CreatorActivityLayout : AnkoComponent<CreatorActivity> {
|
||||
lateinit var listView: ListView
|
||||
lateinit var addFrameButton: FloatingActionButton
|
||||
lateinit var createButton: FloatingActionButton
|
||||
lateinit var optimiseCheckBox: CheckBox
|
||||
lateinit var toolbar: Toolbar
|
||||
override fun createView(ui: AnkoContext<CreatorActivity>) = with(ui) {
|
||||
relativeLayout {
|
||||
backgroundColor = Color.WHITE
|
||||
val bar = verticalLayout {
|
||||
id = View.generateViewId()
|
||||
backgroundColor = Color.WHITE
|
||||
appBarLayout {
|
||||
/**toolbar = xToolbar {
|
||||
id = View.generateViewId()
|
||||
}.lparams {
|
||||
width = matchParent
|
||||
height = wrapContent
|
||||
}
|
||||
}.lparams {
|
||||
width = matchParent
|
||||
height = wrapContent
|
||||
bottomMargin = 1
|
||||
}*/
|
||||
}.lparams {
|
||||
width = matchParent
|
||||
height = wrapContent
|
||||
}
|
||||
optimiseCheckBox = checkBox("Optimise APNG, WIP !") {
|
||||
id = View.generateViewId()
|
||||
}.lparams {
|
||||
width = matchParent
|
||||
//below(bar)
|
||||
}
|
||||
listView = listView {
|
||||
id = View.generateViewId()
|
||||
}.lparams {
|
||||
width = matchParent
|
||||
height = matchParent
|
||||
//below(optimiseCheckBox)
|
||||
}
|
||||
addFrameButton = floatingActionButton {
|
||||
imageResource = R.drawable.ic_add_black_24dp
|
||||
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
||||
backgroundTintList = ColorStateList.valueOf(Color.BLACK)
|
||||
isClickable = true
|
||||
}.lparams {
|
||||
width = wrapContent
|
||||
height = wrapContent
|
||||
margin = dip(5)
|
||||
//alignParentBottom()
|
||||
//alignParentEnd()
|
||||
}
|
||||
createButton = floatingActionButton {
|
||||
imageResource = R.drawable.ic_play_arrow_black_24dp
|
||||
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
||||
backgroundTintList = ColorStateList.valueOf(Color.BLACK)
|
||||
isClickable = true
|
||||
}.lparams {
|
||||
width = wrapContent
|
||||
height = wrapContent
|
||||
margin = dip(5)
|
||||
//alignParentBottom()
|
||||
//alignParentStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package oupson.apngcreator.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_creator.*
|
||||
import oupson.apng.Apng
|
||||
import oupson.apngcreator.BuildConfig
|
||||
import oupson.apngcreator.R
|
||||
import oupson.apngcreator.adapter.ImageAdapter
|
||||
import java.io.File
|
||||
|
||||
class CreatorActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val PICK_IMAGE = 999
|
||||
}
|
||||
private var items : ArrayList<Uri> = ArrayList()
|
||||
private var adapter : ImageAdapter? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_creator)
|
||||
|
||||
fabAddImage.setOnClickListener {
|
||||
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
getIntent.type = "image/*"
|
||||
|
||||
val pickIntent = Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||
pickIntent.type = "image/*"
|
||||
|
||||
val chooserIntent = Intent.createChooser(getIntent, "Select Image")
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pickIntent))
|
||||
|
||||
startActivityForResult(chooserIntent,
|
||||
PICK_IMAGE
|
||||
)
|
||||
}
|
||||
|
||||
/* frameListViewAdapter(this, items) */
|
||||
adapter = ImageAdapter(this, items)
|
||||
|
||||
imageRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
imageRecyclerView.setHasFixedSize(true)
|
||||
imageRecyclerView.setItemViewCacheSize(20)
|
||||
if (adapter != null)
|
||||
ItemTouchHelper(SwipeToDeleteCallback(adapter!!)).attachToRecyclerView(imageRecyclerView)
|
||||
|
||||
setSupportActionBar(creatorBottomAppBar)
|
||||
imageRecyclerView.adapter = adapter
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.creator_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
return when (item?.itemId) {
|
||||
R.id.menu_create_apng -> {
|
||||
// TODO
|
||||
val apngCreated = Apng()
|
||||
|
||||
items.forEachIndexed { i, uri ->
|
||||
println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
|
||||
val str = contentResolver.openInputStream(uri)
|
||||
apngCreated.addFrames(BitmapFactory.decodeStream(str), delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f)
|
||||
str?.close()
|
||||
}
|
||||
|
||||
File(cacheDir, "apn0.png").writeBytes(apngCreated.toByteArray())
|
||||
|
||||
apngCreated.apply {
|
||||
// TODO
|
||||
//if (view.optimiseCheckBox.isChecked)
|
||||
// apngCreated.optimiseFrame()
|
||||
}
|
||||
|
||||
// TODO Open
|
||||
val f = File(filesDir, "images/apng.png").apply {
|
||||
if (!exists()) {
|
||||
parentFile.mkdirs()
|
||||
println(createNewFile())
|
||||
}
|
||||
writeBytes(apngCreated.toByteArray())
|
||||
}
|
||||
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", f)
|
||||
startActivity(intent)
|
||||
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when(requestCode) {
|
||||
PICK_IMAGE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (data?.data != null) {
|
||||
items.add(data.data!!)
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class SwipeToDeleteCallback(private val adapter: ImageAdapter) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
) : Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.adapterPosition
|
||||
adapter.delay.removeAt(position)
|
||||
items.removeAt(position)
|
||||
adapter.notifyDataSetChanged()
|
||||
adapter.listeners.forEachIndexed { index, listener ->
|
||||
if (index >= position)
|
||||
listener.position = index
|
||||
}
|
||||
}
|
||||
|
||||
override fun isItemViewSwipeEnabled() = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package oupson.apngcreator
|
||||
package oupson.apngcreator.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
|
@ -13,10 +13,15 @@ import com.google.android.material.shape.ShapeAppearanceModel
|
|||
import com.google.android.material.shape.ShapePath
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.jetbrains.anko.startActivity
|
||||
import oupson.apngcreator.R
|
||||
import oupson.apngcreator.fragments.ApngDecoderFragment
|
||||
import oupson.apngcreator.fragments.JavaFragment
|
||||
import oupson.apngcreator.fragments.KotlinFragment
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
@Suppress("unused")
|
||||
private const val TAG = "MainActivity"
|
||||
}
|
||||
|
||||
|
@ -34,7 +39,10 @@ class MainActivity : AppCompatActivity() {
|
|||
startActivity<CreatorActivity>()
|
||||
}
|
||||
|
||||
val drawerToggle = ActionBarDrawerToggle(this, drawer_layout, bottomAppBar, R.string.open, R.string.close)
|
||||
val drawerToggle = ActionBarDrawerToggle(this, drawer_layout, bottomAppBar,
|
||||
R.string.open,
|
||||
R.string.close
|
||||
)
|
||||
drawer_layout.addDrawerListener(drawerToggle)
|
||||
drawerToggle.syncState()
|
||||
|
||||
|
@ -45,7 +53,10 @@ class MainActivity : AppCompatActivity() {
|
|||
R.id.menu_kotlin_fragment -> {
|
||||
if (selected != 0) {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
replace(R.id.fragment_container, KotlinFragment.newInstance())
|
||||
replace(
|
||||
R.id.fragment_container,
|
||||
KotlinFragment.newInstance()
|
||||
)
|
||||
addToBackStack(null)
|
||||
}.commit()
|
||||
selected = 0
|
||||
|
@ -54,7 +65,10 @@ class MainActivity : AppCompatActivity() {
|
|||
R.id.menu_java_fragment -> {
|
||||
if (selected != 1) {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
replace(R.id.fragment_container, JavaFragment())
|
||||
replace(
|
||||
R.id.fragment_container,
|
||||
JavaFragment()
|
||||
)
|
||||
addToBackStack(null)
|
||||
}.commit()
|
||||
selected = 1
|
||||
|
@ -63,7 +77,10 @@ class MainActivity : AppCompatActivity() {
|
|||
R.id.menu_apng_decoder_fragment -> {
|
||||
if (selected != 2) {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
replace(R.id.fragment_container, ApngDecoderFragment.newInstance())
|
||||
replace(
|
||||
R.id.fragment_container,
|
||||
ApngDecoderFragment.newInstance()
|
||||
)
|
||||
addToBackStack(null)
|
||||
}.commit()
|
||||
selected = 2
|
||||
|
@ -80,21 +97,29 @@ class MainActivity : AppCompatActivity() {
|
|||
when(intent.getStringExtra("fragment")) {
|
||||
"kotlin" -> {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment")
|
||||
add(
|
||||
R.id.fragment_container,
|
||||
KotlinFragment.newInstance(), "KotlinFragment")
|
||||
}.commit()
|
||||
navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
|
||||
selected = 0
|
||||
}
|
||||
"java" -> {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
add(R.id.fragment_container, JavaFragment())
|
||||
add(
|
||||
R.id.fragment_container,
|
||||
JavaFragment()
|
||||
)
|
||||
}.commit()
|
||||
navigationView.setCheckedItem(R.id.menu_java_fragment)
|
||||
selected = 1
|
||||
}
|
||||
"apng_decoder" -> {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
add(R.id.fragment_container, ApngDecoderFragment.newInstance())
|
||||
add(
|
||||
R.id.fragment_container,
|
||||
ApngDecoderFragment.newInstance()
|
||||
)
|
||||
}.commit()
|
||||
navigationView.setCheckedItem(R.id.menu_apng_decoder_fragment)
|
||||
selected = 2
|
||||
|
@ -102,7 +127,9 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
} else {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment")
|
||||
add(
|
||||
R.id.fragment_container,
|
||||
KotlinFragment.newInstance(), "KotlinFragment")
|
||||
}.commit()
|
||||
navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package oupson.apngcreator
|
||||
package oupson.apngcreator.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
|
@ -11,8 +11,9 @@ import kotlinx.android.synthetic.main.activity_viewer.*
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import oupson.apng.ApngDecoder
|
||||
import oupson.apng.CustomAnimationDrawable
|
||||
import oupson.apng.ExperimentalApngDecoder
|
||||
import oupson.apngcreator.R
|
||||
|
||||
class ViewerActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -43,7 +44,7 @@ class ViewerActivity : AppCompatActivity() {
|
|||
val uri = intent.data ?: return
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
//val animator = imageView.loadApng(uri, null)
|
||||
val drawable = ExperimentalApngDecoder.decodeApng(this@ViewerActivity, uri)
|
||||
val drawable = ApngDecoder.decodeApng(this@ViewerActivity, uri)
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
viewerImageView.setImageDrawable(drawable)
|
||||
if (drawable is CustomAnimationDrawable)
|
|
@ -1,29 +0,0 @@
|
|||
package oupson.apngcreator.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
|
||||
class AnkoAdapter<T>(itemFactory: () -> List<T>,
|
||||
val viewFactory: Context.(index: Int, items: List<T>, view: View?) -> View
|
||||
): BaseAdapter() {
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val items: List<T> by lazy { itemFactory() }
|
||||
|
||||
override fun getView(index: Int, view: View?, viewGroup: ViewGroup?): View {
|
||||
return viewGroup!!.context.viewFactory(index, items, view)
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
override fun getItem(index: Int): T {
|
||||
return items[index]
|
||||
}
|
||||
|
||||
override fun getItemId(index: Int): Long {
|
||||
return (items[index] as Any).hashCode().toLong() + (index.toLong() * Int.MAX_VALUE)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package oupson.apngcreator.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import oupson.apngcreator.R
|
||||
|
||||
class ImageAdapter(private val context : Context, private val list : List<Uri>) : RecyclerView.Adapter<ImageAdapter.ImageHolder>() {
|
||||
val delay : ArrayList<String> = arrayListOf()
|
||||
val listeners : ArrayList<Listener> = arrayListOf()
|
||||
inner class Listener : TextWatcher {
|
||||
var position : Int = -1
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (position > -1)
|
||||
delay[position] = s?.toString() ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
inner class ImageHolder(view : View) : RecyclerView.ViewHolder(view) {
|
||||
val imageView = view.findViewById<ImageView?>(R.id.listImageView)
|
||||
val textDelay = view.findViewById<TextInputEditText?>(R.id.textDelay)
|
||||
val listener = Listener()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return ImageHolder(inflater.inflate(R.layout.list_image, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ImageHolder, position: Int) {
|
||||
if (delay.size <= position)
|
||||
delay.add("1000")
|
||||
holder.textDelay?.addTextChangedListener(holder.listener.also{
|
||||
it.position = position
|
||||
listeners.add(position, it)
|
||||
})
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val inputStream = context.contentResolver.openInputStream(list[position])
|
||||
val btm = BitmapFactory.decodeStream(inputStream, null, BitmapFactory.Options().apply {
|
||||
inPreferredConfig = Bitmap.Config.RGB_565
|
||||
})
|
||||
inputStream?.close()
|
||||
withContext(Dispatchers.Main) {
|
||||
holder.imageView?.setImageBitmap(btm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: ImageHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
holder.textDelay?.removeTextChangedListener(holder.listener)
|
||||
listeners.remove(holder.listener)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = list.count()
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package oupson.apngcreator
|
||||
package oupson.apngcreator.fragments
|
||||
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
@ -9,7 +9,9 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.fragment.app.Fragment
|
||||
import oupson.apng.ExperimentalApngDecoder
|
||||
import oupson.apng.ApngDecoder
|
||||
import oupson.apngcreator.BuildConfig
|
||||
import oupson.apngcreator.R
|
||||
import java.net.URL
|
||||
|
||||
class ApngDecoderFragment : Fragment() {
|
||||
|
@ -30,11 +32,11 @@ class ApngDecoderFragment : Fragment() {
|
|||
val imageView : ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view
|
||||
|
||||
if (context != null) {
|
||||
ExperimentalApngDecoder.decodeApngAsyncInto(
|
||||
ApngDecoder.decodeApngAsyncInto(
|
||||
this.context!!,
|
||||
URL("http://littlesvr.ca/apng/images/GenevaDrive.png"),
|
||||
URL("https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"),
|
||||
imageView,
|
||||
callback = object : ExperimentalApngDecoder.Callback {
|
||||
callback = object : ApngDecoder.Callback {
|
||||
override fun onSuccess(drawable: Drawable) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, "onSuccess()")
|
|
@ -1,4 +1,4 @@
|
|||
package oupson.apngcreator;
|
||||
package oupson.apngcreator.fragments;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -14,7 +14,8 @@ import androidx.fragment.app.Fragment;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import oupson.apng.ExperimentalApngDecoder;
|
||||
import oupson.apng.ApngDecoder;
|
||||
import oupson.apngcreator.R;
|
||||
|
||||
|
||||
public class JavaFragment extends Fragment {
|
||||
|
@ -45,7 +46,7 @@ public class JavaFragment extends Fragment {
|
|||
return Unit.INSTANCE;
|
||||
});
|
||||
*/
|
||||
ExperimentalApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ExperimentalApngDecoder.Callback() {
|
||||
ApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ApngDecoder.Callback() {
|
||||
@Override
|
||||
public void onSuccess(@NotNull Drawable drawable) {
|
||||
Log.i(TAG, "Success");
|
|
@ -1,4 +1,4 @@
|
|||
package oupson.apngcreator
|
||||
package oupson.apngcreator.fragments
|
||||
|
||||
|
||||
import android.os.Bundle
|
||||
|
@ -13,6 +13,8 @@ import androidx.fragment.app.Fragment
|
|||
import com.squareup.picasso.Picasso
|
||||
import oupson.apng.ApngAnimator
|
||||
import oupson.apng.loadApng
|
||||
import oupson.apngcreator.BuildConfig
|
||||
import oupson.apngcreator.R
|
||||
|
||||
|
||||
class KotlinFragment : Fragment() {
|
||||
|
@ -102,9 +104,9 @@ class KotlinFragment : Fragment() {
|
|||
if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, "onPause()")
|
||||
|
||||
// animator = null
|
||||
animator = null
|
||||
normalImageView?.setImageDrawable(null)
|
||||
// apngImageView?.setImageDrawable(null)
|
||||
apngImageView?.setImageDrawable(null)
|
||||
|
||||
playButton?.setOnClickListener(null)
|
||||
pauseButton?.setOnClickListener(null)
|
|
@ -0,0 +1,21 @@
|
|||
package oupson.apngcreator.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
|
||||
class SquareImageView : AppCompatImageView {
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyle
|
||||
)
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
val height : Int = measuredHeight
|
||||
setMeasuredDimension(height, height)
|
||||
}
|
||||
}
|
|
@ -4,6 +4,6 @@
|
|||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M8,5v14l11,-7z"/>
|
||||
</vector>
|
|
@ -1,6 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</LinearLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/imageRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="?actionBarSize" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:tint="#fff"
|
||||
app:backgroundTint="@color/secondary"
|
||||
app:layout_anchor="@id/creatorBottomAppBar"
|
||||
app:layout_anchorGravity="bottom|right|end"
|
||||
app:srcCompat="@drawable/ic_add_black_24dp" />
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/creatorBottomAppBar"
|
||||
style="@style/Widget.MaterialComponents.BottomAppBar.Colored"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -7,8 +7,7 @@
|
|||
android:id="@+id/drawer_layout">
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#323232">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/viewerImageView"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ApngDecoderFragment">
|
||||
tools:context=".fragments.ApngDecoderFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/apngDecoderImageView"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".JavaFragment">
|
||||
tools:context=".fragments.JavaFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/javaImageView"
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -20,7 +19,8 @@
|
|||
app:layout_constraintBottom_toTopOf="@id/NormalImageView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/description_viewer_imageView"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/NormalImageView"
|
||||
|
@ -33,7 +33,8 @@
|
|||
app:layout_constraintBottom_toTopOf="@+id/SpeedSeekBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ApngImageView" />
|
||||
app:layout_constraintTop_toBottomOf="@id/ApngImageView"
|
||||
android:contentDescription="@string/description_viewer_imageView" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/SpeedSeekBar"
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<oupson.apngcreator.views.SquareImageView
|
||||
android:id="@+id/listImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintBottom_toBottomOf="@id/textInputLayout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/textInputLayout"
|
||||
tools:srcCompat="@tools:sample/avatars" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="@string/delay"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/listImageView"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/textDelay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:inputType="number"
|
||||
android:text="1000"
|
||||
android:textColorHint="@color/colorPrimary"
|
||||
tools:ignore="HardcodedText" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_create_apng"
|
||||
android:icon="@drawable/ic_play_arrow_white_24dp"
|
||||
android:title="@string/create"
|
||||
app:showAsAction="ifRoom"
|
||||
app:iconTint="@color/control"/>
|
||||
</menu>
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_theme_light">false</bool>
|
||||
<bool name="nav_bar_light">true</bool>
|
||||
</resources>
|
|
@ -6,5 +6,5 @@
|
|||
|
||||
<color name="background">#000</color>
|
||||
|
||||
<color name="control">#fff</color>
|
||||
<color name="control">#000</color>
|
||||
</resources>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_theme_light">true</bool>
|
||||
<bool name="nav_bar_light">false</bool>
|
||||
</resources>
|
|
@ -1,10 +1,6 @@
|
|||
<resources>
|
||||
<string name="app_name">APNGCreator</string>
|
||||
|
||||
<string name="create_apng">Create APNG</string>
|
||||
<string name="java_activity">Java Activity</string>
|
||||
<string name="apng_imageView">An ImageView with an APNG</string>
|
||||
|
||||
<string name="description_viewer_imageView">Loaded Image</string>
|
||||
|
||||
<string name="title_playButton">Play</string>
|
||||
|
@ -18,7 +14,5 @@
|
|||
|
||||
<string name="open">open</string>
|
||||
<string name="close">close</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="delay">Delay (ms)</string>
|
||||
</resources>
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
<item name="android:textColorPrimary">@color/text</item>
|
||||
<item name="android:textColorSecondary">@color/text</item>
|
||||
|
||||
<item name="android:windowBackground">@color/background</item>
|
||||
|
||||
<item name="android:windowLightStatusBar" tools:targetApi="m">@bool/is_theme_light</item>
|
||||
<item name="android:navigationBarColor">@color/colorPrimary</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/nav_bar_light</item>
|
||||
</style>
|
||||
|
||||
<style name="AppShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<!--<external-path name="external_files" path="."/>-->
|
||||
<files-path path="images/" name="images" />
|
||||
</paths>
|
Loading…
Reference in New Issue