RecyclerView not updating size after setData() with notifyDataSetChanged - android

The expected behavior is to have a multiple drop down menu with mutual exclusion.
Example, I have:
array = ["A", "B", "C", "D", "E"]
menu1
menu2
menu3
If in menu1 I choose "A" then in menu2 and menu3 "A" doesn't have to appear.
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var scdd1: SingleCheckBoxDropDown
private lateinit var scdd2: SingleCheckBoxDropDown
private lateinit var scdd3: SingleCheckBoxDropDown
private val strArr = ArrayList<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
strArr.add("string 1")
strArr.add("string 2")
strArr.add("string 3")
strArr.add("string 4")
strArr.add("string 5")
/**
* =============
* Single Select
* =============
*/
/*val scdd: SingleCheckBoxDropDown = findViewById(R.id.scdd1)
scdd.setData(strArr)
val tv1: TextView = findViewById(R.id.tv1)
scdd.setOnItemClickListenerWithSingleSelect {
val checked = scdd.getChecked()
if (checked != -1)
tv1.text = strArr[checked]
}*/
/**
* ====================================
* Single Select with Mutual Exclusion
* ====================================
*/
scdd1 = findViewById(R.id.category1)
scdd2 = findViewById(R.id.category2)
scdd3 = findViewById(R.id.category3)
scdd1.setData(strArr)
scdd2.setData(strArr)
scdd3.setData(strArr)
/** 1 */
scdd1.setOnItemClickListenerWithSingleSelect {
val filter1 = scdd1.getCheckedStr()
val filter2 = scdd2.getCheckedStr()
val filter3 = scdd3.getCheckedStr()
scdd1.setAnswer(filter1)
val data2 = strArr.filter { it != filter1 && it != filter3 }
val data3 = strArr.filter { it != filter1 && it != filter2 }
scdd2.setData(data2)
scdd3.setData(data3)
scdd1.setChecked(filter1)
scdd2.setChecked(filter2)
scdd3.setChecked(filter3)
}
/** 2 */
scdd2.setOnItemClickListenerWithSingleSelect {
val filter1 = scdd1.getCheckedStr()
val filter2 = scdd2.getCheckedStr()
val filter3 = scdd3.getCheckedStr()
scdd2.setAnswer(filter2)
val data1 = strArr.filter { it != filter2 && it != filter3 }
val data3 = strArr.filter { it != filter1 && it != filter2 }
scdd1.setData(data1)
scdd3.setData(data3)
scdd1.setChecked(filter1)
scdd2.setChecked(filter2)
scdd3.setChecked(filter3)
}
/** 3 */
scdd3.setOnItemClickListenerWithSingleSelect {
val filter1 = scdd1.getCheckedStr()
val filter2 = scdd2.getCheckedStr()
val filter3 = scdd3.getCheckedStr()
scdd3.setAnswer(filter3)
val data1 = strArr.filter { it != filter2 && it != filter3 }
val data2 = strArr.filter { it != filter1 && it != filter3 }
scdd1.setData(data1)
scdd2.setData(data2)
scdd1.setChecked(filter1)
scdd2.setChecked(filter2)
scdd3.setChecked(filter3)
}
/**
* Bug 1
*
* [x] 1 [ ] 1
* [ ] 4 --> [x] 4
* [ ] 5 [ ] 5
*
* [x] 2 [x] 1
* [ ] 4 [ ] 2
* [ ] 5 [ ] 5
*
* [x] 3 [x] 1
* [ ] 4 [ ] 3
* [ ] 5 [ ] 5
*/
}
}
activity_main.xml
<ScrollView 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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#002E2A"
android:gravity="center_horizontal"
android:padding="20dp"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<jk.singlecheckboxdropdown.scdd.SingleCheckBoxDropDown
android:id="#+id/category1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/tv1"
app:my_string_entries="#array/arr_str"
app:question="Category 1" />
<jk.singlecheckboxdropdown.scdd.SingleCheckBoxDropDown
android:id="#+id/category2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/category1"
app:my_string_entries="#array/arr_str"
app:question="Category 2" />
<jk.singlecheckboxdropdown.scdd.SingleCheckBoxDropDown
android:id="#+id/category3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/category2"
app:my_string_entries="#array/arr_str"
app:question="Category 3" />
</RelativeLayout>
</ScrollView>
SingleCheckBoxDropDown
class SingleCheckBoxDropDown #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
private val scddContainer: RelativeLayout
private val dropdownMenu: RecyclerView
private val questionTV: TextView
private val answerTV: TextView
private var adapter: ScddAdapter
init {
LayoutInflater.from(context)
.inflate(R.layout.scdd, this, true) // true
/** GET CUSTOM ATTRIBUTES */
val typedArr = context.theme.obtainStyledAttributes(
attrs,
R.styleable.SingleCheckBoxDropDown,
defStyleAttr,
0
)
val question = typedArr.getString(R.styleable.SingleCheckBoxDropDown_question)
val charSequenceArr: Array<CharSequence> =
typedArr.getTextArray(R.styleable.SingleCheckBoxDropDown_my_string_entries)
scddContainer = findViewById(R.id.scdd_container)
dropdownMenu = findViewById(R.id.dropdown_menu)
answerTV = findViewById(R.id.answer)
questionTV = findViewById(R.id.question)
questionTV.text = question
scddContainer.setOnClickListener {
if (dropdownMenu.visibility == View.GONE)
dropdownMenu.visibility = View.VISIBLE
else dropdownMenu.visibility = View.GONE
}
adapter = ScddAdapter()
adapter.addAll(charSequenceArr)
dropdownMenu.layoutManager = LinearLayoutManager(context)
dropdownMenu.adapter = adapter
}
fun resetHard(data: List<String>) {
adapter = ScddAdapter()
adapter.addAll(data.toTypedArray())
dropdownMenu.adapter = adapter
}
fun setData(strArr: List<String>) {
/** bug 1 - hide bug */
// dropdownMenu.removeAllViews()
// dropdownMenu.removeAllViewsInLayout()
adapter.setData(strArr as ArrayList<String>)
// RecyclerView won't update view after data change.
Log.i("try", "strArr size: ${strArr.size}")
Log.i("try", "dropdownmenu size: ${dropdownMenu.size}")
}
fun setAnswer(filter: CharSequence) {
answerTV.text = filter
}
fun getCheckedStr(): CharSequence {
return adapter.getCheckedStr()
}
fun getChecked(): Int {
return adapter.getChecked()
}
/**
* Single Select Core
*/
fun setOnItemClickListenerWithSingleSelect(function: () -> Unit) {
adapter.setOnItemClickListener {
for (i in 0 until dropdownMenu.size)
if (i != it)
(dropdownMenu[i] as CheckBox).isChecked = false
function()
}
}
/** bug 1 - fix part 1 */
fun setChecked(filter: CharSequence) {
for (i in 0 until dropdownMenu.size) {
(dropdownMenu[i] as CheckBox).isChecked = false
if ((dropdownMenu[i] as CheckBox).text == filter) {
(dropdownMenu[i] as CheckBox).isChecked = true
// adapter.notifyItemChanged(i)
}
}
}
}
scdd.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#android:color/transparent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="#+id/scdd_container"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#android:color/holo_green_dark"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical">
<TextView
android:id="#+id/question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:textColor="#android:color/white"
android:textSize="12sp"
tools:text="The Question" />
<TextView
android:id="#+id/answer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:gravity="end"
android:textColor="#android:color/white"
tools:text="The Answer" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/dropdown_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/scdd_container"
android:background="#android:color/holo_green_light"
android:translationZ="999dp"
android:visibility="visible" />
</RelativeLayout>
ScddAdapter
/** ADAPTER */
class ScddAdapter : RecyclerView.Adapter<ScddAdapter.ScddViewHolder>() {
/** VIEW HOLDER */
class ScddViewHolder(view: View) :
RecyclerView.ViewHolder(view) {
val cb: CheckBox = view.findViewById(R.id.cb)
}
private var callback = { }
private var list = ArrayList<CharSequence>()
private var checked: Int = -1
private var checkedStr = ""
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScddViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_single_checkbox, parent, false) // false
return ScddViewHolder(view)
}
override fun onBindViewHolder(holder: ScddViewHolder, position: Int) {
holder.cb.text = list[position]
holder.itemView.setOnClickListener {
if(holder.cb.isChecked) {
checkedStr = holder.cb.text as String
checked = holder.layoutPosition
}
else {
checkedStr = ""
checked = -1
}
this.callback()
}
}
override fun getItemCount(): Int {
return list.size
}
fun addAll(charSequenceArr: Array<CharSequence>) {
list.addAll(charSequenceArr)
}
fun setData(datas: ArrayList<String>) {
// for (i in 0 until strArr.size) Log.i("try", strArr[i])
list.clear()
notifyDataSetChanged()
this.list.addAll(datas)
notifyDataSetChanged()
}
fun getChecked(): Int {
return checked
}
fun getCheckedStr(): CharSequence {
return checkedStr
}
fun setOnItemClickListener(function: (holderPosition: Int) -> Unit) {
this.callback = {
function(checked)
}
}
}
item_single_checkbox.xml
<CheckBox
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/spinnerItemStyle"
tools:text="hello"
android:id="#+id/cb"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textColor="#color/white"
android:background="#android:color/background_dark"
android:buttonTint="#color/white"
android:textAlignment="inherit"/>
The problem that i noticed is that in setData() in SingleCheckBoxDropDown:
Log.i("try", "strArr size: ${strArr.size}")
Log.i("try", "dropdownmenu size: ${dropdownMenu.size}")
My RecyclerView (dropdownmenu) does not return the right size, idk why.

