【问题标题】:What is the best way to instantiate widgets which are pre-defined in XML layouts in a custom View class?在自定义 View 类中实例化在 XML 布局中预定义的小部件的最佳方法是什么?
【发布时间】:2011-07-29 22:23:40
【问题描述】:

一般来说,我对 Android 编程比较陌生,并且在使用 xml/java UI shuffle 时遇到了特别困难...我有一个布局,我想将其用作在实例化自定义视图类时显示的视图活动课。只需调用就可以很好地工作

setContentView(R.layout.mylayout) ;

在活动中或通过一个句柄从自定义视图类到活动。当我希望与布局上的小部件进行交互时,麻烦就来了——我尝试使用

来处理按钮
myButton = (Button) findViewById(R.id.mybuttonid);

并分别与

Button myButton = new Button(contextHandle);
myButton = (Button) findViewById(R.layout.mybuttonid);

但是在这两种情况下,每当我尝试从假定的 myButton 对象调用任何方法时,我都会在 logcat 报告中得到 NullPointerException;显然 myButton 在上面给出的任何一种情况下都没有正确实例化。在这种结合 xml 和 java 以便它们可以动态调用方法的情况下,实例化视图组件的正确方法是什么?

谢谢, CCJ

编辑:感谢大家的回复,但我认为截至 2011 年 8 月 1 日,建议主要针对在活动类中实例化小部件的实现;我希望从自定义视图类中的 xml 布局实例化小部件——一个完全独立于扩展 View 并实现其自己的 OnClickListener 接口的活动类的类。以下是我的代码:

MyActivity 类:

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");
   }

}


}

自定义视图类:

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:

<?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:

<?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>

好的,如果有人可以解释为什么从按钮对象 anotherB(上面的另一个 B.setOnClickListener(this),还有更简单的另一个 B.bringToFront())调用任何方法会导致在 logcat 中使用上述实现强制关闭和 nullpointerexception我将不胜感激。谢谢! CCJ

【问题讨论】:

  • 您能否从您创建 UI 的 Activity 中发布布局和相关 Java 代码。

标签: java android android-layout android-widget


【解决方案1】:

我会在没有 contextHandle 参数的情况下在 onCreate 之外声明您的按钮...上下文将在实例化时嵌入到您的按钮中(据我了解)。

尝试:

class YOUR_CLASS {
  Button myButton;

  onCreate() {
    myButton = (Button) findViewById(R.id.WHATEVER_YOU_CALLED_IT_IN_XML);

然后你可以设置一个 onClickListener 或其他功能(你可以用谷歌搜索,很简单)

    myButton.setOnClickListener(myOnClickListener);
    myButton.setText("click me!");
  }
}

【讨论】:

    【解决方案2】:

    当导入不正确时,我有时会发生这种情况。有时Eclipse会将导入填写为:

    import android.R;
    

    当然,这永远不会找到您的 ID。您应该没有导入,或者有类似的东西

    import com.myco.mytestapp.R;
    

    如果你这样做,那么第一种方法是正确的:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
    
        Button b = (Button) findViewById(R.id.mybutton);
    }  
    

    【讨论】:

      【解决方案3】:

      好的,感谢 android 开发者 google 小组的一些建议,我想我至少找到了解决最紧迫问题(NPE 和强制关闭)的答案:

      我需要在我的自定义 View 类中覆盖 onFinishInflate;正是在这一点上,我的 XML 布局子视图(如 anotherB)才真正被实例化。这个类现在看起来像这样

      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!");
      }
      
      
      
      
      }
      

      现在它可以正确地拉起布局并且不会抛出 NPE。当然,onClickListener 回调仍然无法正常工作(消息“得到自定义点击!”从未出现在 logcat 中),但这是另一个问题......

      谢谢大家 CCJ

      【讨论】:

      • 哎呀,事实证明这并没有(必然)解决问题;出于某种原因,onFinishInflate 中的代码永远不会到达,因此 NPE 消失也就不足为奇了...... onFinishInflate 的开发人员文档说:“即使子类覆盖 onFinishInflate,他们也应该始终确保调用超级方法,这样我们被召唤”——这到底是什么意思?我们是否应该显式调用 super.onFinishInflate,如果是,在哪里调用?
      【解决方案4】:

      好的,终于有时间重新审视这个问题,我相信我已经找到了答案: 首先,在 xml 布局或其组件可以被处理之前,它们需要被膨胀。我知道这一点,但我不确定它们到底是什么时候膨胀的。事实证明,setContextView(可能还有 addContextView)会触发 xml 膨胀。为了拥有完全模块化的活动/视图类,我需要执行以下操作:

      活动类--

      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);
      }
      }
      

      自定义视图类-

      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");
      }
      
      }
      

      此代码可以正常加载预定义的 xml 视图 ultimap.xml,然后从 Java 动态寻址小部件(完全在活动类之外),将位置文本视图的文本从“位置”更改为“哟” ' 当 twodRB 单选按钮被点击时!

      希望这可以帮助一些谷歌员工:)

      -CCJ

      【讨论】:

        猜你喜欢
        • 2013-03-24
        • 2012-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-11
        • 1970-01-01
        • 2012-04-30
        • 1970-01-01
        相关资源
        最近更新 更多