Android ListView getChildAt View 为空

本文详细介绍了如何在Android中使用自定义Adapter配合ListView,实现在滚动时保持CheckBox状态的功能,包括创建自定义布局、定义POJO类、实现自定义Adapter及处理滚动事件。

困扰我多久的问题终于解决,这里共享下:

原网站:http://www.linuxidc.com/Linux/2011-09/43139p4.htm

 

我们在每个 ListView 条目的最后增加一个 CheckBox ,即每一个 ListView 条目所包含的内容为:

ImageView 、 TextView 和 CheckBox 。

 

我们可以通过点击每个条目最后的 CheckBox ,在选中相关 ListView 中的条目。需要注意的地方:

+      除 CheckBox 外,不让 ListView 中的条目接受 Click 事件

+      在 ListView 中条目比较多 ( 比较多的意思是,比如,屏幕只能显示 6 个条目,而 ListView 中一共有 8 个条目,也就是说, ListView 的条目数量大于屏幕可以显示的条目数量 ) 的时候,有两个问题需要特别注意:

-       因为 ListView 可以在垂直方向滚动,那么总有一些条目是在屏幕上看不到的,这些看不到的条目,如果你试图用 ListView.GetChildAt(intposition) 去获取它时,你会发现得到的结果将会是 null 。

-       要考虑到 CheckBox 的状态保持。比如程序开始运行后,在屏幕上显示 ListView 中的第 0~ 第5 个条目,第 6 、 7 两个条目在屏幕上不可见,这时候,我们点击第 0 个条目的 CheckBox ,那么这个 CheckBox 就会被显示为 Checked 的状态,然后我们将整个 ListView 向下滚动到底,那么第 0 个条目就不可见了。如果我们再将整个 ListView 向上滚动到头,那么此时第 0 个条目又可见了,如果不做一些处理,我们将会发现,第 0 个条目对应的 CheckBox 本应该处于 Checked 状态,但在它重新出现时,居然自动变成了 unChecked 的状态。

+       在这个例子中,我们采取第三个例子中自己定义一个 Adapter 的方式来进行相关的处理。所不同的是,在第三个例子中的 CustomizedAdapter.getView ,完全用代码实现返回的 View 及其 Layout ,以及其中所包含的 ImageView 和 TextView 对象,而在这个例子中,我们准备先用 xml 文件,定义一个 xml 文件,用作 ListView 中每个条目的 View ,然后再通过 LayoutInflater 中 inflate 方法,获取 getView 方法所需要返回的 View 对象。

 

下面开始描述具体的步骤:

1.     在第三例子项目的基础上,我们在 res/layout 文件夹中,创建一个 xml 文件: rowcheckboxlayout.xml ,如下:

这个 rowcheckboxlayout.xml 就是 ListView 中每个条目所需要用的 View 。编辑该 xml 文件,使其内容如下:

<? xml version = "1.0" encoding = "utf-8" ?>

<!-- 这次使用 RelativeLayout-->

< RelativeLayout

  xmlns:Android = "http://schemas.android.com/apk/res/android"

  Android:id = "@+id/row_checkbox_item"

  Android:layout_width = "fill_parent"

  Android:layout_height = "wrap_content"

  Android:orientation = "horizontal" >

 

       <!-- 用于显示图片 -->

         < ImageView

                   Android:id = "@+id/row_checkbox_icon"

                   Android:layout_width = "48px"

                 Android:layout_height = "80px"

       />

 

       <!-- 用于显示文字,注意其相关的属性 -->

         < TextView

                   Android:id = "@+id/row_checkbox_text"

                   Android:layout_width = "wrap_content"

                   Android:layout_height = "wrap_content"

                   Android:textSize = "20px"

                   Android:layout_toRightOf = "@id/row_checkbox_icon"

                   Android:layout_marginLeft = "8px"

                   Android:layout_centerVertical = "true" >

         </ TextView >

        

         <!-- 用于显示 ChechBox , 注意其相关的属性 -->

         < CheckBox

                   Android:id = "@+id/row_checkbox"

                   Android:layout_width = "wrap_content"

                   Android:layout_height = "wrap_content"

                   Android:layout_marginLeft = "4px"

                   Android:layout_marginRight = "10px"

                   Android:layout_alignParentRight = "true"

                   Android:layout_centerVertical = "true"

         >

         </ CheckBox >  

         <!--

         Android:focusable="false"

         Android:focusableInTouchMode="false"

         如果想让 ListView 中的整个条目可以接收 click 事件,那么需要将

         上面两个属性,加入到 CheckBox 对象的属性中即可。

         -->

</ RelativeLayout >

 

