《Android群英传》阅读笔记——第四章:ListView使用技巧
昨天把自定义View看完了,真是受益颇多啊,今天又来看ListView了,我们都知道现在已经出了RecyclerView不可谓不强大,但是我们在一般情况下,用ListView就够了,除非有一些特殊的需求,所以ListView的地位还是挺稳的,这就促使我们必须必的将它掌握,虽然在这之前我用ListView用的也挺多的,但是就是差一个总结了,现在正好借着群英传阅读笔记的这个机会来将我所理解的记下来,当然还是以书为主哈,下面我们来看书吧!
一、ListView常用优化技巧
- 使用ViewHolder模式提高效率
ViewHolder模式是提高ListView效率的一个很重要的方法,他充分利用了ListView的视图缓存机制,避免每次调用getView()的时候去通过findViewById()实例化控件,有人测试结果效率可以提高50%,我们只需要在自定义的Adapter里面定义一个内部类ViewHolder即可,代码如下:
private class ViewHolder {
private ImageView img;
private TextView title;
}
剩下的,我们只要在getView()方法中通过视图缓存机制来重用以缓存即可,完整的ListView的Adapter如下:
package com.llx.lenovo.listviewdemo;
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;
/*
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: MyAdapter
* 创建者: LLX
* 创建时间: 2017/2/27 17:08
* 描述: ListView适配器
*/
public class MyAdapter extends BaseAdapter {
private Context mContext;
private List<String> mListData;
private LayoutInflater mLayoutInflater;
private ViewHolder mViewHolder;
public MyAdapter(Context mContext, List<String> mListData) {
this.mContext = mContext;
this.mListData = mListData;
//获取系统服务
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
//返回长度
@Override
public int getCount() {
return mListData.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
//判断是否有缓存
if (view == null) {
//实例化ViewHolder
mViewHolder = new ViewHolder();
//加载item布局
view = mLayoutInflater.inflate(R.layout.item, viewGroup, false);
//绑定id
mViewHolder.img = (ImageView) view.findViewById(R.id.item_imag);
mViewHolder.title = (TextView) view.findViewById(R.id.item_text);
//将实例化后的ViewHolder存储到view中
view.setTag(mViewHolder);
} else {
//若有缓存则通过view.getTag的方法获取到ViewHolder
mViewHolder = (ViewHolder) view.getTag();
}
//设置布局中控件要显示的布局
mViewHolder.title.setText(mListData.get(position));
return view;
}
private class ViewHolder {
private ImageView img;
private TextView title;
}
}
好的,现在我们的adapter已经设置好了,下一步就是将为该adapter添加数据,并和ListView绑定到一块儿了,修改MainActivity.java如下:
package com.llx.lenovo.listviewdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView mListView;
private MyAdapter myAdapter;
private List<String> mListData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//数据初始化
initData();
//控件初始化
initView();
}
//数据初始化
private void initData() {
//实例话List
mListData = new ArrayList<>();
//循环向List中添加数据
for (int i = 0; i < 10; i++) {
mListData.add("第" + i + "张");
}
}
//控件初始化
private void initView() {
//实例化adapter
myAdapter = new MyAdapter(this, mListData);
//绑定id
mListView = (ListView) findViewById(R.id.mlistView);
//设置adapter
mListView.setAdapter(myAdapter);
}
}
这样我们一个简单的ListView就完成了,运行效果如下;
1、设置item间分隔线
ListView每个item之间,可以通过分隔线进行区分,系统也提供了divider和dividerHeight两个属性来帮助我们实现这一功能。通过这两个属性,也可以控制每个item之间的分隔线和它的高度,分隔线不仅仅可以设置一个颜色,也可以设置成一个图片资源,用法如下:
android:divider="@color/colorPrimary"
android:dividerHeight="5dp"
设置好后,效果如下:
假如我们不想要分隔线,则将divider属性改为如下:
android:divider="@null"
是否生效了呢,看下图:
2、隐藏ListView的滚动条
这个就看需求了,虽然感觉取消和不取消并没有大的影响,但是,毕竟有这个方法,那么我们就写出来吧,很简单,就一个属性,如下:
android:scrollbars="none"
3、取消ListView的Item点击效果
用过ListView的同学肯定熟悉,当点击其中的Item时会出现一个灰色的点击效果,如下:
若是我们不想要这个灰色的点击效果该怎么办呢?其实也和去除滚动条的一样,添加一个属性就好了,如下:
android:listSelector="@android:color/transparent"
当然里面的值不是固定的哈,可以用”#00000000”代替,这个颜色就是透明的了,这样我们就把点击的颜色去掉了。若是要反馈其他的颜色,直接设置就好了,只是这里有个坑,等以后再说,毕竟现在是看书嘛。
4、设置ListView需要显示在第几项:
listview.setSelection(15);//显示第15个item
5、瞬间的平滑到第几项:
listview.smoothScrollBy(1,15);
listview.smoothScrollByOffset(15);
listview.smoothScrollToPosition(15);
6、动态修改ListView:
这个就很常用了,listView的数据并不是固定的,我们会根据我们的需求对listView的数据进行修改,最常用的无非就是,添加和删除了,我们先在我们的布局中增加一个Button,如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.llx.lenovo.listviewdemo.MainActivity">
<ListView
android:id="@+id/mlistView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:dividerHeight="5dp"
android:listSelector="@android:color/transparent"
android:scrollbars="none" />
<Button
android:id="@+id/bt_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="添加数据" />
</RelativeLayout>
修改MainActivity.java代码如下:
....
//Button
private Button bt_add;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
//控件初始化
initView();
bt_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//添加数据
mListData.add("新添加数据");
//刷新适配器
myAdapter.notifyDataSetChanged();
}
});
}
....
//控件初始化
private void initView() {
....
bt_add = (Button) findViewById(R.id.bt_add);
....
}
}
然后,看下效果吧:
7、遍历所有的item
ListView作为一个ViewGroup,他提供了很多操纵子View的方法,最常用的就是getChilaAt()来获取View,如下:
for (int i = 0; i < mListView.getChildCount(); i++) {
View view = mListView.getChildAt(i);
}
8、处理空ListView
我们知道,ListView用于展示列表数据,但当列表中没有数据可让ListView展示时,ListView不会显示任何数据或提示,为了完善用户体验的需求,这里应该给以无数据的提示。我们来添加下吧,xml更改如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.llx.lenovo.listviewdemo.MainActivity">
...
<TextView
android:id="@+id/tv_null"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="无数据"
android:textSize="50sp" />
...
</RelativeLayout>
然后在MainActivity.java添加如下两行代码:
...
//控件初始化
private void initView() {
...
tv_null = (TextView) findViewById(R.id.tv_null);
mListView.setEmptyView(tv_null);
...
}
...
效果如下:
9、ListView滑动监听
ListView的滑动监听可是ListView的很重要的一个技巧,很多应用场景需要重写ListView的其实都要在滑动监听上下很大的功夫,通过判断滑动事件来处理不同的逻辑这是很有必要的,开发者通常还需要GestureDetector手势识别,VelocityTracker滑动速度检测来辅助监听,这里介绍两种监听方法,一种是OnTouchListener,另一中是OnScrollListener。
9.1:OnTouchListener
OnTouchListener是View的监听事件,包括ACTION_DOWN,UP,MOVE等,通过事件发生时的坐标,就可以根据坐标判断用户滑动的方向,并在不同的事件中进行相应的逻辑处理,这种方式的使用代码如下所示:
mListView.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;
}
});
然后在相应的case中进行相应的逻辑处理就好了!~
9.2:OnScrollListener
OnScrollListener是AbsListView的监听事件,他封装了很多ListView的相关信息,所以用起来很灵活,一般的使用方法如下:
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
//滚动停止
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
//正在滚动
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
//手指抛动时,即手指用力滑动的时候
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滚动的时候一直在调用
}
});
OnScrollListener中有两个回调方法onScrollStateChanged()和onScroll(),我们先来看看onScrollStateChanged吧,这个方法的参数scrollState来决定其回调的次数,有三种模式:
- OnScrollListener.SCROLL_STATE_IDLE://滚动停止
- OnScrollListener.SCROLL_STATE_TOUCH_SCROLL://正在滚动
- OnScrollListener.SCROLL_STATE_FLING://手指抛动时,即手指用力滑动的时候
当用户没有做手指抛动的动作时,这个方法只会调用2次,否则就调用3次,差别就在于手指抛动的这个状态,通常情况下,我们会在这个方法中通过不同的状态来标注一些FLAG,我们来看下onScrill()这个方法:
onScrill()这个回调方法,它在ListView滚动时会一直回调,而方法中的后三个int类型的参数,则非常精确地显示了当前ListView滚动的状态,这三个参数如下:
- firstVisibleItem:当前能看见的第一个Item的ID(从0开始)。
- visibleItemCount:当前能看见的Item总数。
- totalItemCount:整个ListView的Item总数。
这里有一点要注意的是,当前能看见的Item数,包括没有显示完整的Item,即显示一小半的Item也包括在内了,通过这几个参数,可以很方便地进行一些判断,比如判断是否滚动到最后一行,就可以使用以下代码了,当前可视的另一个Item的ID加上当前可视Item的和等于Item总和的时候,即滚动到而来最后一行:
if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {
//滚动到最后一行
}
再比如监听滑动方向
int lastVisibleItemPosition = firstVisibleItem;
if (firstVisibleItem > lastVisibleItemPosition) {
//上滑
} else if (firstVisibleItem < lastVisibleItemPosition) {
//下滑
}
通过一个成员变量lastVisibleItemPosition来记录上一个可视item的ID并与当前可视的item的ID相比较就知道滑动的方向了,当然,ListView还提供了很多获取位置信息的方法。
//获取可视区域内最后一个item的id
listview.getLastVisiblePosition();
//获取可视区域内第一个item的id
listview.getFirstVisiblePosition();
二、Listview常用拓展
虽然ListView应用很广泛,但是毕竟是一个显示的东西,扩展性肯定要的,我们接下来说几种常见的扩展:
1、具有弹性的ListView
Android默认滑动到顶部或者底部只会有一个阴影,而在5.X之后改变成了半圆的阴影:
而在iOS上,列表是具有弹性的,即滚动到顶部或者底部,会再滚动一段距离,这样的设计感觉还是挺友好的,我们也来模仿一下
我们在查看ListView的源码的时候会发现一个控制滑动到边缘的处理方法,如下:
@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, maxOverScrollY, isTouchEvent);
}
我们可以看到这样一个参数maxOverScrollY,就是他负责控制滑动的个数的,默认是0,我们重写ListView:
package com.llx.lenovo.listviewdemo;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.ListView;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: MyListView
* 创建者: LLX
* 创建时间: 2017/3/12 2:01
* 描述: 弹性ListView
*/
public class MyListView extends ListView {
private static int mMaxOverDistance = 50;
private Context mContext;
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
//初始化
initView();
}
//初始化
private void initView() {
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
float density = metrics.density;
mMaxOverDistance = (int) (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);
}
}
修改布局文件中如下:
...
<com.llx.lenovo.listviewdemo.MyListView
android:id="@+id/mlistView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null"
android:dividerHeight="5dp"
android:listSelector="@android:color/transparent"
android:scrollbars="none" />
...
</RelativeLayout>
相对应的MainActivity.java中的绑定ID也要相对应的修改一下,就一样代码的事儿,这里就不再演示了,运行效果如下:
可以看到,是可以的~
2、自动显示,隐藏布局的ListView
相信看过Google最新的应用,或者使用了MD风格的应用都知道,列表滑动的时候actionbar可以根据状态显示或者隐藏的。
看了半天没看明白,然后就自己摸索了,其实逻辑还是很简单的,就是当我们向下滑动一段距离或者向上滑动一段距离,Toolbar显示隐藏就好了。OK,那么我们就来处理一下哈,首先呢,我们一定要知道我们滑动的距离时候符合显示或隐藏的条件,那么这个滑动的距离就是我们手指首次触摸屏幕的坐标,然后最后滑动结束时的坐标两个值的差,是不是符合我们的条件,若是符合显示或者隐藏的条件,我们还要判断当时的手指是向上移动的,还是向下移动的,从而调用传相应的参数,进行对Toolbar的显示或者隐藏!逻辑就是这样子了,我们新建一个ScrollHideActivity.java,代码如下:
package com.llx.lenovo.listviewdemo;
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
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;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: ScrollHideActivity
* 创建者: LLX
* 创建时间: 2017/3/12 21:17
* 描述: 自动显示、隐藏布局的ListView
*/
public class ScrollHideActivity extends AppCompatActivity implements View.OnTouchListener {
private static final String TAG = "ScrollHideActivity";
//头部显示
private View header;
//Toolbar
private Toolbar mtoolbar;
//ListView
private ListView listview_scrollhide;
//ListView 适配器
private MyAdapter myAdapter;
//数据存储
private List<String> mListData;
//动画
private ObjectAnimator mAnimator;
//滑动最短距离
private int mTouchSlop;
//第一次距离(手机的宽为x,手机的长为y)
private float mFirstY;
//当前的距离(手机的宽为x,手机的长为y)
private float mCurrentY;
//方向
private int direction;
//是否显示
private boolean mShow = true;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrollhide);
//数据初始化
initData();
//控件初始化
initView();
}
//数据初始化
private void initData() {
//实例化List
mListData = new ArrayList<>();
//循环向List中添加数据
for (int i = 0; i < 20; i++) {
mListData.add("第" + i + "张");
}
}
//控件初始化
private void initView() {
//实例化adapter
myAdapter = new MyAdapter(this, mListData);
//实例化一个View
header = new View(this);
//设置头部布局参数
header.setLayoutParams(new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT, (int) getResources().getDimension(
R.dimen.abc_action_bar_default_height_material)));
//获取滑动最短距离
mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
//绑定id
mtoolbar = (Toolbar) findViewById(R.id.mtoolbar);
listview_scrollhide = (ListView) findViewById(R.id.listview_scrollhide);
//将头部View添加到listView上方
listview_scrollhide.addHeaderView(header);
//设置adapter
listview_scrollhide.setAdapter(myAdapter);
//设置监听事件
listview_scrollhide.setOnTouchListener(this);
}
private void toolbarAnim(int flag) {
//启动新的动画前,必须要取消之前的动画
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
//若flag==0则滑动的方向为down
if (flag == 0) {
//toolbar.getTranslationY()获取的是Toolbar距离自己顶部的距离
mAnimator = ObjectAnimator.ofFloat(mtoolbar, "translationY", mtoolbar.getTranslationY(), 0);
}
//若flag==1则滑动的方向为up
else if (flag == 1) {
mAnimator = ObjectAnimator.ofFloat(mtoolbar, "translationY", mtoolbar.getTranslationY(), -mtoolbar.getHeight());
}
mAnimator.start();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//判断id
switch (v.getId()) {
//若是listview_scrollhide的id
case R.id.listview_scrollhide: {
//判断模式
switch (event.getAction()) {
//手指落下
case MotionEvent.ACTION_DOWN:
//获取起始位置的y
mFirstY = event.getY();
break;
//手指移动
case MotionEvent.ACTION_MOVE:
//获取当前位置的y
mCurrentY = event.getY();
//判断listView的滑动方向
//当前减起始且大于滑动的最短距离则滑动方向down
if (mCurrentY - mFirstY > mTouchSlop) {
direction = 0;
//反之则滑动的方向为up
} else if (mFirstY - mCurrentY > mTouchSlop) {
direction = 1;
}
//当滑动的方向为up时
if (direction == 1) {
//且当前的toolbar为显示状态
if (mShow) {
toolbarAnim(1);
Log.d(TAG, "onTouch: 隐藏");
mShow = !mShow;
}
}
//当滑动的方向为down时
else if (direction == 0) {
//且当前的toolbar为隐藏状态
if (!mShow) {
toolbarAnim(0);
Log.d(TAG, "onTouch: 显示");
mShow = !mShow;
}
}
break;
case MotionEvent.ACTION_UP:
break;
}
}
break;
}
return false;
}
}
新建一个activity_scrollhide.xml文件,并修改如下:
<?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/mtoolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
<ListView
android:id="@+id/listview_scrollhide"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
修改清单文件,修改如下:
...
<activity android:name=".ScrollHideActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
只是将首次显示的Activity设置为了ScrollHideActivity,运行效果如下:
可以看到,是可以实现隐藏和显示Toolbar的。
3、 聊天ListView
相信看过郭神第一行代码的同学都应该知道这个东西的(第一版用listView实现的,第二版用RecycleView实现的),其实这个,不管是那个控件,逻辑都是一样的,我们先来实现用ListView的哈,先写布局吧:
聊天布局:
<?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="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:id="@+id/left_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left">
<TextView
android:id="@+id/left_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/right_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right">
<TextView
android:id="@+id/right_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp" />
</LinearLayout>
</LinearLayout>
这个布局和书上的不太一样了,书上的是写了两个布局,但是,我感觉一个布局足矣~,再写一个activity_chat.xml用来显示,具体如下:
<?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/chat_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/type_conversion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送" />
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something here"
android:maxLines="2" />
<Button
android:id="@+id/Send_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送" />
</LinearLayout>
</LinearLayout>
然后我们就需要新建一个实体类了,代码如下:
package com.llx.lenovo.listviewdemo;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: Msg
* 创建者: LLX
* 创建时间: 2017/3/14 16:37
* 描述: 消息实体类
*/
public class Msg {
//收到消息
public static final int TYPE_RECEIVED = 0;
//发送消息
public static final int TYPE_SENT = 1;
//内容
private String content;
//类型(发送、收到)
private int type;
public Msg(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
在这个实体类中,我们加了个参数,就是,若当前的状态为发送端,那么我们的type的值就为1,若是收到的消息那type的值就为0;然后我们根据这个值去判断我们应该显示左边的布局还是右边的布局,OK,这个逻辑就是在adapter中实现的了,adapter代码如下:
package com.llx.lenovo.listviewdemo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: MsgAdapter
* 创建者: LLX
* 创建时间: 2017/3/14 16:40
* 描述: 消息适配器(ListView)
*/
public class MsgAdapter extends BaseAdapter {
private Context mContext;
private List<Msg> mMsgList;
private LayoutInflater mInflater;
private ViewHolder mViewHolder;
public MsgAdapter(Context mContext, List<Msg> mMsgList) {
this.mContext = mContext;
this.mMsgList = mMsgList;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return mMsgList.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
mViewHolder = new ViewHolder();
view = mInflater.inflate(R.layout.msg_item, parent, false);
mViewHolder.leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);
mViewHolder.leftMsg = (TextView) view.findViewById(R.id.left_msg);
mViewHolder.rightLayout = (LinearLayout) view.findViewById(R.id.right_layout);
mViewHolder.rightMsg = (TextView) view.findViewById(R.id.right_msg);
view.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) view.getTag();
}
if (mMsgList.get(position).getType() == Msg.TYPE_RECEIVED) {
//如果接受到的类型是TYPE_RECEIVED则左布局显示,右布局隐藏
mViewHolder.leftLayout.setVisibility(View.VISIBLE);
mViewHolder.rightLayout.setVisibility(View.GONE);
mViewHolder.leftMsg.setText(mMsgList.get(position).getContent());
} else if (mMsgList.get(position).getType() == Msg.TYPE_SENT) {
//如果接受到的类型是TYPE_SENT则左布局隐藏,右布局显示
mViewHolder.leftLayout.setVisibility(View.GONE);
mViewHolder.rightLayout.setVisibility(View.VISIBLE);
mViewHolder.rightMsg.setText(mMsgList.get(position).getContent());
}
return view;
}
private class ViewHolder {
//左布局
private LinearLayout leftLayout;
private TextView leftMsg;
//右布局
private LinearLayout rightLayout;
private TextView rightMsg;
}
}
好了,一切都准备好了,来看下我们的Activity吧,如下:
package com.llx.lenovo.listviewdemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: ChatActivity
* 创建者: LLX
* 创建时间: 2017/3/14 16:33
* 描述: 聊天Activity
*/
public class ChatActivity extends AppCompatActivity {
private static final String TAG = "ChatActivity";
private List<Msg> mMsgList;
private MsgAdapter msgAdapter;
private ListView chat_list;
private EditText input_text;
private Button Send_bt;
private Button type_conversion;
private Boolean flag = true;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
//控件初始化
initView();
//数据初始化
initData();
//角色翻转
type_conversion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (flag) {
type_conversion.setText("收到");
} else if (!flag) {
type_conversion.setText("发送");
}
flag = !flag;
}
});
Send_bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//判断输入框是否为空
if (!TextUtils.isEmpty(input_text.getText())) {
if (flag) {
Msg msg = new Msg(input_text.getText().toString().trim(), Msg.TYPE_SENT);
mMsgList.add(msg);
} else if (!flag) {
Msg msg1 = new Msg(input_text.getText().toString().trim(), Msg.TYPE_RECEIVED);
mMsgList.add(msg1);
}
//清空
input_text.setText("");
//刷新适配器
msgAdapter.notifyDataSetChanged();
//显示listView的最后一行
chat_list.smoothScrollToPosition(mMsgList.size()-1);
}
}
});
}
//数据初始化
private void initData() {
Msg msg1 = new Msg("Hello", Msg.TYPE_RECEIVED);
mMsgList.add(msg1);
Msg msg2 = new Msg("Hello,who is that?", Msg.TYPE_SENT);
mMsgList.add(msg2);
}
//初始化
private void initView() {
mMsgList = new ArrayList<>();
chat_list = (ListView) findViewById(R.id.chat_list);
input_text = (EditText) findViewById(R.id.input_text);
Send_bt = (Button) findViewById(R.id.Send_bt);
type_conversion = (Button) findViewById(R.id.type_conversion);
//适配器初始化
msgAdapter = new MsgAdapter(this, mMsgList);
chat_list.setAdapter(msgAdapter);
}
}
这里我加了个boolean类型的flag,用来判断当前的状态,为模拟发送端还是接收端,然后用一个button来控制,OK,看下效果吧:
可以看到,效果是可以实现的,就是界面有点儿丑哈,可以在这个基础上进行美化嘛~比如把那根分隔线去了,然后把文字的背景换一下,再加个头像,也是可以的,嘻嘻~
4、动态改变ListView布局
通常情况下,如果要动态的改变点击item的布局来达到Focus的效果,一般有两种方法,一是将两个布局写在一起,通过布局的显示隐藏来达到切换布局的效果,另外一种则是在getView的时候,通过判断来选择不同的加载不同的布局,两种方法都有利弊,关键还是要看看应用场景,所以我们还是得在Adapter作手脚了。直接上代码吧,没什么说的~
新建activity_dynamic.xml布局文件,代码如下:
<?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/focus_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
新建DynamicAdapter.java用来实现动态改变ListView布局的适配器,代码如下:
package com.llx.lenovo.listviewdemo;
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;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: DynamicAdapter
* 创建者: LLX
* 创建时间: 2017/3/14 17:46
* 描述: 动态改变listView布局适配器
*/
public class DynamicAdapter extends BaseAdapter {
private List<String> mDatalist;
private Context mContext;
private int mCurrentItem = 0;
public DynamicAdapter(Context mContext, List<String> mDatalist) {
this.mDatalist = mDatalist;
this.mContext = mContext;
}
@Override
public int getCount() {
return mDatalist.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout = new LinearLayout(mContext);
//方向
layout.setOrientation(LinearLayout.VERTICAL);
if (mCurrentItem == position) {
//点击的item
layout.addView(addFocusView(position));
} else {
//未选中的item
layout.addView(addNormalView(position));
}
return layout;
}
public void setCurrentItem(int currentItem) {
this.mCurrentItem = currentItem;
}
private View addFocusView(int i) {
//只显示图片并居中
ImageView iv = new ImageView(mContext);
iv.setImageResource(R.mipmap.ic_launcher);
return iv;
}
private View addNormalView(int i) {
//实例一个LinearLayout
LinearLayout layout = new LinearLayout(mContext);
//设置方向
layout.setOrientation(LinearLayout.HORIZONTAL);
//实例ImageView并设置图片,并添加到layout中
ImageView iv = new ImageView(mContext);
iv.setImageResource(R.mipmap.ic_launcher);
layout.addView(iv, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
//实例TextView,内容从mDatalist得到,添加到layout中,并设置为居中
TextView tv = new TextView(mContext);
tv.setText(mDatalist.get(i));
layout.addView(tv, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
layout.setGravity(Gravity.CENTER);
return layout;
}
}
新建DynamicActivity.java代码如下:
package com.llx.lenovo.listviewdemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
/**
* 项目名: ListViewDemo
* 包名: com.llx.lenovo.listviewdemo
* 文件名: DynamicActivity
* 创建者: LLX
* 创建时间: 2017/3/14 17:45
* 描述: 动态改变ListView
*/
public class DynamicActivity extends AppCompatActivity {
private List<String> mlists;
private ListView focus_listView;
private DynamicAdapter mdynamicAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dynamic);
//初始化
initView();
focus_listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//传参(点击的item是那个)
mdynamicAdapter.setCurrentItem(position);
//刷新,目的调用getView方法
mdynamicAdapter.notifyDataSetChanged();
}
});
}
private void initView() {
mlists = new ArrayList<>();
mlists.add("item1");
mlists.add("item2");
mlists.add("item3");
mlists.add("item4");
mlists.add("item5");
mlists.add("item6");
mlists.add("item7");
focus_listView = (ListView) findViewById(R.id.focus_listView);
mdynamicAdapter = new DynamicAdapter(this, mlists);
focus_listView.setAdapter(mdynamicAdapter);
}
}
效果如下:
嘿嘿,注释写的已经够详细的了哈,就不多解释了,终于看完了,上周有英语补考,对于我一个英语渣来讲,太头疼了,然后就一直拖拖拖,拖到现在才写完,虽然现在ListView大部分都已经被RecycleView代替了,但是,为什么我还要写呢,哎!毕竟是笔记嘛,一开始学Android时,被ListView搞的头大啊,所以现在写出来也有一种,哼!你在厉害,我这不也把你拿下了?
嘿嘿,最后贴上自认为用ListView写的不错的时间轴,刘某人出品哦~
Android实训案例(三)——实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果!
然后再贴上鸿洋大神的RecycleView哈~
大家若是有什么不懂的,可以在下面评论区中留言哈,我看到后会回的,另外对android有兴趣的同学可以加我们程序员刘某人的群:555974449,群里面有很多大神的,而且很热情,很热心的,大家不懂的可以问的。