How to pass AttributeSet to custom View - android

How do I pass the current AttributeSet to a custom View class? If I use a constructor that only has Context in the arguments, I lose all themes and the ability to use "style" tags in the xml for that custom View.
What I've done is create an activity that contains my custom view already in the xml file, and then programmatically create a new one and add it to the layout. What I find is the one that is made in the xml has the proper styling, and the one I create programmatically doesn't.
The difference between the two as far as I can tell is that the system uses the CustomLayout1(Context context, AttributeSet attrs) constructor. The problem is I can't figure out how to get the AttributeSet for the application to pass to this custom view when I create it programmatically.
Here's the Activity:
import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
public class ThemeOne extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout layout = (LinearLayout) findViewById(R.id.mainlayout);
layout.addView(new CustomLayout1(getApplicationContext()));
}
}
Here's the main xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="#+id/mainlayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.clearsync.test.theme1.CustomLayout1 android:id="#+id/maincustom"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
The custom view class:
import com.clearsync.test.theme1.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
public class CustomLayout1 extends LinearLayout {
private Context context = null;
public CustomLayout1(Context context) {
super(context);
this.context = context;
create();
}
public CustomLayout1(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
create();
}
private void create(){
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.inflateme, this, true);
}
}
and finally, the custom view xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Oh, Hewroh..."
style="?textview_style1" />
</LinearLayout>

Instead of building it with layout.addView(new CustomLayout1(getApplicationContext())); inflate it with the LayoutInflater in your Activity.
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.yourcustomviewxml, layout);

Your code creates LinearLayout inside of linear layout for your custom view. Correct way of doing this is changing your custom view xml from:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Oh, Hewroh..."
style="?textview_style1" />
</LinearLayout>
to
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Oh, Hewroh..."
style="?textview_style1"
/>
</merge>

What are you trying to accomplish here? Looks like you have an endless recursive loop here using your create method, as inflate() will call the constructor that takes attributes. Anyways to answer your question you get the attributes in the constructor with the attributes!
That is the constructor that is called when loading from XML, otherwise it calls one of the other constructors that you supply.
One other helpful thing, you can get a reference to the inflater much easier from the static View method. View.inflate :D

Related

How do I get a custom component that extends RelativeLayout programmatically?

