package jp.juggler.apng.sample import android.Manifest import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.os.SystemClock import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.BaseAdapter import android.widget.ListView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import jp.juggler.apng.ApngFrames import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext class ActList : AppCompatActivity(), CoroutineScope { companion object { const val TAG = "ActList" const val PERMISSION_REQUEST_CODE_STORAGE = 1 } class ListItem(val id: Int, val caption: String) private lateinit var listView: ListView private lateinit var listAdapter: MyAdapter private var timeAnimationStart: Long = 0L private lateinit var activityJob: Job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + activityJob override fun onCreate(savedInstanceState: Bundle?) { activityJob = Job() super.onCreate(savedInstanceState) setContentView(R.layout.act_list) this.listView = findViewById(R.id.listView) listAdapter = MyAdapter() listView.adapter = listAdapter listView.onItemClickListener = listAdapter timeAnimationStart = SystemClock.elapsedRealtime() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Assume thisActivity is the current activity if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission( this, Manifest.permission.WRITE_EXTERNAL_STORAGE ) ) { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE_STORAGE ) } } load() } override fun onDestroy() { super.onDestroy() activityJob.cancel() } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) { when (requestCode) { PERMISSION_REQUEST_CODE_STORAGE -> { // If request is cancelled, the result arrays are empty. if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the // contacts-related task you need to do. } else { // permission denied, boo! Disable the // functionality that depends on this permission. } return } // other 'case' lines to check for other // permissions this app might request } } private fun load() = launch { val list = withContext(Dispatchers.IO) { // RawリソースのIDと名前の一覧 R.raw::class.java.fields .mapNotNull { it.get(null) as? Int } .map { id -> ListItem( id, resources.getResourceName(id) .replaceFirst(""".+/""".toRegex(), "") ) } .toMutableList() .apply { sortBy { it.caption } } } listAdapter.list.addAll(list) listAdapter.notifyDataSetChanged() } inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener { val list = ArrayList() override fun getCount(): Int { return list.size } override fun getItem(position: Int): Any { return list[position] } override fun getItemId(position: Int): Long { return list[position].id.toLong() } override fun getView( position: Int, viewArg: View?, parent: ViewGroup? ): View { val view: View val holder: MyViewHolder if (viewArg == null) { view = layoutInflater.inflate(R.layout.lv_item, parent, false) holder = MyViewHolder(view, this@ActList) view.tag = holder } else { view = viewArg holder = view.tag as MyViewHolder } holder.bind(list[position]) return view } override fun onItemClick( parent: AdapterView<*>?, view: View?, position: Int, id: Long ) { val item = list[position] ActViewer.open(this@ActList, item.id, item.caption) } } inner class MyViewHolder( viewRoot: View, _activity: ActList ) { private val tvCaption: TextView = viewRoot.findViewById(R.id.tvCaption) private val apngView: ApngView = viewRoot.findViewById(R.id.apngView) init { apngView.timeAnimationStart = _activity.timeAnimationStart } private var lastId: Int = 0 private var lastJob: Job? = null fun bind(listItem: ListItem) { tvCaption.text = listItem.caption val resId = listItem.id if (lastId != resId) { lastId = resId apngView.apngFrames?.dispose() apngView.apngFrames = null launch { var apngFrames: ApngFrames? = null try { lastJob?.cancelAndJoin() val job = async(Dispatchers.IO) { try { ApngFrames.parse(128) { resources?.openRawResource(resId) } } catch (ex: Throwable) { ex.printStackTrace() null } } lastJob = job apngFrames = job.await() if (apngFrames != null && lastId == resId) { apngView.apngFrames = apngFrames apngFrames = null } } catch (ex: Throwable) { ex.printStackTrace() Log.e(TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}") } finally { apngFrames?.dispose() } } } } } }