2.     定义一个用于记录 ListView 条目状态的 POJO 类: ListItemData.java ,使其内容如下:

package com.pat.gui;

 

public class ListItemData

{

         private int os_id ;

         private int drawable_id ;

         private boolean selected ;

 

         public ListItemData( int os_id, int drawable_id)

         {

                   this . os_id =os_id;

                   this . drawable_id =drawable_id;

                   selected = false ;

         }

 

         public int getOs_id()

         {

                   return os_id ;

         }

 

         public void setOs_id( int osId)

         {

                   os_id =osId;

         }

 

         public int getDrawable_id()

         {

                   return drawable_id ;

         }

 

         public void setDrawable_id( int drawableId)

         {

                   drawable_id =drawableId;

         }

 

         public boolean isSelected()

         {

                   return selected ;

         }

 

         public void setSelected( boolean selected)

         {

                   this . selected =selected;

         }

}

 

3.     创建一个自定义的 Adapter 类,这次我们将自定义的 Adapter 独立出来存放于另外一个 Java 文件 CustomizedAdapter.java 中,而不是像再第三个例子那样,将 CustomizedAdapter 作为内部类。编辑CustomizedAdapter.java ,使之如下:

package com.pat.gui;

 

import java.util.List;

 

import Android.content.Context;

import Android.graphics.Color;

import Android.view.LayoutInflater;

import Android.view.View;

import Android.view.ViewGroup;

import Android.widget.ArrayAdapter;

import Android.widget.CheckBox;

import Android.widget.CompoundButton;

import Android.widget.ImageView;

import Android.widget.TextView;

import Android.widget.Toast;

import Android.widget.CompoundButton.OnCheckedChangeListener;

 

// 自定义的 Adapter , 重写 getCount 、 getItem 、 getItemId 和 getView 方法。其中的 getView 方法最为重要

class CustomizedAdapter extends ArrayAdapter<ListItemData>

{

         // 声明一个 LayoutFlater 对象

         private LayoutInflater inflater ;

         private Context ctx ;

         // 声明一个 List 对象 , 其元素的数据类型为 ListItemData 。因此这个 list 对象实际上

         // 就是 ListView 对象的数据。

         private final List<ListItemData> list ;

        

         public CustomizedAdapter(Contextctx, List<ListItemData> list)

         {

                   super (ctx,R.layout. rowcheckboxlayout ,list);

                   this . ctx =ctx;

                   this . list =list;

                   inflater =(LayoutInflater)ctx.getSystemService(Context. LAYOUT_INFLATER_SERVICE );

         }

        

         public int getCount()

         {

                   return list .size();

         }

 

         public ListItemDatagetItem( int position)

         {

                   return list .get(position);

         }

 

         public long getItemId( int position)

         {

                   return position;

         }

 

         // 返回一个 RelativeLayout 对象 , 其中包括一个 ImageView 、一个 TextView 以及一个 CheckBox

         public ViewgetView( int position, View convertView, ViewGroup parent)

