expand all items in a expandable recyclerview using single button press - android

this is the code for expanding/collapsing an item. how can I Expand all items in a recycler view with a single button click?
private fun expandParentRow(position: Int){ \\to expand an item
val currentBoardingRow = list[position]
val services = currentBoardingRow.gameDetes
currentBoardingRow.isExpanded = true
var nextPosition = position
if(currentBoardingRow.type == Constants.PARENT) {
services?.forEach { service ->
val parentModel = IndividualReport()
parentModel.type = Constants.CHILD
val subList: ArrayList<GameDete> = ArrayList()
subList.add(service)
parentModel.gameDetes = subList
list.add(++nextPosition, parentModel)
}
notifyDataSetChanged()
}
}
private fun collapseParentRow(position: Int){ \\ to collapse an item
val currentBoardingRow = list[position]
val services = currentBoardingRow.gameDetes
list[position].isExpanded = false
if(list[position].type==Constants.PARENT){
services?.forEach { _ ->
list.removeAt(position + 1)
}
notifyDataSetChanged()
}
}

Just simply do a forEach or a for loop to iterate all items of the recycle view, set the .isExpanded to true and finally call notifyDataSetChanged().
For example:
fun expandAllItems() {
val currentList = list.map { it.copy() }
currentList.forEachIndexed { index, item ->
item.isExpanded = true
val services = item.gameDetes
if(item.type == Constants.PARENT) {
services?.forEach { service ->
val parentModel = IndividualReport()
parentModel.type = Constants.CHILD
val subList: ArrayList<GameDete> = ArrayList()
subList.add(service)
parentModel.gameDetes = subList
list.add(++index, parentModel)
}
}
}
notifyDataSetChanged()
}
This should do the job.

you can expand with animation (new animate() api) with this code block:
private fun RecyclerView.showAnimation(
duration: Long,
startFrom: Int = 0,
itemsDelay: Long = 50,
fromAlpha: Float = 1.0f,
toAlpha: Float = 1.0f,
fromScaleY: Float = 1.0f,
toScaleY: Float = 1.0f,
onAnimationStart: () -> Unit = {},
onAnimationEnd: () -> Unit = {}
) {
viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
for (i in startFrom until childCount) {
val v: View = getChildAt(i)
v.alpha = fromAlpha
v.scaleY = fromScaleY
v.pivotY = 0f
v.animate().apply {
scaleY(toScaleY)
alpha(toAlpha)
setDuration(duration)
startDelay = (i * itemsDelay)
withStartAction(onAnimationStart)
withEndAction(onAnimationEnd)
start()
}
}
return true
}
})
}

Related

Combined Chart mpAndroidChart