It seems to be because your adapter's addAll() method does not notify for changes.
Try changing it to:
fun addAll(charSequenceArr: Array<CharSequence>) {
val oldSize = getItemCount();
list.addAll(charSequenceArr)
notifyItemRangeInserted(oldSize, charSequenceArr.size())
}
Also, in setData() you can call notifyDataSetChanged() at the end only.

Related

Android Animated Carousel TextView within RecyclerView width wider than screen text gets clipped off. Even when animated into view

I am building a stock ticker type carousel that has market prices running along the screen as shown. The 'CRC' Ticker has only the partial view showing, it clips off at the edge of the parent which is the width of the device even when it is animated into view. I want it to have a width large enough hold all the children that goes past the device width.
Here if the Carousel layout xml:
<?xml version="1.0" encoding="utf-8"?>
<com.android.forexwatch.views.timepanel.CarouselView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/carouselLinear"
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal"
android:singleLine="true"
android:animateLayoutChanges="true"
/>
Here is the class:
package com.android.forexwatch.views.timepanel
import android.content.Context
import android.util.AttributeSet
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.widget.RelativeLayout
import androidx.appcompat.widget.LinearLayoutCompat
import com.android.forexwatch.model.Index
import com.android.forexwatch.R
import com.android.forexwatch.model.CityInfoDetailed
import com.github.ybq.android.spinkit.SpinKitView
class CarouselView(context: Context, attrs: AttributeSet): LinearLayoutCompat(context, attrs) {
companion object {
private const val IndexWidth = 600F
}
private var contextThemeWrapper: ContextThemeWrapper? = null
init {
contextThemeWrapper = ContextThemeWrapper(context.applicationContext, R.style.Theme_ForexWatch)
}
fun setupView ( cityInfo: CityInfoDetailed?, isMirror: Boolean = false) {
createPriceTickerViews(cityInfo, isMirror)
}
private fun createPriceTickerViews(cityInfo: CityInfoDetailed?, isMirror: Boolean = false) {
destroy()
removeAllViews()
var calculatedWidth = 0
cityInfo?.indexes?.forEachIndexed {
index, element ->
if (isMirror) index.plus(cityInfo?.indexes.count())
val loader: SpinKitView = LayoutInflater.from(contextThemeWrapper).inflate(R.layout.preloader, null) as SpinKitView
val priceTicker: PriceTicker = LayoutInflater.from(contextThemeWrapper).inflate(R.layout.price_ticker, null) as PriceTicker
addView(priceTicker)
addView(loader)
// priceTicker.x = IndexWidth * index
calculatedWidth.plus(IndexWidth)
priceTicker.initialize(element, cityInfo, loader)
}
layoutParams.width = calculatedWidth
}
private fun destroy() {
for (idx in 0 .. this.childCount) {
val view = getChildAt(idx)
if (view is PriceTicker) {
view.destroy()
}
}
}
}
Here is the parent class:
package com.android.forexwatch.views.timepanel
import android.animation.ValueAnimator
import android.graphics.Color
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.android.forexwatch.R
import com.android.forexwatch.adapters.TimePanelRecyclerViewAdapter
import com.android.forexwatch.events.TimeEvent
import com.android.forexwatch.model.CityInfoDetailed
import com.android.forexwatch.model.TimeObject
import com.android.forexwatch.utils.TimeKeeper
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class TimePanelView(view: View, container: ViewGroup, timePanelAdapter: TimePanelRecyclerViewAdapter) : RecyclerView.ViewHolder(view) {
companion object {
private const val OffsetTime: Long = 15 * 60
private const val FrameRate: Long = 32
}
private var fxTime: TextView? = null
private var stockTime: TextView? = null
private var city: TextView? = null
private var currentTime: TextView? = null
private var fxTimeLabel: TextView? = null
private var stockTimeLabel: TextView? = null
private var carouselView: CarouselView? = null
private var carouselViewMirror: CarouselView? = null
private var parentContainer: ViewGroup? = null
private var adapter: TimePanelRecyclerViewAdapter? = null
private var cityInfo: CityInfoDetailed? = null
private var view: View? = null
init {
adapter = timePanelAdapter
this.view = view
this.parentContainer = container
EventBus.getDefault().register(this)
}
fun setupView(cityInfo: CityInfoDetailed?) {
this.cityInfo = cityInfo
city = view?.findViewById(R.id.city)
stockTime = view?.findViewById(R.id.stockTime)
fxTime = view?.findViewById(R.id.fxTime)
fxTimeLabel = view?.findViewById(R.id.fxTimeLabel)
stockTimeLabel = view?.findViewById(R.id.stockTimeLabel)
currentTime = view?.findViewById(R.id.currentTime)
carouselView = view?.findViewById(R.id.carouselLinear)
carouselViewMirror = view?.findViewById(R.id.carouselLinearMirror)
city?.text = cityInfo?.cityName
createCarousel()
}
#Subscribe
fun update(event: TimeEvent.Interval?) {
updateCurrentTime()
updateFxTime()
updateStockTime()
}
private fun createCarousel() {
carouselView?.setupView(cityInfo)
carouselViewMirror?.setupView(cityInfo, true)
carouselView?.bringToFront()
carouselViewMirror?.bringToFront()
animateCarousel()
}
private fun updateCurrentTime() {
val timezone: String? = cityInfo?.timeZone
currentTime?.text = TimeKeeper.getCurrentTimeWithTimezone(timezone, "EEE' 'HH:mm:ss")
}
private fun updateFxTime() {
updateTime(fxTime, fxTimeLabel, cityInfo?.forexOpenTimes, cityInfo?.forexCloseTimes, cityInfo?.timeZone, "FX", Companion.OffsetTime * 8)
}
private fun updateStockTime() {
updateTime(stockTime, stockTimeLabel, cityInfo?.stockOpenTime, cityInfo?.stockCloseTime, cityInfo?.timeZone, cityInfo?.stockExchangeName, Companion.OffsetTime)
}
private fun updateTime(timeView: TextView?, timeLabel: TextView?, open: TimeObject?, close: TimeObject?, timezone: String?, type: String?, timeOffset: Long) {
if (TimeKeeper.isMarketOpen(open, close, timezone)) {
timeView?.text = TimeKeeper.getTimeDifference(close, close, timezone, true)
displayMarketColors(timeView, timeOffset, close, timezone, Color.GREEN, true)
timeLabel?.text = "To " + type + " Close"
} else {
timeView?.text = TimeKeeper.getTimeDifference(open, close, timezone, false)
displayMarketColors(timeView, timeOffset, open, timezone, Color.RED, false)
timeLabel?.text = "To " + type + " Open"
}
}
private fun displayMarketColors(timeView: TextView?, timeOffset: Long, time: TimeObject?, timezone: String?, outRangeColor: Int, isMarketOpen: Boolean?) {
val color = if (TimeKeeper.isTimeWithinRange(timeOffset, time, timezone, isMarketOpen)) Color.parseColor("#FF7F00") else outRangeColor
timeView?.setTextColor(color)
}
private fun animateCarousel() {
if (cityInfo?.indexes?.count() == 1) {
return
}
// carouselView?.x = carouselView?.x?.minus(3.0F)!!
/* CoroutineScope(Dispatchers.Main).launch {
delay(FrameRate)
animateCarousel()
}*/
val animator = ValueAnimator.ofFloat(0.0f, 1.0f)
animator.repeatCount = ValueAnimator.INFINITE
animator.interpolator = LinearInterpolator()
animator.duration = 18000L
animator.addUpdateListener { animation ->
val progress = animation.animatedValue as Float
val width: Int? = carouselView?.width
val translationX = -width?.times(progress)!!
carouselView?.translationX = translationX!!
carouselViewMirror?.translationX = translationX!! + width!!
}
animator.start()
}
}
The PriceTicker layout which extends AppCompatTextView
<com.android.forexwatch.views.timepanel.PriceTicker
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
class="com.android.forexwatch.views.timepanel.PriceTicker"
android:layout_width="240dp"
android:layout_height="match_parent"
android:textAlignment="center"
android:textSize="13sp"
android:singleLine="true"
android:background="#drawable/rectangle_shape"
app:layout_constraintBottom_toBottomOf="parent"
/>
I found the solution by invalidating and requestingLayout on the priceTicker TextView and the container. Also changed the width of the container and priceTicker to the desired width.
calculatedWidth = calculatedWidth.plus(IndexWidth).toInt()
priceTicker.initialize(index, cityInfo, loader)
priceTicker.width = IndexWidth.toInt()
resetLayout(priceTicker)
}
layoutParams.width = (calculatedWidth)
resetLayout(thisContainer)
}
}
private fun destroy() {
for (idx in 0 .. this.childCount) {
val view = getChildAt(idx)
if (view is PriceTicker) {
view.destroy()
}
}
}
private fun resetLayout(view: View?) {
view?.invalidate()
view?.requestLayout()
}

