列表和适配器是安卓开发中需要重点掌握的两个概念,在这章中,我们将学习列表和适配器的一些技巧。
1.处理空列表
当列表中没有内容的时候,可以展示一张图片或者文字进行提示,只需要使用ListView的setEmpltyView(view)方法即可。当adapter为null或者adapter的 isEmpty()方法返回true的时候就会展示出来。
例如:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/my_list_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<ImageView
android:id="@+id/empty_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/empty_view" />
</FrameLayout>
当ListView为空时我们想展示ImageView这张图片,那么在java代码里可以这样写:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView = (ListView) findViewById(R.id.my_list_view);
mListView.setEmptyView(findViewById(R.id.empty_view));
}
2.使用ViewHolder创建高效适配器
地球人都知道怎么用,这里就不重复了。
3.给ListView添加section headers
效果如图:
具体实现步骤如下:
首先,section header布局:把背景设置为蓝色,与listview的item区分开来
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
android:textSize="24sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#0000ff" />
然后,页面布局:通过include把header添加进来,这样section header会保持在listview顶部
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<ListView
android:id="@+id/my_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/header" />
</FrameLayout>
再是listview的item布局:每一个item都包含一个header,需要时显示,不需要时隐藏
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<include layout="@layout/header" />
<TextView
android:id="@+id/label"
style="@android:style/TextAppearance.Large"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
adapter的getview方法中:当item在顶部,或者item的首字母与前一个item的首字母不一样时,就显示section header
public View getView(int position, View view, ViewGroup parent) {
final ViewHolder viewHolder;
if (view == null) {
view = activity.getLayoutInflater().inflate(R.layout.list_item, parent, false);
viewHolder=new ViewHolder();
viewHolder.header=(TextView) view.findViewById(R.id.header);
viewHolder.label=(TextView)view.findViewById(R.id.label);
view.setTag(viewHolder);
}else {
viewHolder=(ViewHolder)view.getTag();
}
//赋值
String label = countries[position];
if (position == 0 || countries[position - 1].charAt(0) != label.charAt(0)) {
viewHolder.header.setVisibility(View.VISIBLE);
viewHolder.header.setText(label.substring(0, 1));
} else {
viewHolder.header.setVisibility(View.GONE);
}
viewHolder.label.setText(countries[position]);
return view;
}
static class ViewHolder{
TextView header;
TextView label;
}
然后在activity中:初始化ListView,并添加滑动监听,使得处于listview顶部的section header的标签与item的首字母相同。
public class MainActivity extends Activity {
private TextView topHeader;
private int topVisiblePosition = -1;
private ListView mList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
topHeader = (TextView) findViewById(R.id.header);
mList=(ListView)findViewById(R.id.my_list);
mList.setAdapter(new SectionAdapter(this, Countries.COUNTRIES));
mList.setOnScrollListener(
new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
// Empty.
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (firstVisibleItem != topVisiblePosition) {
topVisiblePosition = firstVisibleItem;
setTopHeader(firstVisibleItem);
}
}
});
setTopHeader(0);
}
private void setTopHeader(int pos) {
final String text = Countries.COUNTRIES[pos].substring(0, 1);
topHeader.setText(text);
}
}
4.使用委托delegate让Activity和Adapter通信
比如一个ListView中有一个按钮,点击按钮能删除当前item。如果Activity和Adapter写在两个不同的java文件中,那么在adapter中需要能修改ListView中的数据,就可以使用委托来实现。
adapter代码如下:
public class NumbersAdapter extends ArrayAdapter<Integer> {
public static interface NumbersAdapterDelegate {
void removeItem(Integer value);
}
private LayoutInflater mInflator;
private NumbersAdapterDelegate mDelegate;
public NumbersAdapter(Context context, List<Integer> objects) {
super(context, 0, objects);
mInflator = LayoutInflater.from(context);
}
@Override
public View getView(int position, View cv, ViewGroup parent) {
if (null == cv) {
cv = mInflator.inflate(R.layout.number_row, parent, false);
}
final Integer value = getItem(position);
TextView tv = (TextView) cv.findViewById(R.id.numbers_row_text);
tv.setText(value.toString());
View button = cv.findViewById(R.id.numbers_row_button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null != mDelegate) {
mDelegate.removeItem(value);
}
}
});
return cv;
}
public void setDelegate(NumbersAdapterDelegate delegate) {
mDelegate = delegate;
}
}
其实就是在adapter中定义了一个接口,然后在item的按钮点击事件中调用接口中的方法。
Activity代码如下:让activity继承adapter中的接口,然后实现接口中的方法。当用户点击item中的按钮,就会调用activity中的removeItem方法
public class MainActivity extends Activity implements NumbersAdapterDelegate {
private ListView mListView;
private ArrayList<Integer> mNumbers;
private NumbersAdapter mAdapter;
private EditText mEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mEditText = (EditText) findViewById(R.id.main_edittext);
mListView = (ListView) findViewById(R.id.main_listview);
mNumbers = new ArrayList<Integer>();
mAdapter = new NumbersAdapter(this, mNumbers);
mListView.setAdapter(mAdapter);
}
@Override
protected void onResume() {
super.onResume();
mAdapter.setDelegate(this);
}
@Override
protected void onPause() {
super.onPause();
mAdapter.setDelegate(null);
}
@Override
public void removeItem(Integer value) {
mNumbers.remove(value);
Toast.makeText(this, "Removed object: " + value, Toast.LENGTH_SHORT).show();
mAdapter.notifyDataSetChanged();
}
}
5.其它
1.设定屏幕的方向
其中一个方法是在manifest.xml里设定activity的属性 android:screenOrientation="landscape"
也可在Activity的Java文件中通过代码设定:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
setRequestedOrientation 参数说明:
- 系统默认:此为默认值,由Android系统自己选择适当的方向,选择策略视具体设备的配置情况而定,因此不同的设备会有不同的方向选择;
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - 锁定直式:竖屏 (肖像照) , 显示时高度大于宽度 ;
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - 锁定横式:横屏(风景照) ,显示时宽度大于高度;
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - 随使用者当下:用户当前的首选方向;
ActivityInfo.SCREEN_ORIENTATION_USER - 与活动线程下相同的设定:继承Activity堆栈中当前Activity下面的那个Activity的方向;
ActivityInfo.SCREEN_ORIENTATION_BEHIND - 不随SENSOR改变:忽略物理感应器 即显示方向与物理感应器无关,不管用户如何旋转设备显示方向都不会随着改变("unspecified"设置除外);
ActivityInfo.SCREEN_ORIENTATION_NOSENSOR - 随SENSOR改变:由物理感应器决定显示方向,它取决于用户如何持有设备,当 设备 被旋转时方向会随之变化 在横屏与竖屏之间;
ActivityInfo.SCREEN_ORIENTATION_SENSOR
android:choiceMode="multipleChoice"choiceMode属性解析
Constant | Value | Description |
---|---|---|
none | 0 | Normal list that does not indicate choices. |
singleChoice | 1 | The list allows up to one choice. |
multipleChoice | 2 | The list allows multiple choices. |
multipleChoiceModal | 3 | The list allows multiple choices in a custom selection mode. |