How do I make a draggable LimitLine in MPAndroidChart - android

I have managed to create a line chart using MP Android Chart, And want to create a draggable line to set a limit. So if the value crosses the line value, then the user gets an alert. My use case is similar to the Android system Data usage limit.
I came across LimitLines - https://github.com/PhilJay/MPAndroidChart/wiki/The-Axis and also dragging using touch event callbacks https://github.com/PhilJay/MPAndroidChart/wiki/Interaction-with-the-Chart
My question is whether I can add the limit line dynamically on the response to a translate (onChartTranslate) event so I can simulate the limit setting? Is this a better approach than trying to overload the MarkerView ?

I managed to create a draggable (horizontal) LimitLine by using OnChartGestureListener to listen for begin and end of drag events:
chart.onChartGestureListener = object: OnChartGestureListener {
override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?) {
if (lastPerformedGesture == ChartTouchListener.ChartGesture.DRAG && limitLineDragEnabled) {
me?.let {
displayLimitLine(me)
}
limitLineDragEnabled = false
}
}
override fun onChartLongPressed(me: MotionEvent?) {
if (binding.chart.isFullyZoomedOut) {
limitLineDragEnabled = true
me?.let {
displayLimitLine(me)
}
}
}
}
and a ChartTouchListener to catch MotionEvents between drag begin and end (sadly, OnChartGestureListener wasn't able to help me there):
chart.onTouchListener = object: BarLineChartTouchListener(chart, chart.viewPortHandler.matrixTouch, 3f) {
#SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, me: MotionEvent?): Boolean {
if (limitLineDragEnabled) {
me?.let {
displayLimitLine(me)
}
}
return super.onTouch(v, me)
}
}

Related

AndroidTV IPTV change channel by number input

Need to change channel by numpad of remote controller but don't know correct method. Numbers can be pressed multiple times and need to wait for all of them and get 1 String like "123".
Now I override onKey up like below
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return when(keyCode){
KeyEvent.KEYCODE_1->{
//I don't know how to wait here next key up and get correct full channel number
true
}
KeyEvent.KEYCODE_2->{
...
true
}
//EPG
KeyEvent.KEYCODE_3->{
...
true
}
...
...
else -> super.onKeyUp(keyCode, event)
}
}
To perform searching you need first to collect all digits of the number in range of some time, you can use StringBuilder to append one digit inside onKeyUp or OnKeyDown depend on your requirements
You need to delay performing search until the user write the full number, you can use CountDownTimer and reset the time every time the use write new digit (You can also create a progress bar represent the timer and update it) or you can use simple Timer
When the time is finish you should perform the search operation and clear the last number in StringBuilder and Update UI
val channelNumber = StringBuilder()
val numberSearchTimer : CountDownTimer?
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return when(keyCode){
KeyEvent.KEYCODE_1 -> {
cannelNumber.append("1")
perfomNumberSearch()
true
}
KeyEvent.KEYCODE_2 -> {
cannelNumber.append("2")
perfomNumberSearch()
true
}
...
else -> super.onKeyUp(keyCode, event)
}
}
private fun perfomNumberSearch() {
// Update UI With the new number
binding.searchChannelNumber.text = channelNumber.toString()
// Cancel the current time of it exists
if (numberSearchTimer != null) numberSearchTimer.cancel()
numberSearchTimer = object : CountDownTimer(1000, 100) {
override fun onTick(millisUntilFinished: Long) {
// Update UI With a progress until it perform search
// or replace CountDownTimer with Timer
}
override fun onFinish() {
changeChannelByNumber(channelNumber.toString())
// Clear the last search number after performing it
channelNumber.clear()
binding.searchChannelNumber.text = channelNumber.toString()
}
}.start()
}

"Parameter specified as non-null is null" when using GestureDetector.SimpleOnGestureListener() onScroll() function

