4.1.8 处理空 ListView
ListView 用于展示列表数据,但当列表中无数据时,ListView 不会显示任何数据或提示,按照完善用户体验的需求,这里应该给以无数据的提示。幸好,ListView 提供了一个方法 —— setEmptyView(),通过这个方法我们可以给 ListView 设置一个在空数据下显示的默认提示。包含 ListView 的布局设置如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
<ImageView
android:id="@+id/img_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/ic_launcher" />
</FrameLayout>
在代码中,我们通过以下方式给 ListView 设置空数据时要显示的布局,代码如下所示:
listView = (ListView) findViewById(R.id.list_view);
imgEmpty = (ImageView) findViewById(R.id.img_empty);
listView.setEmptyView(imgEmpty);
通过以上代码,就给 ListView 在空数据时显示一张默认的图片,用来提示用户;而在有数据时,则不会显示。MainActivity 的代码如下所示:
package com.example.test;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
private ImageView imgEmpty;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
imgEmpty = (ImageView) findViewById(R.id.img_empty);
//下面代码注释掉后表示没有数据
// for (int i = 0; i < 30; i++) {
// mData.add(i + "");
// }
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
listView.setEmptyView(imgEmpty);
}
}
4.1.9 ListView 滑动监听
ListView 的滑动监听,是 ListView 中最重要的技巧,很多重写的 ListView,基本上都是在滑动事件的处理上下功夫,通过判断滑动事件进行不同的逻辑处理。而为了更加精确地监听滑动事件,开发者通常还需要使用 GestureDetector 手势识别、VelocityTracker 滑动速度检测等辅助类来完成更好的监听。这里介绍两种监听 ListView 滑动事件的方法,一个是通过 OnTouchListener 来实现监听,另一个是使用 OnScrollListener 来实现监听。
(1)OnTouchListener
OnTouchListener 是 View 中的监听事件,通过监听 ACTION_DOWN、ACTION_MOVE、ACTION_UP 这三个事件发生时的坐标,就可以根据坐标判断用户滑动的方向,并在不同的事件中进行相应的逻辑处理,这种方式的使用代码如下所示:
listView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//触摸时操作
break;
case MotionEvent.ACTION_MOVE:
//移动时操作
break;
case MotionEvent.ACTION_UP:
//离开时操作
break;
}
return false;
}
});
(2)OnScrollListener
OnScrollListener 是 AbsListView 中的监听事件,它封装了很多与 ListView 相关的信息,使用起来也更加灵活。首先来看一下 OnScrollListener 的一般使用方法,代码如下所示:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_IDLE:
//滑动停止时
Log.i(TAG, "SCROLL_STATE_IDLE");
break;
case SCROLL_STATE_TOUCH_SCROLL:
//正在滚动
Log.i(TAG, "SCROLL_STATE_TOUCH_SCROLL");
break;
case SCROLL_STATE_FLING:
//手指抛动时,即手指用力滑动,在离开后 ListView 由于惯性继续滑动
Log.i(TAG, "SCROLL_STATE_FLING");
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滚动时一直调用
Log.i(TAG, "onScroll");
}
});
OnScrollListener 中有两个回调方法 —— OnScrollStateChanged() 和 onScroll()。
先来看第一个方法 OnScrollStateChanged(),这个方法根据它的参数 scrollState 来决定其回调的次数,scrollState 有以下三种模式:
- SCROLL_STATE_IDEL:滚动停止时。
- SCROLL_STATE_TOUCH_SCROLL:正在滚动时。
SCROLL_STATE_FLING:手指抛动时,即手指用力滑动,在离开后 ListView 由于惯性继续滑动的状态。
当用户没有做手指抛动的状态时,这个方法只会回调 2 次,否则会回调 3 次,差别就是手指抛动的这个状态。通常情况下,我们会在这个方法中通过不同的状态来设置一些标志 Flag,来区分不同的滑动状态,供其他方法处理。
下面再来看看 OnScroll() 这个回调方法,它在 ListView 滚动时会一直回调,而方法中的后三个 int 类型的参数,则非常精确地显示了当前 ListView 滚动的状态,这三个参数如下所示:- firstVisibleItem:当前能看见的第一个 Item 的 ID(从0开始)。
- visibleItemCount:当前能看见的 Item 总数。
totalItemCount:整个 ListView 的 Item 总数。
这里需要注意的是,当前能看见的 Item 数,包括没有显示完整的 Item,即显示一小半的 Item 也包括在内了。通过这几个参数,可以很方便地进行一些判断,比如判断是否滚动到最后一行,就可以使用如下代码进行判断,当前可视的第一个 Item 的 ID 加上当前可视 Item 的和等于 Item 总数的时候,即滚动到了最后一行。
if (firstVisibleItem + visivleItemCount == totalItemCount & totalItemCount >0){
//滚动到最后一行
}
再比如,可以通过如下代码来判断滚动的方向,代码如下所示:
if (firstVisibleItem > lastVisibleItemPosition){
//上滑
}else if (firstVisibleItem < lastVisibleItemPosition){
//下滑
}
lastVisibleItemPosition = firstVisibleItem;
通过一个成员变量 lastVisibleItemPosition 来记录上次第一个可视的 Item 的 ID 并与当前的可视 Item 的 ID 进行比较,即可知道当前滚动的方向。
要理解整个 OnScrollListener,最好的方法还是在代码中添加 Log,并打印出状态信息来进行分析学习。在以上代码中,已经添加了相应的 Log,对照 Log 进行分析,会很快掌握 OnScrollListener 的用法。
当然,ListView 也给我们提供了一些封装的方法来获得当前可视的 Item 的位置等信息:
//获取可视区域内最后一个 Item 的 ID
listView.getLastVisiblePosition();
//获取可视区域内第一个 Item 的 ID
listView.getFirstVisiblePosition();
4.2 ListView 常用拓展
ListView 虽然使用广泛,但系统原生的 ListView 显然是不能满足用户在审美、功能上不断提高需求的。不过也不要紧,Android 完全可以定制化,让我们非常方便地对原生 ListView 进行拓展、修改。于是,在开发者的创新下,ListView 越来越丰富多彩,各种各样的基于原生 ListView 的拓展让人目不暇接。下面来看几个常用的 ListView 拓展。
4.2.1 具有弹性的 ListView
Android 默认的 ListView 在滚动到顶端或者底端的时候,并没有很好的提示,在 Android 5.X 中,Google 为这样的行为只添加了一个半月形的阴影效果。而在 IOS 系统中,列表都是具有弹性的,即滚动到底端或者顶端后会继续往下或者往上滑动一段距离。不得不说,这样的设计的确更加的友好,虽然不知道 Google 为什么不模仿这样的设计,但我们可以自己修改 ListView,让ListView 也可以“弹性十足”。
这里可以使用一种非常简单的方法来实现这个效果,我们在查看 ListView 源代码的时候可以发现,ListView 中有一个控制滑动到边缘的处理方法,如下所示:
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
可以看见这样一个参数:maxOverScrollY,注释中这样写道 —— Number of pixels to overscroll by in either direction along the Y axis。由此可以发现,虽然它的默认值是0,但其实只要修改这个参数的值,就可以让 ListView 具有弹性了!重写这个方法,并将 maxOverScrollY 改为设置的值 —— mMaxOverDistance,代码如下所示:
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent);
}
这样,通过对这个值的修改,就实现了一个具有弹性的 ListView 了。当然,为了能够满足多分辨率的需求,我们可以在修改 maxOverScrollY 值的时候,可以通过屏幕的 density 来计算具体的值,让不同分辨率的弹性距离基本一致,代码如下所示:
private void initView() {
DisplayMetrics dm = this.getResources().getDisplayMetrics();
mMaxOverDistance = (int) (dm.density * mMaxOverDistance);
}
最后整理一下所有的代码就是下面这样:
自定义的 ListView:
package com.example.test;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.ListView;
/**
* 自定义 ListView
* Created by HourGlassRemember on 2016/9/28.
*/
public class CustomListView extends ListView {
private int mMaxOverDistance = 60;
public CustomListView(Context context) {
this(context, null);
}
public CustomListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
/**
* 初始化
*/
private void initView() {
DisplayMetrics dm = this.getResources().getDisplayMetrics();
mMaxOverDistance = (int) (dm.density * mMaxOverDistance);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
maxOverScrollX, mMaxOverDistance, isTouchEvent);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.test.CustomListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
Adapter:
package com.example.test;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<String> mData;
private LayoutInflater mInflater;
public ViewHolderAdapter(Context context, List<String> mData) {
this.mData = mData;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//判断是否缓存
if (convertView == null) {
holder = new ViewHolder();
//通过 LayoutInflater 实例化布局
convertView = mInflater.inflate(R.layout.viewholder_item, null);
holder.imgIcon = (ImageView) convertView.findViewById(R.id.img_icon);
holder.txtTitle = (TextView) convertView.findViewById(R.id.txt_title);
convertView.setTag(holder);
} else {
//通过 tag 找到缓存的布局
holder = (ViewHolder) convertView.getTag();
}
//设置布局中控件要显示的视图
holder.imgIcon.setBackgroundResource(R.mipmap.ic_launcher);
holder.txtTitle.setText(mData.get(position));
return convertView;
}
public final class ViewHolder {
public ImageView imgIcon;
public TextView txtTitle;
}
}
MainActivity 类:
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private CustomListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (CustomListView) findViewById(R.id.list_view);
for (int i = 0; i < 30; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
}
}
4.2.2 自动显示、隐藏布局的 ListView
相信用过 Google+ 的朋友应该非常熟悉这样一个效果:当我们在 ListView 上滑动的时候,顶部的 ActionBar 或者 Toolbar 就会相应的隐藏或显示。大家可以发现,在滚动前界面上加载了上方的标题栏和右下角的悬浮编辑按钮,当用户向下滚动时,标题栏和悬浮按钮消失,让用户有更大的空间去阅读。下面我们就来仿照这个例子设计一个类似的效果。代码如下所示:
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000" />
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
MainActivity 类:
package com.example.test;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView;
private List<String> mData = new ArrayList<>();
private ViewHolderAdapter adapter;
//系统认为的最低滑动距离
private float mTouchSlop;
private float mFirstY, mCurrentY;
//描述手指的滑动方向,direction为1表示向上,direction为0表示向下
private int direction;
private boolean mShow = true;
private Toolbar mToolbar;
private ObjectAnimator mAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
listView = (ListView) findViewById(R.id.list_view);
//给 ListView 添加 HeaderView,避免第一个 Item 被 Toolbar 遮挡
View headerView = new View(this);
headerView.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
(int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));
listView.addHeaderView(headerView);
for (int i = 0; i < 30; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
//获得系统认为的最低滑动距离
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
listView.setOnTouchListener(mTouchListener);
}
View.OnTouchListener mTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mFirstY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
mCurrentY = event.getY();
//计算手指是向上滑动还是向下滑动
direction = mCurrentY - mFirstY > mTouchSlop ? 0 : 1;
if (direction == 1 && mShow) {//向上滑动
toolbarAnim(1);//隐藏HeaderView
} else if (direction == 0 && !mShow) {//向下滑动
toolbarAnim(0);//显示HeaderView
}
mShow = !mShow;
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
};
/**
* 控制布局显示隐藏的动画
*
* @param flag
*/
private void toolbarAnim(int flag) {
//开始新动画之前要先取消掉之前的动画
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY",
mToolbar.getTranslationY(), flag == 0 ? 0 : -mToolbar.getHeight());
mAnimator.start();
}
}
4.2.3 聊天 ListView
通常我们使用的 ListView 的每一项都具有相同的布局,所以展现出来的时候,除了数据不同,只要你不隐藏布局,其他的布局应该都是类似的。而我们熟知的 QQ、微信等聊天 App,在聊天界面,会展示至少两种布局,即收到的消息和自己发送的消息,其实这样的效果也是通过 ListView 来实现的,下面我们就来模仿一个聊天软件的聊天列表界面,其效果如下图所示:
这样的一个 ListView 与我们平时所使用的 ListView 最大的不同,就是它拥有两个不同的布局 —— 收到的布局和发送的布局。要实现这样的效果,就需要拿 ListView 的Adapter“开刀”。具体代码如下所示:
发送方 Item 的布局(chat_item_send.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/img_send"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/img2" />
<TextView
android:id="@+id/txt_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:layout_toLeftOf="@+id/img_send"
android:background="@drawable/chat_send"
android:gravity="center"
android:text="发送方" />
</RelativeLayout>
接收方 Item 的布局(chat_item_receive.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/img_receive"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/img1" />
<TextView
android:id="@+id/txt_receive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/img_receive"
android:background="@drawable/chat_receive"
android:gravity="center"
android:text="接收方" />
</RelativeLayout>
ListView 的适配器(ViewHolderAdapter):
package com.example.test;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<ChatItemBean> mData;
private LayoutInflater mInflater;
public ViewHolderAdapter(Context context, List<ChatItemBean> mData) {
this.mData = mData;
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 获得第 position 个 Item 是何种类型
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
return mData.get(position).getType();
}
/**
* 返回不同类型的布局总数
*
* @return
*/
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
//判断是否缓存
if (convertView == null) {
//根据 getItemViewType 的类型来判断是哪个布局
if (getItemViewType(position) == 0) {//接收方——“对方”
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.chat_item_receive, parent, false);
holder.imgHead = (ImageView) convertView.findViewById(R.id.img_receive);
holder.txtContent = (TextView) convertView.findViewById(R.id.txt_receive);
} else {//发送方——“我”
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.chat_item_send, parent, false);
holder.imgHead = (ImageView) convertView.findViewById(R.id.img_send);
holder.txtContent = (TextView) convertView.findViewById(R.id.txt_send);
}
convertView.setTag(holder);
} else {
//通过 tag 找到缓存的布局
holder = (ViewHolder) convertView.getTag();
}
if (mData != null && mData.size() > 0) {
holder.imgHead.setImageBitmap(mData.get(position).getHead());
holder.txtContent.setText(mData.get(position).getContent());
}
return convertView;
}
public final class ViewHolder {
public ImageView imgHead;
public TextView txtContent;
}
}
MainActivity 的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
MainActivity 类:
package com.example.test;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
List<ChatItemBean> data = new ArrayList<>();
data.add(addChatContent(1, "Hi,你是?!"));
data.add(addChatContent(0, "Hello!"));
data.add(addChatContent(1, "你好"));
data.add(addChatContent(0, "在吗?"));
data.add(addChatContent(1, "你是哪里的呢?"));
data.add(addChatContent(0, "你猜"));
listView.setAdapter(new ViewHolderAdapter(this, data));
listView.setDivider(null);
}
/**
* 添加聊天内容
*
* @param type
* @param content
*/
private ChatItemBean addChatContent(int type, String content) {
ChatItemBean chatItemBean = new ChatItemBean();
chatItemBean.setType(type);
chatItemBean.setHead(BitmapFactory.decodeResource(getResources(),
type == 0 ? R.drawable.img1 : R.drawable.img2));
chatItemBean.setContent(content);
return chatItemBean;
}
}
4.2.4 动态改变 ListView 布局
通常情况下,如果要动态地改变点击 Item 的布局来打到一个 Focus 的效果,一般有两种方法,一种是将两种布局写在一起,通过控制布局的显示、隐藏,来达到切换布局的效果;另一种则是在 getView() 的时候,通过判断来选择加载不同的布局。两种方法各有利弊,关键还是看使用的场合。下面就以第二种方法,来演示一下这样的效果,程序运行后初始效果如下所示,第一个 Item 默认 Focus 状态。当点击其他 Item 的时候,点击的 Item 变为 Focus 状态,其他 Item 还原。
(程序初始状态)
(Focus 改变)
具体实现的代码如下所示:
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" />
</LinearLayout>
ListView 的适配器(ViewHolderAdapter):
package com.example.test;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {
private List<String> mData;
private Context mContext;
//当前 Item 的位置
private int mCurrentItem;
public ViewHolderAdapter(Context mContext, List<String> mData) {
this.mContext = mContext;
this.mData = mData;
}
public void setCurrentItem(int mCurrentItem) {
this.mCurrentItem = mCurrentItem;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 添加选中后 Item 的布局
*
* @param i
* @return
*/
private View addFocusView(int i) {
LinearLayout layout = new LinearLayout(mContext);
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(R.drawable.img1);
layout.addView(imageView, new LinearLayout.LayoutParams(200, 200));
layout.setGravity(Gravity.CENTER);
return layout;
}
/**
* 添加正常 Item 的布局
*
* @param i
* @return
*/
private View addNormalView(int i) {
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.HORIZONTAL);
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(R.drawable.img3);
layout.addView(imageView, new LinearLayout.LayoutParams(200, 200));
TextView textView = new TextView(mContext);
textView.setText(mData.get(i));
layout.addView(textView, new LinearLayout.LayoutParams(200, 200));
return layout;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
//通过判断当前 CurrentItem 是否是那个点击的 position,就可以动态控制显示的布局了
layout.addView(mCurrentItem == position ? addFocusView(position) : addNormalView(position));
return layout;
}
}
MainActivity 类:
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private ListView listView;
private ViewHolderAdapter adapter;
private List<String> mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
for (int i = 0; i < 20; i++) {
mData.add(i + "");
}
adapter = new ViewHolderAdapter(this, mData);
listView.setAdapter(adapter);
//重写 ListView 中 Item 点击的监听事件,记录当前点击的 Item 的位置,
//并让 ListView 刷新一次以便更新界面的内容
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapter.setCurrentItem(position);
adapter.notifyDataSetChanged();
}
});
}
}