Android Kotlin - How to extend ConstraintLayout? - android

I'd like my ConstaintLayout to carry extra additional properties, but I have trouble extending it. More precisely I have trouble putting correct constructor into
class myCL(): ConstraintLayout(???) {
}

To make sure you don't get any quirks in behavior, you should implement it like this.
class myCL: ConstraintLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?,
#AttrRes defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

The constructor you really need is the one with all arguments:
class myCL(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
ConstraintLayout(context, attrs, defStyleAttr) {
}
if you want to implement all three constructors without much hassle you can use #JvmOverloads and use sensible defaults.
class myCL #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
}
see https://developer.android.com/reference/android/support/constraint/ConstraintLayout

You can also do something like this to restict to only one super call --
class MyConstraintLayout: ConstraintLayout {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, -1)
constructor(context: Context, attrs: AttributeSet?, #AttrRes defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

Related

Extending TextView does not show text

I want to implement an OnLongClickListener to some of my TextViews, but I don't want to repeat the same code everywhere, so I want to extend TextView and implement the OnLongClickListener just once.
class LongClickToCopyTextView : TextView {
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
setOnLongClickListener {
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
val clip = ClipData.newPlainText(context?.packageName, text)
clipboard?.primaryClip = clip
true
}
}
}
The implementation of the listener is used copy the text of the TextView into the clipboard when a user long presses it.
The problem is the text of the custom TextView is not shown. But if I use regular TextView the text is displayed correctly.
XML
<com.dzboot.myips.custom.LongClickToCopyTextView
android:id="#+id/simNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="00"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
The issue with setting default parameters for defStyleAttr is, that the base class might do the same to actually handle styles and states. Your initialisation happens in init {} anyhow.
class LongClickToCopyTextView : TextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
setOnLongClickListener {
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
val clip = ClipData.newPlainText(context?.packageName, text)
clipboard?.primaryClip = clip
true
}
}
}
Also you might want to extend fro AppCompatTextView instead. It has some newer features backported.

How to create base class for custom views in Android using Kotlin?

I want to create a base class for all of my custom views. It can be a different type, for example, RelativeLayout or NavigationView.
So I've created abstract class with generics which is implementing the deepest class of these views which is connecting them - View. This is what I've got:
abstract class MyCustomView<VS : ViewState, V : View<VS>, P : Presenter<VS, V, *>>(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) { ... }
But the problem is that now I'm able to inherit only from the View. How can I build somehow generic that must be a child of the View so I could implement it and still to be able inside my base class to override methods like onAttachedToWindow?
Try to declare your class like this:
abstract class MyCustomView<VS : ViewState, V : View<VS>, P : Presenter<VS, V, *>>() : View{
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
(...)
}

How to extend android.widget.FrameLayout in Kotlin (RequiresApi 21 but I need use minSdk 19)

I'm trying to extend android.widget.FrameLayout in my class in kotlin. The problem is that since I upgraded to kotlin 1.2.21 I can't compile because the longest(4 arguments) constructor of FrameLayout requires minSdk 21, but my lib needs to work on api level 19 as well. I already gave up on using the #JvmOverloads (it seems to be buggy and Instant Run crashes) so I try to write the "long" code. Something like:
First way I tried:
class PullDownAnimationLayout : FrameLayout, Animator.AnimatorListener, PullDownAnimation {
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
super(context, attrs, defStyleAttr, defStyleRes)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?) :
this(context, attrs, 0)
constructor(context: Context) :
this(context, null)
override val MAX_PULL_HEIGHT_PX: Int
override val REFRESH_TRIGGER_HEIGHT_PX: Int
init {
initFoo(attrs) // ERROR: Unresolved reference: attrs
MAX_PULL_HEIGHT_PX = dpToPx(MAX_PULL_HEIGHT_DP)
REFRESH_TRIGGER_HEIGHT_PX = dpToPx(REFRESH_TRIGGER_HEIGHT_DP)
}
The problem in this version is that init doesn't see attrs: Unresolved reference: attrs
So here's a second way I tried by adding the 3-args constructor as primary constructor:
class PullDownAnimationLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
FrameLayout(context, attrs, defStyleAttr), Animator.AnimatorListener, PullDownAnimation {
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
super(context, attrs, defStyleAttr, defStyleRes) // ERROR: Primary constructor call expected
constructor(context: Context, attrs: AttributeSet?) :
this(context, attrs, 0)
constructor(context: Context) :
this(context, null)
override val MAX_PULL_HEIGHT_PX: Int
override val REFRESH_TRIGGER_HEIGHT_PX: Int
init {
initFoo(attrs)
MAX_PULL_HEIGHT_PX = dpToPx(MAX_PULL_HEIGHT_DP)
REFRESH_TRIGGER_HEIGHT_PX = dpToPx(REFRESH_TRIGGER_HEIGHT_DP)
}

Custom View constructor in Android 4.4 crashes on Kotlin, how to fix?

I have a custom view written in Kotlin using JvmOverloads that I could have default value.
class MyView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyle, defStyleRes)
All works fine in Android 5.1 and above.
However it crashes in 4.4, since the constructor in 4.4 doesn't have defStyleRes. How could I have that supported that in 5.1 and above I could have defStyleRes but not in 4.4, without need to explicitly having 4 constructors defined like we did in Java?
Note: The below would works fine in 4.4, but then we loose the defStyleRes.
class MyView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle)
Best way is to have your class this way.
class MyView : LinearLayout {
#JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
#TargetApi(Build.VERSION_CODES.LOLLIPOP) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
}
I got a way of doing so. Just overload the first 3 functions will do, leave the 4th one for Lollipop and above wrap with #TargetApi.
class MyView : LinearLayout {
#JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: super(context, attrs, defStyleAttr)
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int)
: super(context, attrs, defStyleAttr, defStyleRes)
}
Just define the constructors like this:
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
#TargetApi(21)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)

How to access constructor argument that's not member variable in init function?

I have a custom layout as below
class CustomComponent : FrameLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
initAttrs(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initAttrs(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
initAttrs(attrs)
}
init {
LayoutInflater.from(context).inflate(R.layout.view_custom_component, this, true)
}
fun initAttrs(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.custom_component_attributes, 0, 0)
my_title.text = resources.getText(typedArray
.getResourceId(R.styleable.custom_component_attributes_custom_component_title, R.string.component_one))
typedArray.recycle()
}
}
Now for each constructor, I have to explicitly call initAttrs(attrs) as I can't find way to access attrs in my init function.
Is there a way I could access attrs in init function, so I could call initAttrs(attrs) from init instead of having to explicitly call it in each of the constructor?
Use a constructor with default arguments:
class CustomComponent #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : FrameLayout(context, attrs, defStyle) {
fun init {
// Initialize your view
}
}
The #JvmOverloads annotation tells Kotlin to generate three overloaded constructors so they can be called in Java as well.
In your init function, attrs becomes available as a nullable type:
fun init {
LayoutInflater.from(context).inflate(R.layout.view_custom_component, this, true)
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.custom_component_attributes, 0, 0)
my_title.text = resources.getText(typedArray
.getResourceId(R.styleable.custom_component_attributes_custom_component_title, R.string.component_one))
typedArray.recycle()
}
}
Note the usage of it in the let block.

Categories

Resources