ListView
目录:
1.ListView如何提高效率?
- 1.复用convertView
- 2.使用静态的ViewHolder
- 3.使用弱引用WeakReference引用View对象
- 4.采用分页加载(比如上拉加载更多)
效果图:
(PS:如果ListView里面的Item里设置了按钮,那么按钮可点击,item不能点击,解决方法:Buttton声明参数:android:focusable=”false”)
代码案例如下
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
layout_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:weightSum="7"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv"
android:src="@drawable/ic_launcher"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="80dp"/>
<LinearLayout
android:layout_marginTop="10dp"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="match_parent">
<TextView
android:id="@+id/name"
android:textSize="20sp"
android:text="Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:singleLine="true"
android:textSize="18sp"
android:text="Num : 123412344"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:singleLine="true"
android:textSize="17sp"
android:text="Time : 2016-8-8 8:11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<Button
android:id="@+id/bt"
android:layout_width="0dp"
android:layout_weight="2"
android:textSize="20sp"
android:text="拨打电话"
android:focusable="false"
android:layout_height="match_parent"/>
</LinearLayout>
MainActivity.java
package goodjobtome.com.viewtest;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.lang.ref.WeakReference;
public class MainActivity extends Activity {
private ListView mLv;
private MyAdapter mAdapter;
private String[] mNames;
private int[] mPics;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
initView();
initData();
initEvent();
}
private void initView() {
mLv = (ListView) findViewById(R.id.lv);
}
private void initData() {
mAdapter = new MyAdapter();
mNames = new String[]{"一","二","三","四","五","六","七","八","九","十","十一","十二"};
mPics = new int[]{R.mipmap.n1,R.mipmap.n2,R.mipmap.n3,R.mipmap.n4,R.mipmap.n5,R.mipmap.n6,
R.mipmap.pre1,R.mipmap.pre2,R.mipmap.pre3,R.mipmap.pre4,R.mipmap.pre5,R.mipmap.pre6};
mLv.setAdapter(mAdapter);
}
private void initEvent() {
}
class MyAdapter extends BaseAdapter {
//决定listView一共有多少个item
@Override
public int getCount() {
return mPics.length;
}
//可以不写,不是在base中调用,可以直接返回null
@Override
public Object getItem(int position) {
return null;
}
//返回的是position对应的item的id,直接返回position
@Override
public long getItemId(int position) {
return position;
}
//返回条目
@Override
public View getView(int position, View convertView, ViewGroup parent) {
WeakReference<ImageView> weakIv;
ViewHolder holder = null;
//刚开始为空先定义填充item,convertView
if (convertView == null) {
System.out.println(position+" 位置还未复用");
holder = new ViewHolder();
convertView = View.inflate(MainActivity.this, R.layout.layout_item, null);
holder.tv = (TextView) convertView.findViewById(R.id.name);
//holder.iv = (ImageView) convertView.findViewById(R.id.iv);
holder.bt = (Button) convertView.findViewById(R.id.bt);
/*--------------采用弱引用,引用ImvageView对象--------------*/
ImageView iv = (ImageView) convertView.findViewById(R.id.iv);
WeakReference<ImageView> weakIv = new WeakReference<ImageView>(iv);
holder.iv = weakIv.get();
//给convertView设置标记
convertView.setTag(holder);
} else {
//复用了之后,holder = convertView设置标记
holder = (ViewHolder) convertView.getTag();
System.out.println(position+" 位置复用");
}
holder.tv.setText(mNames[position]);
holder.iv.setImageBitmap(BitmapFactory.decodeResource(getResources(), mPics[position]));
return convertView;
}
}
//静态viewHolder,避免了对外部的引用
static class ViewHolder {
TextView tv;
ImageView iv;
}
}
2.ViewHolder为什么要声明为静态类?
- 一般的(非static)内部类对外部类具有强引用
- 为了避免对外部类(Activity)对象的引用,所以才声明.
3.在Activity中如何使用Handlder去除警告消息?
解决方法: static内部类+弱引用:
我们一般使用Handler都会写成一下形式,然后AndroidStudio就会提出警告(一大片被颜色渲染)
或者如图(警告):
翻译过来是:这个handler类应该为”静态”,否则可能内存泄露.解释:
- 执行了Activity的finish,但是被延迟处理还未处理的消息包含对Handler的引用.
- Handler是“匿名内部类”,持有外部的Activity的引用
- 导致Activity无法回收,很多资源都无法回收,产生了内存泄露
解决
- 静态内部类不对外部持有引用,所以定义成静态类的handler
- 加上个static警告就消失了
- 所要想解决全部问题,就还需要使用”弱引用”.
- 官方给出的建议写法:
- 内部类声明弱引用<引用外部类>对象
- 内部类构造时创建”弱引用<引用外部类>”对象
- 内部类的方法通过弱引用获取外部类对象,进行判断非空再操作
class OuterClass {
class InnerClass {
private final WeakReference<OuterClass> mTarget;
InnerClass(OuterClass target) {
mTarget = new WeakReference<OuterClass>(target);
}
void doSomething() {
OuterClass target = mTarget.get();
if (target != null) {
target.do();
}
}
}
所以根据介绍我们改进handler为:
static class myHandler extends Handler {
//弱引用<引用外部类>
WeakReference<Activity> mActivity;
myHandler(Activity activity) {
//构造创建弱引用
mActivity = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(android.os.Message msg) {
//通过弱引用获取外部类.
Activity activity = mActivity.get();
//进行非空再操作
if (activity != null) {
//doSomething
}
}
}
完美解决问题.
4.谈谈ListView中的MVC思想?
M: model
数据模型,比如里面的图片和名字资源.
V: view
显示布局,item和listView.
C: controller,控制器
控制器,比如适配器和调用的方法.
5.ListView使用了哪些设计模式?
- 适配器模式
- 观察者模式
- 享元模式
6.当ListView数据集改变之后,如何更新ListView?
- 调用ListView的adapter的notifyDatasetChange()方法,即可重新绘制
- 比如需求:我点击按钮,要改变图片和名字
代码如下:
//点击事件
holder.bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "update", Toast.LENGTH_SHORT).show();
mNames[position] = "update";
mPics[position] = R.mipmap.ic_launcher;
//适配器更新方法
mAdapter.notifyDataSetChanged();
}
});
效果如下:
点击之后:
7.ListView如何实现分页加载?
① ListView 对象设置滚动监听,要求重写两个方法:
滚动状态发生变化的方法(onScrollStateChanged),有三种状态:
- //1:手指按下触摸滑动:SCROLL_STATE_TOUCH_SCROL,对应1
- //2.滑翔:SCROLL_STATE_FLING,对应2
- //3.静止:SCROLL_STATE_IDLE,对应0
- listView 被滚动时调用的方法(onScroll)
分页处理逻辑:
- 1.只关心静止状态:关心最后一个可见的条目
- 2.如果最后一个可见条目就是数据适配器(集合)里
的最后一个 - 此时即加载更多的数据。
- 每次加载的时,计算出滚动的数量
- 当滚动的数量大于等于总数量的时,提示无更多数据。
代码如下:
private void initEvent() {
//滚动监听
mLv.setOnScrollListener(new AbsListView.OnScrollListener() {
//滚动状态改变的方法
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//打印状态:触摸,静止
Log.d("AAA", "scrollState:" + scrollState);
//1:手指按下触摸滑动:SCROLL_STATE_TOUCH_SCROL,对应1
//2.滑翔:SCROLL_STATE_FLING,对应2
//3.静止:SCROLL_STATE_IDLE,对应0
}
//滚动时的方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//重点是:最后一个可见条目
//firstVisibleItem : 第一个可见条目
//visibleItemCount : 当前可见条目总数
//totalItemCount : 存在的条目总数
//通过第一个可见+可见总数就能得到最后显示的了
Log.d("last View", firstVisibleItem + visibleItemCount + "");
int lastView = firstVisibleItem + visibleItemCount;
if (lastView == mNames.length) {
Toast.makeText(MainActivity.this, "最后一个项目到了,要加载更多数据了", Toast.LENGTH_SHORT).show();
}
}
});
}
打印结果:
07-07 08:30:07.216 8985-8985/? D/last View: 9
07-07 08:30:07.224 8985-8985/? D/last View: 9
07-07 08:30:07.236 8985-8985/? D/last View: 9
07-07 08:30:07.260 8985-8985/? D/last View: 10
07-07 08:30:07.284 8985-8985/? D/last View: 11
07-07 08:30:07.296 8985-8985/? D/last View: 11
07-07 08:30:07.316 8985-8985/? D/last View: 12
拉倒最后一个
显示效果:
8.ListView可以显示多类型的条目吗?
- 可以
- 原则上可以让每个条目设置不同的类型,通过getItem()方法就能获取条目
- adapter里还提供两个方法
- 获取条目类型数量(有多少种类型):getTypeCount
- 获取条目指定位置的条目类型:getItemViewType(position)
- 可以在getView方法里根据viewtype进行相应的加载.
9.ListView如何定位到指定位置?
- 用ListView 控件的.设置选择(位置):lv.setSlection(0);
- 比如刚刚的点击按钮后跳到指定位置
//点击事件
holder.bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "update", Toast.LENGTH_SHORT).show();
mNames[position] = "update";
mPics[position] = R.mipmap.ic_launcher;
//适配器更新方法
mAdapter.notifyDataSetChanged();
//点击后定位到指定位置,lv设置选择位置
mLv.setSelection(0);
}
});
结果:每次点击后都跳到首位;
10.如何在ScrollView中嵌套ListView?
(PS:1.一般不嵌套,因为两个控件都能滚动,会冲突,所以我们要进行另外的处理)
(最快方法)设置ListView高度的方法:
- 1.必须在ScrollView里边设置线性布局LinearLayout.
- 2.ListVeiw放到线性布局里
- 3.必须给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="match_parent"
android:orientation="vertical">
<TextView
android:gravity="center"
android:background="#ca6363"
android:text="我是标题"
android:textSize="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="300dp">
</ListView>
</LinearLayout>
</ScrollView>
</LinearLayout>
显示效果:
11.ScrollView嵌套listView方法除了设置高度外还有什么方法?
一.测量高度方法
(效果:下拉完所有的条目才显示其他布局,当listView数据少的时候就少)
(原理就是测出ListView所有条目的高度),然后给布局参数赋值,ListView设置参数即可.
* 1.循环条目的数量(适配器获取条目里的view),获取view
* 2.测量view(0,0)
* 3.得到总高度
* 2.获取布局参数
* 3.给参数赋值
* 4.ListView设置布局参数
代码如下
/*--------------测绘高度--------------*/
ListAdapter adapter = mLv.getAdapter();
int totalHeight = 0;
for (int i = 0; i < mAdapter.getCount(); i++) {
//获取每个位置的view
View view =mAdapter.getView(i, null, mLv);
view.measure(0, 0);
int measuredHeight = view.getMeasuredHeight();
totalHeight += measuredHeight;
}
//获取布局参数对象
ViewGroup.LayoutParams params = mLv.getLayoutParams();
//赋值参数
params.height = totalHeight;
//如果有分割线那么就加上所有分割线的高度
//params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
//设置参数
mLv.setLayoutParams(params);
效果如下:
可以改一下修改
更改scrollView的高度为固定,且button往外面.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:gravity="center"
android:background="#ca6363"
android:text="我是标题"
android:textSize="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="300dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
效果:
二.用单个ListView取代ScrollView
* 把scrollView里的所有内容,都放到ListView里,都做为ListView的一个条目,为此大家请看另一个图:
* 然后适配器的getView方法,通过position来决定具体填充哪一个布局:
总的布局就一个lv,等下的上下靠填充
逻辑图:
代码如下:
布局:
<ListView
android:id="@+id/act_solution_2_lv"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
上下靠填充:
逻辑代码:
public View getView(int position, View convertView, ViewGroup parent) {
//条目第一个位置填充的顶部布局
if(position == 0){
convertView = inflater.inflate(R.layout.item_solution2_top, null);
return convertView;
}
//条目第二个填充的ListView
else if(position == 21){
convertView = inflater.inflate(R.layout.item_solution2_bottom, null);
return convertView;
}
//条目第三项填充的底部布局
ViewHolder h = null;
if(convertView == null || convertView.getTag() == null){
convertView = inflater.inflate(R.layout.item_listview_data, null);
h = new ViewHolder();
h.tv = (TextView) convertView.findViewById(R.id.item_listview_data_tv);
convertView.setTag(h);
}else{
h = (ViewHolder) convertView.getTag();
}
h.tv.setText("第"+ position + "条数据");
return convertView;
}
三.用LinearLayout取代ListView
(PS:这个已经是替代了可能偏题了,但起码还算个方法)
逻辑就是: 建立一个类继承LinearLayout,然后为他加上对BaseAdapter的适配.然后把ListView改成线性布局.
最终的效果是,每个布局加一个条目而已.
四.自定义ListView来适应ScrollView
(PS:逻辑就是:一个类继承ListView,然后重写onMeasure方法,达到对ScrollView的适配),核心逻辑就是第一种方法的测绘而已.
总结:第一种第二种就能解决问题,面试时就聊测绘方法,还有第二种(统一ListVeiw)自定义每个条目的方法就好了.
11.ListView中如何优化图片?
(PS:ListView里的条目如果涉及到大量的图片,那么一定要对图片进行处理,说白了核心就是为了内存着想)
- 1.对于大图片,不要直接拿到路径就去循环解析*Bitmap.decodeFile(),不要直接加载到内存*
- 应该进行图片边界压缩,除非后台服务器的图片都是(优化过匹配的)
- Option保持图片大小
- 应该进行图片边界压缩,除非后台服务器的图片都是(优化过匹配的)
-
- 在ListView中取图片的时候而不要直接取,应用用弱引用代替强引用
- getView中图片转换的时候,变量要及时转换
- 在ListView中取图片的时候而不要直接取,应用用弱引用代替强引用
- 3.异步加载图片思想(框架):
- 1.采用线程池
- 2.采用三级缓存.
12.ListView中图片错位问题是如何产生的?怎么解决?
一.产生的原因:
- 1.因为我们采用的是缓存复用convertView
- 2.比如屏幕能显示10个item,当第一个item显示完,但是第11个因网络问题延迟了,获取数据慢了发现没有图片资源,就会显示它所重用的convertView 原来显示的图片.
- 3.导致当加载获取到图片的时候,item已经不在该位置了.
- 4.导致可能在第十几个条目显示图像
二.错乱分类:
1. 行item图片显示重复
这个显示重复是指当前行item显示了之前某行item的图片。
比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,且滑动过程中该图片加载结束,第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,这样我们看到的就是第14行显示了本该属于第2行的图片,造成显示重复。2. 行item图片显示错乱
这个显示错乱是指某行item显示了不属于该行item的图片。
比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,第14行显示了第2行的View,这时之前的图片加载结束,就会显示在第14行,造成错乱。3. 行item图片显示闪烁
上面b的情况,第14行图片又很快加载结束,所以我们看到第14行先显示了第2行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。
二.解决的办法:
(PS:加上判断和提示解决)
If (!t == null){
mViewHolder.nameText.setText(t);
}else{
mViewHolder.nameText.setText("unknow");
}
If(!p == null){
mViewHolder.photoView.setImageBitmap(p);
}else{
mViewHolder.photoView.setImageBitmap("显示提示的图片");
}
这样避免了显示的错乱