I am currently creating a chart to display statistics.
The problem is that on the combinedChart I can't display the lineDataChart
The expected result is this
The second image is the superposition of a barChart and a lineChart but the problem is i can't use properly the scroll mode because it will only scroll on one graph.
Here is the code for the first graph
class GraphCombinedChart (context: Context) : CombinedChart(context, null, 0) {
private var chart: CombinedChart? = null
private fun dtoToBarEntry(floats: List<Float>?) : ArrayList<BarEntry> {
return ArrayList<BarEntry>().apply {
floats?.forEachIndexed { index, fl ->
add(BarEntry((index + 1).toFloat() , fl))
}
}
}
private fun dtoToEntry(floats: List<Float>?) : ArrayList<Entry> {
return ArrayList<Entry>().apply {
floats?.forEachIndexed { index, fl ->
add(Entry((index + 1).toFloat() , fl))
}
}
}
fun setupChart(
values: List<PlayerGraphStatModel>?,
landScapeMode: Boolean = false
) {
val indexPerformanceList = dtoToEntry(values?.map { it.timePlayed.toFloat() }?.plus(values.map { it.timePlayed.toFloat() }))
val timePlayedList = dtoToBarEntry( values?.map { it.indexPerf }?.plus(values.map { it.indexPerf }))
val entryData = LineData(LineDataSet(indexPerformanceList, "").apply {
color = Color.White.hashCode()
setDrawValues(true)
})
val barEntryData = BarData(BarDataSet(timePlayedList, "").apply {
color = Color.AiaRed.hashCode()
setDrawValues(false)
})
val combinedData = CombinedData()
combinedData.setData(barEntryData)
combinedData.setData(entryData)
chart = this
formatChart(combinedData, landScapeMode)
configureXAxis()
configureYAxis()
chart?.renderer = BarChartRenderer(chart, chart?.animator, chart?.viewPortHandler)
}
private fun formatChart(combinedData: CombinedData, landScapeMode: Boolean = false) {
val barWidth = 0.75f
chart?.drawOrder = arrayOf(
DrawOrder.LINE,
DrawOrder.BAR,
DrawOrder.LINE,
)
chart?.data = combinedData
chart?.description?.isEnabled = false
chart?.legend?.isEnabled = false
chart?.barData?.barWidth = barWidth
chart?.animateY(1000)
chart?.isDoubleTapToZoomEnabled = false
chart?.setScaleEnabled(false)
chart?.isHorizontalScrollBarEnabled = true
// barChart?.setVisibleXRangeMaximum(8f)
// barChart?.moveViewToX(0f)
if (!landScapeMode) {
chart?.setVisibleXRangeMaximum(8f)
chart?.moveViewToX(11f)
}
chart?.resetViewPortOffsets()
chart?.resetZoom()
chart?.notifyDataSetChanged()
}
private fun configureXAxis() {
chart?.xAxis?.apply {
setDrawGridLines(false)
setDrawAxisLine(false)
setDrawLabels(true)
position = XAxis.XAxisPosition.BOTTOM
valueFormatter = MyXAxisFormatter()
granularity = 1f
labelRotationAngle = +0f
textColor = Color.White.hashCode()
textSize = 12f
textAlignment = TEXT_ALIGNMENT_CENTER
axisMinimum = data.xMin - 0.75f
axisMaximum = data.xMax + 0.75f
disableScroll()
}
}
private fun configureYAxis() {
chart?.axisRight?.apply {
setDrawGridLines(false)
legend?.isEnabled = true
isEnabled = false
}
chart?.axisLeft?.apply {
setAxisMinValue(0f)
setAxisMaxValue(100f)
valueFormatter = MyLeftAxisFormatter()
setDrawGridLines(true)
setDrawAxisLine(true)
textColor = Color.White.hashCode()
textSize = 14f
}
}
inner class MyXAxisFormatter : IndexAxisValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return "${value.toInt()}"
}
}
inner class MyLeftAxisFormatter : IndexAxisValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return "${value.toInt()}"
}
}
}
I have checked and the data is not empty.
Thank you for your help!

How to deal with recyclerview showing empty row

