Android学习笔记之(三):UI(3)自定义控件的ListView

1、自定义控件一般是有多个控件组合,例如标题栏等。

一般在程序中,可能会有多个活动的标题栏,如果每个活动编写同样的标题栏代码,就会有大量重复代码。可以使用引入布局的方式来解决
<Button
	    android:id="@+id/title_back"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:layout_gravity="center"
	    android:layout_margin="5dip"
	    android:background="@drawable/btn_03_n"
	    android:text="Back"
	    android:textColor="#fff" />
	
	<TextView
	    android:id="@+id/title_text"
	    android:layout_width="0dip"
    	android:layout_height="wrap_content"
    	android:layout_gravity="center"
    	android:layout_weight="1" 
    	android:text="Title Text"
    	android:textColor="#fff"
    	android:textSize="24sp" />
	
	<Button 
	    android:id="@+id/title_edit"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:layout_gravity="center"
	    android:layout_margin="5dip"
	    android:background="@drawable/btn_03_p"
	    android:text="Edit"
	    android:textColor="#fff" />

上面是一个linearLayout中分别加入了两个button和一个TextView,然后在另一个布局中使用

<include layout="@layout/title" />
就可以把自定义控件加入布局中。
引入的自定义控件中,左边的button主要功能是返回的作用,这里可以通过一个继承于LinearLayout的类实现
public class TitleLayout extends LinearLayout {

	public TitleLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		LayoutInflater.from(context).inflate(R.layout.title, this);
		Button titleBack = (Button) findViewById(R.id.title_back);
		Button titleEdit = (Button) findViewById(R.id.title_edit);
		titleBack.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				((Activity) getContext()).finish();
			}
		});
		
		titleEdit.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				Toast.makeText(getContext(), "You clicked Edit button", Toast.LENGTH_SHORT).show();
			}
			
		});
	}
}
这里我们重写了LinearLayout中的带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这两个构造函数。然后在构造函数中需要使用LayoutInflater度标题栏进行动态加载。通过LayoutInflater的from()方法构建出 LayoutInflater的对象,然后调用inflate()方法就可以动态加载一个布局文件。inflate()方法的两个参数,第一个是要加载布局文件的id,另一个参数是给加载好布局在添加一个父布局。建立好布局后,只需要在另一个控件中调用
<com.example.uiwidgettest.TitleLayout
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    ></com.example.uiwidgettest.TitleLayout>
这样就能调用之前定义好的自定义控件。如图:

2、ListView是Android中最常用的控件之一,它允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内。

(1)、ListView的简单用法,在UI布局中加入
<ListView 
	    android:id="@+id/list_view"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent" >
	</ListView>
接下来只需要调用就可以
private String[] data = {
			"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango"
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main_4);
		
		// listView
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(
				MainActivity.this, android.R.layout.simple_list_item_1, data );
		ListView listView = (ListView) findViewById(R.id.list_view);
		listView.setAdapter(adapter);
}
这样就可以用ListView展示大量的数据,这里就只用data数组简单的测试。
不过,数组中的数据是无法直接传递给ListView的,我们还需要借助适配器来完成。在这里,使用了ArrayAdapter。他可以通过反省来指定要适配的数据类型然后在构造函数中吧要适配的数据传入即可。这里ArrayAdapter的构造函数中一次传入当前上下文、ListView子项布局id,一级要适配的数据。这里还是用了Android.R.layout.simple_list_item_1作为ListView子项布局的id。布局如图:

(2)、定制ListView的界面
ListView不但能显示文字,还能显示图片。
首先定义一个实体类,作为ListView适配器的适配类型。新建Fruit,代码如下:
public class Fruit {
	private String name;
	private int imageId;
	
	public Fruit(String name, int imageId) {
		this.name = name;
		this.imageId = imageId;
	}
	
	public String getName() {
		return name;
	}
	
	public int getImageId() {
		return imageId;
	}
}

Fruit中的两个字段,name表示水果名字,imageId表示水果对应的图片资源id。然后在建立一个LinearLayout布局

<ImageView 
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="50dip"
        android:layout_gravity="center"/>

接下来需要穿件一个自定义适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建FruitAdapter:

public class FruitAdapter extends ArrayAdapter<Fruit> {

	private int resoutceId;
	
	public FruitAdapter(Context context, int resource, List<Fruit> fruitList) {
		super(context, resource, fruitList);
		// TODO Auto-generated constructor stub
		resoutceId = resource;
	}
	