I am using the GestureDetector.SimpleOnGestureListener() to react to UserGestures and expand a BottomSheet.
The problem is that a small percentage of the users experience the following crash. ( stacktrace stripped to make the question more readable)
Fatal Exception: java.lang.NullPointerException: Parameter specified
as non-null is null: method
kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter e1
at com.app.ui.fragments.HomeFragment$MyGestureListener.onScroll(:2)
at android.view.GestureDetector.onTouchEvent(GestureDetector.java:784)
at androidx.core.view.GestureDetectorCompat$GestureDetectorCompatImplJellybeanMr2.onTouchEvent(GestureDetectorCompat.java:484)
at androidx.core.view.GestureDetectorCompat.onTouchEvent(GestureDetectorCompat.java:548)
at com.app.ui.fragments.HomeFragment.onCreateView$lambda-24(HomeFragment.kt:427)
at com.app.ui.fragments.HomeFragment.$r8$lambda$UnvNeUUw-6dzbc2zrdFAHiK2duI()
at com.app.ui.fragments.HomeFragment$$ExternalSyntheticLambda35.onTouch(:2)
at android.view.View.dispatchTouchEvent(View.java:15072)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3917)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:3581)
And this is my implementation of the class.
inner class MyGestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onScroll(
e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float
): Boolean {
val diffY: Float = (e1.y.let { e2.y.minus(it) } ?: 0).toFloat()
if (diffY < 0) {
Log.d("====ON SCROLL====", "On Scroll top")
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
binding.root.setOnClickListener(null)
}
return true
}
}
Used like this
val mDetector = GestureDetectorCompat(requireContext(), MyGestureListener())
binding.root.setOnTouchListener { v, event ->
mDetector.onTouchEvent(event)
v.performClick()
}
The screen in which this is used then display a bottomSheet dialog with a recyclerView which allows the user to scroll and press on the items inside the list. The crash usually occurs when users scroll in this recyclerView.
I can't seem to figure out what's wrong with the written code, since the function overriden must have matching signature, so making e1 nullable is not possible.
Any advice is appreciated.
EDIT
After more investigation, this issue occures only after updating the Target and Compile SDK to API level 33. This forced me to make the values non nullable. For previous API level 32 and lower, this code worked as intended and without crashes ( since e1 can be nullable).
inner class MyGestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onScroll(
e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float
): Boolean {
val diffY: Float = (e1.y.let { e2.y.minus(it) } ?: 0).toFloat()
if (diffY < 0) {
Log.d("====ON SCROLL====", "On Scroll top")
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
binding.root.setOnClickListener(null)
}
return true
}
}
I managed to find the issue causing the crash in my case. It was because of a standard Bottom Sheet ( not modal ) that was being displayed on top of a fragment that had this gesture listener set up.
As a possible workaround for now, disabling the listener when the Bottom Sheet is expanded seems to have solved the crash.
Hope this helps anyone else with this issue

How to define behavior from the object instance