I am building a movies app that uses "The Movie DB" API.
My app currently displaying a recyclerview filled with movies.
The problem is that some movies don't have the values I need to display the movie and it looks like this:
https://ibb.co/5FBCP5v
My question is how can I deal with it the correct way ?
Should I use recyclerview with 2 view types ?
Is there any way I can prevent the row to inflate empty data in the first place ?
This is the recyclerView Adapter:
class MainRecyclerViewAdapter(var moviesList: List<Movie>,var listener : MainRecyclerViewAdapter.MovieListener) :
RecyclerView.Adapter<MainRecyclerViewAdapter.MovieHolder>() {
inner class MovieHolder(var itemView : View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
fun bindViews(movie : Movie){
val imageBaseUrl = "https://image.tmdb.org/t/p/w185"
val movieTitle = itemView.findViewById<TextView>(R.id.movieTitle)
val movieYear = itemView.findViewById<TextView>(R.id.movieYear)
val movieImage = itemView.findViewById<ImageView>(R.id.movieImage)
//Rating Bar
val ratingBar = itemView.findViewById<RatingBar>(R.id.ratingBar)
val ratingInNumbers = itemView.findViewById<TextView>(R.id.ratingInNumbers)
val peopleRated = itemView.findViewById<TextView>(R.id.peopleRated)
itemView.setOnClickListener(this)
if (movie.movieName != "null" && movie.movieVoteCount != "null" && movie.movieVoteCount != "null") {
// Movie Name
movieTitle.text = movie.movieName
// Movie Year
val inputFormat = SimpleDateFormat("yyyy", Locale.getDefault())
if (movie.movieReleasedDate != null && movie.movieReleasedDate.isNotEmpty()){
val date = inputFormat.parse(movie.movieReleasedDate)
val movieYearString: String = inputFormat.format(date?.time)
movieYear.text = movieYearString
}
// Image
Glide.with(itemView.context).load(imageBaseUrl + movie.moviePoster).into(movieImage)
Log.e("ImageuRL", "URL: " + movie.moviePoster)
// Rating Bar
if (movie.movieVotesAverge != null){
val ratingDivider = movie.movieVotesAverge.toDouble() / 2
ratingBar.rating = ratingDivider.toFloat()
ratingInNumbers.text = ratingDivider.toString()
}
// Rating
if (movie.movieVoteCount != null){
movie.movieVoteCount = when {
abs(movie.movieVoteCount.toDouble() / 1000000) > 1 -> {
(movie.movieVoteCount.toDouble() / 1000000).toString().toString() + "m"
}
abs(movie.movieVoteCount.toDouble() / 1000) > 1 -> {
(movie.movieVoteCount.toDouble() / 1000).toString().toString() + "k"
}
else -> {
movie.movieVoteCount.toInt().toString()
}
}
}
val ratedPeopleString = "(${movie.movieVoteCount})"
peopleRated.text = ratedPeopleString
}
}
override fun onClick(view: View?) {
val position = adapterPosition
listener.onMovieClickedListener(moviesList[position])
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder {
val rootView = LayoutInflater.from(parent.context).inflate(R.layout.recycler_view_row_change,parent,false)
return MovieHolder(rootView)
}
override fun onBindViewHolder(holder: MovieHolder, position: Int) {
holder.bindViews(moviesList[position])
}
override fun getItemCount(): Int {
return moviesList.size
}
fun changeCurrentList(newMovieList : List<Movie>) {
this.moviesList = newMovieList
notifyDataSetChanged()
}
interface MovieListener{
fun onMovieClickedListener(movie:Movie)
}
}
Thank you!
Filter your items before passing it to recycler view in your activity or your fragment like below
moviesList.filter{ movie ->
movie != null &&
!movie.movieName.isNullOrEmpty() &&
!movie.movieVoteCount.isNullOrEmpty() &&
!movie.movieVoteCount.isNullOrEmpty() &&
!movie.moviePoster.isNullOrEmpty()
}

Retaining expandable recyclerview's layout while retrieving and updating data from API

I currently have a fully functioning expandable RecyclerView (with two distinct views: one for the header and the other for the contents of each header). The contents of each card under a header is retrieved from an API call to my back end.
Today, I managed to implement swipe to refresh functionality but the issue is that when I do the refresh, the groups all collapse. From a UI/UX perspective, this isn't very appealing and adds extra steps for the user. As such, what I'm looking for is a way to perform the swipe to refresh without causing the groups to collapse (i.e. the contents of the card get updated but the cards remain at the same position as when the refresh is called). I've looked at using onSaveInstanceState and onRestoreInstanceState as detailed in this answer here and here but to no avail.
My code for the adapter is as follows:
class BusStopsServicesArrivalRVAdapter :
RecyclerView.Adapter<RecyclerView.ViewHolder>(), BusStopNoSectionHeaderViewHolder.HeaderViewHolderCallback {
private val SERVICE_TYPE = 1
private val STOP_TYPE = 2
private var busStopServiceList: ArrayList<BusStopService>? = null
private var busStopList: ArrayList<String>? = null
private var busArrivalViewTypes: SparseArray<BusArrivalViewType>? = null
private var headerExpandTracker: SparseIntArray? = null
lateinit var context: Context
var nineOneCount = 0
var threeOneCount = 0
var threeNineCount = 0
var fiveNineCount = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View
when (viewType) {
SERVICE_TYPE -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.card_bus_stop_service, parent, false)
return BusStopServiceHolder(view)
}
STOP_TYPE -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.card_bus_stop_section, parent, false)
return BusStopNoSectionHeaderViewHolder(view, this)
}
else -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.card_bus_stop_service, parent, false)
return BusStopServiceHolder(view)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemViewType = getItemViewType(position)
val viewType = busArrivalViewTypes!!.get(position)
if (itemViewType == SERVICE_TYPE) {
bindBusStopServiceViewHolder(holder, viewType)
} else {
bindHeaderViewHolder(holder, position, viewType)
}
}
private fun bindHeaderViewHolder(holder: RecyclerView.ViewHolder, position: Int, busArrivalViewType: BusArrivalViewType) {
val dataIndex = busArrivalViewType.dataIndex
val headerViewHolder = holder as BusStopNoSectionHeaderViewHolder
when(busStopList!![dataIndex]) {
"D" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_D)
}
"C" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_C)
}
"B" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_B)
}
"A" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_A)
}
}
// headerViewHolder.busStopNoTextView.text = busStopList!![dataIndex]
if (isExpanded(position)) {
headerViewHolder.busStopNoTextView
.setCompoundDrawablesWithIntrinsicBounds(null, null, headerViewHolder.arrowUp, null)
} else {
headerViewHolder.busStopNoTextView
.setCompoundDrawablesWithIntrinsicBounds(null, null, headerViewHolder.arrowDown, null)
}
}
private fun bindBusStopServiceViewHolder(holder: RecyclerView.ViewHolder, busArrivalViewType: BusArrivalViewType) {
val dataIndex = busArrivalViewType.dataIndex
(holder as BusStopServiceHolder).bindData(busStopServiceList!![dataIndex], context)
}
override fun getItemCount(): Int {
var count = 0
if (busStopList != null && busStopServiceList != null) {
busArrivalViewTypes!!.clear()
var collapsedCount = 0
for (i in busStopList!!.indices) {
busArrivalViewTypes!!.put(count, BusArrivalViewType(i, STOP_TYPE))
count += 1
val userType = busStopList!![i]
val childCount = getChildCount(userType)
if (headerExpandTracker!!.get(i) != 0) {
// Expanded State
for (j in 0 until childCount) {
busArrivalViewTypes!!.put(count, BusArrivalViewType(count - (i + 1) + collapsedCount, SERVICE_TYPE))
count += 1
}
} else {
// Collapsed
collapsedCount += childCount
}
}
}
return count
}
override fun getItemViewType(position: Int): Int {
return if (busArrivalViewTypes!!.get(position).type === STOP_TYPE) {
STOP_TYPE
} else {
SERVICE_TYPE
}
}
private fun getChildCount(type: String): Int {
when (type) {
"D" -> return nineOneCount
"C" -> return threeOneCount
"B" -> return threeNineCount
"A" -> return fiveNineCount
else -> return 0
}
}
fun setUserListAndType(busStopServiceList: ArrayList<BusStopService>?, busStopNoList: ArrayList<String>?, c: Context) {
if (busStopServiceList != null && busStopNoList != null) {
this.busStopServiceList = busStopServiceList
this.busStopList = busStopNoList
this.context = c
busArrivalViewTypes = SparseArray<BusArrivalViewType>(busStopServiceList.size + busStopNoList.size)
headerExpandTracker = SparseIntArray(busStopNoList.size)
notifyDataSetChanged()
for (i in busStopServiceList.indices) {
when(busStopServiceList[i].busStopCode) {
"D" -> {
nineOneCount += 1
}
"C" -> {
threeOneCount += 1
}
"B" -> {
threeNineCount += 1
}
"A" -> {
fiveNineCount += 1
}
}
}
}
}
override fun onHeaderClick(position: Int) {
val viewType = busArrivalViewTypes!!.get(position)
val dataIndex = viewType.dataIndex
val userType = busStopList!![dataIndex]
val childCount = getChildCount(userType)
if (headerExpandTracker!!.get(dataIndex) == 0) {
headerExpandTracker!!.put(dataIndex, 1)
notifyItemRangeInserted(position + 1, childCount)
} else {
headerExpandTracker!!.put(dataIndex, 0)
notifyItemRangeRemoved(position + 1, childCount)
}
}
override fun isExpanded(position: Int): Boolean {
val dataIndex = busArrivalViewTypes!!.get(position).dataIndex
return headerExpandTracker!!.get(dataIndex) == 1
}
}