Being new to Android the following issue drives me crazy, and not being able to Google an answer indicates that the solution is really simple...
I try to add a custom component (ArticleView extends RelativeLayout) to a ViewGroup (LinearLayout) from code but I cannot get access to the ArticleView object, trying to cast to it just throws an
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.eo.read/com.example.eo.read.ArticleInfoActivity}: java.lang.ClassCastException: android.widget.RelativeLayout cannot be cast to com.example.eo.read.view.ArticleView
Caused by: java.lang.ClassCastException: android.widget.RelativeLayout cannot be cast to com.example.eo.read.view.ArticleView
at com.example.eo.read.ArticleInfoActivity.onCreate(ArticleInfoActivity.java:44)
In my Activity class I do:
package com.example.eo.read;
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.example.eo.read.content.Article;
import com.example.eo.read.content.ArticleDB;
import com.example.eo.read.view.ArticleView;
...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article_info);
_article = ArticleDB.getInstance().getArticle("test");
LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//get the linear layout into which the ArticleView is going
LinearLayout container = (LinearLayout) findViewById(R.id.recommendation_container);
//get the custom component
RelativeLayout ra = (RelativeLayout)inflater.inflate(R.layout.article_view, container, false);
//this causes the classcast exception, although this RelativeLayout really should be an ArticleView
((ArticleView)ra).setArticle(_article);
//adding the ArticleView to the container works fine, and the customizations
//I have made in ArticleView are visible, so indeed it seems ra is an ArticleView ??
container.addView(ra);
}
The (simplified) article_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="260dp" android:layout_height="wrap_content" android:background="#drawable/stroked_grey_plate">
<TextView
android:id="#+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:text="Sample text"
android:textColor="#111111"
android:scrollHorizontally="true"
android:ellipsize="end"
android:maxLines="1"
/>
</RelativeLayout>
The layout for the activity contains the id/recommedation_container into which the ArticleView is being inserted. Below is also the same view inserted declaratively, just for clarity:
<LinearLayout
android:id="#+id/recommendation_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<com.example.eo.read.view.ArticleView
android:layout_width="wrap_content" android:layout_height="wrap_content"
custom:titleText="my title text"
/>
</LinearLayout>
The ArticleView class is essentially:
package com.example.eo.read.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.eo.read.R;
import com.example.eo.read.content.Article;
public class ArticleView extends RelativeLayout {
private TextView _titleView;
private Article _article;
public ArticleView(Context context) {
this(context,null);
}
public ArticleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ArticleView, 0, 0);
//in the case where the ArticleView is declared in XML the title is retreived from a custom attribute, this works fine.
String titleText = a.getString(R.styleable.ArticleView_titleText);
a.recycle();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.article_view, this, true);
ViewGroup rl = (ViewGroup)getChildAt(0); //get the RelativeLayout
_titleView = (TextView) rl.getChildAt(0);
_titleView.setText(titleText);
}
//in the case where the ArticleView is initiated from code the title should be set by calling this method,
//which I never can reach since I cannot get to this ArticleView object from my activity :-(
//I realize this class is maybe not fully functional yet but first step is to actually be able to initiate it...
public void setArticle(Article a) {
_article = a;
_titleView.setText(_article.getTitle());
}
}
So, my question is pretty much.. why can't I do:
ArticleView ra = (ArticleView)inflater.inflate(R.layout.article_view, container, false);
and what should I instead do to get to my ArticleView?
Replace ArticleView in your XML file with [packagename].ArticleView
For example, if your ArticleView class is contained in com.john.article, then your ArticleView should be replaced by com.john.article.ArticleView.
If I understand you correctly, you want to add the custom view programmatically and not have it defined in the XML?
If that is the case, what happens if you simply do:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article_info);
_article = ArticleDB.getInstance().getArticle("test");
LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//get the linear layout into which the ArticleView is going
LinearLayout container = (LinearLayout) findViewById(R.id.recommendation_container);
//get the custom component
ArticleView av = new ArticleView(this);
av.setArticle(_article);
container.addView(av);
}
In case you haven't stumbled upon it yet, it seems this blog has some nice tips regarding custom views: http://trickyandroid.com/protip-inflating-layout-for-your-custom-view/
If you would like to have your Custom view inflated together with your layout, then you can do:
<LinearLayout
android:id="#+id/recommendation_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<com.example.eo.read.view.ArticleView
android:id="#+id/article"
android:layout_width="wrap_content" android:layout_height="wrap_content"
custom:titleText="my title text"
/>
</LinearLayout>
Just taking the XML from your question as an example, not sure how well it fits your situation.
But now, inflating the above XML to a view called, say, root and then doing root.findViewById(R.id.article) should return a view which can be cast to ArticleView.
Thinking about it, if you have an XML file like this:
<com.example.eo.read.view.ArticleView
android:layout_width="wrap_content" android:layout_height="wrap_content"
custom:titleText="my title text"
/>
You actually should be able to inflate it, as you are trying, and cast to ArticleView, since com.example.eo.read.view.ArticleView is now the root of the layout.

How the display bug of TextView drawableStart can be explained?