I have an EditText that I overrode in order to detect clicks on the compound drawables.
class MyEditText
constructor(
context: Context,
attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText( context, attrs) {
override fun onTouchEvent(event: MotionEvent?): Boolean {
val result = super.onTouchEvent(event)
if (event != null) {
// All advice says to use ACTION_UP but that never gets here
if( event.action == MotionEvent.ACTION_DOWN ){
if(event.x <= this.totalPaddingStart){
onStartDrawableClick()
}
if(event.x >= this.width - this.totalPaddingEnd){
onEndDrawableClick()
}
performClick()
return true
}
}
return result
}
// Overriding this avoids an accessibility warning
override fun performClick(): Boolean {
return super.performClick()
}
public fun onEndDrawableClick(){
Log.e(TAG, "onEndDrawableClick: ")
}
public fun onStartDrawableClick(){
Log.e(TAG, "onStartDrawableClick: ")
}
}
This works but I want to be able to define what happens in onEndDrawableClick() from the MyEditText object instance, not in the class. I cant pass a closure to the constructor since its a view with params for xml instantiation. Is there an elegant way to do this?
(Extra bonus points for figuring out why ACTION_UP is never seen)
You can define callback properties that can be set from outside the class. Also, you can make the MotionEvent parameter non-nullable since the Android function will never pass you a null value. Then you don't have to do the null check.
Also, if you don't want other click events to happen (like if you set a click listener on the TextView) when you click on this item, you should not call super when the icon is clicked. And you should call through when the touch misses the icon instead of returning true. Example of rearranging the logic like this below.
class MyEditText(
context: Context,
attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText(context, attrs) {
var onEndDrawableClick: (()->Unit)? = null
var onStartDrawableClick: (()->Unit)? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
if( event.action == MotionEvent.ACTION_DOWN ){
if(event.x <= this.totalPaddingStart){
onStartDrawableClick?.invoke()
performClick()
return true
}
if(event.x >= this.width - this.totalPaddingEnd){
onEndDrawableClick?.invoke()
performClick()
return true
}
}
return super.onTouchEvent(event)
}
// Overriding this avoids an accessibility warning
override fun performClick(): Boolean {
return super.performClick()
}
}
Handling a click that behaves as users are probably accustomed, i.e. has visual/audible feedback on touch down, is cancellable by moving your finger off the target before releasing, and firing only if released over the touch target; is not trivial. You might want to build this UI component out of a RelativeLayout or ConstraintLayout that contains an EditText and two Buttons in it to provide a nicer experience.
Apologies for my rusty Kotlin, but something like this should work:
var endClickHandler: View.OnClickListener?
public fun onEndDrawableClick(){
Log.e(TAG, "onEndDrawableClick: ")
endClickHandler?.onClick(this)
}

Android KTX: how to override Kotlin added property extension

I am trying to override View.setRotation() method in Kotlin.
Since AndroidKTX already provided property extension "rotation", caller's can simply call
viewObject.rotation = 90.0f
to rotate the view.
However, I want to add some additional operation when user change the rotation, like
override fun setRotation(newRotation: Float) {
if (rotation == newRotation)
return
rotation = newRotation
doSomethingElse()
}
This will crash because of StackOverflow error.
So, I have to add some additional code to achieve the goal:
private var _rotation: Float = 0.0f
override fun setRotation(newRotation: Float) {
if (_rotation == newRotation) {
return
}
_rotation = newRotation
updateRotationInternally()
}
private fun updateRotationInternally() {
super.setRotation(_rotation)
doSomethingElse()
}
This works, but I wonder if there is some other more elegant way of doing this, like "override the property extension setter"?
I disagree with one aspect of your implementation: your return shortcut. You are assuming that calling setRotation() with the existing rotation value has no effect. It would not surprise me if that is true in the official Google version of View, but for all we know, that is not a safe assumption on some Oppo device running their modified version of Android 8.0. Try not to assume the behavior of stuff that you didn't write. If you want to skip doSomethingElse() when the old and new rotations are equal, that's fine.
I am guessing that your setRotation() functions are in some subclass of View. If so, and taking my above complaint into account, here's the simplest that I could come up with:
class Bar : View() {
override fun setRotation(f: Float) {
val needSomething = getRotation() != f
super.setRotation(f)
if (needSomething) doSomethingElse()
}
fun doSomethingElse() {
println("got here!")
}
}
My overall test code was done in a Kotlin scratchpad (outside of Android), so I tested with a fake View implementation and fake rotation extension property:
open class View {
private var r: Float = 0.0f
open fun setRotation(f: Float) {
r = f
}
fun getRotation() = r
}
var View.rotation: Float
get() = getRotation()
set(value) = setRotation(value)
class Bar : View() {
override fun setRotation(f: Float) {
val needSomething = getRotation() != f
super.setRotation(f)
if (needSomething) doSomethingElse()
}
fun doSomethingElse() {
println("got here!")
}
}
fun main() {
val bar = Bar()
bar.rotation = 15.0f
bar.rotation = 15.0f
}
If you run this in that scratchpad, you will see got here printed on the console once, showing that while we successfully updated the extension property twice, we skipped getSomethingElse() on the second call, as the old and new rotation were the same.

What methods do i use to implement Android Gestures like "Tap , Double Tap, Hold, Drag , Flick, Swipe " in Touch mode

I was implementing Different Gestures in Android.
I want to know what methods do I use for implementing different gestures like "Tap,Double Tap,Hold,Drag,Flick,Swipe" etc
Here is the Kotlin example that I have implemented
private val gestureDetector by lazy {
GestureDetector(applicationContext, object : GestureDetector.SimpleOnGestureListener() {
override fun onLongPress(e: MotionEvent?) {
//Your action onLongPress
super.onLongPress(e)
}
// here you can implement other function like onDoubleTap ....
})
}
To use this variable you should use #setOnTouchListener() as #ADM suggested
your_view.setOnTouchListener { _, event ->
gestureDetector.onTouchEvent(event)
return#setOnTouchListener true
}
Hope it helps, let me know if you want this code in Java

Categories

Resources