Nested recycler view's unable to show two independent lists

I have a vertical chat recycler-view i am trying to show multiple horizontal recycler-view's in which contain a "carousel" of card views. I am able to inflate the horizontal views but when i try to add two of the same kind of horizontal view as independent items in the parent view i either get the new data added to the original and then displayed twice or if i clear the old array the new data replaces the old data in the recycler-view and a second one is not added.
I am unsure if this is due to me not generating individual adapters per horizontal view or some array issue.
My adapter generation, called in the onBindViewHolder of the parent recycler-view:
fun generateAdapter(holder: ViewHolder): CarouselAdapter{
val layoutManager = LinearLayoutManager(appContext, LinearLayoutManager.HORIZONTAL, false )
holder.carouselView.layoutManager = layoutManager
val adapterCarousel = CarouselAdapter()
holder.carouselView.adapter = adapterCarousel
snapHelper.attachToRecyclerView(holder.carouselView)
holder.carouselView.addItemDecoration(ItemOffsetDecoration(12))
holder.carouselView.isNestedScrollingEnabled = false
layoutManager.isSmoothScrollbarEnabled = true
layoutManager.stackFromEnd = true
return adapterCarousel
}
Calling of data onto child recycler-view:
"rates" -> {
if (items[i].ratesData != null) {
val adapter = generateAdapter(holder)
for (j in 0 until (items[i].ratesData!!.size)) {
adapter.addCarousel(ChatNew(items[i].msgTime, null, null, null, items[i].ratesData!![j], null, null, null, null))
}
}
//ParseHelper.clearArrays()
}
Child adapter:
class CarouselAdapter : RecyclerView.Adapter<ViewHolderCarousel>() {
private val RATES = 1
private val IMG_BUTTON = 2
private val IMG_NO_BUTTON = 3
private val BLUE = 4
private val NO_IMG_BUTTON = 5
private val ERROR = 0
lateinit var type : String
var items: ArrayList<ChatNew> = ArrayList()
fun addCarousel(carousel: ChatNew){
items.add(carousel)
notifyItemInserted(itemCount)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderCarousel {
return when(viewType) {
RATES -> RatesViewHolderNew(LayoutInflater.from(appContext).inflate(R.layout.carousel_rate, parent, false))
IMG_BUTTON -> ImgButtonViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_img_btn, parent, false))
IMG_NO_BUTTON -> ImgNoButtonViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_img_no_btn, parent, false))
NO_IMG_BUTTON-> NoImgButtonViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_no_img_btn, parent, false))
else -> BlueViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_noimg_nobtn, parent, false))
}
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolderCarousel, i: Int) {
type = findType(holder)
when (type){
"rates" -> {
val ri = items[i]
//for (i in 0 until (ri.ratesData!!.size)){
holder.pnView.text = ri.ratesData?.pn
holder.mpView.text =ri.ratesData?.mp
holder.itView.text = ri.ratesData?.it
holder.brView.text = ri.ratesData?.br
holder.pfView.text = ri.ratesData?.pf
holder.aprcView.text = ri.ratesData?.aprc
holder.tpView.text = ri.ratesData?.tp
holder.headDivider!!.setBackgroundColor(ContextCompat.getColor(appContext!!, R.color.NWBlue))
// }
}
"carousel button" -> {
val ci = items[i]
holder.imgView.setImageResource(ci.carouselData?.img!!)
holder.number.text = ci.carouselData?.number
holder.title.text = ci.carouselData?.title
holder.content.text = ci.carouselData?.content
holder.magicString.text = ci.carouselData?.magicString
}
"img no button" -> {
val ci = items[i]
holder.imgViewNoBtn.setImageResource(ci.carouselData?.img!!)
holder.numberNoBtn.text = ci.carouselData?.number
holder.titleNoBtn.text = ci.carouselData?.title
holder.contentNoBtn.text = ci.carouselData?.content
holder.noButtonString.text = ci.carouselData?.noButtonString
}
"no img no button" -> {
val ci = items[i]
holder.imgViewNoImgNoBtn.setBackgroundColor(ContextCompat.getColor(appContext!!,R.color.NWBlue))
holder.numberNoImgNoBtn.text = ci.carouselData?.number
holder.titleNoImgNoBtn.text = ci.carouselData?.title
holder.contentNoImgNoBtn.text = ci.carouselData?.content
}
"no img button" -> {
val ci = items[i]
holder.imgViewNoImg.setBackgroundColor(ContextCompat.getColor(appContext!!,R.color.NWBlue))
holder.numberNoImg.text = ci.carouselData?.number
holder.titleNoImg.text = ci.carouselData?.title
holder.contentNoImg.text = ci.carouselData?.content
holder.magicStringNoImg.text = ci.carouselData?.magicString
}
}
}
override fun getItemViewType(i: Int): Int {
return when (items.isNotEmpty()) {
true -> {
when {
items[i].ratesData != null -> RATES
items[i].carouselData != null -> {
when (items[i].carouselData?.img != null) {
true -> {
return if (items[i].carouselData?.magicString != null) {
//has image and button
IMG_BUTTON
} else {
//has image but no button
IMG_NO_BUTTON
}
}
false -> {
return if (items[i].carouselData?.magicString != null) {
//has no image but button
NO_IMG_BUTTON
} else {
//has no image and no button
BLUE
}
}
}
}
else -> ERROR
}
}
false -> ERROR
}
}
fun findType(holder: ViewHolderCarousel): String {
return when (holder) {
is RatesViewHolderNew -> "rates"
is ImgButtonViewHolder -> "carousel button"
is ImgNoButtonViewHolder -> "img no button"
is NoImgButtonViewHolder -> "no img button"
is BlueViewHolder -> "no img no button"
else -> "error"
}
}
}
open class ViewHolderCarousel(view: View) : RecyclerView.ViewHolder(view) {
// looks at the view and finds components on that view
// carousel for rates
val headDivider: LinearLayout? = view.head_divider
val pnView = view.pn_view
val mpView = view.mp_view
val itView = view.it_view
val brView = view.br_view
val pfView = view.pf_view
val aprcView = view.aprc_view
val tpView = view.tp_view
//TODO: set up links for "r" sections
//carousel with img and button
val imgView = view.imageView_btn
val number = view.number_btn
val title = view.title_btn
val content = view.content_btn
val magicString = view.carousel_button_btn
//carousel with img but no button
val imgViewNoBtn = view.imageView_no_btn
val numberNoBtn = view.number_no_btn
val titleNoBtn = view.title_no_btn
val contentNoBtn = view.content_no_btn
val noButtonString = view.magic_string_no_btn
//carousel with no img or button
val imgViewNoImgNoBtn = view.imageView_no_img_no_btn
val numberNoImgNoBtn = view.number_no_img_no_btn
val titleNoImgNoBtn = view.title_no_img_no_btn
val contentNoImgNoBtn = view.content_no_img_no_btn
val imgViewNoImg = view.imageView_no_img
val numberNoImg = view.number_no_img
val titleNoImg = view.title_no_img
val contentNoImg = view.content_no_img
val magicStringNoImg = view.carousel_button_no_img
}
class RatesViewHolderNew(itemView: View) : ViewHolderCarousel(itemView)
class ImgButtonViewHolder(itemView: View) : ViewHolderCarousel(itemView)
class ImgNoButtonViewHolder(itemView: View) : ViewHolderCarousel(itemView)
class NoImgButtonViewHolder(itemView: View) : ViewHolderCarousel(itemView)
class BlueViewHolder(itemView: View) : ViewHolderCarousel(itemView)
Any ideas on how i could ensure that each time i add a new set of carousel data it gets added as an independent item in the parent view would be appreciated.
It turns out that you can fix this by having each addition to the parent adapter/view as an async task. Am unsure why this works but am not complaining.
doAsync {
adapter.addChatMessage(Chat(time, null, null, null, contentRates, null, null, null, null))
}

