Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed yesterday.
Improve this question
The app I'm going to write is an obstacle avoidance game.
Enemies and obstacles appear, and enemies move freely, and obstacles are created with random coordinates at a specific time. The game ends when the character hits an enemy or an obstacle. Set a record by raising 1 point per second.
It's my first time developing, so I have a lot of problems with the code.There are many problems with the corresponding kotlin code when writing the obstacle avoidance game. Please solve the problem.
Obstacles or enemies are not visible or are in a fixed position when launching the app. We want to keep our enemies free, and obstacles are created at a particular time and in a particular place.
I don't think the crash was handled.
I want to raise my score by 1 point per second.
I want to move the character to the desired point through a touch event.
The default activity file contains the following:
package com.example.myapplication
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Rect
import android.media.MediaPlayer
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.view.MotionEvent
import android.view.View
import android.widget.Button
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import java.util.*
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
private lateinit var startButton: Button
private lateinit var character: View
private lateinit var scoreTextView: TextView
private lateinit var gameOverTextView: TextView
private lateinit var obstacleList: MutableList<View>
private lateinit var enemyList: List<View>
private lateinit var backgroundMusic: MediaPlayer
private lateinit var collisionSound: MediaPlayer
private lateinit var gameOverText: String
private lateinit var res: Resources
private val handlerThread = HandlerThread("MyHandlerThread")
private var timer = Timer(true)
private val enemyRect = Rect()
private val obstacleRect = Rect()
private lateinit var obstacleMovementTask: ObstacleMovementTask
private lateinit var moveEnemyTask: MoveEnemyTask
private lateinit var gameoverDetectionTask: GameoverDetectionTask
private lateinit var handler: Handler
private var gameover = false
private var score = 0
private var obstacleSpeed = 10
private var enemySpeed = 20
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
val screenHeight = Resources.getSystem().displayMetrics.heightPixels
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startButton = findViewById(R.id.start_button)
startButton.setOnClickListener {
startGame()
}
character = findViewById(R.id.character)
character.setOnTouchListener { _, event -> handleTouch(event) }
scoreTextView = findViewById(R.id.score_textview)
gameOverTextView = findViewById(R.id.game_over_textview)
obstacleList = mutableListOf()
enemyList = getEnemyList()
backgroundMusic = MediaPlayer.create(this, R.raw.background_music)
collisionSound = MediaPlayer.create(this, R.raw.collision_sound)
res = resources
gameOverText = res.getString(R.string.game_over_text)
handlerThread.start()
handler = Handler(handlerThread.looper)
obstacleMovementTask = ObstacleMovementTask()
moveEnemyTask = MoveEnemyTask()
gameoverDetectionTask = GameoverDetectionTask()
}
private fun handleTouch(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
character.x = event.x - character.width / 2f
character.y = event.y - character.height / 2f
}
}
return true
}
private fun View.jump() {
val jumpHeight = 400
val jumpDuration = 800L
val jumpHandler = Handler()
var jumpStartTime: Long = 0
var jumpStartY = 0f
jumpHandler.post(object : Runnable {
override fun run() {
if (jumpStartTime == 0L) {
jumpStartTime = System.currentTimeMillis()
jumpStartY = translationY
}
val elapsedTime = System.currentTimeMillis() - jumpStartTime
val t = elapsedTime.toFloat() / jumpDuration
val y = jumpHeight * 4 * (t - 0.5f) * (t - 0.5f)
translationY = jumpStartY - y
if (t < 1.0f) {
jumpHandler.postDelayed(this, 16)
} else {
jumpHandler.removeCallbacks(this)
}
}
})
}
private fun View.cancelAnimation() {
clearAnimation()
}
private val outRect = Rect()
private fun startGame() {
gameover = false
score = 0
scoreTextView.text = "Score: 0"
gameOverTextView.visibility = View.INVISIBLE
character.translationY = 0f
obstacleList.takeIf { it.size > 0 }?.forEachIndexed { index, obstacle ->
obstacle.translationX = screenWidth.toFloat()
enemyList[index].translationX = screenWidth.toFloat()
}
startButton.visibility = View.GONE
backgroundMusic.isLooping = true
backgroundMusic.start()
character.x = screenWidth / 4f
character.y = screenHeight / 2f
obstacleList = mutableListOf()
val obstacleColors = listOf(
Color.parseColor("#FF4081"),
Color.parseColor("#2196F3"),
Color.parseColor("#4CAF50"),
Color.parseColor("#9C27B0"),
Color.parseColor("#FF5722")
)
findViewById<RelativeLayout>(R.id.obstacles_container).apply {
for (i in 0..4) {
val obstacle = layoutInflater.inflate(R.layout.obstacle_layout, null)
obstacle.setBackgroundColor(obstacleColors[i])
addView(obstacle)
obstacleList.add(obstacle)
}
}
val obstacleMovementRunnable = object : Runnable {
override fun run() {
obstacleList.forEach {
it.translationX -= obstacleSpeed
it.getHitRect(obstacleRect)
character.getHitRect(outRect)
if (obstacleRect.intersect(outRect)) {
gameover = true
}
}
if (gameover) {
gameOver()
}
handler.postDelayed(this, 20)
}
}
val moveEnemyRunnable = object : Runnable {
override fun run() {
enemyList.forEach {
it.translationX -= enemySpeed
it.getHitRect(enemyRect)
character.getHitRect(outRect)
if (enemyRect.intersect(outRect)) {
gameover = true
}
}
}
}
val gameoverDetectionRunnable = object : Runnable {
override fun run() {
if (gameover) {
handler.removeCallbacks(obstacleMovementRunnable)
handler.removeCallbacks(this)
}
score += 1
scoreTextView.text = "Score: $score"
handler.postDelayed(this, 10)
}
}
handler.post(obstacleMovementRunnable)
handler.postDelayed(moveEnemyRunnable, 0)
handler.postDelayed(gameoverDetectionRunnable, 0)
}
private fun updateScore() {
scoreTextView.text = "Score: $score"
}
private fun stopGame() {
gameover = true
timer.cancel()
backgroundMusic.stop()
collisionSound.start()
gameOverTextView.text = "$gameOverText\nScore: $score"
gameOverTextView.visibility = View.VISIBLE
}
private fun getObstacleList(): List<View> {
val obstacleList = mutableListOf<View>()
val obstacleColors = mutableListOf<Int>()
obstacleColors.add(Color.parseColor("#FF4081"))
obstacleColors.add(Color.parseColor("#2196F3"))
obstacleColors.add(Color.parseColor("#4CAF50"))
obstacleColors.add(Color.parseColor("#9C27B0"))
obstacleColors.add(Color.parseColor("#FF5722"))
for (i in 0..4) {
val obstacle = layoutInflater.inflate(R.layout.obstacle_layout, null)
obstacle.setBackgroundColor(obstacleColors[i])
(findViewById<RelativeLayout>(R.id.obstacles_container)).addView(obstacle)
obstacleList.add(obstacle)
}
return obstacleList
}
private fun getEnemyList(): List<View> {
val enemyList = mutableListOf<View>()
for (i in 0..4) {
val enemy = layoutInflater.inflate(R.layout.enemy_layout, null)
val enemyView = enemy.findViewById<View>(R.id.enemy)
enemyList.add(enemyView)
}
return enemyList
}
private inner class ObstacleMovementTask : TimerTask() {
override fun run() {
if (!gameover) {
obstacleList.forEach { obstacle ->
runOnUiThread {
val screenHeight = Resources.getSystem().displayMetrics.heightPixels
val translationY = (0..(screenHeight - obstacle.height)).random().toFloat()
obstacle.translationY = translationY
}
}
} else {
obstacleList.forEach { it.cancelAnimation() }
}
}
}
private val characterHitBox: Rect by lazy {
val left = character.left
val top = character.top
val right = character.right
val bottom = character.bottom
Rect(left, top, right, bottom)
}
private inner class MoveEnemyTask : TimerTask() {
override fun run() {
runOnUiThread {
for (enemy in enemyList) {
enemy.getHitRect(enemyRect)
enemyRect.left -= enemySpeed
enemyRect.right -= enemySpeed
enemy.translationX -= enemySpeed
if (enemy.translationX < -enemy.width) {
enemy.translationX = screenWidth.toFloat()
enemy.translationY = (0..(screenHeight - enemy.height)).random().toFloat()
}
}
}
}
}
private inner class GameoverDetectionTask : TimerTask() {
override fun run() {
if (gameover) {
handler.post {
stopGame()
}
}
val characterRect = Rect(
character.left + character.width / 3,
character.top + character.height / 3,
character.right - character.width / 3,
character.bottom - character.height / 3
)
for (i in 0..4) {
enemyList[i].getHitRect(enemyRect)
if (characterRect.intersect(enemyRect)) {
runOnUiThread {
stopGame()
}
}
obstacleList[i].getHitRect(obstacleRect)
if (characterRect.intersect(obstacleRect)) {
runOnUiThread {
stopGame()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
handlerThread.quitSafely()
timer.cancel()
backgroundMusic.release()
collisionSound.release()
}
companion object {
private var screenWidth = 0
private var screenHeight = 0
}
}
I tried various functions such as collision processing, object movement, and score increase.
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
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!
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 was making Count-up Timer.
It works well when I press pause button or stop button, but the app freezes when I press start button.
I suspected that I was stuck in while loop but algorithm doesn't reach the codes before while loop.
What is wrong in my codes?
class MainActivity( var second : Int = 0 , var minute : Int = 0 , var hour : Int = 0 , var ongoing : Boolean = false ) : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initLayout()
}
private fun initLayout(): Unit {
val startButton: Button = findViewById(R.id.startButton)
val pauseButton: Button = findViewById(R.id.pauseButton)
val stopButton: Button = findViewById(R.id.stopButton)
var realTime : TextView = findViewById(R.id.Realtime)
startButton.setOnClickListener {
realTime.text = "0"//doesn't reach here
buttonOn()
countTime(realTime)
}
pauseButton.setOnClickListener {
buttonOff()
countTime(realTime)
}
stopButton.setOnClickListener {
buttonOff()
countTime(realTime)
}
}
private fun buttonOn(): Unit {
this.ongoing = true
}
private fun buttonOff(): Unit {
this.ongoing = false
}
private fun showTime(second: Int, minute: Int, hour: Int): String {
var secondShow: String = if (second < 10) "0$second" else second.toString()
var minuteShow: String = if (minute < 10) "0$minute" else minute.toString()
var hourShow: String = if (hour < 10) "0$hour" else hour.toString()
return "$hourShow:$minuteShow:$secondShow"
}
private fun countTime(textView: TextView): Unit {
var text = 0
textView.text = text.toString()
textView.text = showTime(this.second, this.minute, this.hour)
while (this.ongoing) {
Thread.sleep(1_000)
this.second++
if (this.second == 60) {
this.second = 0
this.minute++
if (this.minute == 60) {
this.minute = 0
this.hour++
}
}
textView.text = showTime(this.second, this.minute, this.hour)
//buttonOff reach here
}
}
}
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
}
}