Update: Simpler code created and presented here, full layout XML definitions.
When trying to replace the row layout with the icon on the left by the new drawableStart attribute of the TextView element in XML layout, I can observe the following wrongly displayed last item (captured on Nexus 7):
The related code is -- MainActivity:
package cz.skil.android.tut.testtextviewdrawablestart;
import android.os.Bundle;
import android.app.Activity;
import android.widget.ListView;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_list);
ListView listView = (ListView) findViewById(R.id.mylist);
String[] values = new String[] {"item 1", "item 2", "item 3"};
MyAdapter adapter = new MyAdapter(this, values);
listView.setAdapter(adapter);
}
}
MyAdapter
package cz.skil.android.tut.testtextviewdrawablestart;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class MyAdapter extends ArrayAdapter<String> {
private final Context context;
private final String[] values;
public MyAdapter(Context context, String[] values) {
super(context, R.layout.row, values);
this.context = context;
this.values = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.row, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.mylabel);
textView.setText(values[position]);
return rowView;
}
}
The main_list.xml layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/mylist"
android:layout_width="match_parent"
android:layout_height="wrap_content" > <!-- Update: here is the bug -->
</ListView>
</LinearLayout>
and finally the row.xml layout:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mylabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="4dp"
android:drawablePadding="8dp"
android:drawableStart="#drawable/reminder"
android:lines="1"
android:textSize="24sp" >
</TextView>
I guess the reason is that the height of the area for displaying the items is calculated from dimensions declared for the text; however, the icon possibly forces a bigger height for the list item.
Can you confirm the behaviour? If yes, why it behaves so? I am new to Android. It is likely I am doing it wrongly.
(The question is loosely related to this one: Android layout: on TextView and android:drawableStart -- setting size of the icon?).
It's always wrong to set android:layout_height="wrap_content" for a ListView. It works as expected with android:layout_height="fill_parent". More detailed explanation could be found here The World of ListView
Though I don't know if this is intended behaviour or bug, this happens when you use drawableStart. Change it to drawableLeft and it will draw the layout properly.
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mylabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="4dp"
android:drawablePadding="8dp"
android:drawableLeft="#drawable/reminder"
android:lines="1"
android:textSize="24sp" >
</TextView>

View Custom Layout in grapical layout edit screen

This is my source of testing Custom Layout (shows image and label).
XML CODE
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/screen_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#3535ff"
android:orientation="vertical"
android:padding="1dp" >
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000000"
android:padding="20dp" >
<ImageView
android:id="#+id/screen_image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:background="#000000"
android:scaleType="centerInside"
android:src="#drawable/cam_1_20130117_105601_118" />
<TextView
android:id="#+id/screen_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="#id/screen_image"
android:layout_centerHorizontal="true"
android:layout_marginBottom="3dp"
android:background="#95000000"
android:gravity="center_vertical|center_horizontal"
android:text="사출성형기 1호기"
android:textColor="#ffffff" />
</RelativeLayout>
</LinearLayout>
JAVA CODE
package com.example.testlayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.testscreen.R;
public class CustomScreen extends LinearLayout {
LinearLayout mLayout = null;
TextView mLabel = null;
ImageView mImage = null;
public CustomScreen(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public CustomScreen(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomScreen(Context context) {
super(context);
init();
}
void init() {
LayoutInflater inflater = (LayoutInflater)getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.screen, this, true);
// mLabel = (TextView)findViewById(R.id.screen_label);
// mImage = (ImageView)findViewById(R.id.screen_image);
if (isInEditMode()) {
return;
}
}
}
With this code, I checked on Nexus One. It displays well.
The problem is on editor mode. Exactly on xml editer with preview.
When I add this view, error message appeared.
The following classes could not be instantiated :
- com.example.testlayout.CustomScreen (Open Class, Show Error Log) See the Error Log (Window > Show View) for more details. Tip: Use
View.isInEditMode() in your custom views to skip code when shown in
Eclipse
I want to check it on edit mode.
How can I check well like other views?
you have to add (!isInEditMode()) in each constructor. which tells if it not in edit Mode then initialized the stuff
if(!isInEditMode())
init(context);
and for initializing stuff follow this
also have a look at this

Drawing on a screen with other views in android

I have been searching for the past few hours to the answer to a very dumb question. I know how to draw on the canvas in android if you extend the view class, modify onDraw and set setContentView() to a new instance of that class. However, I need to also have 2 TextViews and 2 EditTexts at the bottom of the activity and if setContentView() is set to only have that view, these views will, obviously, not display. How can put all of these on the screen?
EDIT: Here is my code: (the package name is android.physicsengine)
package android.physicsengine;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.RelativeLayout;
public class ResultantForceEngine extends Activity {
private EditText mag;
private EditText dir;
private View image;
private RelativeLayout layout;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.resultant_force);
mag = (EditText)findViewById(R.id.magnitude);
dir = (EditText)findViewById(R.id.direction);
}
public class MyView extends View{
public MyView(Context context){
super(context);
}
public MyView(Context context, AttributeSet attrs){
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
#Override
protected void onDraw(Canvas canvas){
canvas.drawColor(Color.BLACK);
Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(Color.RED);
canvas.drawLine(canvas.getWidth()/2, canvas.getHeight()/2-200, canvas.getWidth()/2 ,canvas.getHeight()/2+200, circlePaint);
canvas.drawLine(canvas.getWidth()/2-200, canvas.getHeight()/2, canvas.getWidth()/2+200 ,canvas.getHeight()/2, circlePaint);
}
}
}
and the xml
<view class="android.physicsengine.ResultantForceEngine$MyView"
android:id="#+id/image"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<TextView android:id="#+id/magText"
android:text="Magnitude (N) ="
android:textSize="15dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"
android:gravity="center_vertical"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true" />
<EditText android:id="#+id/magnitude"
android:inputType="numberDecimal"
android:layout_alignParentBottom="true"
android:layout_toRightOf ="#id/magText"
android:layout_width="wrap_content"
android:padding="3dip"
android:layout_height="wrap_content" />
<TextView android:id="#+id/dirText"
android:text="Angle (deg) ="
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15dip"
android:layout_alignParentBottom="true"
android:layout_toRightOf ="#id/magnitude"
android:padding="3dip"
android:gravity="center_vertical" />
<EditText android:id="#+id/direction"
android:inputType="numberDecimal"
android:layout_alignParentBottom="true"
android:layout_toRightOf ="#id/dirText"
android:layout_alignParentRight="true"
android:layout_height="wrap_content"
android:padding="3dip"
android:layout_width="wrap_content"/>
Basically you need to define your XML file with your custom view class and the other widgets you have. In your case, there would be the custom view, 2 textviews and 2 edittexts in the XML file.
You define the custom view in XML just like any other widget, except you use the namespace of the view and the class name.
<com.example.android.myCustomView
android:id="#+id/my_custom_view"
...
Then inside your activity a simple call
setContentView(R.layout.main);
Edit: The problem is your class is private, so it "isn't seen" when your activity makes a call the layout and tries to inflate it.
Edit2: Of course this won't work, you're using an inner class! You have to communicate stuff like this if you expect to get answers.
The XML syntax for an inner class is different.
<view class="com.example.android.MyClass$MyInnerClass"
You should be able to use the class that you extended in your res/layout/main.xml just like you would any other View class, and add your TextView and EditTexts in the layout .xml file like normal. I have not personally done this, but I have used custom View classes in this way.
Then for your setContentView you would just use the layout .xml file like:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...

