If we are building a custom View, for example, something like this:
class FrameLayoutNormal: FrameLayout{
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) {
textView{
lparams(...)
}
}
we can't define lparams, because the compiler doesn't know who the parent is. If we wrap the textView inside a FrameLayout it works, and you scan specify a layout parameter. But in a custom view, the parent is itself. So how can we make the children be aware of that so we can use the extension?
Is there any way to get it working, besides extending from: _FrameLayout ?`
An old question, but since it is common ...
Applying the answer from https://github.com/Kotlin/anko/issues/267
I think you might want something like this:
class FrameLayoutNormal: AnkoComponent<Context> {
override fun createView(ui: AnkoContext<Context>): View {
return with(ui) {
frameLayout {
textView("Hello") {
}.lparams()
}
}
}
}
inline fun ViewManager.frameLayoutNormal(theme: Int = 0) = frameLayoutNormal(theme) {}
inline fun ViewManager.frameLayoutNormal(theme: Int = 0, init: View.(frameLayoutNormal: FrameLayoutNormal) -> Unit): View {
val fln = FrameLayoutNormal()
return ankoView({ fln.createView(AnkoContext.create(it))}, theme, {init(fln)})
}
This allows the component to be used in the ANKO DSL. One downside to this approach is that the custom component is a View, not a ViewGroup, and thus can not have additional children added outside of its definition. It is challenging/laborious to make a custom component which is a ViewGroup that can be used in ANKO DSL (if I understand correctly).
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 am implementing a rotary knob based on this library.
This View comes with a onStateChanged listener that tells me the current position of the knob.
val knob = findViewById<View>(R.id.knob) as Knob
knob.setOnStateChanged(object: Knob.OnStateChanged{
override fun onState(state: Int) {
// do stuff
}
})
In addition to that, I want to know when the user is no longer pressing/holding the knob (similar to a button release). I tried to achieve this with a onTouch listener.
knob.setOnTouchListener(object: View.OnTouchListener{
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
// do stuff
}
})
Problem: When I add a second onTouch listener, it is no longer possible to hold and rotate the view for some reason. I do not know whether this is a problem of this particular library or Android in general.
Any suggestions on how to implement the wanted features?
A view can only have a single OnTouchListener. Unfortunately, the author of that library implemented some of its functionality using an OnTouchListener, which prevents users from using an OnTouchListener.
An alternative for you would be to subclass Knob and override onMotionEvent, like this:
class MyKnob: Knob {
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)
override fun onTouchEvent(event: MotionEvent): Boolean {
val result = super.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_UP) {
// User released knob
}
return result
}
}
Then you'd need to use this class in your layout instead of the original Knob class.
I want to implement a custom view within some Fragments, and this view doesn't have any dependency on activity, this way I can share it as a common UI component to others.
But I have a problem when trying to use FragmentManager to manage those Fragments inside the custom view, how can I get a FragmentManager instance? Generally, we can get it from Activity, but in my case, what should I do would be more reasonable?
If your custom view is inflated by a fragment you have access to getParentFragmentManager there is more info here
But I have a problem when trying to use FragmentManager to manage those Fragments inside the custom view, how can I get a FragmentManager instance? Generally, we can get it from Activity, but in my case, what should I do would be more reasonable?
The best way to do it is to define an interface in the view to describe what events it has, then the fragment that hosts this view will implement this interface and actually handle it.
fun View.onClick(clickListener: (View) -> Unit) {
setOnClickListener(clickListener)
}
class MyView: 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)
interface Listener {
fun onSomeEvent(someData: SomeData)
}
var listener: Listener? = null
override fun onFinishInflate() {
super.onFinishInflate()
val binding = MyViewBinding.bind(this)
with(binding) {
someButton.onClick {
listener?.onSomeEvent(someData)
}
}
}
}
And then
class MyFragment: Fragment(R.layout.my_fragment), MyView.Listener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = MyFragmentBinding.bind(view)
with(binding) {
myView.listener = this#MyFragment
}
}
}
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)
}
With a Switch I can do this:
< Switch
android:id="#+id/normal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="end|center_vertical"
android:switchPadding="16dp"
android:thumbTextPadding="16dp"
android:text="Hello World" />
Pay attention to this lines:
android:switchPadding="16dp"
android:thumbTextPadding="16dp"
Now, I made a custom view that extends this Switch. I didn't made any special change:
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.SwitchCompat
class BetterSwitchCompat : SwitchCompat {
private var listener: OnCheckedChangeListener? = null
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 setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
if (listener != null) {
this.listener = listener
}
super.setOnCheckedChangeListener(listener)
}
fun setCheckedSilent(checked: Boolean) {
toggleListener(false)
isChecked = checked
toggleListener(true)
}
private fun toggleListener(value: Boolean) {
if (value) setOnCheckedChangeListener(listener)
else setOnCheckedChangeListener(null)
}
}
As you can see, there is nothing more than a business logic for check method.
Why I can't use the attributes I referred to before for the new class?
There are just a few posts about this, but none of them clarify what I am asking.
Android custom view inherit all styles and attributes from parent
How to make a custom view inherit it's parent's style
Switch isn't SwitchCompat. SwitchCompat comes from AndroidX so it uses application-defined attributes.
Try prefixing the attributes in question with app: instead of android:, such as app:switchPadding.