I assume I'm missing something simple, but I can't seem to get joystick actions (X,Y,Z,RZ) from a game pad using View.OnGenericMotionListener in Android API 30. I followed an example (I thought) but joystick motions are undetected. If I replace OnGenericMotionListener and my onGenericMotion functions with OnTouchListener and onTouch I see exactly what I would expect from touches of the touchpad. In the code below I have reduced to a minimum to see the issue. With joystick = BasicMotion I do not get my debug log messages- I only get
I/ViewRootImpl#cc64931[MainActivity]: ViewPostIme key 1
If I change to joystick = BasicTouch I get my debug log.
What am I missing to get input from a game pad? main_activity and BasicMotion/BasicTouch classes below. Also note if I add the onGenericMotionEvent function to MainActivity, then I see joystick inputs there, so I know the game pad is working. Code is in Kotlin, but I'd be happy with a java solution.
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
// val joystick = BasicMotion(this)
val joystick = BasicTouch(this)
setContentView(joystick)
super.onCreate(savedInstanceState)
}
}
BasicMotion class:
class BasicMotion : SurfaceView, SurfaceHolder.Callback, View.OnGenericMotionListener {
constructor(context: Context?) : super(context) {
holder.addCallback(this)
setOnGenericMotionListener(this)
}
constructor(context: Context?, attributes: AttributeSet?) : super(context, attributes) {
holder.addCallback(this)
setOnGenericMotionListener(this)
}
constructor(context: Context?, attributes: AttributeSet?, style: Int) : super(
context, attributes, style
) {
holder.addCallback(this)
setOnGenericMotionListener(this)
}
override fun surfaceCreated(holder: SurfaceHolder) {}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
override fun onGenericMotion(v: View, event: MotionEvent): Boolean {
Log.d("Debug Motion", "detected")
return true
}
}
Equivalent BasicTouch class:
class BasicTouch : SurfaceView, SurfaceHolder.Callback, View.OnTouchListener {
constructor(context: Context?) : super(context) {
holder.addCallback(this)
setOnTouchListener(this)
}
constructor(context: Context?, attributes: AttributeSet?) : super(context, attributes) {
holder.addCallback(this)
setOnTouchListener(this)
}
constructor(context: Context?, attributes: AttributeSet?, style: Int) : super(
context, attributes, style
) {
holder.addCallback(this)
setOnTouchListener(this)
}
override fun surfaceCreated(holder: SurfaceHolder) {}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
override fun onTouch(v: View, event: MotionEvent): Boolean {
Log.d("Debug Touch", "detected")
return true
}
}
Related
I am trying to set up a canvas to redraw whenever the user clicks a button. Whenever I trigger the redraw via invalidate() the app freezes and crashes. I am very new to android development so I am struggling to see what I neglecting. Any insight would be appriciated.
The relevent classes are included below.
Thanks,
mainactivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun submit(view: View) {
val text = findViewById<View>(R.id.editTextNumberDecimal) as EditText
val canvas = findViewById<View>(R.id.customCanvas) as CustomCanvas
val value = text.text.toString()
val t = findViewById<View>(R.id.textView) as TextView
t.text = value
canvas.setWeightandRedraw(value.toFloat())
}
}
canvas
class CustomCanvas : View {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
}
var weight: Float = 0.0f;
override fun onDraw(canvas: Canvas?) {
drawBar(canvas);
drawWeightKg(canvas, this.weight)
}
fun setWeightandRedraw(_weight: Float) {
weight = _weight;
invalidate();
}
fun drawBar(canvas: Canvas?) {
.........
}
fun drawWeightKg(canvas: Canvas?, weight: Float) {
.........
}
}
Moving invalidate() to onDraw() appeard to fix this.
override fun onDraw(canvas: Canvas?) {
drawBar(canvas);
drawWeightKg(canvas, this.weight)
invalidate();
}
How to save scroll state of scrollview properly.In my code, I'm using :
scroll_x = scrollView.getScrollX();
scroll_y = scrollView.getScrollY();
when activity pause,i'm stored x and y as you can see here, and when activity start, i'm scroll scrollView to x and y.
But crux is (main problem) is, scrollview not scrollview to x and y properly, it scroll up or down a little bit automatically. How to fix it?
You can manage the instance state by using this class:
class SaveScrollNestedScrollViewer : NestedScrollView {
constructor(context: Context) : super(context)
constructor(context: Context, attributes: AttributeSet) : super(context, attributes)
constructor(context: Context, attributes: AttributeSet, defStyleAttr: Int) : super(context, attributes, defStyleAttr)
public override fun onSaveInstanceState(): Parcelable? {
return super.onSaveInstanceState()
}
public override fun onRestoreInstanceState(state: Parcelable?) {
super.onRestoreInstanceState(state)
}
}
use on your xml:
<yourClassNamePlace.SaveScrollNestedScrollViewer
android:id="#+id/my_scroll_viewer"
android:layout_width="match_parent"
android:layout_height="match_parent">
</yourClassNamePlace.SaveScrollNestedScrollViewer>
and then use in activity like this:
class MyActivity : AppCompatActivity() {
companion object {
var myScrollViewerInstanceState: Parcelable? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_activity)
if (myScrollViewerInstanceState != null) {
my_scroll_viewer.onRestoreInstanceState(myScrollViewerInstanceState)
}
}
public override fun onPause() {
super.onPause()
myScrollViewerInstanceState = my_scroll_viewer.onSaveInstanceState()
}
}
I want to use a centered spinner where the width of the spinner is only as wide as the selected item text. From my research it seems that this is not natively supported out of the box with an attribute so I found another StackOverflow question/answer and tried implementing that but ran into some issues with it.
So I took option 1 from this SO response and implemented it in Kotlin and It's not working for me
class DynamicWidthSpinner #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatSpinner(context, attrs, defStyleAttr) {
override fun setAdapter(adapter: SpinnerAdapter?) {
super.setAdapter(if (adapter != null) WrapperSpinnerAdapter(adapter) else null)
}
inner class WrapperSpinnerAdapter(val baseAdapter: SpinnerAdapter) : SpinnerAdapter {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getView(selectedItemPosition, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getDropDownView(position, convertView, parent)
}
override fun getCount(): Int = baseAdapter.count
override fun getItem(position: Int): Any = baseAdapter.getItem(position)
override fun getItemId(position: Int): Long = baseAdapter.getItemId(position)
override fun getItemViewType(position: Int): Int = baseAdapter.getItemViewType(position)
override fun getViewTypeCount(): Int = baseAdapter.viewTypeCount
override fun hasStableIds(): Boolean = baseAdapter.hasStableIds()
override fun isEmpty(): Boolean = baseAdapter.isEmpty
override fun registerDataSetObserver(observer: DataSetObserver) {
baseAdapter.registerDataSetObserver(observer)
}
override fun unregisterDataSetObserver(observer: DataSetObserver) {
baseAdapter.unregisterDataSetObserver(observer)
}
}
}
and in my MainActivity I'm doing this from onCreate
val spinner: DynamicWidthSpinner = findViewById(R.id.global_toolbar_location_spinner)
val tempLocationList = ArrayList<String>()
tempLocationList.add("Test1")
tempLocationList.add("Much longer test string 2")
spinner.adapter = ArrayAdapter(
this,
R.layout.global_toolbar_spinner_item,
tempLocationList
)
spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, arg3: Long) {
// TODO: do stuff on selection here
}
override fun onNothingSelected(arg0: AdapterView<*>) {
// TODO: do nothing... yet
}
}
spinner.setSelection(0)
and I am using my custom Spinner in the layout xml (ommitting everything else that is not necessary because I am able to get it work just fine using the native <Spinner> or androidx compat Spinner
<com.blablabla.app.ui.DynamicWidthSpinner
android:id="#+id/global_toolbar_location_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:spinnerMode="dialog"
/>
What I see is just the first item "Test1" and nothing happens when I tap on it and arrow seems to have disappeared now as well
I figured out the issue. Turns out that this is one of those cases where the #JvmOverloads doesn't work. Once I converted it to the multiple constructor kotlin syntax it worked without a problem
class DynamicWidthSpinner : AppCompatSpinner {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
...
}
I am so confused why no one uses the init block in a android custom view to initialize and inflate the view. lets take an example of how i do this:
class MyCompoundView : ConstraintLayout {
constructor(p0: Context) : super(p0)
constructor(p0: Context, p1: AttributeSet?) : super(p0, p1)
constructor(p0: Context, p1: AttributeSet?, p2: Int) : super(p0, p1, p2)
init {
inflate(context, R.layout.my_view_container, this)
//etc
}
}
is there anything wrong with this as opposed to what i see all over the internet:
class MyCompoundView : ConstraintLayout {
constructor(p0: Context) : super(p0){initialize()}
constructor(p0: Context, p1: AttributeSet?) : super(p0, p1){initialize()}
constructor(p0: Context, p1: AttributeSet?, p2: Int) : super(p0, p1, p2){initialize()}
private fun initialize() {
inflate(context, R.layout.ride_hail_otp_container, this)
}
}
ps. i do not favor jvmOverload in customViews so no need to mention that. just want to know about init block vs calling it in each constructor. i see no one doing it online and i wonder why ?
Yes, It's totally fine, I my self used this approach many times and ain't facing any problems.
one of the examples I'm currently working with :
class MaterialSearchBar (context: Context, val attributeSet: AttributeSet) : Toolbar(context, attributeSet) {
init {
inflate(context, R.layout.material_search_toolbar, this)
updateUi()
requestFocus()
setUpListeners()
}
//...
}
There's nothing wrong with inflating the view in the init block (I do it in my custom views) just like there is nothing wrong with #JvmOVerloads.
I'm having a difficult time trying how to figure out how to create a callback in Kotlin using lambdas. I have a custom TextInputEditText and I want to implement a function that the activity can call when text changes.
Here is my custom EditText:
class EditTextEx : TextInputEditText, TextWatcher {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// Call the callback onTextAvailable with the EditText's text (s.toString)
}
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
}
In my activity I want to have a callback that gets called when the onTextChanged event gets called. The callback in the custom control sends only the text back to the client. So in my activity, I want something like this:
editText.onTextAvailable(text -> do something )
It's actually quite easy to do, look:
inline fun EditText.onTextChanged(crossinline onTextChange: (String) -> Unit): TextWatcher {
val textWatcher = object: TextWatcher {
override fun afterTextChanged(editable: Editable) {
onTextChange(editable.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
this.addTextChangeListener(textWatcher)
return textWatcher
}
Now you can call
editText.onTextChanged { text -> /* do something */ }
In addition to the solution by #EpicPandaForce, there are a couple other solutions. If you want to stick with using a class as you've shown in your example, then you can do this:
class EditTextEx : TextInputEditText, TextWatcher {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private var mOnTextWatcherCallback: (m: String) -> Unit = {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (mOnTextWatcherCallback != null)
mOnTextWatcherCallback(s.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
fun onTextChange(callback: (text: String) -> Unit) {
mOnTextWatcherCallback = callback
}
}
Then in your activity create a function:
fun onTextChange(text: String) {
// Do something with the text.
}
And then setup your callback as follows:
my_edittext.onTextChange(::onTextChange)
This solution allows you to re-use the same onTextChange function for other controls that want to use it as well.
If you prefer to use an interface to define the callback, do this:
class EditTextEx : TextInputEditText, TextWatcher {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private var mOnTextWatcherCallback: ITextWatcher? = null
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (mOnTextWatcherCallback != null)
mOnTextWatcherCallback!!.onTextChanged(s.toString())
}
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
fun onTextChange(callback: ITextWatcher) {
mOnTextWatcherCallback = callback
}
}
Then in your activity, create the callback as follows:
val textChangeHandler = object: ITextWatcher {
override fun onTextChanged(text: String) {
var t = text
}
}
And then setup your callback for your edittext controls as follows:
my_edittext.onTextChange(textChangeHandler)
Try something like this:
fun getEditTextChange(editText: EditText, onTextChange: (String) -> Unit){
val tw = object: TextWatcher {
private var region = Locale.getDefault().language
override fun afterTextChanged(s: Editable?) {
onTextChange.invoke(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
editText.addTextChangedListener(tw)
}
Hope it helps