I want to implement a SeekBar that automatically updates a TextView, for the actual value, the maximum value and a minimum value. I derive from SeekBar and define 3 named attributes with the format being reference.
In the constructor, I get a TypedArray by calling obtainStyledAttributes(). TypedArray contains a lot of getters for every kind of attribute types. What I am missing is some kind of Object getReference() or int getReferenceId().
How do I obtain the value for a reference attribute?
Edit:
I have an attribute definition for a class MinMaxSlider, that looks like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MinMaxSlider">
<attr name="min" format="integer" />
<attr name="valueView" format="reference" />
</declare-styleable>
</resources>
a snipped out of the layout definition looks like this:
<LinearLayout
style="#style/ParameterLabelLayout"
android:orientation="horizontal">
<TextView
style="#style/ParameterSliderLabel"
android:text="min. Interval" />
<TextView
android:id="#+id/min_connection_interval_slider_value"
style="#style/ParameterSliderValue"/>
</LinearLayout>
<com.roche.rcpclient.MinMaxSlider
style="#style/ParameterSlider"
android:id="#+id/min_connection_interval_slider"
android:max="3200"
custom:valueView="#id/min_connection_interval_slider_value"
custom:min="1"/>
Here, the MinMaxSlider should reference one of the TextViews above to display its current value there.
From within the constructor of MinMaxSlider, I can lookup the min attributes value.
If I try to lookup the valueView attribute as an integer, I always get the default value (0), not R.id.min_connection_interval_slider as I would expect.
Edit: the right function to lookup a reference seems to be getResourceId. The obtained integer id can be used to use findViewById later, when the overall object hierarchy is constructed.
In my case, I register an OnSeekBarChangeListener() and lookup the View in the callback, when the callback is fired.
You can't receive reference via findViewById in constructor. Because it is not attached to layout yet.
Reference: https://developer.android.com/training/custom-views/create-view.html#applyattr
When a view is created from an XML layout, all of the attributes in
the XML tag are read from the resource bundle and passed into the
view's constructor as an AttributeSet. Although it's possible to read
values from the AttributeSet directly, doing so has some
disadvantages:
Resource references within attribute values are not resolved Styles
are not applied Instead, pass the AttributeSet to
obtainStyledAttributes(). This method passes back a TypedArray array
of values that have already been dereferenced and styled.
You can receive in another methods.
For example:
attrs.xml
<declare-styleable name="CustomTextView">
<attr name="viewPart" format="reference"></attr>
</declare-styleable>
yourLayout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<tr.com.ui.utils.CustomTextView
android:id="#+id/chat_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:focusableInTouchMode="false"
android:gravity="left"
android:text="test"
app:viewPart="#id/date_view" />
<TextView
android:id="#+id/date_view"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:gravity="right" />
</LinearLayout>
CustomTextView.java
public class CustomTextView extends TextView {
private View viewPart;
private int viewPartRef;
public CustomTextView(Context context) {
super(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, 0, 0);
try {
viewPartRef = a.getResourceId(R.styleable.CustomTextView_viewPart, -1);
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
viewPart = ((View) this.getParent()).findViewById(viewPartRef);
}
public View getViewPart() {
return viewPart;
}
public void setViewPart(View viewPart) {
this.viewPart = viewPart;
}}
You can decide your scenario and modify this code.
I have an ImageView subclass with custom states. onCreateDrawableState is not called when the widget is instantiated and the image graphic does not appear in my layout. Even if I call refreshDrawableState(), it does not work. I single stepped through the latter and the View code is expecting m_background to be already set (it's still null in my case).
What am I missing that would cause m_background to have an initial value?
values/attrs.xml
<resources>
<declare-styleable
name="toggle_states">
<attr name="state_left" format="boolean"/>
<attr name="state_right" format="boolean"/>
</declare-styleable>
</resources>
drawables/selector_toggle.xml
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/com.example.test" >
<item
myapp:state_left="true"
android:drawable="#drawable/toggle_left" />
<item
myapp:state_right="true"
android:drawable="#drawable/toggle_right" />
</selector>
Toggle.java
public class Toggle extends ImageView
{
private static final int[] STATE_LEFT = {R.attr.state_left};
private static final int[] STATE_RIGHT= {R.attr.state_right};
public enum State {LEFT, RIGHT};
private State state = State.LEFT;
public Toggle (Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public int[] onCreateDrawableState (int extraSpace)
{
final int[] drawableState = super.onCreateDrawableState (extraSpace + 1);
if (state == State.LEFT)
mergeDrawableStates (drawableState, STATE_LEFT);
else
mergeDrawableStates (drawableState, STATE_RIGHT);
return drawableState;
}
}
some_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/com.example.test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
...
<com.example.test.Toggle
android:id="#+id/toggle"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
myapp:state_left="true"
/>
...
</LinearLayout>
You've missed android:background="#drawable/selector_toggle.xml" in com.example.test.Toggle
<com.example.test.Toggle
android:id="#+id/toggle"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="#drawable/selector_toggle.xml"
myapp:state_left="true"
/>
I'd like to define custom attributes in Android fragment using XML (without using bundle additional parameters) like declare-styleable in custom controls. But there are no constructors with AttrSet parameters, so is it possible? Can i just override public void onInflate(android.app.Activity activity, android.util.AttributeSet attrs, android.os.Bundle savedInstanceState) in order to get attributes support?
The Link for Support4Demos is changed or can be changed so posting the complete solution. Here it goes.
Create attrs.xml file in res/values folder. Or add the below content if file already exists.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyFragment">
<attr name="my_string" format="string"/>
<attr name="my_integer" format="integer"/>
</declare-styleable>
Override the onInflate delegate of fragment and read attributes in it
/**
* Parse attributes during inflation from a view hierarchy into the
* arguments we handle.
*/
#Override
public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
super.onInflate(activity, attrs, savedInstanceState);
Log.v(TAG,"onInflate called");
TypedArray a = activity.obtainStyledAttributes(attrs,R.styleable.MyFragment);
CharSequence myString = a.getText(R.styleable.MyFragment_my_string);
if(myString != null) {
Log.v(TAG, "My String Received : " + myString.toString());
}
int myInteger = a.getInt(R.styleable.AdFragment_my_integer, -1);
if(myInteger != -1) {
Log.v(TAG,"My Integer Received :" + myInteger);
}
a.recycle();
}
Pass these attributes in your layout file as following. Just an example
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is android activity" />
<fragment
android:id="#+id/ad_fragment"
android:name="com.yourapp.packagename.MyFragment"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
app:my_string="Hello This is HardCoded String. Don't use me"
app:my_integer="30" />
</RelativeLayout>
Thats all. Its a working solution.
While doing this if you see any namespace error in xml.
try project cleaning again and again.
This is pathetic but Eclipse and adt misbehaves sometimes.
#Override
public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
super.onInflate(activity, attrs, savedInstanceState);
// Your code here to process the attributes
}
I've got a class that extends a button. I want to add an additional attribute to it. Is there any way to initialize that attribute from XML? For example:
<MyButton android:id="#+id/myBtn" myValue="Hello World"></MyButton>
public class MyButton extends Button{
private String myValue = "";
public CalcButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
Yes. You can have custom attributes and assign values to it in XML. You can even assign methods to handle custom events on your widgets. Long press definition at XML layout, like android:onClick does
The needed steps.
Implement your custom widget.
Then add file attrs.xml in res/values/ with following content:
<xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyButton">
<attr name="myValue" format="string"/>
</declare-styleable>
</resources>
Retrieve values from XML in MyButton constructor. Remember to implement all constructors, not only one.
Use new attribute in your layout xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/**your.package**"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<your.package.MyButton
android:id="#+id/theId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:myValue="myDoSomething"
/>
<!-- Other stuff -->
</LinearLayout>
I am trying to create a custom View that would replace a certain layout that I use at multiple places, but I am struggling to do so.
Basically, I want to replace this:
<RelativeLayout
android:id="#+id/dolphinLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#drawable/background_box_light_blue"
android:padding="10dip"
android:layout_margin="10dip">
<TextView
android:id="#+id/dolphinTitle"
android:layout_width="200dip"
android:layout_height="100dip"
android:layout_alignParentLeft="true"
android:layout_marginLeft="10dip"
android:text="#string/my_title"
android:textSize="30dip"
android:textStyle="bold"
android:textColor="#2E4C71"
android:gravity="center"/>
<Button
android:id="#+id/dolphinMinusButton"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_toRightOf="#+id/dolphinTitle"
android:layout_marginLeft="30dip"
android:text="#string/minus_button"
android:textSize="70dip"
android:textStyle="bold"
android:gravity="center"
android:layout_marginTop="1dip"
android:background="#drawable/button_blue_square_selector"
android:textColor="#FFFFFF"
android:onClick="onClick"/>
<TextView
android:id="#+id/dolphinValue"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_marginLeft="15dip"
android:background="#android:drawable/editbox_background"
android:layout_toRightOf="#+id/dolphinMinusButton"
android:text="0"
android:textColor="#2E4C71"
android:textSize="50dip"
android:gravity="center"
android:textStyle="bold"
android:inputType="none"/>
<Button
android:id="#+id/dolphinPlusButton"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_toRightOf="#+id/dolphinValue"
android:layout_marginLeft="15dip"
android:text="#string/plus_button"
android:textSize="70dip"
android:textStyle="bold"
android:gravity="center"
android:layout_marginTop="1dip"
android:background="#drawable/button_blue_square_selector"
android:textColor="#FFFFFF"
android:onClick="onClick"/>
</RelativeLayout>
By this:
<view class="com.example.MyQuantityBox"
android:id="#+id/dolphinBox"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:myCustomAttribute="#string/my_title"/>
So, I do not want a custom layout, I want a custom View (it should not be possible for this view to have child).
The only thing that could change from one instance of a MyQuantityBox to another is the title. I would very much like to be able to specify this in the XML (as I do on the last XML line)
How can I do this? Should I put the RelativeLayout in a XML file in /res/layout and inflate it in my MyBoxQuantity class? If yes how do I do so?
Thanks!
A bit old, but I thought sharing how I'd do it, based on chubbsondubs' answer:
I use FrameLayout (see Documentation), since it is used to contain a single view, and inflate into it the view from the xml.
Code following:
public class MyView extends FrameLayout {
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyView(Context context) {
super(context);
initView();
}
private void initView() {
inflate(getContext(), R.layout.my_view_layout, this);
}
}
Here is a simple demo to create customview (compoundview) by inflating from xml
attrs.xml
<resources>
<declare-styleable name="CustomView">
<attr format="string" name="text"/>
<attr format="reference" name="image"/>
</declare-styleable>
</resources>
CustomView.kt
class CustomView #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
ConstraintLayout(context, attrs, defStyleAttr) {
init {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
View.inflate(context, R.layout.custom_layout, this)
val image_thumb = findViewById<ImageView>(R.id.image_thumb)
val text_title = findViewById<TextView>(R.id.text_title)
val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
try {
val text = ta.getString(R.styleable.CustomView_text)
val drawableId = ta.getResourceId(R.styleable.CustomView_image, 0)
if (drawableId != 0) {
val drawable = AppCompatResources.getDrawable(context, drawableId)
image_thumb.setImageDrawable(drawable)
}
text_title.text = text
} finally {
ta.recycle()
}
}
}
custom_layout.xml
We should use merge here instead of ConstraintLayout because
If we use ConstraintLayout here, layout hierarchy will be ConstraintLayout->ConstraintLayout -> ImageView + TextView => we have 1 redundant ConstraintLayout => not very good for performance
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.support.constraint.ConstraintLayout">
<ImageView
android:id="#+id/image_thumb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ContentDescription"
tools:src="#mipmap/ic_launcher" />
<TextView
android:id="#+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="#id/image_thumb"
app:layout_constraintStart_toStartOf="#id/image_thumb"
app:layout_constraintTop_toBottomOf="#id/image_thumb"
tools:text="Text" />
</merge>
Using
activity_main.xml
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<your_package.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#f00"
app:image="#drawable/ic_android"
app:text="Android" />
<your_package.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0f0"
app:image="#drawable/ic_adb"
app:text="ADB" />
</LinearLayout>
Result
See full code on:
Github
Yes you can do this. RelativeLayout, LinearLayout, etc are Views so a custom layout is a custom view. Just something to consider because if you wanted to create a custom layout you could.
What you want to do is create a Compound Control. You'll create a subclass of RelativeLayout, add all our your components in code (TextView, etc), and in your constructor you can read the attributes passed in from the XML. You can then pass that attribute to your title TextView.
http://developer.android.com/guide/topics/ui/custom-components.html
Use the LayoutInflater as I shown below.
public View myView() {
View v; // Creating an instance for View Object
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.myview, null);
TextView text1 = v.findViewById(R.id.dolphinTitle);
Button btn1 = v.findViewById(R.id.dolphinMinusButton);
TextView text2 = v.findViewById(R.id.dolphinValue);
Button btn2 = v.findViewById(R.id.dolphinPlusButton);
return v;
}
In practice, I have found that you need to be a bit careful, especially if you are using a bit of xml repeatedly. Suppose, for example, that you have a table that you wish to create a table row for each entry in a list. You've set up some xml:
In my_table_row.xml:
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="#+id/myTableRow">
<ImageButton android:src="#android:drawable/ic_menu_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="#+id/rowButton"/>
<TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="TextView" android:id="#+id/rowText"></TextView>
</TableRow>
Then you want to create it once per row with some code. It assume that you have defined a parent TableLayout myTable to attach the Rows to.
for (int i=0; i<numRows; i++) {
/*
* 1. Make the row and attach it to myTable. For some reason this doesn't seem
* to return the TableRow as you might expect from the xml, so you need to
* receive the View it returns and then find the TableRow and other items, as
* per step 2.
*/
LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.my_table_row, myTable, true);
// 2. Get all the things that we need to refer to to alter in any way.
TableRow tr = (TableRow) v.findViewById(R.id.profileTableRow);
ImageButton rowButton = (ImageButton) v.findViewById(R.id.rowButton);
TextView rowText = (TextView) v.findViewById(R.id.rowText);
// 3. Configure them out as you need to
rowText.setText("Text for this row");
rowButton.setId(i); // So that when it is clicked we know which one has been clicked!
rowButton.setOnClickListener(this); // See note below ...
/*
* To ensure that when finding views by id on the next time round this
* loop (or later) gie lots of spurious, unique, ids.
*/
rowText.setId(1000+i);
tr.setId(3000+i);
}
For a clear simple example on handling rowButton.setOnClickListener(this), see Onclicklistener for a programmatically created button.
There are multiple answers which point the same way in different approaches, I believe the below is the simplest approach without using any third-party libraries, even you can use it using Java.
In Kotlin;
Create values/attr.xml
<resources>
<declare-styleable name="DetailsView">
<attr format="string" name="text"/>
<attr format="string" name="value"/>
</declare-styleable>
</resources>
Create layout/details_view.xml file for your view
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text_label"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.5"
tools:text="Label" />
<TextView
android:id="#+id/text_value"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.5"
tools:text="Value" />
</LinearLayout>
</merge>
Create the custom view widget DetailsView.kt
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.payable.taponphone.R
class DetailsView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val attributes: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailsView)
private val view: View = View.inflate(context, R.layout.details_view, this)
init {
view.findViewById<TextView>(R.id.text_label).text = attributes.getString(R.styleable.DetailsView_text)
view.findViewById<TextView>(R.id.text_value).text = attributes.getString(R.styleable.DetailsView_value)
}
}
That's it now you can call the widget anywhere in your app as below
<com.yourapp.widget.DetailsView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:text="Welcome"
app:value="Feb" />
A simple Custom View using Kotlin
Replace FrameLayout with whatever view you Like to extend
/**
* Simple Custom view
*/
class CustomView#JvmOverloads
constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: FrameLayout(context, attrs, defStyleAttr) {
init {
// Init View
val rootView = (getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
.inflate(R.layout.custom_layout, this, true)
val titleView= rootView.findViewById(id.txtTitle)
// Load Values from XML
val attrsArray = getContext().obtainStyledAttributes(attrs, R.styleable.CutsomView, defStyleAttr, 0)
val titleString = attrsArray.getString(R.styleable.cutomAttrsText)
attrsArray.recycle()
}
}