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!
Related
We are developing an app that determines whether or not to board an escalator by applying changes in sensor data to a function. Here, I made a separate escalator judgment part, but the change of the sensor is not applied and only the initial declared value of 0 continues to come in. Why does this happen?
import android.content.Context
import android.graphics.Color
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.core.view.ContentInfoCompat.Flags
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.w3c.dom.Text
import kotlin.math.abs
import kotlin.math.sign
class MainActivity : AppCompatActivity(), SensorEventListener {
// ----------------------- Sensor var -------------------------
// Pressure
private var thresholdPreMin = 0.01f
private var thresholdPreMax = 0.1f
private var currPressure = 0f
// Acceleration
private var thresholdAccMin = 0.15f
private var currAccX = 0f
private var currAccY = 0f
private var currAccZ = 0f
// Common
private var onEsCheckCount = 4
// ----------------------- Filter -----------------------
private var filterWindow = 8
// Sensor Manager
private val mSensorManager by lazy {
getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
private lateinit var escalatorDetector: EscalatorDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btnPreVar : Button = findViewById(R.id.btn_pre_min)
val preMinVar : EditText = findViewById(R.id.pre_min_var)
val btnAccVar : Button = findViewById(R.id.btn_acc_min)
val accMinVar : EditText = findViewById(R.id.acc_min_var)
val btnCheckCount : Button = findViewById(R.id.btn_pre_check_count)
val etCheckCount : EditText = findViewById(R.id.pre_check_count)
btnPreVar.setOnClickListener{
val minpreVar = preMinVar.text.toString()
thresholdPreMin = minpreVar.toFloat()
}
btnAccVar.setOnClickListener{
val minaccVar = accMinVar.text.toString()
thresholdAccMin = minaccVar.toFloat()
}
btnCheckCount.setOnClickListener{
val checkVar = etCheckCount.text.toString()
onEsCheckCount = checkVar.toInt()
}
start()
}
private fun start(){
// FindViewById List
val tvPreList : TextView = findViewById(R.id.tv_pre_list)
val tvPre : TextView = findViewById(R.id.tv_pre)
val tvPreCount : TextView = findViewById(R.id.tv_pre_count)
val tvSituation : TextView = findViewById(R.id.tv_situation)
val tvAccXCount : TextView = findViewById(R.id.tv_accXCount)
val tvAccYCount : TextView = findViewById(R.id.tv_accYCount)
val tvAccZCount : TextView = findViewById(R.id.tv_accZCount)
val tvPreBigCount : TextView = findViewById(R.id.tv_pre_big_count)
escalatorDetector = EscalatorDetector(onEsCheckCount, filterWindow, thresholdPreMin, thresholdPreMax, thresholdAccMin)
escalatorDetector.esJudgmentStart(currPressure, currAccX, currAccY, currAccZ, tvPreList, tvPre, tvPreCount, tvSituation, tvAccXCount, tvAccYCount, tvAccZCount, tvPreBigCount)
}
// -------------------- Sensor -----------------------
override fun onResume() {
super.onResume()
mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE)?.also {
pressure -> mSensorManager.registerListener(
this, pressure, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI
)
}
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also {
accelerometer -> mSensorManager.registerListener(
this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI
)
}
}
override fun onPause() {
super.onPause()
mSensorManager.unregisterListener(this)
}
// pressure and acceleration
override fun onSensorChanged(event: SensorEvent) {
val tvPreValue: TextView = findViewById(R.id.tv_pre_value)
if (event.sensor.type == Sensor.TYPE_PRESSURE) {
currPressure = event.values[0]
tvPreValue.text = "PreMin:$thresholdPreMin\nAccMin:$thresholdAccMin\nCheckCount:${onEsCheckCount}\nCurrPre:${currPressure}\nWindowSize:${filterWindow}"
}
if(event.sensor.type == Sensor.TYPE_ACCELEROMETER){
currAccX = event.values[0]
currAccY = event.values[1]
currAccZ = event.values[2]
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Do nothing
}
}
package com.example.checkonescalator
import android.os.Looper
import android.widget.TextView
import kotlin.math.abs
class EscalatorDetector(private val onEsCheckCount : Int, private val filterWindow: Int,
private val thresholdPreMin : Float, private val thresholdPreMax : Float,
private val thresholdAccMin : Float) {
// Pressure Variable
private var preList : ArrayList<Float> = arrayListOf()
private var preChanges : ArrayList<Float> = arrayListOf()
private var preUpCount = 0
private var preDownCount = 0
private var preBigUpCount = 0
private var preBigDownCount = 0
private var prevPressure = 0f
// Acc Variable
private val accXList : ArrayList<Float> = arrayListOf()
private val accYList : ArrayList<Float> = arrayListOf()
private val accZList : ArrayList<Float> = arrayListOf()
private val accXChanges : ArrayList<Float> = arrayListOf()
private val accYChanges : ArrayList<Float> = arrayListOf()
private val accZChanges : ArrayList<Float> = arrayListOf()
private var accXCount = 0
private var accYCount = 0
private var accZCount = 0
private var prevAccX = 0f
private var prevAccY = 0f
private var prevAccZ = 0f
fun esJudgmentStart(currPre : Float, currAccX : Float, currAccY : Float, currAccZ : Float, // 이 앱에서만 쓰이는 변수
tvPreList: TextView,
tvPre: TextView,
tvPreCount: TextView,
tvSituation: TextView,
tvAccXCount: TextView,
tvAccYCount: TextView,
tvAccZCount: TextView,
tvPreBigCount: TextView){
val filter = MovingAvgFilter(filterWindow)
val handler = android.os.Handler(Looper.getMainLooper())
val thread = Thread(Runnable{
while(true){
// Calculate Avg Value
val finalPre = filter.makeAverageFilter(preList, currPre)
val finalAccX = filter.makeAverageFilter(accXList, currAccX)
val finalAccY = filter.makeAverageFilter(accYList, currAccY)
val finalAccZ = filter.makeAverageFilter(accZList, currAccZ)
// Calculate Change in Value
val preChange = finalPre - prevPressure
val accXChange = abs(finalAccX - prevAccX)
val accYChange = abs(finalAccY - prevAccY)
val accZChange = abs(finalAccZ - prevAccZ)
// Add an acceleration value and see if it moves
addAccXChange(accXChange)
addAccYChange(accYChange)
addAccZChange(accZChange)
val isMoving = checkMoving()
// Add an Pressure value and check
addPreChange(preChange)
// --------------- Using in this app -----------------------
// Update UI background thread
handler.post {
// Update UI here
val esSituation = judgmentEscalator(isMoving, preChange)
tvSituation.text = esSituation
// Output Up or Down Count and each preList with a new line
updateAccCountUI(tvAccXCount, tvAccYCount, tvAccZCount)
updatePreCountUI(tvPreCount, tvPreBigCount)
updatePreChangesUI(tvPreList)
// Display Value
tvPre.text = "preChange : ${preChange}\navgPre : ${finalPre}\nprePre : ${prevPressure}\n" +
"avgAccX : ${finalAccX}\navgAccY : ${finalAccY}\navgAccZ : ${finalAccZ}\n" +
"accChanges : ${accXChange}\n${accYChange}\n${accZChange}"
}
// Save the value on cycle ago
prevPressure = finalPre
prevAccX = finalAccX
prevAccY = finalAccY
prevAccZ = finalAccZ
// Delay
Thread.sleep(500)
}
})
thread.start()
}
fun judgmentEscalator(isMoving : Boolean, preChange : Float): String {
// Count Up or Down Count
preDownCount = preChanges.count(){it > thresholdPreMin}
preUpCount = preChanges.count(){it < -thresholdPreMin}
// Count Up or Down Count for check Elevator
preBigDownCount = preChanges.count(){it > thresholdPreMax}
preBigUpCount = preChanges.count(){it < -thresholdPreMax}
// Display
if(!isMoving){
if(preDownCount >= onEsCheckCount && preChange >= 0){
if(preBigDownCount >= onEsCheckCount){
return "Going down the Elevator"
} else{
return "Going down the escalator"
}
} else if(preUpCount >= onEsCheckCount && preChange <= 0){
if(preBigUpCount >= onEsCheckCount){
return "Going Up the Elevator"
} else {
return "Going up the escalator"
}
} else {
return "Currently on the ground"
}
} else {
return "It's not stationary"
}
}
fun updatePreCountUI(tvPreCount : TextView, tvPreBigCount : TextView){
tvPreCount.text = "Escalator\nUp : ${preUpCount}\nDown : ${preDownCount}"
tvPreBigCount.text = "Elevator\nUp : ${preBigUpCount}\nDown : ${preBigDownCount}"
}
fun updatePreChangesUI(tvPreList: TextView){
val preList = preChanges.joinToString(separator = "\n")
tvPreList.text = preList
}
fun addPreChange(change: Float){
if(preChanges.size == filterWindow) preChanges.removeAt(0)
preChanges.add(change)
}
fun checkMoving():Boolean{
accXCount = accXChanges.count(){it > thresholdAccMin}
accYCount = accYChanges.count(){it > thresholdAccMin}
accZCount = accZChanges.count(){it> thresholdAccMin}
if (accXCount >= onEsCheckCount || accYCount >= onEsCheckCount || accZCount >= onEsCheckCount) {
return true
} else {
return false
}
}
fun updateAccCountUI(tvAccXCount :TextView, tvAccYCount : TextView, tvAccZCount : TextView){
tvAccXCount.text = "accX\n${accXCount}"
tvAccYCount.text = "accY\n${accYCount}"
tvAccZCount.text = "accZ\n${accZCount}"
}
fun addAccXChange(change:Float){
if(accXChanges.size == filterWindow) accXChanges.removeAt(0)
accXChanges.add(change)
}
fun addAccYChange(change: Float){
if(accYChanges.size == filterWindow) accYChanges.removeAt(0)
accYChanges.add(change)
}
fun addAccZChange(change: Float){
if(accZChanges.size == filterWindow) accZChanges.removeAt(0)
accZChanges.add(change)
}
package com.example.checkonescalator
class MovingAvgFilter(private val filterWindow : Int) {
fun makeAverageFilter(list: ArrayList<Float>, component: Float):Float{
if(list.size == filterWindow){
list.removeAt(0)
}
list.add(component)
val sumComponent = sumArrayList(list)
val avgComponent = sumComponent / (list.size)
return avgComponent
}
fun sumArrayList(list: ArrayList<Float>):Float{
var sum = 0f
for(element in list){
sum += element
}
return sum
}
}
}
here is my code
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
}
})
}
i'm using BulletSpan which is customized.
i want to display long text that has '\n'.
every lines are fine except for the text line which has '\n'.
bulleetspan can't apply the indent to the newline text.
this is the result.
and the last text is one text. and the text has '\n' inside.
and the code is..
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val source = listOf("Spans are powerful markup objects that you can use to style text at a character or paragraph level.",
"By attaching spans to text objects, you can change text in a variety of ways, ",
"including adding color, making the text clickable,\scaling the text size,\nand drawing text in a customized way.")
val sb = SpannableStringBuilder()
for (i in source.indices) {
val length = sb.length
sb.append(source[i])
sb.append("\n")
sb.setSpan(CustomBulletSpan(
bulletRadius = dip(8),
gapWidth = dip(14),
mColor = color(),
mWantColor = true
), length, length + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
binding.tvResult.text = sb
}
private fun dip(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
resources.displayMetrics
).toInt()
}
private fun color(): Int {
return ContextCompat.getColor(applicationContext, R.color.gray);
}
}
and the customBullentSpan code is..
class CustomBulletSpan(
val bulletRadius: Int = STANDARD_BULLET_RADIUS,
val gapWidth: Int = STANDARD_GAP_WIDTH,
val mColor: Int = STANDARD_COLOR,
val mWantColor: Boolean = false
) : LeadingMarginSpan {
companion object {
// Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
private const val STANDARD_BULLET_RADIUS = 4
private const val STANDARD_GAP_WIDTH = 2
private const val STANDARD_COLOR = 0
}
private var mBulletPath: Path? = null
override fun getLeadingMargin(first: Boolean): Int {
return 2 * bulletRadius + gapWidth
}
override fun drawLeadingMargin(
c: Canvas,
p: Paint,
x: Int,
dir: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
first: Boolean,
layout: Layout?
) {
if ((text as Spanned).getSpanStart(this) == start) {
val style = p.style
p.style = Paint.Style.FILL
var oldColor = 0
if (mWantColor) {
oldColor = p.color
p.color = mColor
}
val yPosition = if (layout != null) {
val line = layout.getLineForOffset(start)
layout.getLineBaseline(line).toFloat() - bulletRadius * 1.3f
} else {
(top + bottom) / 1.3f
}
val xPosition = (x + dir * bulletRadius).toFloat()
if (c.isHardwareAccelerated) {
if (mBulletPath == null) {
mBulletPath = Path()
mBulletPath!!.addCircle(0.0f, 0.0f, bulletRadius.toFloat(), Path.Direction.CW)
}
c.save()
c.translate(xPosition, yPosition)
c.drawPath(mBulletPath!!, p)
c.restore()
} else {
c.drawCircle(xPosition, yPosition, bulletRadius.toFloat(), p)
}
if (mWantColor) {
p.color = oldColor
}
p.style = style
}
}
}
how can i solve this problem??
You could just get the string and split by \n and apply span
var len = 0
for (i in source.indices) {
if (source[i].contains("\n")) {
val splitted = source[i].split("\n")
for (k in splitted.indices) {
len = sb.length
sb.append(splitted[k])
sb.append("\n")
sb.setSpan(
CustomBulletSpan(
bulletRadius = dip(8),
gapWidth = dip(14),
mColor = color(),
mWantColor = true
), len, len + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
} else {
len = sb.length
sb.append(source[i])
sb.append("\n")
sb.setSpan(
CustomBulletSpan(
bulletRadius = dip(8),
gapWidth = dip(14),
mColor = color(),
mWantColor = true
), len, len + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
The other way is to split and add it to new list and iterate over the same and apply spans
val newList = mutableListOf<String>()
for (item in source) {
if(item.contains("\n")) {
val split = item.split("\n")
for (splitItem in split){
newList.add(splitItem)
}
} else{
newList.add(item)
}
}
Now i am writing my small molar mass calculator and i can't fix one bug. In MainActivity.kt i fill array from my .xml file, after that i use Regex to parse user input. BUT if i type, for example "C" (carbon) in my program it doesn't recognize it. WHY?
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = MoleculeAdapter(this)
moleculeView.layoutManager = LinearLayoutManager(this)
moleculeView.setHasFixedSize(true)
moleculeView.adapter = adapter
val parser = resources.getXml(R.xml.elements)
val elements = Array(127) { Element() }
thread {
var i = 0
while (parser.eventType != END_DOCUMENT) {
if (parser.eventType == START_TAG && parser.name == "element") {
elements[i].number = parser.getAttributeIntValue(null, "number", 0)
elements[i].letter = parser.getAttributeValue(null, "letter")
elements[i].name = parser.getAttributeValue(null, "name")
val weight = parser.getAttributeValue(null, "weight")
elements[i].weight = if (weight.isNotEmpty()) weight.toFloat() else 0F
i++
}
parser.next()
}
parser.close()
}.join()
Log.i("elements:", elements.joinToString { it.toString() + "\n" })
val lowerCaseLetters = "abcdefghiklmnopqrstuy"
val elementsRegex = Regex("""[ABCDEFGHIKLMNOPRSTUVWXYZ]([$lowerCaseLetters]{2}|[$lowerCaseLetters]?)\d*""")
val digitsRegex = Regex("""\d+""")
formulaInput.doOnTextChanged { text, _, _, _ ->
lateinit var foundedElements: List<Element>
thread {
foundedElements = elementsRegex
.findAll(text ?: "")
.map {
elements.find { element ->
Log.i("value", it.value + " " + it.value)
if (it.value.filter { it.isLetter() } == element.letter) {
val number = digitsRegex.find(it.value)
if (number != null) {
try {
element.moleculeCount = number.value.toInt()
element.weight = element.weight * number.value.toInt()
} catch (e: NumberFormatException) { }
}
element.percentage = adapter.getTotalWeight(element.weight) * 100
true
} else false
}
}.filterNotNull().toList()
}.join()
adapter.insertElements(foundedElements)
}
}
}
Element.kt:
data class Element(var number: Int = -1,
var letter: String = "",
var name: String = "",
var weight: Float = 0F,
var percentage: Float = 100F,
var moleculeCount: Int = 1)
xml file item example:
<element
number="6"
letter="С"
name="Углерод"
weight="12.011" />
I can't believe it, in my xml file letter "С" was a cyrillic letter "C" (\u0421)! And because of this equals check "С" == "C" was failing.
Huge Thanks to Wiktor Stribiżew for his comment.
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
}
}