         {

                   //getView 方法中的第二个参数 convertView 有时候可能会是 null , 在这样的情况下 ,

                   // 我们就必须创建一个新的 rowView(ListView 中每一个条目需要用到的 ) 。但是,如果

                   //convertView 不为 null 的时候,它是什么呢?它实际上就是前面通过 inflate 方法

                   // 得到的 rowView( 见下面代码 ) 。这种情况主要发生在 ListView 滚动的时候:当一个

                   // 新的条目 ( 行 ) 出现的时候, Android 首先会试图重复使用被移除屏幕的那些条目所

                   // 对应的 rowView 对象。由于每一行都有相同的结构,因此可以通过 findViewById 方法

                   // 得到 rowView 中各个对象,根据相关的数据改变这些对象,然后将 contentView 对象

                   // 返回,而不需要重新构建一个 rowView 对象。

                  

                   // 所以,在这里,我们先检查 convertView 是否为 null ,如果是 null 的,那么我们创建

                   // 一个新的 rowView ,否则,我们重用 convertView 。这样做可以大大减少耗时和耗资源

                   // 的 inflate 的调用。根据 2010 年 GoogleI/O 大会,这样做比每次都 inflate 的做法的

                   // 性能快出 150% ,如果 rowView 包含的对象很复杂的话,快出 150% 也许都是低估了。

 

                   // 另外, 这样做,还可以节省内存。如果如下面重复利用业已存在的 rowView ,那么

                   // 仅需要 6 个 rowView 对象即可 ( 假定屏幕可以显示的行数是 6) ,假定每个 rowView 所占用的

                   // 内存是 6kB( 有图像的时候,超过这个数字很容易 ) ,那么一共需要的内存是 36kB 。如果不

                   // 采取这种重复利用的方式,在假定有 1000 行,那么所需要的内存就是 6MB 了,而且所需要

                   // 的内存和 ListView 中的行数有关,这本身也不符合可扩展性的原则,容易造成性能上

                   // 的不稳定。

 

                   final int pos= position;

        

                   ViewrowView = (View)convertView;

                  

                   if (rowView== null )

                   {

                            rowView= (View) inflater .inflate(R.layout. rowcheckboxlayout , null , true );

                   }

                  

// 获得 ImageView 对象

                   ImageViewiv = (ImageView)rowView.findViewById(R.id. row_checkbox_icon );

           // 指定对应 position 的 Image

           iv.setImageResource( list .get(pos).getDrawable_id());

       

           // 获得 TextView 对象

           TextViewtv = (TextView)rowView.findViewById(R.id. row_checkbox_text );

           // 指定对应 position 的 Text

           tv.setText( list .get(pos).getOs_id());

           // 设定文字颜色

           if (position%2== 0)

           {

                    tv.setTextColor(Color. YELLOW );

           }

           else

           {

                    tv.setTextColor(Color. GREEN );

           }

           // 为 TextView 对象增加一个 Tag , 以便在后续的处理中 , 可以通过

            //findViewWithTag 方法来获取这个 TextView 对象,注意 setTag 的参数可以是任意对象

           tv.setTag( "tagTextView" );

 

           // 获得 CheckBox 对象

           CheckBoxchkbox = (CheckBox)rowView.findViewById(R.id. row_checkbox );

           // 为 CheckBox 对象增加一个 Tag , 以便在后续的处理中 , 可以通过

            //findViewWithTag 方法来获取这个 TextView 对象,注意 setTag 的参数可以是任意对象

           chkbox.setTag( "tagCheckBox" );

           // 为 CheckBox 设定 CheckedChangedListener

           chkbox.setOnCheckedChangeListener( new OnCheckedChangeListener()

           {

                            public void onCheckedChanged(CompoundButtonbuttonView, boolean isChecked)

                            {

                                     // 如果有 CheckBox 被点击了 ( 有可能是由 unchecked 变为 checked , 也有可能是由 checked 变为 unchecked) ,

                                     // 那么,我们在 list 中保存对应位置上的 CheckBox 的状态

                                     list .get(pos).setSelected(isChecked);

                                     StringcheckedItems = "The following items are checked:/n/n" ;

                                    

                                     int j= 0;     // 一个标记

                                     // 根据 list 中记录的状态 , 输出 ListView 中对应 CheckBox 状态为 checked 的条目

                                     for ( int i= 0; i < list .size(); ++i)

                                     {

                                               if ( list .get(i).isSelected())

                                               {

                                                        // 通过 getString 方法 (Context 中定义的 ) 获取 id 对应的字符串

                                                        checkedItems+= i + "/t" + ctx .getString( list .get(i).getOs_id())+ "/n" ;

                                                        ++j;

                                               }

                                     }

                                     if (j== 0)

                                     {

                                               checkedItems+= "NO ITEM CHECKED." ;

                                     }

                                     Toast.makeText ( ctx ,checkedItems, Toast. LENGTH_SHORT ).show();

                            }

                 });

                   // 下面这行特别重要 , 否则 ListView 中的 CheckBox 不能正常显示。

                 chkbox.setChecked( list .get(pos).isSelected());

                 return rowView;

         }

}

4.     修改 Activity 所对应代码,使之如下:

( 下面代码中被注释的部分,曾想用 OnScrollListener 来处理 ListView 滚动时界面元素的重画,但由于 GetChildAt 可能返回 null ,而导致程序崩溃,详见下面 onScrollStateChanged 方法中的说明 )

package com.pat.gui;

 

import java.util.ArrayList;

import java.util.List;

import Android.app.Activity;

import Android.os.Bundle;

//import Android.util.Log;

//import Android.view.View;

//import Android.widget.AdapterView;

//import Android.widget.CheckBox;

import Android.widget.ListView;

//import Android.widget.AbsListView.OnScrollListener;

//import Android.widget.AdapterView.OnItemClickListener;

 

public class ControlListView extends Activity

//implements

//OnItemClickListener