Set data in Custom view

I am building a custom view that makes an HTTP request to a Rest API every 6 seconds and displays a different question with its possible answers. A Buff in this case would be the object that contains the question and its possible answers.
Right now, the views are injected, but, when setting the data (see setAnswer function), the first view shows the text from the last element of the answer list. The rest of the views don't show any text.
Buff object received from API
With this data, I should show a question with 3 possible answers: "No goals!", "One goal!", "Two or more" in that order.
{
"result": {
"id": 1,
"author": {
"first_name": "Ronaldo"
},
"question": {
"id": 491,
"title": "Kaio Jorge has 4 goals this tournament – I think he will score again today. What do you think?"
},
"answers": [
{
"id": 1163,
"buff_id": 0,
"title": "No goals!"
},
{
"id": 1164,
"buff_id": 0,
"title": "One goal!"
},
{
"id": 1165,
"buff_id": 0,
"title": "Two or more!"
}
]
}
}
This is how is displayed at the moment
First element displays the text from the 3rd answer and the other two are empty
BuffView.kt (my custom view)
class BuffView(context: Context, attrs: AttributeSet? = null): LinearLayout(context, attrs) {
private val apiErrorHandler = ApiErrorHandler()
private val getBuffUseCase = GetBuffUseCase(apiErrorHandler)
private var buffIdCount = 1
private val buffView: View
init {
buffView = inflate(context, R.layout.buff_view, this)
}
fun start() {
getBuff()
}
private fun getBuff() {
getBuffUseCase.invoke(buffIdCount.toLong(), object : UseCaseResponse<Buff> {
override fun onSuccess(result: Buff) {
displayBuff(result)
}
override fun onError(errorModel: ErrorModel?) {
//Todo: show error toast
Log.e("AppDebug", "onError: errorModel $errorModel")
}
})
val delay = 6000L
RepeatHelper.repeatDelayed(delay) {
if (buffIdCount < 5) {
buffIdCount++
getBuff()
}
}
}
private fun displayBuff(buff: Buff) {
setQuestion(buff.question.title)
setAuthor(buff.author)
setAnswer(buff.answers)
setCloseButton()
buffView.visibility = VISIBLE
}
private fun setQuestion(questionText: String) {
question_text.text = questionText
}
private fun setAuthor(author: Buff.Author) {
val firstName = author.firstName
val lastName = author.lastName
sender_name.text = "$firstName $lastName"
Glide.with(buffView)
.load(author.image)
.into(sender_image)
}
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
answersContainer.addView(answerView)
}
}
private fun setCloseButton() {
buff_close.setOnClickListener {
buffView.visibility = GONE
}
}
}
buff_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="#layout/buff_sender"/>
<include layout="#layout/buff_question"/>
<LinearLayout
android:id="#+id/answersContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
buff_answer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="#drawable/light_bg"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<ImageView
android:id="#+id/answer_image"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_gravity="center_vertical"
android:src="#drawable/ic_generic_answer"
android:padding="4dp" />
<TextView
android:id="#+id/answer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:textColor="#color/test_color_dark"
tools:text="The keeper's right"
android:textStyle="bold" />
</LinearLayout>
Try to call invalidate() after addView() to tell the View it should re-render. Also you need to set text on answerView, so the method should look like this:
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answerView.answer_text?.text = answer.title
answersContainer.addView(answerView)
}
invalidate() // or invalidateSelf()
}
Another thing is that it's a very bad practice to call your api from the custom view. Executing the usecase should be on the ViewModel/Presenter etc. level. Then the buffs should be provided to the Fragment/Activity and then set in the CustomView. You are also not cancelling the request in some view-destroying callback like onDetachedFromWindow() which can lead to memory leaks
You cannot add the same view more than once without recreating its new variable, this is not as simple as one would think.
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
var viewlist = ArrayList()
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
viewlist.add(answerView)
}
for(view in viewlist)
answersContainer.addView(view)
}