How to use Compound Controls

I've created a custom ViewGroup based on a LinearLayout.
ClearableEditText.java
package test.todolist;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
public class ClearableEditText extends LinearLayout{
private EditText editText;
private Button button;
public ClearableEditText (Context context){
super (context);
String service = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater)getContext ().getSystemService (service);
li.inflate (R.layout.clearable_edit_text, this, true);
editText = (EditText)findViewById (R.id.clearEditText);
button = (Button)findViewById (R.id.clearButton);
configButton ();
}
private void configButton (){
button.setOnClickListener (new Button.OnClickListener (){
public void onClick (View v){
editText.setText ("");
}
});
}
}
clearable_edit_text.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/clearEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="#+id/clearButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/clear"
/>
</LinearLayout>
How can I use ClearableEditText now?
I've tried to put a node inside a layout (main.xml) in 2 ways:
<test.todolist.ClearableEditText/>
and
<test.todolist.clearable_edit_text/>
but none of them have worked.
My main.xml:
<?xml version="1.0" encoding="utf-8"?>
<test.todolist.ClearableEditText/>
My ToDoList.java (main activity):
package test.todolist;
import android.app.Activity;
import android.os.Bundle;
public class ToDoList extends Activity{
#Override
public void onCreate (Bundle savedInstanceState){
super.onCreate (savedInstanceState);
setContentView (R.layout.main);
}
}
Thanks.
Solved. The main.xml should be like:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<test.todolist.ClearableEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</merge>
First, merge tag because it is needed when using custom views. My custom view have a LinearLayout root, so it's inefficient if I set another LinearLayout or FrameLayout root in main.xml to use my custom view. merge solves that.
And second, all views must have the layout_width and layout_height attributes.

Categories

Resources