I try to subclass EditText for convenience reasons (NumberEdit) using kotlin but the rendered View loses most of the EditText properties. The look is that of a TextView and it is not focusable with the mouse (in the emulator). When I click into the activity I can then edit the first of the NumberEdit widgets and can cycle to the next one with the tab key.
I added two emulator screenshots to illustrate the difference.
An EditText looks like this
The new NumberEdit looks like this
The extended class looks like this:
import android.content.Context
import android.text.InputType
import android.util.AttributeSet
import android.widget.EditText
class EditNumber(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int, defStyleRes: Int)
: EditText(context, attributeSet, defStyleAttr, defStyleRes) {
constructor(context: Context) : this(context, null, 0, 0)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0, 0)
constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int)
: this(context, attributeSet, defStyleAttr, 0)
init {
inputType = InputType.TYPE_CLASS_NUMBER + InputType.TYPE_NUMBER_FLAG_DECIMAL
}
}
Does anyone have a clue what I am doing wrong? Do I have to reference some attributes explicitly?
I'm not a kotlin expert but if you look at the java source code for edittext you have following:
public class EditText extends TextView {
public EditText(Context context) {
this(context, null);
}
public EditText(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.editTextStyle);
}
public EditText(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
It doesn't look like you pass the right parameters to the constructor... You pass a lot of 0s and nulls...
In Kotlin you can write this much more concise:
class EditNumber #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = R.attr.editTextStyle,
defStyleRes: Int = 0
) : EditText(context, attrs, defStyle, defStyleRes)
Notice the defStyle parameter.
Related
I'm making my custom view. Before I add constructor contain defStyleRes, the original constructor is primary constructor. so I can use attrs at init. How can I use it now?
class StoreTotalPanel : RelativeLayout {
#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)
init {
LayoutInflater.from(context)
.inflate(layout.panel_store_total, this, true)
attrs?.let { // <- Here is error
val typedArray = context.obtainStyledAttributes(it, R.styleable.custom_card_view)
val myString = typedArray.getString(R.styleable.custom_card_view_command)
}
}
...
}
It is because init relies on the primary constructor. Since there is none, it fails to find attrs. The solution is to replace init with your own function, which is called by both secondary connstructors:
class StoreTotalPanel : RelativeLayout {
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
LayoutInflater.from(context).inflate(layout.panel_store_total, this, true)
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.custom_card_view)
val myString = typedArray.getString(R.styleable.custom_card_view_command)
}
}
}
Be aware that inflate() returns a view which you do not use yet; the layout will be empty with this code snippet.
Have you tried to create a val on constructor itself?
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
private val defStyleRes: Int)
: super(context, attrs, defStyleAttr, defStyleRes)
I am trying to make a class extends from Vertical linerLayout and holds two views for ex ImageView and EditText how I can add and control the two views in the parent class
class GoogleSearchBar : LinearLayout {
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr:
Int) : super(context, attrs, defStyleAttr) {
}
}
class GoogleSearchBar : LinearLayout {
constructor(context: Context) : super(context) {
setupView()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
setupView()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr:
Int) : super(context, attrs, defStyleAttr) {
setupView()
}
private void setupView(){
View view = inflater.inflate(R.layout.my_view, null, false);
addlayout.addView(view);
}
}
create xml layout with name my_view.
I want to use library which is in Java and it has alot of errors so I'm trying to change it to Kotlin. And AndroidStudio is not converting Java to Kotlin properly so I have to do it function by function and check it manually. But these 3 constructors gives error:
Error: None of these following functions can be called with the arguments supplied
Java:
public class CountryCodePicker extends RelativeLayout
...
public CountryCodePicker(Context context) {
super(context);
if (!isInEditMode()) init(null);
}
public CountryCodePicker(Context context, AttributeSet attrs) {
super(context, attrs);
if (!isInEditMode()) init(attrs);
}
public CountryCodePicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (!isInEditMode()) init(attrs);
}
Kotlin:
class CountryCodePicker: RelativeLayout
...
constructor(context: Context): this{
super(context)
if (!isInEditMode) init(null)
}
constructor(context: Context, attrs: AttributeSet): this{
super(context, attrs)
if (!isInEditMode) init(attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): this{
super(context, attrs, defStyleAttr)
if (!isInEditMode) init(attrs)
}
class CountryCodePicker: RelativeLayout {
constructor(context: Context) : super(context) {
if (!isInEditMode) init(null)
}
constructor(context: Context, attrs: AttributeSet): super(context, attrs){
if (!isInEditMode) init(attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
if (!isInEditMode) init(attrs)
}
}
You should use JvmOverloads for this:
class CountryCodePicker #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
init {
if (!isInEditMode()) {
init(attrs) // or better inline the function body
}
}
}
The JvmOverloads will create the 3 constructors with the default values as defined. That is the behavior you would expect and makes the code cleaner.
I have the following question, if I have a class extending a LinearLayout like:
public class ExtendedSeekbarLayout extends LinearLayout { ..}
and I would like to pass additional Arguments to my Layout, how do I do this? I know that I could have the following constructors like:
public ExtendedSeekbarLayout (Context context) {
super(context);
}
public ExtendedSeekbarLayout (Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public ExtendedSeekbarLayout (Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
}
but I would like to have something like:
public ExtendedSeekbarLayout (Context context, AttributeSet attributeSet, int defStyle, int position) {
super(context, attributeSet, defStyle);
init(position);
}
I'm not sure if this is possible, if not, which would be the way to go then?
Thanks a lot and cheers, pingu
This constructor that you shared should work exactly as you expect.
public ExtendedSeekbarLayout (Context context, AttributeSet attributeSet, int defStyle, int position) {
super(context, attributeSet, defStyle);
init(position);
}
Btw you don't necessarily need to have this constructor, as long as you call
super(context);
You can do this in case of programmatically instantiating a view:
public ExtendedSeekbarLayout (Context context, int position) {
super(context);
init(position);
}
But if you are talking about sending a custom value from xml, where you don't actually call a constructor, then you should look at this answer:
https://stackoverflow.com/a/7608739/2534007
Is there a way to set the minimum, maximum and default values of a NumberPicker from the XML Layout?
I'm doing it from within the Activity code:
np = (NumberPicker) findViewById(R.id.np);
np.setMaxValue(120);
np.setMinValue(0);
np.setValue(30);
XML is obviously more appropriate , because it defines property, not behaviour.
Is there a way to set these using the XML layout?
I had the same problem, this is how I solved it (according to the comment of MKJParekh):
I created my own NumberPicker-Class
package com.exaple.project;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.NumberPicker;
#TargetApi(Build.VERSION_CODES.HONEYCOMB)//For backward-compability
public class MyNumberPicker extends NumberPicker {
public MyNumberPicker(Context context) {
super(context);
}
public MyNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
processAttributeSet(attrs);
}
public MyNumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
processAttributeSet(attrs);
}
private void processAttributeSet(AttributeSet attrs) {
//This method reads the parameters given in the xml file and sets the properties according to it
this.setMinValue(attrs.getAttributeIntValue(null, "min", 0));
this.setMaxValue(attrs.getAttributeIntValue(null, "max", 0));
}
}
Now you can use this NumberPicker in your xml layout file
<com.exaple.project.myNumberPicker
android:id="#+id/numberPicker1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
max="100"
min="1" />
Thanks to MKJParekh for his useful comment
You can try this:
<NumberPicker
android:id="#+id/number_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:minValue="#{0}"
app:maxValue="#{120}"
app:value="#{30}"/>
This works as part of Android Databinding. So, you might want to set it to true in your app level build.gradle.
android {
...
dataBinding {
enabled true
}
}
Here is an updated version that follows the Android Docs
(and thus supports theming & Android Studio designer preview)
values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberPickerWithXml">
<attr name="maxValue" format="integer" />
<attr name="minValue" format="integer" />
<attr name="defaultValue" format="integer" />
</declare-styleable>
</resources>
NumberPickerWithXml.kt:
package com.example.library.ui
import android.content.Context
import android.util.AttributeSet
import android.widget.NumberPicker
import com.example.library.ui.R
class NumberPickerWithXml : NumberPicker {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
processXmlAttributes(attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
processXmlAttributes(attrs, defStyleAttr)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
processXmlAttributes(attrs, defStyleAttr, defStyleRes)
}
private fun processXmlAttributes(attrs: AttributeSet, defStyleAttr: Int = 0, defStyleRes: Int = 0) {
val attributes = context.theme.obtainStyledAttributes(attrs, R.styleable.NumberPickerWithXml, defStyleAttr, defStyleRes)
try {
this.minValue = attributes.getInt(R.styleable.NumberPickerWithXml_minValue, 0)
this.maxValue = attributes.getInt(R.styleable.NumberPickerWithXml_maxValue, 0)
this.value = attributes.getInt(R.styleable.NumberPickerWithXml_defaultValue, 0)
} finally {
attributes.recycle()
}
}
}
...or NumberPickerWithXml.java (untested):
package com.example.library.ui
import android.content.Context;
import android.util.AttributeSet;
import android.widget.NumberPicker;
import com.example.library.ui.R;
public class NumberPickerWithXml extends NumberPicker {
public NumberPickerWithXml(Context context) {
super(context);
}
public NumberPickerWithXml(Context context, AttributeSet attrs) {
super(context, attrs);
processXmlAttributes(attrs, 0, 0);
}
public NumberPickerWithXml(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
processXmlAttributes(attrs, defStyleAttr, 0);
}
public NumberPickerWithXml(Context context, AttributeSet attrs, int: defStyleAttr, int: defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
processXmlAttributes(attrs, defStyleAttr, defStyleRes);
}
private void processXmlAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TypedArray attributes = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.NumberPickerWithXml, defStyleAttr, defStyleRes);
try {
this.setMinValue(attributes.getInt(R.styleable.NumberPickerWithXml_minValue, 0));
this.setMaxValue(attributes.getInt(R.styleable.NumberPickerWithXml_maxValue, 0));
this.setValue(attributes.getInt(R.styleable.NumberPickerWithXml_defaultValue, 0));
} finally {
attributes.recycle();
}
}
}
Usage in your layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.example.library.ui.NumberPickerWithXml
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:defaultValue="30"
custom:maxValue="120"
custom:minValue="0" />
</LinearLayout>