Is it possible to place ItemDecoration along evenly spaced intervals (regardless of the items)?

I'm trying to make my own DayView/TimelineView/AgendaView it has many names, but essentially - something like this:
I couldn't find a single library that works + supports API version 21 + allows the customization that I need (ex. custom time formatting, event item layout, etc). Therefore, I decided to make my own. I'm curious if it's possible to create this using simple ItemDecoration as the time, where the events will be what actually populates the RecyclerView.
I have created something similar using ItemDecoration hope it helps.
in your Activity/Fragment call setAdapter()
#RequiresApi(Build.VERSION_CODES.O)
private fun setAdapter() {
val data = arrayListOf<EventPOJO>(
EventPOJO(
"Entry 1 \n Lorem Ipsum is simply dummy text of the printing and typesetting industry. ",
"12:00 AM"
),
EventPOJO("Entry 2 ", "01:00 AM")
)
val mAdapter = Adapter<EventPOJO>(data)
recyclerDay.run {
adapter = mAdapter
layoutManager = LinearLayoutManager(context)
addItemDecoration(ScheduleTimeHeadersDecoration(data))
}
}
your normal RecyclerView.Adapter
class Adapter<T>(
private val eventList: ArrayList<T>
) : RecyclerView.Adapter<Adapter.EventListViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventListViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view: View = layoutInflater.inflate(R.layout.item_day, parent, false)
return EventListViewHolder(view)
}
override fun getItemCount() = eventList.size
override fun onBindViewHolder(holder: EventListViewHolder, position: Int) {
holder.bind(eventList[position])
}
class EventListViewHolder(var view: View) :
RecyclerView.ViewHolder(view) {
private var name: TextView? = null
init {
name = view.findViewById(R.id.name)
name?.creatRandomColor()
}
fun <T> bind(bindObj: T) {
if (bindObj is EventPOJO) {
name?.text = bindObj.name
}
}
private fun TextView.creatRandomColor() {
val rnd = Random()
val color = Color.argb(
255,
rnd.nextInt(256),
rnd.nextInt(256),
rnd.nextInt(256)
)
this.setBackgroundColor(color)
}
}}
the data Object
data class EventPOJO(
val name: String,
val time: String
)
your item_day.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:minHeight="50dp"
android:padding="5dp"
android:text="Entry 1"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<android.support.constraint.Guideline
android:id="#+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="100dp" />
</android.support.constraint.ConstraintLayout>
and finally your itemDecoration
/**
* A [RecyclerView.ItemDecoration] which draws sticky headers for a given list of
sessions.
*/
#RequiresApi(Build.VERSION_CODES.O)
class ScheduleTimeHeadersDecoration(
sessions: ArrayList<EventPOJO>
) : RecyclerView.ItemDecoration() {
private val paint: TextPaint = TextPaint(ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
textSize = 50f
}
private val width: Int = 350
// Get the sessions index:start time and create header layouts for each
#RequiresApi(Build.VERSION_CODES.O)
private val timeSlots: Map<Int, StaticLayout> =
sessions
.mapIndexed { index, session ->
index to session.time
}.map {
it.first to createHeader(it.second)
}.toMap()
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val parentPadding = parent.paddingTop
var earliestPosition = Int.MAX_VALUE
var previousHeaderPosition = -1
var previousHasHeader = false
var earliestChild: View? = null
for (i in parent.childCount - 1 downTo 0) {
val child = parent.getChildAt(i)
if (child.y > parent.height || (child.y + child.height) < 0) {
// Can't see this child
continue
}
val position = parent.getChildAdapterPosition(child)
if (position < 0) {
continue
}
if (position < earliestPosition) {
earliestPosition = position
earliestChild = child
}
val header = timeSlots[position]
if (header != null) {
drawHeader(c, child, parentPadding, header, child.alpha, previousHasHeader)
previousHeaderPosition = position
previousHasHeader = true
} else {
previousHasHeader = false
}
}
if (earliestChild != null && earliestPosition != previousHeaderPosition) {
// This child needs a sicky header
findHeaderBeforePosition(earliestPosition)?.let { stickyHeader ->
previousHasHeader = previousHeaderPosition - earliestPosition == 1
drawHeader(c, earliestChild, parentPadding, stickyHeader, 1f, previousHasHeader)
}
}
}
private fun findHeaderBeforePosition(position: Int): StaticLayout? {
for (headerPos in timeSlots.keys.reversed()) {
if (headerPos < position) {
return timeSlots[headerPos]
}
}
return null
}
private fun drawHeader(
canvas: Canvas,
child: View,
parentPadding: Int,
header: StaticLayout,
headerAlpha: Float,
previousHasHeader: Boolean
) {
val childTop = child.y.toInt()
val childBottom = childTop + child.height
var top = (childTop).coerceAtLeast(parentPadding)
if (previousHasHeader) {
top = top.coerceAtMost(childBottom - header.height - 25)
}
paint.alpha = (headerAlpha * 255).toInt()
canvas.withTranslation(y = top.toFloat()) {
header.draw(canvas)
}
}
/**
* Create a header layout for the given [startTime].
*/
private fun createHeader(startTime: String): StaticLayout {
val text = SpannableStringBuilder().apply {
append(startTime.split(" ")[0])
append(System.lineSeparator())
append(startTime.split(" ")[1])
}
return newStaticLayout(text, paint, width, ALIGN_CENTER, 1f, 0f, false)
}
fun newStaticLayout(
source: CharSequence,
paint: TextPaint,
width: Int,
alignment: Layout.Alignment,
spacingmult: Float,
spacingadd: Float,
includepad: Boolean
): StaticLayout {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
StaticLayout.Builder.obtain(source, 0, source.length, paint, width).apply {
setAlignment(alignment)
setLineSpacing(spacingadd, spacingmult)
setIncludePad(includepad)
}.build()
} else {
#Suppress("DEPRECATION")
StaticLayout(source, paint, width, alignment, spacingmult, spacingadd, includepad)
}
}
private inline fun Canvas.withTranslation(
x: Float = 0.0f,
y: Float = 0.0f,
block: Canvas.() -> Unit
) {
val checkpoint = save()
translate(x, y)
try {
block()
} finally {
restoreToCount(checkpoint)
}
}
}
reference Google I/O 2019 Android App

