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.
Related
I have passed few online courses on Android. And started my first project. Currently i'm stuck on creating my own Widget.
I cannot find any information on how to override default control styles or how to group default controls into custom control. The problem is:
When we create extend an Activity there is a method onCreated where we point which XML layout to use. In my opinion in the same way custom controls should be created.
For example, i want to create a control, that will have ImageView and some buttons, which type will depend on data coming from the server ( email button, skype button or any other )
So i created a class:
public class InteractiveHeaderControl extends View {
public InteractiveHeaderControl(Context context)
{
super(context);
}
}
I created a layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="TEST"
android:layout_marginLeft="20dp"/>
<LinearLayout
android:id="#+id/button_stack"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true">
<Button
android:layout_width="50dp"
android:layout_height="50dp" />
<Button
android:layout_width="50dp"
android:layout_height="50dp" />
<Button
android:layout_width="50dp"
android:layout_height="50dp" />
</LinearLayout>
</RelativeLayout>
So the question is, how can i apply this layout to my control and poulate the button_stack in runtime?
For example in code i want to create this control, like:
InteractiveHeaderControl control = new InteractiveHeaderControl(List<button>);
and in control constructor handle the passed parameter.
Use a Layout instead of a view. Here is a example:
public class DefaultHeading extends FrameLayout {
public DefaultHeading(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = mInflater.inflate(R.layout.default_heading, this, true);
addView(view);
}
And if you want to use some attributes:
Add this to res->values->attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="awesomeedit">
<attr name="regex" format="string"/>
<attr name="name" format="string"/>
<attr name="hint" format="string"/>
<attr name="error" format="string"/>
<attr name="required" format="boolean"/>
<attr name="link" format="string"/>
<attr name="email" format="boolean"/>
<attr name="number" format="boolean"/>
<attr name="password" format="boolean"/>
</declare-styleable>
</resources>
use it in your layout file:
<yourpackage.DefaultHeading
...
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:error="#string/not_valid_email_msg"
...
>
Get values:
public class DefaultHeading extends FrameLayout {
public DefaultHeading(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.awesomeedit, 0, 0);
String name;
String regex;
String hint;
String error;
String link;
try {
name = ta.getString(R.styleable.awesomeedit_name);
regex = ta.getString(R.styleable.awesomeedit_regex);
hint = ta.getString(R.styleable.awesomeedit_hint);
error = ta.getString(R.styleable.awesomeedit_error);
link = ta.getString(R.styleable.awesomeedit_link);
required = ta.getBoolean(R.styleable.awesomeedit_required, false);
email = ta.getBoolean(R.styleable.awesomeedit_email, false);
password = ta.getBoolean(R.styleable.awesomeedit_password, false);
nummber = ta.getBoolean(R.styleable.awesomeedit_number, false);
} finally {
ta.recycle();
}
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = mInflater.inflate(R.layout.default_heading, this, true);
addView(view);
}
There are many ways how to do it.
For start read:
How to create custom components
How to create custom views in general
First, you should rather create a custom ViewGroup instead of a custom View.
I advice not to pass the List of actuall view instances (in your case Buttons) through your constructor, but better to just pass some information (title, what to do after click,...) and then build your buttons in the component itself..
I would like to create a custom linear layout (to work as some basic list) which accepts a custom parameter from xml, like this:
<MyLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
myns:layout_to_inflate="#layout/list_item"/>
Then, use it in the constructor:
String layoutToInflate = attrs.getAttributeValue(NAMESPACE, "layout_to_inflate");
I get "#layout/list_item". It is not resolved by the system into the int value which is accessible in R.layout.list_item.
Sure I can parse it and use Resources.getIdentifier to look up the ID, then inflate it, but I think that is not the way.
Then... what is the way? Can I get the system to resolve it directly into the int?
UPDATE:
list_item.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Text here!" />
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myns="http://com.example.layoutinflate"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.layoutinflate.MyLinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
myns:layout_to_inflate="#layout/list_item" />
</RelativeLayout>
Contents MyLinearLayout.java:
public class MyLinearLayout extends LinearLayout {
private static final String TAG = MyLinearLayout.class.getSimpleName();
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.MyLinearLayout);
int layoutId = styledAttributes.getResourceId(R.styleable.MyLinearLayout_layout_to_inflate, -1);
int layoutIdInt = styledAttributes.getInt(R.styleable.MyLinearLayout_layout_to_inflate, -1);
String str = styledAttributes.getString(R.styleable.MyLinearLayout_layout_to_inflate);
Log.d(TAG, Integer.toString(layoutId) + ";" + str + ";" + layoutIdInt); //-1; null; -1
styledAttributes.recycle();
}
}
Thanks!
Make sure you correctly define this custom attribute inside your attrs.xml. It has to be like this:
<attr name="layout_to_inflate" format="reference" />
<declare-styleable name="MyLinearLayout">
<attr name="layout_to_inflate" />
</declare-styleable>
Then you can get this value inside your MyLinearLayout object with this:
int layoutToInflateId = attributes.getResourceId(R.styleable.MyLinearLayout_layout_to_inflate, R.layout.<default_layout_to_inflate>);
I have a custom Table Row that I am in the process of making. I want to use an XML file to define what a single row looks like. I would like to have a class extend TableRow and define itself to be the file as defined in the XML. The XML file might look like:
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="10dp"
android:text="#string/loading"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:text="#string/loading"
android:textAppearance="?android:attr/textAppearanceLarge" />
</TableRow>
And the code might look like:
public class SpecialTableRow extends TableRow {
public SpecialTableRow (Context context) {
}
}
Is there something that I can put into the constructor to have the class assume it is the tableRow in it's entirety? Alternatively, is there another structure which would work better? The best that I've figured out is this:
TableRow tr=(TableRow) LayoutInflater.from(context).inflate(R.layout.text_pair,null);
TextView mFieldName=(TextView) tr.findViewById(R.id.label);
TextView mValue=(TextView) tr.findViewById(R.id.data);
tr.removeAllViewsInLayout();
addView(mFieldName);
addView(mValue);
But this removes the layout parameters from the XML. Anything better out there?
Take a look at the tutorial on creating custom views. You will want to subclass TableRow and add the additional views you want to display. Then, you can use your new view directly in your XML layouts and additionally create any custom attributes you might want. I've included an example which creates a custom TableRow named TextPairRow, inflates a layout with two TextViews to show within the TableRow and adds showLabel and showData custom attributes which show/hide the two TextViews. Finally, I've included how you would use your new view directly in your XML layouts.
class TextPairRow extends TableRow {
private TextView label, data;
public TextPairRow (Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.TextPairRow, 0, 0);
try {
showLabel = a.getBoolean(R.styleable.TextPairRow_showLabel, false);
showData = a.getBoolean(R.styleable.TextPairRow_showData, false);
} finally {
a.recycle();
}
initViews();
}
private void initViews(){
// Here you can inflate whatever you want to be in your
// view or add views programatically.
// In this example, we'll just assume you have a basic XML
// layout which defines a LinearLayout with two TextViews.
LinearLayout mLayout = (LinearLayout)
LayoutInflater.from(getContext()).inflate(R.layout.textview_layout, this);
label = (TextView) mLayout.findViewById(R.id.label);
data = (TextView) mLayout.findViewById(R.id.data);
if(showLabel)
label.setVisibility(View.VISIBLE);
else
label.setVisibility(View.GONE); // can also use View.INVISIBLE
// depending on your needs
if(showData){
data.setVisibility(View.VISIBLE);
else
data.setVisibility(View.GONE); // can also use View.INVISIBLE
// depending on your needs
}
}
This is where you define your custom XML attributes (locate or create this file: res/values/attrs.xml)
<resources>
<declare-styleable name="TextPairRow">
<attr name="showText" format="boolean" />
<attr name="showLabel" format="boolean" />
</declare-styleable>
</resources>
Finally, to use your new view directly in your XML layouts:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<com.thefull.packageforyourview.TextPairRow
android:orientation="horizontal"
custom:showData="true"
custom:showLabel="true" />
</LinearLayout>
Note that you might need to use xmlns:custom="http://schemas.android.com/apk/res/com.thefull.packageforyourview" depending on if your custom view will be in a library project. Regardless, either this or what's in the example will work.
The real trick to doing this is actually quite simple. Use the second parameter of the inflate method. In fact, the best thing to do is this:
LayoutInflater.from(context).inflate(R.layout.text_pair,this);
This will inflate the R.layout.text_pair into this, effectively using the entire row. No need to add the view manually, Android takes care of it for you.
The only thing I can think of is to use a static method instead of constructor. For example:
public static void newInstance (Context context) {
this = context.getLayoutInflater().inflate(R.layout.text_pair, null, null);
}
Then don't use constructor for initializing an object, call this method.
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>