Good morning folks,
I'm working on an Android app and I've run into a bit of a problem. I have created a new class that extends View. I have overridden the appropriate methods (the Constructor, onDraw, onMeasure) and am instantiating the View through the applications layout XML (which is called main.xml).
Within the source code of my app I have the following code:
public class CustomViewTest extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* I *think* I need to setContentView before I actually can use any of the widgets on the form. */
setContentView(R.layout.main);
Button b = (Button)findViewById(R.id.button);
b.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// my new widget
newWidget s = (newWidget)findViewById(R.id.testWidget);
s.setRequestedViewSize(300);
}
});
}
}
the problem here, is that s.setRequestedViewSize(300) throws a NullPointerException. Has anyone run into this before or can lend me some advice?
[EDIT]
The main.XML looks like this:
It returning null, but the XML looks like this:
<?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"
>
<com.testing.CustomViewTest02.newWidget
android:id="#+id/testWidget"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<Button android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
></Button>
</LinearLayout>
Thanks you!
Sam
The problem was in my widget, I had the correct constructor (Context context AttributeSet attr); HOWEVER - super(context) was being called - not super(context, attr). Once I fixed that, life was better.
Thanks for the info everyone!
You have no view with the R.id.testWidget ID: it's not about nonstatic methods. Since findViewById() returns null, the next line throws an exception.
Do you reference your custom view inside your main.xml? #Pontus is right. There is no View being returned from the findViewById() method.
It would probably be better to get your reference to it in the onCreate method (call the constructor there if necessary) and then set it visible or add it to another view (whatever you are trying to do with it) once the button is clicked.
EDIT: Based on your XML above. Does your custom class implement a constructor that takes a Context and an AttributeSet? The constructor with the AttributeSet is the constructor that Android will call to create your class from the XML layout.
Related
I follow butterknife instruction, as they said: "Custom views can bind to their own listeners by not specifying an ID."
Grade
Gradle
compile 'com.jakewharton:butterknife:7.0.0'
I create a BaseButton:
public class BaseButton extends Button {
public BaseButton(Context context) {
super(context);
}
public BaseButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
#OnClick
public void onClick() {
System.out.println("Hello");
}}
activity_main
<RelativeLayout 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:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.BaseButton
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test"/>
And MainActivity
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
// #OnClick(R.id.button)
// public void buttonPressed(){
// System.out.println("buttonPressed");
// }
}
If I un-comment buttonPressed method, "buttonPressed" shows up. When I comment, nothing happens. What I expect is to see "hello" first and then "buttonPressed".
Do you have any ideas? Thank you.
There's two problems here:
The view never calls ButterKnife.bind(this) to bind the click listener to itself. Without this, there's no way of knowing when to attach the listener. The call in the your activity does not implicitly bind the view to itself.
If you put an #OnClick for the button in your activity and an #OnClick for the button inside of itself one of them is going to overwrite the other. Butter Knife is just creating a click listener and calling setOnClickListener on the view. Since you'd have two, one will simply replace the other and you'll stop seeing the log line for whichever called bind first.
I am new to android and would like to know as to why thiswont work.If i have a set of images in my res folder and i want to display them based on users choice such that the Mainactivity is like below:-
public class Activity3 extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.drawable.img1);//here i put the image name
}
}
Suppose i use this code snippet
public class Activity3 extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(R.id.text1=='1')
setContentView(R.drawable.img1);
else
setContentView(R.drawable.img2);
}
}
This doesnt work.But i would like to know why,how is the android stuff really working.This does seem to me logically right.
You can't set drawable to setContentView, but you can do this instead:
setContentView(R.layout.MyLayout);
ImageView view = (ImageView)findViewById(R.id.myviewid);
view.setImageResource(R.drawable.img1);
In each click add view.setImageResource(R.drawable.img1); to change the imageView resources.
if you see more carefully the documentation, can see the definition for setContentView method:
public void setContentView (int layoutResID)
if you note, the method receive a layoutResId, the key word is layout, remember that android used the logic (view - controller), an activity is the controller and a layout is the view. You can have visual elements in your view, like an image (in android can be contained in an ImageView).
For example, you can define an xml layout to the next way:
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/selected_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</ImageView>
this xml only contain an ImageView, in this ImageView with the id selected_image you will show the choice image.
In your activity you need put the next code (imagine the xml layout call image_layout.xml):
public class Activity3 extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_layout);
//here you need find the ImageView in this layout
((ImageView) findViewById(R.id.selected_image).setImageResource(R.drawable.img1);
}
}
There are quite a few questions about this subject, but could not find any with the specific problem I have...
In my layout.xml, I use the android:onClick tag for a Button to call the right onClickListener. I get the error :
java.lang.IllegalStateException: Could not find a method handle_cancel(View) in the activity class com.matthieu.HelloWorldApplication for onClick handler on view class android.widget.Button with id 'button_cancel'
I have that method implemented in the Activity, but it is looking for it in the class that extends Application... I don't understand why. The View and all that is setup only in the Activity.
If anyone needs, here is the declaration of that method (in my activity, NOT in HelloWorldApplication):
public void handle_cancel(View v) {
// do something useful here
}
Edit (from adamp request)... and probably answering my own question :
Here is part of the code where that layout is used...
public class AddVocabularyActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.top); // that layout contains an empty LinearLayout id/main_content
}
private some_other_function() {
LinearLayout main_content = (LinearLayout) findViewById(R.id.main_content);
main_content.removeAllViews();
View.inflate(getApplicationContext(), R.layout.hello, main_content); // layout.hello is the one containing the button
}
// some other stuff
}
While copy/paste this code, I am guessing the problem is that I used getApplicationContext to inflate the View with that Button...
As mentioned in my edit, changing the getApplicationContext() with the Activity context fixes it...
The convention works like this:
In the layout xml file, you give this attribute:
android:onClick:"methodname"
Then, inside a class, you define a method like this:
public void methodname(View v){
//your method code
}
Any other way of doing this is not documented. If you need parameters, just call another method inside that method.
My layout contain one header in which i inculded in each Activity, in this header there is a image button. Is it possible to write a common onClick event for this imageButton??
You can write a class that extends OnClickListener and the onClick method. Then in each activity's onCreate method, find the ImageButton and set its onClickListener to that class:
MyOnclickListener implements OnClickListener {
private Context context;
public MyOnclickListener(Context context) {
this.context = context;
}
#Override
public void onClick(View arg0) {
Intent intent = new Intent(context, MyActivity.class);
context.startActivity(intent);
}
}
In your activities:
protected void onCreate(...) {
setContentView(...);
((ImageButton) findViewById(R.id.mybutton)).setOnClickListener(new MyOnclickListener(this));
}
EDIT: Sorry, of course implements.
EDIT2: See updated code for Context reference.
Yes. Create a singleton class that implements the required listener and add that instance to the button on each screen.
The answers provided by cant0na and Juhani are most likely the answers you are looking for (with a small note on cant0na's answer). If you need a more self-maintained and fault tolerant solution you can define your very own "widget" which handles its own events. For this you'll need:
A xml-layout file which will describe your header.
A custom class which will (automatically) inflate the above XML layout and manage any "common events".
The benefit of this solution is that you don't have to add a new instance of your common OnClickListener in each and every activity which will show your header. You simply add your header to your activitys layout-XML (see example code below) and nothing else. Foolproof. You also get a more "decoupled" code this way (your header doesn't depend on any implementation specifics of your application and its activities).
The drawback is that it's a more complex solution and it might seem a bit over-kill for small projects. It's also a bit tricky to keep this solution "decoupled" if you want to do any activity specific actions on the button click. You might want to consider "default behaviour" in combination with "code injection" in the MyHeader class. The code injection would then require further manipulation on the header class (inject the onClick implementation) in the activities which deviates from the default behaviour.
Example header.xml
<com.dbm.widget.MyHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:layout_width="60dp"
android:layout_height="20dp"
android:src="#drawable/myIcon"
android:id="#+id/myButton" />
</com.dbm.widget.MyHeader>
Example MyHeader.java
package com.dbm.widget;
public class MyHeader extends LinearLayout implements OnClickListener {
// Constructor.
public MyButton() {
((ImageButton) findViewById(R.id.myButton)).setOnClickListener(this);
}
// OnClick event callback.
public void onClick(View view) {
// Do whatever you need to do here.
}
}
Example activity.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.dbm.MyHeader
android:layout_width="match_parent"
android:layout_height="20dp" />
<!-- Your other content goes here -->
</LinearLayout>
I have created a custom dialog and a layout xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tap Me"
android:onClick="dialogClicked" />
</LinearLayout>
In the dialog class I've implemented the method "dialogClicked(View v)":
public class TestDialog extends Dialog {
public TestDialog(final Context context)
{
super(context);
}
#Override
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog);
}
public void dialogClicked(final View view)
{
System.out.println("clicked");
}
}
When I tap the button I get a NoSuchMethodException 'dialogClicked'. Setting the onClick handler in layout xml works fine for activities, but not in dialogs. Any ideas? What I'm doing wrong?
Define the method (dialogClicked) in Activity.
And modify TestDialog like the following code:
public class TestDialog extends Dialog {
Context mContext;
public TestDialog(final Context context)
{
super(context);
mContext=context;
}
#Override
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
LinearLayout ll=(LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.dialog, null);
setContentView(ll);
}
}
I think it works :)
I think the issue is one of scope. I'm not sure how'd you address this in xml, but essentially the dialogueClicked method in your layout xml doesn't know where to find the method you've defined in the dialog class.
The standard approach i've seen to bind buttons in custom layouts is as follows.
implement the OnClickListener class
Bind the buttons click event to the dialog class
Switch out the buttons in the onClick button based on id. You'd need to add an id to your button.
.
public class TestDialog extends Dialog implements android.view.View.OnClickListener
{
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog);
((Button)findViewById(R.id.dialog_btn_mybutton)).setOnClickListener(this);
}
public void onClick(View view)
{
switch (view.getId())
{
case R.id.dialog_btn_mybutton:
//do stuff
// dismiss();
// cancel etc.
break;
}
}
}
Hope that helps. Would still be interested in knowing if there was a solution to using xml onClick to bind to the method. Perhaps an additional argument in the setContentView? something r'other.
I've found the following code in the View.java source:
public void onClick(View v) {
if (mHandler == null) {
try {
mHandler = getContext().getClass().getMethod(handlerName,
View.class);
...
-> The views uses its context to resolve the onclick handler method.
Noew the following code from Dialog.java source:
public Dialog(Context context, int theme) {
mContext = new ContextThemeWrapper(context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
...
In the constructor of the dialog an instance of ContextThemeWrapper gets created and set as context. This instance is neither the custom dialog class, nor the calling activity, which can be the place for implementing the handler method. Therefore views are not able to find the onclick handler method.
But I have to use the onclick XML attribut. Any workarounds available?
Dialogs need the signature
dialogClicked(DialogInterface dialog, int id) { ... }
android:onClick="method" is pretty cool, but it doesn't work on Android 1.5 so I am avoiding for some time.
An easy workaround:
Make your Dialog an Activity and use android:theme="#android:style/Theme.Dialog" in you AndroidManifest.
Following on from Jett Hsieh's post, I've implemented my dialogs slightly differently using showDialog and dismissDialog, but the fundamentals of getting the android:onClick working have been the same, my example code is below for future reference.
public class ExampleActivity extends Activity {
static final int DIALOG_DISCLAIMER = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
showDialog(DIALOG_DISCLAIMER);
}
protected Dialog onCreateDialog(int id)
{
Dialog dialog;
switch(id)
{
case DIALOG_DISCLAIMER:
dialog = new Dialog(this);
dialog.setContentView(R.layout.main_disclaimer);
LinearLayout ll = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.main_disclaimer, null);
dialog.setContentView(ll);
break;
default:
dialog = null;
}
return dialog;
}
public void onClick(View v)
{
switch(v.getId())
{
case R.id.maindisclaimer_button_accept:
dismissDialog(DIALOG_DISCLAIMER);
break;
}
}
}
And the layout file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/linearLayout1"
android:padding="10dp"
android:orientation="vertical"
android:background="#drawable/roundedcorners">
<Button
android:id="#+id/maindisclaimer_button_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="#string/string_accept"
android:onClick="onClick" >
</Button>
</LinearLayout>
Try to define that method (dialogClicked) in the activity and not in the dialog.
It might use reflection so if you use different activities just write that method in each activity that might show that dialog
A dialog is always created and displayed as part of an Activity. According to Android References:
If you decide to create a dialog outside of the onCreateDialog() method,
it will not be attached to an Activity. You can, however,
attach it to an Activity with setOwnerActivity(Activity).
Also, are you passing the object returned by getApplicationContext(); to the constructor of TestDialog?
system looks for the method in the where the layout has been inflated from, or in the activity class to which the xml was set as content.