How to dispose a view holder linked to certain card in RecyclerView?

I am working on an android project in which I want to remove the item from vertical recycler view by swiping an item to right or left. As far as it comes to deleting the item, I am able to do that correctly. The issue arises after I delete the item and call notifyDataSetChanged().
Now while refreshing items, Adapter uses previous view holders to display cards. But while deleting an item I displaced some of its layouts to left and right. So when new item occupies the same view holder all the translation is preserved and
some views of the new item are thus created out of bounds of the screen(as I displaced that view holder while deleting the previous item that occupied that spot).
So my question is (solution to any one of the following will solve the issue I am facing),
Is there "free" function like c++ in Java? So that I can free that view holder.
How to make sure that recycler view doesn't reuse particular view holder?
How to reset layout of to the original state before all animations were done? So that I can just reset all translations
Thank you.
Edits:
Below are the codes for adapter and view holder if someone wants to have a look at them.
As view holder is too large to understand directly, SO here is summery for functions used in it:
init -> just set on click listener.
updateUI -> sets some more listener and set text and other fields from card.
pressDown -> is called when MotionEvent.ACTION_DOWN.
pressUP -> is called when MotionEvent.ACTION_UP.
animateViewDisappear -> listener for animation.
archive -> delete an element and tell adapter data is change.
pressMove -> sense when motion happens after clicking button.
toggleShowHideView -> change visibility of some view.
closeOpenedLayout -> Close some expanded layouts.
Adapter:
class LocalAdapter(var localCardsInfo : ArrayList<LocalModel>?,var fragment: LocalListingFragment) : RecyclerView.Adapter<LocalHolder>() {
fun refreshDataOnOrientationChange(mLocalCards : ArrayList<LocalModel>?){
if (localCardsInfo!!.size>0)
localCardsInfo!!.clear()
localCardsInfo = mLocalCards
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return localCardsInfo!!.size
}
override fun onBindViewHolder(holder: LocalHolder, position: Int) {
if (localCardsInfo!=null)
holder.updateUI(position,localCardsInfo!![position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalHolder {
val card_a : View = LayoutInflater.from(parent.context).inflate(R.layout.card_local_a,parent,false)
return LocalHolder(card_a,fragment)
}
}
view holder:
class LocalHolder(itemView : View,val fragment: LocalListingFragment) : RecyclerView.ViewHolder(itemView),OpenedLayoutManagerLocal{
private val TAG = "iotcontrollerapp.#Debug"
private var _xDelta: Float = 0f
private var originalDelta : Float = 0f
private var directionOut1 = false
private var directionOut = false
private var previousX = 0f
var isFavourite : Boolean = false
var isPropertyPanelOpen : Boolean = false
val deviceName : TextView = itemView.findViewById(R.id.deviceNameLocal)
val deviceRoom : TextView = itemView.findViewById(R.id.deviceRoomLocal)
val deviceOnOff : TextView = itemView.findViewById(R.id.deviceOnOffLocal)
val showHideLayout : LinearLayout = itemView.findViewById(R.id.showHideLayoutLocal)
val showHideProperties : TextView = itemView.findViewById(R.id.showHidePropertiesLocal)
val showHideButton : ImageView = itemView.findViewById(R.id.showHideButtonLocal)
val favouriteButton : ImageButton = itemView.findViewById(R.id.imageFavouriteButtonLocal)
val moveButton : MoveableViewImageButton = itemView.findViewById(R.id.imageMoveButtonLocal)
val changeFragmentToDetail : Button = itemView.findViewById(R.id.changePropertiesLocal)
val layoutForProperties : LinearLayout = itemView.findViewById(R.id.layoutForPropertyLocal)
val baseForProperties : LinearLayout = itemView.findViewById(R.id.baseForPropertyLocal)
private var model : LocalModel? = null
init {
itemView.elevation = 0f
showHideLayout.setOnClickListener({
isPropertyPanelOpen = !isPropertyPanelOpen
if (isPropertyPanelOpen){
if (openedLayoutManagerLocal != this)
openedLayoutManagerLocal?.closeOpenedLayout()
openedLayoutManagerLocal = this
showHideProperties.text = fragment.getString(R.string.close_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_local)
}
else{
showHideProperties.text = fragment.getString(R.string.open_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_reverse_local)
}
val mDrawable = showHideButton.drawable
if (mDrawable is Animatable)
{
(mDrawable as Animatable).start()
}
toggleShowHideView()
})
}
fun changeFavouriteButtonState(localModel: LocalModel){
isFavourite = !isFavourite
if (isFavourite){
favouriteButton.setImageResource(R.drawable.avd_heart_fill)
val a = favouriteButton.drawable
if(a is Animatable){
a.start()
}
ALL_STATIC_CONSTANTS_AND_METHODS.saveIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName,true)
}
else{
favouriteButton.setImageResource(R.drawable.avd_heart_break)
val a = favouriteButton.drawable
if(a is Animatable){
a.start()
}
ALL_STATIC_CONSTANTS_AND_METHODS.saveIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName,false)
}
}
fun updateUI(position : Int , localModel: LocalModel){
itemView.elevateLayoutLocal.visibility = View.VISIBLE
itemView.archiveLayout.visibility = View.VISIBLE
model = localModel
originalDelta = itemView.elevateLayoutLocal.x
changeFragmentToDetail.setOnClickListener({
fragment.handlerForDetail.sendEmptyMessage(position)
})
favouriteButton.setOnClickListener({
changeFavouriteButtonState(localModel)
})
moveButton.setOnTouchListener({v: View?, event: MotionEvent? ->
if (v == null || event == null) return#setOnTouchListener true
when (event.action){
MotionEvent.ACTION_UP -> {
v.performClick()
pressUP(v,event)
}
MotionEvent.ACTION_DOWN -> {
pressDown(v,event)
}
MotionEvent.ACTION_MOVE -> {
pressMove(v,event)
}
else -> {
Log.e(TAG,"Something happened")
}
}
return#setOnTouchListener true
})
isFavourite = (ALL_STATIC_CONSTANTS_AND_METHODS.getIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName) ?: false)
favouriteButton.setImageResource(if (isFavourite) R.drawable.vd_trimclip_heart_full else R.drawable.vd_trimclip_heart_empty)
deviceRoom.text = localModel.roomName.split("_").dropLast(1).toTypedArray().joinToString(" ")
deviceName.text = localModel.deviceName.split("_").dropLast(1).toTypedArray().joinToString(" ")
deviceOnOff.text = if (localModel.isON) fragment.getString(R.string.device_on_off_on) else fragment.getString(R.string.device_on_off_off)
val components = localModel.componentName.split(" ")
val value = localModel.value.split(" ")
val maxValue = localModel.maxValue.split(" ")
val minValue = localModel.minValue.split(" ")
if (layoutForProperties.childCount > 0)
layoutForProperties.removeAllViews()
if (components.size == value.size && minValue.size == maxValue.size && components.size == minValue.size){
for (i in 0 until components.size){
val layout : LinearLayout = LinearLayout.inflate(fragment.activity , R.layout.card_local_b , null) as LinearLayout
layout.findViewById<TextView>(R.id.propertyLocal).text = components[i].replace("_"," ")
layout.findViewById<TextView>(R.id.valueLocal).text = value[i]
layout.findViewById<TextView>(R.id.rangeLocal).text = minValue[i] + " to " + maxValue[i]
layoutForProperties.addView(layout)
}
}
}
private fun pressDown(imageButton: View, event: MotionEvent){
fragment.mLayoutManager?.setScrollEnabled(false)
openedLayoutManagerLocal?.closeOpenedLayout()
_xDelta = itemView.elevateLayoutLocal.x - event.rawX
}
private fun pressUP(imageButton: View, event: MotionEvent){
Log.e(TAG,"itemView.elevateLayoutLocal.x :: ${(itemView.elevateLayoutLocal.width / 3.toFloat()) - itemView.elevateLayoutLocal.x}")
val status = (itemView.elevateLayoutLocal.width / 3.toFloat()) < itemView.elevateLayoutLocal.x
val status1 = (itemView.elevateLayoutLocal.width / 2.toFloat()) < itemView.elevateLayoutLocal.x
itemView.elevateLayoutLocal.animate()
.x(if ((directionOut && status) || (status1 && directionOut1)) itemView.elevateLayoutLocal.width.toFloat() else originalDelta)
.setDuration(100)
.setListener(object : Animator.AnimatorListener{
override fun onAnimationCancel(animation: Animator?) {
if ((directionOut && status) || (status1 && directionOut1)) {
itemView.elevateLayoutLocal.visibility = View.GONE
val anim = ValueAnimator.ofInt(itemView.archiveLayout.measuredHeight, 0)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams = itemView.archiveLayout.layoutParams
layoutParams.height = `val`
itemView.archiveLayout.layoutParams = layoutParams
}
anim.addListener(animateViewDisappear(this#LocalHolder))
anim.duration = 1000
anim.start()
}else{
fragment.mLayoutManager?.setScrollEnabled(true)
}
}
override fun onAnimationEnd(animation: Animator?) {
if ((directionOut && status) || (status1 && directionOut1)) {
itemView.elevateLayoutLocal.visibility = View.GONE
val anim = ValueAnimator.ofInt(itemView.archiveLayout.measuredHeight, 0)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams = itemView.archiveLayout.layoutParams
layoutParams.height = `val`
itemView.archiveLayout.layoutParams = layoutParams
}
anim.addListener(animateViewDisappear(this#LocalHolder))
anim.duration = 1000
anim.start()
}else{
fragment.mLayoutManager?.setScrollEnabled(true)
}
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}).start()
}
class animateViewDisappear(var i : LocalHolder) :Animator.AnimatorListener {
override fun onAnimationCancel(animation: Animator?) {
i.itemView.archiveLayout.visibility = View.GONE
i.itemView.elevateLayoutLocal.visibility = View.GONE
i.itemView.elevateLayoutLocal.x = i.originalDelta
val layoutParams = i.itemView.archiveLayout.layoutParams
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
i.itemView.archiveLayout.layoutParams = layoutParams
i.archived()
}
override fun onAnimationEnd(animation: Animator?) {
i.itemView.archiveLayout.visibility = View.GONE
i.itemView.elevateLayoutLocal.visibility = View.GONE
i.itemView.elevateLayoutLocal.x = i.originalDelta
val layoutParams = i.itemView.archiveLayout.layoutParams
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
i.itemView.archiveLayout.layoutParams = layoutParams
i.archived()
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}
private fun archived(){
fragment.mLayoutManager?.setScrollEnabled(true)
if (model != null) {
ALL_STATIC_CONSTANTS_AND_METHODS.addToArchive(fragment.activity!!, model!!.roomName, model!!.deviceName)
fragment.mAdapter?.refreshDataOnOrientationChange(LocalDataService.ourInstance.getNonArchivedItems(fragment.activity!!))
}
}
private fun pressMove(imageButton: View, event: MotionEvent){
directionOut1 = itemView.elevateLayoutLocal.x >= (previousX)
directionOut = itemView.elevateLayoutLocal.x >= (previousX + 20)
previousX = itemView.elevateLayoutLocal.x
fragment.mLayoutManager?.setScrollEnabled(false)
itemView.elevateLayoutLocal.animate()
.x(event.rawX + _xDelta)
.setDuration(0)
.start()
}
fun toggleShowHideView(){
changeFragmentToDetail.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
layoutForProperties.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
baseForProperties.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
}
override fun closeOpenedLayout() {
if (isPropertyPanelOpen){
isPropertyPanelOpen = !isPropertyPanelOpen
showHideProperties.text = fragment.getString(R.string.open_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_reverse_local)
val mDrawable = showHideButton.drawable
if (mDrawable is Animatable)
{
(mDrawable as Animatable).start()
}
toggleShowHideView()
}
}
companion object {
var openedLayoutManagerLocal : OpenedLayoutManagerLocal? = null
}
}

Categories

Resources