Как исправить таймер при прокрутке на RecyclerView на Android? - PullRequest
1 голос
/ 22 марта 2019

В моем приложении я хочу использовать несколько CountDownTimer, чтобы показать оставшееся время для предложений в RecyclerView.Я написал код ниже в Kotlin, но при прокрутке таймеры продолжают перезапускаться.Например, таймер запускается в 4:19 и во время прокрутки вместо показа 4:09 через 10 секунд по-прежнему показывает 4: 19.

Код активности:

class MainActivity : AppCompatActivity() {

    private lateinit var apisList: ApisList
    private lateinit var retrofit: Retrofit
    private lateinit var todayAdapter: AuctionsTodayAdapter
    private val todayModel: MutableList<Today> = mutableListOf()
    private lateinit var layoutManager: RecyclerView.LayoutManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //Initialize
        retrofit = ApiClient.instance
        apisList = retrofit.create(ApisList::class.java)
        todayAdapter = AuctionsTodayAdapter(themedContext, todayModel)
        layoutManager = LinearLayoutManager(themedContext)
        //RecyclerView
        main_list.setHasFixedSize(true)
        main_list.layoutManager = layoutManager
        main_list.adapter = todayAdapter

        if (isNetworkAvailable()) getData(1, 10)
    }

    private fun getData(page: Int, limit: Int) {
        main_loader.visibility = View.VISIBLE
        val call = apisList.getAuctionsToday(page, limit)
        call.let {
            it.enqueue(object : Callback<AuctionsTodayResponse> {
                override fun onFailure(call: Call<AuctionsTodayResponse>, t: Throwable) {
                    main_loader.visibility = View.GONE
                    Log.e("auctionsTodayList", t.message)
                }

                override fun onResponse(call: Call<AuctionsTodayResponse>, response: Response<AuctionsTodayResponse>) {
                    if (response.isSuccessful) {
                        response.body()?.let { itBody ->
                            main_loader.visibility = View.GONE
                            if (itBody.toString().isNotEmpty()) {
                                todayModel.clear()
                                todayModel.addAll(itBody.res.today)
                                todayAdapter.notifyDataSetChanged()
                            }
                        }
                    }
                }
            })
        }
    }
}

Код адаптера:

class AuctionsTodayAdapter(val context: Context, val model: MutableList<Today>) :
    RecyclerView.Adapter<AuctionsTodayAdapter.MyHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.row_main_list, parent, false)
        val holder = MyHolder(view)

        //holder.setIsRecyclable(false)

        return holder
    }

    override fun getItemCount(): Int {
        return model.size
    }

    override fun onBindViewHolder(holder: MyHolder, position: Int) {
        val modelUse = model[position]
        holder.setData(modelUse)



        if (holder.newCountDownTimer != null) {
            holder.newCountDownTimer!!.cancel()
        }
        var timer = modelUse.calculateEnd

        timer = timer * 1000

        holder.newCountDownTimer = object : CountDownTimer(timer, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                var seconds = (millisUntilFinished / 1000).toInt()
                val hours = seconds / (60 * 60)
                val tempMint = seconds - hours * 60 * 60
                val minutes = tempMint / 60
                seconds = tempMint - minutes * 60
                holder.rowMain_timer.rowMain_timer.text =
                    String.format("%02d", hours) + ":" + String.format(
                        "%02d",
                        minutes
                    ) + ":" + String.format("%02d", seconds)
            }

            override fun onFinish() {
                holder.rowMain_timer.text = "00:00:00"
            }
        }.start()

    }

    inner class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        var newCountDownTimer: CountDownTimer? = null

        lateinit var rowMain_timer: TextView

        init {
            rowMain_timer = itemView.findViewById(R.id.rowMain_timer)
        }

        fun setData(model: Today) {
            model.image.let {
                Glide.with(context)
                    .load(Constants.MAIN_BASE_URL + it)
                    .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
                    .into(itemView.rowMain_img)
            }
            model.title.let { itemView.rowMain_title.text = it }
        }
}

Как это исправить?

Ответы [ 2 ]

1 голос
/ 23 марта 2019

Если вы хотите использовать Timer в своем адаптере, не следует использовать это в onBindViewHolder.
Потому что onBindViewHolder звоните каждый раз при прокрутке пунктов
Вы должны использовать Timer в конструкторе вашего адаптера, а каждую секунду notifyDataSetChanged в вашем адаптере.
Не волнуйтесь, это неплохая структура и не утечка памяти . Вы можете проверить это в профиль вкладка!

class AuctionsTodayAdapter(val context: Context, val model: MutableList<Today>) :
    RecyclerView.Adapter<AuctionsTodayAdapter.MyHolder>() {

    private var newData: Long = 0

    init {
        for (items in model) {
            items.end.let {
                newData = items.end.toLong()
            }
        }
        //set the timer which will refresh the data every 1 second.
        object : CountDownTimer(newData, 1000) {
            override fun onFinish() {
                notifyDataSetChanged()
            }

            override fun onTick(p0: Long) {
                var i = 0
                val dataLength = model.size
                while (i < dataLength) {
                    val item = model[i]
                    item.end -= 1000
                    i++
                }
                notifyDataSetChanged()
            }
        }.start()
    }

    override fun onBindViewHolder(holder: MyHolder, position: Int) {
        var modelUse = model[position]
        //Img
        modelUse.image.let {
            Glide.with(context)
                .load(Constants.MAIN_BASE_URL + it)
                .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
                .into(holder.rowMAIN_img)
        }
        //Timer
        modelUse.end.let {
            if (modelUse.calculateEnd > 0) {
                holder.rowMAIN_timer.text = getDurationBreakdown(modelUse.end.toLong())
            } else {
                holder.rowMain_timer.text = "Finished"
            }
        }
    }

    private fun millToMins(milliSec: Long): String {
        var seconds = (milliSec / 1000).toInt()
        val hours = seconds / (60 * 60)
        val tempMint = seconds - hours * 60 * 60
        val minutes = tempMint / 60
        seconds = tempMint - minutes * 60

        return String.format("%02d", hours) + ":" + String.format(
            "%02d",
            minutes
        ) + ":" + String.format("%02d", seconds)
    }
}

Надеюсь, я смогу вам помочь.

1 голос
/ 22 марта 2019

У вас есть CountDownTimer для каждого ViewHolder. Каждый раз, когда вы прокручиваете, recyclerView создает новый CountDownTimer. Итак, если я правильно понял ваш вопрос, у вас должен быть один CountDownTimer для всего адаптера. Сделайте это локальным полем класса адаптера.

...