I have custom view of scrollable header with tabs (4 items). I want to add contentDesciption for that like it is with CheckBox, phone automatically recognizes when checkBox is selected and says "Selected" or "Unselected"
Is it possible to implement it on custom view? I tried to inherit from Checkable() and override setChecked(), isChecked() functions but it doesn't work.
class ProductTabs #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr)
{}
I tried to use AccessibilityDelegate
binding.tabLabels.setAccessibilityDelegate(object : AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfo
) {
// Let the default implementation populate the info.
super.onInitializeAccessibilityNodeInfo(host, info)
// Set some other information.
info.isEnabled = host.isEnabled
host.contentDescription = "selected"
}
override fun sendAccessibilityEvent(host: View?, eventType: Int) {
super.sendAccessibilityEvent(host, eventType)
host?.contentDescription = "selected"
}
})
Related
I have a custom view that is initially not visible. However this is determined with a binding adapter. My question as simple as it sounds, how do I write a test to check if the view is visible when the binding adapter method is called?
For example, this is my custom view:
class MyView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
fun setVisibile(visible: Boolean) {
this.visibility = if (visible) VISIBLE else GONE
}
}
And this is the binding adapter method :
#BindingAdapter(“visible)
#JvmStatic
fun setVisible(myView: MyView, someObject: SomeObject?) {
// Some checking on someObject
// ....
myView.setVisible(someObject.someCriteria())
}
I'm a library author and have to intercept all touch events of child views, by overriding ViewGroup.onInterceptTouchEvent().
First I wrote the following code (simplified):
interface touchIntercepter {
// my library set this field to intercept touch event
var touchHandler: ((MotionEvent) -> Boolean)?
}
class LinearLayoutTouchIntercepter #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
)
: touchIntercepter
, LinearLayout(context, attrs, defStyleAttr, defStyleRes)
{
override var touchHandler: ((MotionEvent) -> Boolean)? = null
override fun onInterceptTouchEvent(event: MotionEvent) = touchHandler?.invoke(event) ?: false
}
Library users can use the LinearLayoutTouchInterceptor in their layout xml file instead of standard LinearLayout and then my library code can intercept touch event of the user layout's child views by touchIntercepter interface.
I think it is wonderful if there's something like ViewGroup.setOnInterceptTouchListener(), like View.setOnClickListener(), but I found that there isn't.
Now the problem is, I want to provide the same functionality for RelativeLayout, FrameLayout and other ViewGroup descendants.
For example,
class RelativeLayoutTouchIntercepter #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
)
: touchIntercepter
, RelativeLayout(context, attrs, defStyleAttr, defStyleRes)
{
override var touchHandler: ((MotionEvent) -> Boolean)? = null
override fun onInterceptTouchEvent(event: MotionEvent) = touchHandler?.invoke(event) ?: false
}
As you can see, all code is the same but the only difference is inheriting XXXXXLayout instead of LinearLayout. I don't want to copy and paste them but have no idea how to reduce the duplication.
It seems that Kotlin generics are not helping in this case while C++ template perfectly can help like this pseudo code :
template <typename T>
class TouchInterceptorTmpl : public T
{
void onInterceptTouchEvent() override;
};
using RelativeLayoutTouchInterceptor = TouchInterceptorTmpl<RelativeLayout>;
using FrameLayoutTouchInterceptor = TouchInterceptorTmpl<FrameLayout>;
No way to do like this in Kotlin?
You can reduce duplication a little bit by making a concrete implementation of your interface and using it as a delegate. Unfortunately, you can't avoid overriding onInterceptTouchEvent in each implementation due to how inheritance works, but you can make an extension function for your interface to shorten that code a bit.
Note, interface names in Kotlin are capitalized by convention.
Setup:
interface TouchInterceptor {
var touchInterceptionHandler: ((MotionEvent) -> Boolean)?
}
class TouchInterceptorImpl: TouchInterceptor {
override var touchInterceptionHandler: ((MotionEvent) -> Boolean)? = null
}
fun TouchInterceptor.intercept(event: MotionEvent): Boolean = touchInterceptionHandler?.invoke(event) ?: false
Usage:
class RelativeLayoutTouchIntercepter #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
)
: TouchInterceptor by TouchInterceptorImpl()
, RelativeLayout(context, attrs, defStyleAttr, defStyleRes)
{
override fun onInterceptTouchEvent(event: MotionEvent): Boolean = intercept(event)
}
I have recyclerView with and item as seen below:
Now I want to be able to click anywhere on the item and editText should come into focus.
I can do that by setting onTouchListener on my view like this:
row_item.setOnTouchListener{ _, _ ->
editText.requestFocus()
view.background = Color.GREEN.toDrawable()
true
}
I also want to run some additional code whenever the item is clicked. Here I'm putting background color change for the sake of the example.
The problem is that whenever I click editText itself it is getting focused, but row_item touchListener is ignored, and the background doesn't change its color.
From my research, I've found that I should somehow intercept touch event. I thought I can do that by returning true in row_item.setOnTouchListener, but it doesn't work as you can see.
How can I intercept such touch event?
You should create a custom container class and then override onInterceptTouchEvent method and do your stuff there, then using this custom container class as the root of the item. Look at the following code:
class CustomFrameLayout : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
private var mOnInterceptTouchEventListener: OnTouchListener? = null
fun setOnInterceptTouchEventListener(onInterceptTouchEventListener: OnTouchListener) {
this.mOnInterceptTouchEventListener = onInterceptTouchEventListener
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if(mOnInterceptTouchEventListener != null && mOnInterceptTouchEventListener?.onTouch(this, ev) == true)
return true;
return super.onInterceptTouchEvent(ev)
}
}
And then adding this listener to your row_item
row_item.setOnInterceptTouchEventListener(object: View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
editText.requestFocus()
v?.background = Color.GREEN.toDrawable()
return false
}
})
it may not be the best answer but it works.
I wonder how I can achieve such behaviour of the RecyclerView, that I could scroll the list only when I click and drag only specific view within the ViewHolder?
I've disabled horizontal scroll by creating my own LinearLayoutManager:
class MyOwnLayoutManager : LinearLayoutManager {
constructor(context: Context?) : super(context)
constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
private var isScrollEnabled = false
fun setScrollEnabled(isEnabled: Boolean) {
isScrollEnabled = isEnabled
}
override fun canScrollVertically(): Boolean {
return isScrollEnabled && super.canScrollVertically()
}
override fun canScrollHorizontally(): Boolean {
return isScrollEnabled && super.canScrollHorizontally()
}
}
Then, I try to change the isScrollEnabled by setting the touch listener to the item's header:
item_header.setOnTouchListener { v, event ->
val isScrolling = event.action == ACTION_MOVE
onHeaderIsDragging.invoke(isScrolling)
false
}
Callback in the fragment that changes adapters' layout manager var:
private val onHeaderIsDragging: ((Boolean) -> Unit) = {
recyclerViewLayoutManager.setScrollEnabled(it)
}
By this implementation, I get MOTION_CANCEL after a couple of MOTION_MOVE events in the onTouchListener and RecyclerView is not scrollable after MOTION_MOVE events.
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)
...
}