What is the best way to instantiate widgets which are pre-defined in XML layouts in a custom View class?
I am rather new to Android programming in general and am having particular difficulty with the xml/java UI shuffle... I have a layout which I would like to use as the view displayed when a custom, view class is instantiated in the activity class. This much works fine by simply calling
setContentView(R.layout.mylayout) ;
in the activity or from the custom view class through a handle to the activity. The trouble comes when I wish to interact with the widgets on the layout-- I've tried getting a handle on the buttons with
myButton = (Button) findViewById(R.id.mybuttonid);
and separately with
Button myButton = new Button(contextHandle);
myButton = (Button) findViewById(R.layout.mybuttonid);
but in both cases whenever I try to call any methods from the assumed myButton object I get a NullPointerException in the logcat report; evidently myButton is not properly instantiated in either case given above. What is the proper way to instantiate components of a view in a case like this that combines xml and java so that they can call methods dynamically?
thanks, CCJ
EDIT: Thanks all for the replies, but I think up to 8/1/2011 the advice has been mostly targeted at an implementation wherein the widgets are to be instantiated in the activity class; I wish to instantiate widgets from an xml layout in a custom view class-- a class completely separate from the activity class which extends View and implements its own OnClickListener interface. Below is my code:
MyActivity Class:
package com.ccg.myactivity;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RadioButton;
public class MyActivity extends Activity implements OnClickListener {
private boolean touched = false;
private RadioButton myRB;
private Button runB;
private CustomView myView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainlayout);
myRB = (RadioButton) findViewById(R.id.testrb);
runB = (Button) findViewById(R.id.goButton);
//set onClick listeners for activity class
runB.setOnClickListener(this);
}
public void onResume(){
super.onResume();
}
public void onClick(View v) {
// do something when the button is clicked
if (myRB.isChecked()){
setContentView(R.layout.mylayout);
myView = new CustomView(this,this); //passing in activity and context
//handles to custom View class
//myView.getAnotherB().setOnClickListener(this); //commented out as we
//don't want to register the custom view's button with the Activty class's
//OnClickListener; instead it should be registered with the custom View class's own
//OnClickListener implementation.
}
else{
Log.d("me","alt click");
}
}
}
CustomView Class:
package com.ccg.myactivity;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import android.view.View.OnClickListener;
public class CustomView extends View implements OnClickListener{
private Button anotherB;
private Context contextHandle;
private Activity actHandle;
public CustomView(Context context, Activity act) {
super(context);
contextHandle = context;
actHandle = act;
//anotherB = new Button(contextHandle); //this shouldn't be necessary for
//instantiation from XML widget
initCustomView();
}
public void initCustomView(){
anotherB = (Button) findViewById(R.id.nextbutton);
anotherB.setOnClickListener(this);
}
public Button getAnotherB(){
return anotherB;
}
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Log.d("me", "Got the custom click!");
}
}
mainlayout.xml from which the default view is made:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/widget474"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<RadioGroup android:id="@+id/widget30" android:orientation="horizontal"
android:layout_x="2dip" android:layout_y="57dip" android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton android:layout_height="wrap_content" android:id="@+id/testrb"
android:textSize="15sp" android:text="Run" android:layout_width="wrap_content"
android:textColor="#ffff99ff"></RadioButton>
</RadioGroup>
<Button android:layout_width="wrap_content" android:text="@string/RUN"
android:id="@+id/goButton" android:layout_height="wrap_content"
android:layout_x="222dip" android:layout_y="110dip"></Button>
</LinearLayout>
mylayout.xml from which the custom view's layout is created:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/widget0"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<Button android:id="@+id/nextbutton" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:text="work!!!"
>
</Button>
<开发者_开发技巧;/LinearLayout>
okay, if anybody can explain why any method calls from the button object anotherB (anotherB.setOnClickListener(this) above, but also the simpler anotherB.bringToFront()) cause a force close and a nullpointerexception in logcat with the above implementation I would be most appreciative. thanks! CCJ
I would declare your button outside of onCreate without the contextHandle parameter... The context will be imbedded in your button upon instantiation (as I understand it).
try:
class YOUR_CLASS {
Button myButton;
onCreate() {
myButton = (Button) findViewById(R.id.WHATEVER_YOU_CALLED_IT_IN_XML);
then you can set an onClickListener or other abilities (you can google that, its easy)
myButton.setOnClickListener(myOnClickListener);
myButton.setText("click me!");
}
}
This sometimes happens to me when the import isn't correct. Sometimes Eclipse will fill in the import as:
import android.R;
of course, this will never find your ID. You should either not have an import, or have something like
import com.myco.mytestapp.R;
If you do that, then the first way of doing it is correct:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
Button b = (Button) findViewById(R.id.mybutton);
}
Okay, thanks to some advice from the android developers google group I think I've found the answer to at least the most pressing concern (the NPE and force close):
I needed to override onFinishInflate in my custom View class; it is at that point that my XML layout child views (like anotherB) are truly instantiated. The class now looks like this
package com.ccg.myactivity;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import android.view.View.OnClickListener;
public class CustomView extends View implements OnClickListener{
private Button anotherB;
private Context contextHandle;
private Activity actHandle;
public CustomView(Context context, Activity act) {
super(context);
contextHandle = context;
actHandle = act;
//anotherB = new Button(contextHandle); //this shouldn't be necessary for
//instantiation from XML widget
initCustomView();
}
public void initCustomView(){
anotherB = (Button) findViewById(R.id.nextbutton);
anotherB.setOnClickListener(this);
}
public Button getAnotherB(){
return anotherB;
}
@Override
public void onFinishInflate(){
anotherB.setOnClickListener(this); //it seems any addressing of child
//views of the layout [the widgets] need to be made after the
//framework calls this method.
}
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Log.d("me", "Got the custom click!");
}
}
Now it pulls up the layout properly and does not throw an NPE. Of course, the onClickListener callback still isn't working right (the message 'Got the custom click!' never appears in logcat), but that's another issue...
thanks all CCJ
Okay, finally had some time to revisit this issue and I believe I've found the answer: First, before the xml layout or its components can be addressed they need to be inflated. I knew this, but I wasn't sure when exactly they were inflated. It turns out that setContextView (and probably addContextView) trigger xml inflations. In order to have completely modular activity/view classes, I needed to do something like the following:
Activity Class--
package com.ai.ultimap;
import com.ai.ultimap.views.HomeView;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
public class UltiMapActivity extends Activity {
private View hv;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hv = new HomeView(this);
}
}
Custom View Class-
package com.ai.ultimap.views;
import com.ai.ultimap.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.view.View.OnClickListener;
public class HomeView extends View implements OnClickListener{
private RadioButton twodRB;
private RadioButton threedRB;
private TextView locTV;
private EditText editlocET;
public HomeView(Activity hAct) {
super(hAct);
//THE FOLLOWING LINE INFLATES-- IT (or another function which calls xml inflation)
//MUST COME BEFORE ANY JAVA ADDRESSING OF WIDGETS IN
//THE XML LAYOUT
//Also note that even though you could invoke findViewById from a class extending
//View, in this case you must use hAct.findViewById. I believe this is due to the
//fact that the activity referenced by hAct is the object responsible for inflating
//the xml and thus the widgets need to be instantiated from it.
hAct.setContentView(R.layout.ultimap);
twodRB = (RadioButton) hAct.findViewById(R.id.twodRBV);
threedRB = (RadioButton) hAct.findViewById(R.id.threedRBV);
locTV = (TextView) hAct.findViewById(R.id.locationTV);
editlocET = (EditText) hAct.findViewById(R.id.locationETV);
//After instantiation however they can be freely accessed from java in
//non-activity classes, which is the point; see the next line...
twodRB.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
locTV.setText("yo");
}
}
This code works properly to load up the pre-defined xml view ultimap.xml and then address the widgets dynamically from Java (completely outside the activity class), changing the text of the location text view from 'Location' to 'yo' when the twodRB radiobutton is clicked!
Hope this helps some googlers :)
-CCJ
精彩评论