{

         // 声明一个 ListView 对象

         private ListView listview ;

         private CustomizedAdapter adapter ;

        

// private intFIRST;                // 用于记录在 ListView 停止滚动时,第一条在屏幕上可见的 item 的在 ListView 的位置

// private intVISIBLE;            // 用于记录在屏幕上显示 item 的条数

// private intTOTAL;              // 在 ListView 中 item 的数量

        

         // 定义一个图片资源 ID 数组,代表各种手机操作系统的 logo

         private int [] drawableIDs =

         {

                   R.drawable. Android ,

                   R.drawable. ios ,

                   R.drawable. wp ,

                   R.drawable. symbian ,

                   R.drawable. blackberry ,

                   R.drawable. palm ,

                   R.drawable. ophone ,

                   R.drawable. other

         };

        

         // 定义一个字符串 ID 数组 , 用以代表各种不同的手机操作系统名称 , 和 drawableIDs 有一一对应的关系

         private int [] os =

         {

                   R.string. Android ,

                   R.string. ios ,

                   R.string. wp ,

                   R.string. symbian ,

                   R.string. blackberry ,

                   R.string. palmos ,

                   R.string. ophone ,

                   R.string. other

         };

        

    @Override

    public void onCreate(BundlesavedInstanceState)

    {

        super .onCreate(savedInstanceState);

        setContentView(R.layout. main );

        // 获得 ListView 对象

        listview =(ListView) this .findViewById(R.id. listview );

        listview .setChoiceMode(ListView. CHOICE_MODE_MULTIPLE );

       

         // 构造一个和 listview 对应的 list 对象。 list 用于保存 listview 中各 item 的状态。

        final List<ListItemData>list = new ArrayList<ListItemData>();

        for ( int i= 0; i < 8; ++i)

        {

           list.add( new ListItemData( os [i], drawableIDs [i]));

        }

       

         // 使用自定义的 Adapter

        adapter = new CustomizedAdapter( this ,list);

       

        // 将 adapter 和 listview 关联起来

        listview .setAdapter( adapter );

        //listview.setOnItemClickListener(this);

        //listview.setOnScrollListener(newOnScrollListener()

        //{

        // publicvoid onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, inttotalItemCount)

        // {

        //          FIRST= firstVisibleItem;

        //          VISIBLE= visibleItemCount;

        //          TOTAL= totalItemCount;

        //          Log.e("111","firstVisibleItem = " + firstVisibleItem + ", visibleItemCount =" +

        //                                      visibleItemCount+ ", totalItemCount = " + totalItemCount);

        // }

        //

        // publicvoid onScrollStateChanged(AbsListView view, int scrollState)

        // {

        //          Viewlist_item;

        //          CheckBoxchk_box;

        //          //scrollState 等于 0 的时候 , 也就是不滚动的时候 , 分别取出 FIRST , VISIBLE 和TOTAL 的值         

        //          if(scrollState== 0)

        //          {

        //                   Log.e("111","FIRST = " + FIRST + ", VISIBLE = " + VISIBLE + ",TOTAL = " + TOTAL);

         //

        //                   // 仅处理屏幕上可见的 item , 但是 , 即便如此 view.getChildAt 还是有可能返回 null , 从而导致程序崩溃。

        //                   // 疑是 getChildAt 的 bug 。而按道理而言,只要 item 在屏幕上可见,那么 view.getChildAt 不应该返回 null

        //                   // 因此试图通过这种方式来重画 CheckBox 的状态,似乎不可行。

        //                   for(inti = FIRST; i < (FIRST + VISIBLE); ++i)

        //                   {

        //                             list_item= (View)view.getChildAt(i);

        //                             //if(list_item== null)continue;

        //                                              

        //                             //if(list_item== null) break;

        //                             //if((list.get(i).isSelected())&& (list_item != null) && (list_item.isShown() == true))

        //                             if(list.get(i).isSelected())

        //                             {

        //                                      chk_box= (CheckBox) list_item.findViewWithTag("tagCheckBox");

        //                                      chk_box.setChecked(true);

        //                             }

        //                   }

        //          }

        // }

        //});

    }

 

    //public voidonItemClick(AdapterView<?> parent, View view, int position, long id)

    //{

    //        Log.e("1",""+position);

    //        Viewlist_item = (View)parent.getChildAt(position);

    //        CheckBoxchk_box = (CheckBox)list_item.findViewWithTag("tagCheckBox");

    //        chk_box.setChecked(true);

    //        adapter.notifyDataSetChanged();

    //}

}

 

运行结果:

点击 Android 、 iOS 和 WindowsPhone 右边的 CheckBox 得到:

 

向下滚到到底,并点击 Other 右边的 CheckBox ,得到:

 

现在想上滚到到头,观察 Android 、 iOS 和 WindowsPhone 右边的 CheckBox 的 Checked 是否仍然被保持着:

 

可以看到状态保持得很好。 

 

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值