Function to animate an ImageView in a RecyclerView

To forward my application, i need a function that will rotate an imageView in a recyclerView.
The recyclerView is one of 3 recyclerviews that are already working well, and are clickable through a cardView.
shortvideo there:
https://mega.nz/#!W6AyAYaY!07WG8kr_POYiGsgt0yja6fk8RaclZLw2-MiMFrmEQmk
I created Classes with a few members, which permit to know the state of the objects.
I am newbie in Android programmation, and it was difficult for me to make these recyclerView working well :-)
7 April : i finally succeeded to implement my function
but the result is weird !
can see there :
https://mega.nz/#!DzI2iaiB!VBG9Ex4TZlAsoUYPSRhXXMP4weH-9hXyjbqF1nCRyeA
There is an imageView which is rotating, but this is the left one, not the clicked one, and there is a mix, a swap between pictures.
My question is : are the imageViews clickable and focusable like cardViews ?
if so i can try to make the imageView focusable and clickable (actually the cardView is... )
What about the index is recyclerViews ? isn't it the cardview which is clicked which has the index ? or the left one in the screen ?
I am puzzled :-)
Following: the layout with CardView and imageView; MainActivity where i tried to write the function; and the Adapter for the Top RecyclerView where the function is supposed to
rotate (180 °) the imageView at the index ( index, tag and position )
no change about that xml file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:id="#+id/flag_card_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:focusable="true"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="#+id/flagcountry"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintDimensionRatio="W,55:100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="2dp"/>
<TextView
android:id="#+id/flagname"
android:layout_width="6dp"
android:layout_height="match_parent"
app:layout_constraintLeft_toRightOf="#id/flagcountry"
android:layout_centerVertical="true"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
under new kt file
package training.geography.triplerecyclerview
import android.provider.Settings.Global.getString
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
class FlagAdapter(val flagcountrylist: List<FlagCard>,
val flagitemClickListener : View.OnClickListener
//val flagitemOnLongClickListener : View.OnLongClickListener
):
RecyclerView.Adapter<FlagAdapter.FlagViewHolder> () {
class FlagViewHolder(flagView: View) : RecyclerView.ViewHolder(flagView) {
val flagcardView = flagView.findViewById<CardView>(R.id.flag_card_view)
val flagcountry = flagView.findViewById<ImageView>(R.id.flagcountry)
val flagname = flagView.findViewById<TextView>(R.id.flagname)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
FlagViewHolder {
val inflater = LayoutInflater.from(parent.context)
val viewItem = inflater.inflate(R.layout.item_flag, parent, false)
return FlagViewHolder(viewItem)
}
override fun onBindViewHolder(flagholder: FlagViewHolder, position: Int) {
//val flagcountry: String = flagcountrylist.FlagCard.flagimage[position]
//var flagimageList: List<String> = flagcountrylist.map { it.flagimage }
var flagsouthList: List<String> = flagcountrylist.map { it.flagsouth }
var iflagsouth = flagsouthList[position]
flagholder.flagname.text = iflagsouth
flagholder.flagcardView.tag = position
flagholder.flagcardView.setOnClickListener(flagitemClickListener)
//flagholder.flagcardView.setOnLongClickListener(itemOnLongClickListener)
when (position){
0 -> flagholder.flagcountry.setImageResource(R.drawable.afgcol)
1 -> flagholder.flagcountry.setImageResource(R.drawable.argsvk)
2 -> flagholder.flagcountry.setImageResource(R.drawable.armche)
3 -> flagholder.flagcountry.setImageResource(R.drawable.auttgo)
4 -> flagholder.flagcountry.setImageResource(R.drawable.azepry)
5 -> flagholder.flagcountry.setImageResource(R.drawable.bhrtur)
6 -> flagholder.flagcountry.setImageResource(R.drawable.bratls)
7 -> flagholder.flagcountry.setImageResource(R.drawable.bwasur)
8 -> flagholder.flagcountry.setImageResource(R.drawable.cafbtn)
9 -> flagholder.flagcountry.setImageResource(R.drawable.critun)
10 -> flagholder.flagcountry.setImageResource(R.drawable.pandza)
11 -> flagholder.flagcountry.setImageResource(R.drawable.persau)
12 -> flagholder.flagcountry.setImageResource(R.drawable.svnago)
13 -> flagholder.flagcountry.setImageResource(R.drawable.sweben)
14 -> flagholder.flagcountry.setImageResource(R.drawable.tcdgbr)
15 -> flagholder.flagcountry.setImageResource(R.drawable.thabol)
16 -> flagholder.flagcountry.setImageResource(R.drawable.tjkdeu)
17 -> flagholder.flagcountry.setImageResource(R.drawable.tkmbel)
18 -> flagholder.flagcountry.setImageResource(R.drawable.tzablz)
19 -> flagholder.flagcountry.setImageResource(R.drawable.ukrcmr)
20 -> flagholder.flagcountry.setImageResource(R.drawable.uryblr)
21 -> flagholder.flagcountry.setImageResource(R.drawable.venbgd)
22 -> flagholder.flagcountry.setImageResource(R.drawable.vnmbfa)
23 -> flagholder.flagcountry.setImageResource(R.drawable.yembih)
24 -> flagholder.flagcountry.setImageResource(R.drawable.zmbchl)
25 -> flagholder.flagcountry.setImageResource(R.drawable.zwealb)
}
//var iflagcard:String = imagelist[position]
//holder.flagcountry.setImageResource(iflagcard)
}
override fun getItemCount(): Int {
return flagcountrylist.size
}
}
---------------------------------------
//under new MainActivity file with function modified
// i don't know what to do with the variable
// flagtempup, i use it to set the string in
// iflagtrigramdown, and i have te warning variable not used...
package training.geography.triplerecyclerview
import android.nfc.Tag
import android.os.Bundle
import android.view.View
import android.view.View.OnClickListener
import android.view.animation.Animation
import android.view.animation.RotateAnimation
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_flag.*
data class FlagCard(var flagtrigramup: String, var flagtrigramdown: String,
var flagimage: String, var flagsouth: String, var rotationpossible: Boolean=(true))
data class CountryCard(val countrytrigram: String, val description: String)
data class MapCard(var map_left_up: String,
var map_right_up: String,
var map_left_down: String,
var map_right_down: String,
var maps_img: String,
var mapnorth: String)
class MainActivity : AppCompatActivity(), OnClickListener {
var afg_col = FlagCard("col", "afg", "afgcol", "afg", true)
var arg_svk = FlagCard("arg", "svk", "argsvk", "svk", true)
var arm_che = FlagCard("che", "arm", "armche", "arm", true)
var aut_tgo = FlagCard("tgo", "aut", "auttgo", "aut", true)
var aze_pry = FlagCard("aze", "pry", "azepry", "pry", true)
var bhr_tur = FlagCard("tur", "bhr", "bhrtur", "bhr", true)
var bra_tls = FlagCard("tls", "bra", "bratls", "bra", true)
var bwa_sur = FlagCard("sur", "bwa", "bwasur", "bwa", true)
var caf_btn = FlagCard("btn", "caf", "cafbtn", "caf", true)
var cri_tun = FlagCard("cri", "tun", "critun", "tun", true)
var pan_dza = FlagCard("pan", "dza", "pandza", "dza", true)
var per_sau = FlagCard("per", "sau", "persau", "sau", true)
var svn_ago = FlagCard("svn", "ago", "svnago", "ago", true)
var swe_ben = FlagCard("swe", "ben", "sweben", "ben", true)
var tcd_gbr = FlagCard("tcd", "gbr", "tcdgbr", "gbr", true)
var tha_bol = FlagCard("tha", "bol", "thabol", "bol", true)
var tjk_deu = FlagCard("tjk", "deu", "tjkdeu", "deu", true)
var tkm_bel = FlagCard("tkm", "bel", "tkmbel", "bel", true)
var tza_blz = FlagCard("tza", "blz", "tzablz", "blz", true)
var ukr_cmr = FlagCard("ukr", "cmr", "ukrcmr", "cmr", true)
var ury_blr = FlagCard("ury", "blr", "uryblr", "blr", true)
var ven_bgd = FlagCard("ven", "bgd", "venbgd", "bgd", true)
var vnm_bfa = FlagCard("vnm", "bfa", "vnmbfa", "bfa", true)
var yem_bih = FlagCard("yem", "bih", "yembih", "bih", true)
var zmb_chl = FlagCard("zmb", "chl", "zmbchl", "chl", true)
var zwe_alb = FlagCard("zwe", "alb", "zwealb", "alb", true)
var afg = CountryCard("afg", "afg")
var ago = CountryCard("ago", "ago")
var alb = CountryCard("alb", "alb")
var arg = CountryCard("arg", "arg")
var arm = CountryCard("arm", "arm")
var aut = CountryCard("aut", "aut")
var aze = CountryCard("aze", "aze")
var bel = CountryCard("bel", "bel")
var bfa = CountryCard("bfa", "bfa")
var bhr = CountryCard("bhr", "bhr")
var bih = CountryCard("bih", "bih")
var blz = CountryCard("blz", "blz")
var bol = CountryCard("bol", "bol")
var bra = CountryCard("bra", "bra")
var btn = CountryCard("btn", "btn")
var bwa = CountryCard("bwa", "bwa")
var caf = CountryCard("caf", "caf")
var che = CountryCard("che", "che")
var chl = CountryCard("chl", "chl")
var cmr = CountryCard("cmr", "cmr")
var col = CountryCard("col", "col")
var cri = CountryCard("cri", "cri")
var deu = CountryCard("deu", "deu")
var gbr = CountryCard("gbr", "gbr")
var pan = CountryCard("pan", "pan")
var per = CountryCard("per", "per")
var pry = CountryCard("pry", "pry")
var sau = CountryCard("sau", "sau")
var sur = CountryCard("sur", "sur")
var svk = CountryCard("svk", "svk")
var svn = CountryCard("svn", "svn")
var swe = CountryCard("swe", "swe")
var tcd = CountryCard("tcd", "tcd")
var tgo = CountryCard("tgo", "tgo")
var tha = CountryCard("tha", "tha")
var tjk = CountryCard("tjk", "tjk")
var tkm = CountryCard("tkm", "tkm")
var tls = CountryCard("tls", "tls")
var tun = CountryCard("tun", "tun")
var tur = CountryCard("tur", "tur")
var tza = CountryCard("tza", "tza")
var ukr = CountryCard("ukr", "ukr")
var ury = CountryCard("ury", "ury")
var ven = CountryCard("ven", "ven")
var vnm = CountryCard("vnm", "vnm")
var yem = CountryCard("yem", "yem")
var zmb = CountryCard("zmb", "zmb")
var zwe = CountryCard("zwe", "zwe")
var arm_bol_gbr_ago = MapCard("arm", "bol", "gbr", "ago", "bol", "armbolgbrago")
var aze_bra_aut_ben = MapCard("aze", "bra", "aut", "ben", "bra", "azebraautben")
var bfa_blr_col_bgd = MapCard("bfa", "blr", "col", "bgd", "blr", "bfablrcolbgd")
var bwa_bel_bhr_chl = MapCard("bwa", "bel", "bhr", "chl", "bel", "bwabelbhrchl")
var caf_afg_alb_arg = MapCard("caf", "afg", "alb", "arg", "afg", "cafafgalbarg")
var che_tkm_tur_sur = MapCard("che", "tkm", "tur", "sur", "tkm", "chetkmtursur")
var cri_cmr_btn_bih = MapCard("cri", "cmr", "btn", "bih", "cmr", "cricmrbtnbih")
var sau_blz_deu_dza = MapCard("sau", "blz", "deu", "dza", "blz", "saublzdeudza")
var svn_tha_tcd_pry = MapCard("svn", "tha", "tcd", "pry", "tha", "svnthatcdpry")
var swe_tls_tgo_per = MapCard("swe", "tls", "tgo", "per", "tls", "swetlstgoper")
var tjk_pan_svk_tza = MapCard("tjk", "pan", "svk", "tza", "pan", "tjkpansvktza")
var ven_zwl_yem_ukr = MapCard("ven", "zwl", "yem", "ukr", "zwl", "venzwlyemukr")
var zmb_tur_ury_vnm = MapCard("zmb", "tur", "ury", "vnm", "tur", "zmbtururyvnm")
var flagcountrylist = listOf<FlagCard>(
afg_col,
arg_svk,
arm_che,
aut_tgo,
aze_pry,
bhr_tur,
bra_tls,
bwa_sur,
caf_btn,
cri_tun,
pan_dza,
per_sau,
svn_ago,
swe_ben,
tcd_gbr,
tha_bol,
tjk_deu,
tkm_bel,
tza_blz,
ukr_cmr,
ury_blr,
ven_bgd,
vnm_bfa,
yem_bih,
zmb_chl,
zwe_alb
)
val flagadapter = FlagAdapter(flagcountrylist, this)
var countryList = listOf<CountryCard>(
afg,
ago,
alb,
arg,
arm,
aut,
aze,
bel,
bfa,
bhr,
bih,
blz,
blz,
bol,
bra,
btn,
bwa,
caf,
che,
chl,
cmr,
col,
cri,
deu,
gbr,
pan,
per,
pry,
sau,
sur,
svk,
svn,
swe,
tcd,
tgo,
tha,
tjk,
tkm,
tls,
tun,
tur,
tza,
ukr,
ury,
ven,
vnm,
yem,
zmb,
zwe
)
val countryadapter = CountryAdapter(countryList)
var mapcountrylist = listOf<MapCard>(
arm_bol_gbr_ago,
aze_bra_aut_ben,
bfa_blr_col_bgd,
bwa_bel_bhr_chl,
caf_afg_alb_arg,
che_tkm_tur_sur,
cri_cmr_btn_bih,
sau_blz_deu_dza,
svn_tha_tcd_pry,
swe_tls_tgo_per,
tjk_pan_svk_tza,
ven_zwl_yem_ukr,
zmb_tur_ury_vnm
)
val mapadapter = MapAdapter(mapcountrylist)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val flagsrecyclerView = findViewById<RecyclerView>(R.id.flagsRecyclerView)
flagsrecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
flagsrecyclerView.adapter = flagadapter
val countryrecyclerView = findViewById<RecyclerView>(R.id.countriesRecyclerView)
countryrecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
countryrecyclerView.adapter = countryadapter
val maprecyclerView = findViewById<RecyclerView>(R.id.mapsRecyclerView)
maprecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
maprecyclerView.adapter = mapadapter
}
fun rotateFlagCard (flagCard: FlagCard, position: Int) : FlagCard {
var flagtrigramuplist: List<String> = flagcountrylist.map{it.flagtrigramup }
var iflagtrigramup : String = flagtrigramuplist[position]
var flagimagelist = flagcountrylist.map{it.flagimage}
var iflagimage = flagimagelist[position]
var flagsouthlist = flagcountrylist.map { it.flagsouth }
var iflagsouth = flagsouthlist[position]
var rotationpossiblelist : List<Boolean> = flagcountrylist.map{ it.rotationpossible }
var irotationpossible = rotationpossiblelist[position]
var iflagimageView = findViewById<ImageView>(R.id.flagcountry)
when (position) {
0 -> iflagimageView.setImageResource(R.drawable.afgcol)
1 -> iflagimageView.setImageResource(R.drawable.argsvk)
2 -> iflagimageView.setImageResource(R.drawable.armche)
3 -> iflagimageView.setImageResource(R.drawable.auttgo)
4 -> iflagimageView.setImageResource(R.drawable.azepry)
5 -> iflagimageView.setImageResource(R.drawable.bhrtur)
6 -> iflagimageView.setImageResource(R.drawable.bratls)
7 -> iflagimageView.setImageResource(R.drawable.bwasur)
8 -> iflagimageView.setImageResource(R.drawable.cafbtn)
9 -> iflagimageView.setImageResource(R.drawable.critun)
10 -> iflagimageView.setImageResource(R.drawable.pandza)
11 -> iflagimageView.setImageResource(R.drawable.persau)
12 -> iflagimageView.setImageResource(R.drawable.svnago)
13 -> iflagimageView.setImageResource(R.drawable.sweben)
14 -> iflagimageView.setImageResource(R.drawable.tcdgbr)
15 -> iflagimageView.setImageResource(R.drawable.thabol)
16 -> iflagimageView.setImageResource(R.drawable.tjkdeu)
17 -> iflagimageView.setImageResource(R.drawable.tkmbel)
18 -> iflagimageView.setImageResource(R.drawable.tzablz)
19 -> iflagimageView.setImageResource(R.drawable.ukrcmr)
20 -> iflagimageView.setImageResource(R.drawable.uryblr)
21 -> iflagimageView.setImageResource(R.drawable.venbgd)
22 -> iflagimageView.setImageResource(R.drawable.vnmbfa)
23 -> iflagimageView.setImageResource(R.drawable.yembih)
24 -> iflagimageView.setImageResource(R.drawable.zmbchl)
25 -> iflagimageView.setImageResource(R.drawable.zwealb)
else -> throw AssertionError()
}
if (irotationpossible)
{
iflagimageView.animate().apply {
rotationBy(180f)
duration = 1000L
start()
// permutation of values north south
var flagtrigramdownlist :List<String> = flagcountrylist.map{it.flagtrigramdown}
var iflagtrigramdown : String = flagtrigramdownlist[position]
var flagtempup: String = iflagtrigramup
var flagtempdown: String = iflagtrigramdown
iflagtrigramup = iflagsouth
//flagtempdown = flagtempup
iflagtrigramdown = flagtempup
var iflagCard = FlagCard( iflagtrigramup, iflagtrigramdown, iflagimage, iflagsouth, irotationpossible )
return iflagCard
}
} else {
return flagCard
//Toast.makeText(this, "flag already matched country", Toast.LENGTH_LONG).show()
}
}
override fun onClick(flagcardView: View) {
if (flagcardView.tag != null) {
val index = flagcardView.tag as Int
var flagsouthList: List<String> = flagcountrylist.map{it.flagsouth }
val flags = flagsouthList[index]
Toast.makeText(this, "country : ${flags}", Toast.LENGTH_LONG).show()
var flagcard:FlagCard = flagcountrylist[index]
rotateFlagCard (flagcard, index)
}
}
}
It looks like nobody is interested by what is possible to do with RecyclerViews...
I still have the problem that when an imageView is clicked, this is the one at left which is rotating ! i can't understand what happens, examination in debugger shows the variables are ok, the index is position and do not change...
I tried to apply rotate function to the cardView, same result !
I modified the code in the function and replace the flagCard by iflagCard if rotation was done (because two flags, one up, one down, and of course the FlagCard is different... )
Here the new code :
if (irotationpossible)
{
iflagcardView.animate().apply {
rotationBy(180f)
duration = 1000L
start()
// permutation of values north south
var flagtrigramdownlist :List<String> = flagcountrylist.map{it.flagtrigramdown}
var iflagtrigramdown : String = flagtrigramdownlist[position]
var flagtempup: String = iflagtrigramup
var flagtempdown: String = iflagtrigramdown
iflagtrigramup = iflagsouth
iflagsouth = flagtempup
iflagtrigramdown = flagtempup
var iflagCard = FlagCard( iflagtrigramup, iflagtrigramdown, iflagimage, iflagsouth, irotationpossible )
//flagcountrylist.toSet(position, iflagCard)
//abstract operator fun set(position: Int, iflagCard: FlagCard): FlagCard
flagcountrylist.removeAt(position)
flagcountrylist.add(position, iflagCard)
return iflagCard
}
} else {
return flagCard
//Toast.makeText(this, "flag already matched country", Toast.LENGTH_LONG).show()
}
my question is now about the set function for mutableListOf in Kotlin :
i tried to use " abstract operator fun set(index: Int, element: E): E "
but couldn't build because of abstract !
Is there any other way than my solution to remove then add at the same index ?
Is there any experimented developper curious about the strange rotation in that app ?
https://www.youtube.com/watch?v=d9o7fttLBxk
Why not the image with right index ?

Get binded ViewHolder's positions scope

I want to get range of 'alive' ViewHolders. There is my not working solution.
class NotesRvAdapter : RecyclerView.Adapter<NoteDateViewHolder> {
private var lastBindPosition = 0
private var lastUnbindPosition = 10
override fun onBindViewHolder(holder: NoteDateViewHolder, position: Int) {
...
onScopeChanged()
}
fun onScopeChanged() {
var maxPosition = 0
var minPosition = 0
if(lastBindPosition > lastUnbindPosition) {
maxPosition = lastBindPosition
minPosition = lastUnbindPosition
} else {
maxPosition = lastUnbindPosition
minPosition = lastBindPosition
}
scopeListener.onChanged(NotesRequest(maxPosition, minPosition))
}
override fun onViewDetachedFromWindow(holder: NoteDateViewHolder) {
lastUnbindPosition = holder.adapterPosition
super.onViewDetachedFromWindow(holder)
}
}
As onBindViewHolder(...) and onViewDetachedFromWindow(...) called in shuffled order. And it is not the only problem :)
Any ideas to get 'alive' position's scope? LayoutManager can provide only first/last visible items, not 'bound'.

Categories

Resources