Work in creator activity and improvements in app

Working on apng decoder
This commit is contained in:
oupson 2020-01-25 15:16:36 +01:00
parent 4863dfb2e8
commit acd55f6d5a
30 changed files with 432 additions and 271 deletions

View File

@ -17,6 +17,8 @@ import oupson.apng.utils.Utils.Companion.to4Bytes
import java.io.File import java.io.File
import java.util.zip.CRC32 import java.util.zip.CRC32
// TODO CREATE A BETTER CLASS
/** /**
* Create an APNG file * Create an APNG file
*/ */

View File

@ -92,7 +92,7 @@ fun ImageView.loadApng(@RawRes res : Int, speed : Float? = null, apngAnimatorOpt
/** /**
* Class to play APNG * 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?) { class ApngAnimator(private val context: Context?) {
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@ -112,6 +112,7 @@ class ApngAnimator(private val context: Context?) {
} }
private var imageView: ImageView? = null private var imageView: ImageView? = null
@Suppress("MemberVisibilityCanBePrivate")
var anim: CustomAnimationDrawable? = null var anim: CustomAnimationDrawable? = null
private var activeAnimation: CustomAnimationDrawable? = null private var activeAnimation: CustomAnimationDrawable? = null

View File

@ -24,16 +24,14 @@ import java.io.InputStream
import java.net.URL import java.net.URL
import java.util.zip.CRC32 import java.util.zip.CRC32
// TODO DOC CODE class ApngDecoder {
class ExperimentalApngDecoder {
interface Callback { interface Callback {
fun onSuccess(drawable : Drawable) fun onSuccess(drawable : Drawable)
fun onError(error : java.lang.Exception) fun onError(error : java.lang.Exception)
} }
companion object { companion object {
// TODO Change TAG private const val TAG = "ApngDecoder"
private const val TAG = "ExperimentalApngDecoder"
private val clearPaint : Paint by lazy { private val clearPaint : Paint by lazy {
Paint().apply { Paint().apply {
@ -378,7 +376,7 @@ class ExperimentalApngDecoder {
* @param file File to decode. * @param file File to decode.
* @param imageView Image View. * @param imageView Image View.
* @param speed Optional parameter. * @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error * @param callback [ApngDecoder.Callback] to handle success and error
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -406,7 +404,7 @@ class ExperimentalApngDecoder {
* @param uri Uri to load * @param uri Uri to load
* @param imageView Image View. * @param imageView Image View.
* @param speed Optional parameter. * @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error * @param callback [ApngDecoder.Callback] to handle success and error
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -435,7 +433,7 @@ class ExperimentalApngDecoder {
* @param res Raw resource to load * @param res Raw resource to load
* @param imageView Image View. * @param imageView Image View.
* @param speed Optional parameter. * @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error * @param callback [ApngDecoder.Callback] to handle success and error
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -464,7 +462,7 @@ class ExperimentalApngDecoder {
* @param url URL to load * @param url URL to load
* @param imageView Image View. * @param imageView Image View.
* @param speed Optional parameter. * @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error * @param callback [ApngDecoder.Callback] to handle success and error
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
@ -492,7 +490,7 @@ class ExperimentalApngDecoder {
* @param string URL to load * @param string URL to load
* @param imageView Image View. * @param imageView Image View.
* @param speed Optional parameter. * @param speed Optional parameter.
* @param callback [ExperimentalApngDecoder.Callback] to handle success and error * @param callback [ApngDecoder.Callback] to handle success and error
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic

View File

@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.IOException
import java.net.URL import java.net.URL
class Loader { class Loader {
@ -15,7 +16,7 @@ class Loader {
* @param url Url of the file to download * @param url Url of the file to download
* @return [ByteArray] of the file * @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) = suspend fun load(context: Context, url: URL) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val currentDir = context.filesDir val currentDir = context.filesDir

View File

@ -16,7 +16,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".MainActivity"> <activity android:name=".activities.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
@ -24,7 +24,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".ViewerActivity" android:name=".activities.ViewerActivity"
android:label="APNG Viewer"> android:label="APNG Viewer">
<intent-filter tools:ignore="AppLinkUrlError"> <intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@ -36,8 +36,23 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".CreatorActivity" android:name=".activities.CreatorActivity"
android:exported="true"/> 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> </application>
</manifest> </manifest>

View File

@ -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()
}
}
}
}
}

View File

@ -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
}
}

View File

@ -1,4 +1,4 @@
package oupson.apngcreator package oupson.apngcreator.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
@ -13,10 +13,15 @@ import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.shape.ShapePath import com.google.android.material.shape.ShapePath
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.startActivity 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() { class MainActivity : AppCompatActivity() {
companion object { companion object {
@Suppress("unused")
private const val TAG = "MainActivity" private const val TAG = "MainActivity"
} }
@ -34,7 +39,10 @@ class MainActivity : AppCompatActivity() {
startActivity<CreatorActivity>() 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) drawer_layout.addDrawerListener(drawerToggle)
drawerToggle.syncState() drawerToggle.syncState()
@ -45,7 +53,10 @@ class MainActivity : AppCompatActivity() {
R.id.menu_kotlin_fragment -> { R.id.menu_kotlin_fragment -> {
if (selected != 0) { if (selected != 0) {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, KotlinFragment.newInstance()) replace(
R.id.fragment_container,
KotlinFragment.newInstance()
)
addToBackStack(null) addToBackStack(null)
}.commit() }.commit()
selected = 0 selected = 0
@ -54,7 +65,10 @@ class MainActivity : AppCompatActivity() {
R.id.menu_java_fragment -> { R.id.menu_java_fragment -> {
if (selected != 1) { if (selected != 1) {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, JavaFragment()) replace(
R.id.fragment_container,
JavaFragment()
)
addToBackStack(null) addToBackStack(null)
}.commit() }.commit()
selected = 1 selected = 1
@ -63,7 +77,10 @@ class MainActivity : AppCompatActivity() {
R.id.menu_apng_decoder_fragment -> { R.id.menu_apng_decoder_fragment -> {
if (selected != 2) { if (selected != 2) {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, ApngDecoderFragment.newInstance()) replace(
R.id.fragment_container,
ApngDecoderFragment.newInstance()
)
addToBackStack(null) addToBackStack(null)
}.commit() }.commit()
selected = 2 selected = 2
@ -80,21 +97,29 @@ class MainActivity : AppCompatActivity() {
when(intent.getStringExtra("fragment")) { when(intent.getStringExtra("fragment")) {
"kotlin" -> { "kotlin" -> {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment") add(
R.id.fragment_container,
KotlinFragment.newInstance(), "KotlinFragment")
}.commit() }.commit()
navigationView.setCheckedItem(R.id.menu_kotlin_fragment) navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
selected = 0 selected = 0
} }
"java" -> { "java" -> {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, JavaFragment()) add(
R.id.fragment_container,
JavaFragment()
)
}.commit() }.commit()
navigationView.setCheckedItem(R.id.menu_java_fragment) navigationView.setCheckedItem(R.id.menu_java_fragment)
selected = 1 selected = 1
} }
"apng_decoder" -> { "apng_decoder" -> {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, ApngDecoderFragment.newInstance()) add(
R.id.fragment_container,
ApngDecoderFragment.newInstance()
)
}.commit() }.commit()
navigationView.setCheckedItem(R.id.menu_apng_decoder_fragment) navigationView.setCheckedItem(R.id.menu_apng_decoder_fragment)
selected = 2 selected = 2
@ -102,7 +127,9 @@ class MainActivity : AppCompatActivity() {
} }
} else { } else {
supportFragmentManager.beginTransaction().apply { supportFragmentManager.beginTransaction().apply {
add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment") add(
R.id.fragment_container,
KotlinFragment.newInstance(), "KotlinFragment")
}.commit() }.commit()
navigationView.setCheckedItem(R.id.menu_kotlin_fragment) navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
} }

View File

@ -1,4 +1,4 @@
package oupson.apngcreator package oupson.apngcreator.activities
import android.Manifest import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -11,8 +11,9 @@ import kotlinx.android.synthetic.main.activity_viewer.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import oupson.apng.ApngDecoder
import oupson.apng.CustomAnimationDrawable import oupson.apng.CustomAnimationDrawable
import oupson.apng.ExperimentalApngDecoder import oupson.apngcreator.R
class ViewerActivity : AppCompatActivity() { class ViewerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -43,7 +44,7 @@ class ViewerActivity : AppCompatActivity() {
val uri = intent.data ?: return val uri = intent.data ?: return
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
//val animator = imageView.loadApng(uri, null) //val animator = imageView.loadApng(uri, null)
val drawable = ExperimentalApngDecoder.decodeApng(this@ViewerActivity, uri) val drawable = ApngDecoder.decodeApng(this@ViewerActivity, uri)
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
viewerImageView.setImageDrawable(drawable) viewerImageView.setImageDrawable(drawable)
if (drawable is CustomAnimationDrawable) if (drawable is CustomAnimationDrawable)

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -1,4 +1,4 @@
package oupson.apngcreator package oupson.apngcreator.fragments
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -9,7 +9,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.fragment.app.Fragment 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 import java.net.URL
class ApngDecoderFragment : Fragment() { class ApngDecoderFragment : Fragment() {
@ -30,11 +32,11 @@ class ApngDecoderFragment : Fragment() {
val imageView : ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view val imageView : ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view
if (context != null) { if (context != null) {
ExperimentalApngDecoder.decodeApngAsyncInto( ApngDecoder.decodeApngAsyncInto(
this.context!!, this.context!!,
URL("http://littlesvr.ca/apng/images/GenevaDrive.png"), URL("https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"),
imageView, imageView,
callback = object : ExperimentalApngDecoder.Callback { callback = object : ApngDecoder.Callback {
override fun onSuccess(drawable: Drawable) { override fun onSuccess(drawable: Drawable) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.i(TAG, "onSuccess()") Log.i(TAG, "onSuccess()")

View File

@ -1,4 +1,4 @@
package oupson.apngcreator; package oupson.apngcreator.fragments;
import android.content.Context; import android.content.Context;
@ -14,7 +14,8 @@ import androidx.fragment.app.Fragment;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import oupson.apng.ExperimentalApngDecoder; import oupson.apng.ApngDecoder;
import oupson.apngcreator.R;
public class JavaFragment extends Fragment { public class JavaFragment extends Fragment {
@ -45,7 +46,7 @@ public class JavaFragment extends Fragment {
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
*/ */
ExperimentalApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ExperimentalApngDecoder.Callback() { ApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ApngDecoder.Callback() {
@Override @Override
public void onSuccess(@NotNull Drawable drawable) { public void onSuccess(@NotNull Drawable drawable) {
Log.i(TAG, "Success"); Log.i(TAG, "Success");

View File

@ -1,4 +1,4 @@
package oupson.apngcreator package oupson.apngcreator.fragments
import android.os.Bundle import android.os.Bundle
@ -13,6 +13,8 @@ import androidx.fragment.app.Fragment
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import oupson.apng.ApngAnimator import oupson.apng.ApngAnimator
import oupson.apng.loadApng import oupson.apng.loadApng
import oupson.apngcreator.BuildConfig
import oupson.apngcreator.R
class KotlinFragment : Fragment() { class KotlinFragment : Fragment() {
@ -102,9 +104,9 @@ class KotlinFragment : Fragment() {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.i(TAG, "onPause()") Log.i(TAG, "onPause()")
// animator = null animator = null
normalImageView?.setImageDrawable(null) normalImageView?.setImageDrawable(null)
// apngImageView?.setImageDrawable(null) apngImageView?.setImageDrawable(null)
playButton?.setOnClickListener(null) playButton?.setOnClickListener(null)
pauseButton?.setOnClickListener(null) pauseButton?.setOnClickListener(null)

View File

@ -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)
}
}

View File

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#000" android:fillColor="#fff"
android:pathData="M8,5v14l11,-7z"/> android:pathData="M8,5v14l11,-7z"/>
</vector> </vector>

View File

@ -1,6 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?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:orientation="vertical" android:layout_width="match_parent"
android:layout_height="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>

View File

@ -7,8 +7,7 @@
android:id="@+id/drawer_layout"> android:id="@+id/drawer_layout">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="@color/background">
<FrameLayout <FrameLayout
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View File

@ -2,8 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="#323232">
<ImageView <ImageView
android:id="@+id/viewerImageView" android:id="@+id/viewerImageView"

View File

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ApngDecoderFragment"> tools:context=".fragments.ApngDecoderFragment">
<ImageView <ImageView
android:id="@+id/apngDecoderImageView" android:id="@+id/apngDecoderImageView"

View File

@ -4,7 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".JavaFragment"> tools:context=".fragments.JavaFragment">
<ImageView <ImageView
android:id="@+id/javaImageView" android:id="@+id/javaImageView"

View File

@ -3,8 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="@color/background">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -20,7 +19,8 @@
app:layout_constraintBottom_toTopOf="@id/NormalImageView" app:layout_constraintBottom_toTopOf="@id/NormalImageView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
android:contentDescription="@string/description_viewer_imageView"/>
<ImageView <ImageView
android:id="@+id/NormalImageView" android:id="@+id/NormalImageView"
@ -33,7 +33,8 @@
app:layout_constraintBottom_toTopOf="@+id/SpeedSeekBar" app:layout_constraintBottom_toTopOf="@+id/SpeedSeekBar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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 <SeekBar
android:id="@+id/SpeedSeekBar" android:id="@+id/SpeedSeekBar"

View File

@ -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>

View File

@ -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>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<bool name="is_theme_light">false</bool> <bool name="is_theme_light">false</bool>
<bool name="nav_bar_light">true</bool>
</resources> </resources>

View File

@ -6,5 +6,5 @@
<color name="background">#000</color> <color name="background">#000</color>
<color name="control">#fff</color> <color name="control">#000</color>
</resources> </resources>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<bool name="is_theme_light">true</bool> <bool name="is_theme_light">true</bool>
<bool name="nav_bar_light">false</bool>
</resources> </resources>

View File

@ -1,10 +1,6 @@
<resources> <resources>
<string name="app_name">APNGCreator</string> <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="description_viewer_imageView">Loaded Image</string>
<string name="title_playButton">Play</string> <string name="title_playButton">Play</string>
@ -18,7 +14,5 @@
<string name="open">open</string> <string name="open">open</string>
<string name="close">close</string> <string name="close">close</string>
<string name="delay">Delay (ms)</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources> </resources>

View File

@ -9,8 +9,11 @@
<item name="android:textColorPrimary">@color/text</item> <item name="android:textColorPrimary">@color/text</item>
<item name="android:textColorSecondary">@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:windowLightStatusBar" tools:targetApi="m">@bool/is_theme_light</item>
<item name="android:navigationBarColor">@color/colorPrimary</item> <item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/nav_bar_light</item>
</style> </style>
<style name="AppShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent"> <style name="AppShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">

View File

@ -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>