	public View getView(int position, View convertView, ViewGroup parent) {
		
		// 以下方法每次都将布局重新加载了一遍,如果在快速滚动的时候,有可能成为性能瓶颈
		Fruit fruit = getItem(position); // 获取当前项的Fruit实例
		View view=LayoutInflater.from(getContext()).inflate(resoutceId, null);
		ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
		TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
		fruitImage.setImageResource(fruit.getImageId());
		fruitName.setText(fruit.getName());
        return view;
}

FruitAdapter重写父类的一组构造函数,用于将上下文、ListView子项布局的id和数据都传递进来。另外重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候回调用。在getView方法中,通过getItem()方法得到当前项的Fruit实例。在通过LayoutInflater来为这个子项加载我们传入的布局,通过findViewById()方法分别获取两个控件,并分配调用他们的setImageResource()和setText()方法来设置显示的图片文字。下面修改代码:

public class MainActivity extends Activity{
	private List<Fruit> fruitList = new ArrayList<Fruit>();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main_4);

		// listVew关联fruit
		initFruits();
		FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
		ListView listView = (ListView) findViewById(R.id.list_view);
		listView.setAdapter(adapter);
    }
	private void initFruits() {
		Fruit apple = new Fruit("Apple", R.drawable.settle_lv_0);
		fruitList.add(apple);
		Fruit banana = new Fruit("banana", R.drawable.settle_lv_1);
		fruitList.add(banana);
		Fruit orange = new Fruit("orange", R.drawable.settle_lv_2);
		fruitList.add(orange);
		Fruit watermelon = new Fruit("watermelon", R.drawable.settle_lv_3);
		fruitList.add(watermelon);
		Fruit pear = new Fruit("pear", R.drawable.settle_lv_4);
		fruitList.add(pear);
		Fruit grape = new Fruit("grape", R.drawable.settle_lv_5);
		fruitList.add(grape);
		Fruit pineapple = new Fruit("pineapple", R.drawable.settle_lv_6);
		fruitList.add(pineapple);
		Fruit strawberry = new Fruit("strawberry", R.drawable.settle_lv_7);
		fruitList.add(strawberry);
		Fruit cherry = new Fruit("cherry", R.drawable.settle_lv_8);
		fruitList.add(cherry);
		Fruit mango = new Fruit("mango", R.drawable.settle_lv_9);
		fruitList.add(mango);
	}
}
这里的initFruits()方法,用于初始化所有水果的数据。效果如图:


 (3)、提升ListView的运行效率
在上面的方法中ListView的运行效率很低,因为在FruitAdapter的getView()方法中每次都将布局重新加载了一遍。仔细观察,getView()方法中有一个converView参数,这个参数用于将之前加载好的布局进行缓存,一遍以后可以进行重用,修改代码
public class FruitAdapter extends ArrayAdapter<Fruit> {

    .......	
	public View getView(int position, View convertView, ViewGroup parent) {
		
		// getView参数中的convertView用于将之前加载好的布局进项缓存
		Fruit fruit = getItem(position);
		View view;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(resoutceId, null);
		} else {
			view = convertView;
		}
		ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
		TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
		fruitImage.setImageResource(fruit.getImageId());
		fruitName.setText(fruit.getName());
        return view;
    }
}
可以看到,现在我们在getView()方法中进行了判断,如果convertView为空,则使用LayoutInflater去加载布局,否则直接对convertView重用。
不过目前我们还能继续优化,这里每个getView()方法中,会调用View的findViewById()方法来获取一次控件实现。我们可以借助一个ViewHolder来对着部分进行优化。修改代码
public class FruitAdapter extends ArrayAdapter<Fruit> {

	......
	public View getView(int position, View convertView, ViewGroup parent) {
		
		// 然而在上个方法中,还是使用了findViewById()的方法来获取一次控件实例。可借助ViewHolder来优化
		Fruit fruit = getItem(position);
		View view;
		ViewHolder viewHolder;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(resoutceId, null);
			viewHolder = new ViewHolder();
			viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
			viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
			view.setTag(viewHolder); 	// 将ViewHolder存储在View中
		} else {
			view = convertView;
			viewHolder = (ViewHolder) view.getTag(); 	// 重新获取ViewHolder
		}
		viewHolder.fruitImage.setImageResource(fruit.getImageId());
		viewHolder.fruitName.setText(fruit.getName());
		return view;
	}
	class ViewHolder {
		ImageView fruitImage;
		TextView fruitName;
	}
}
 这里新增了一个内部ViewHolder,用于对控件的实例进行缓存。当converView为空的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。
(4)、ListView的点击事件,如下代码
public class MainActivity extends Activity{
	private List<Fruit> fruitList = new ArrayList<Fruit>();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main_4);
		
		// listVew关联fruit
		initFruits();
		FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
		ListView listView = (ListView) findViewById(R.id.list_view);
		listView.setAdapter(adapter);
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				Fruit fruit = fruitList.get(position);
				Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
			}
			
		});
	}
}
这里使用了setOnItemClickListener()方法来为ListView注册了一个监听器,当前用户点击ListView中的任何一个子控件就会回调onItemClick()方法,方法中,通过position参数判断出用户点击的是哪一个子项
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值