"ListView "-Android面试必问"精华技能点"汇总.

来源:互联网 发布:c语言a =b 编辑:程序博客网 时间:2024/06/12 01:31

ListView


目录:

  • ListView如何提高效率
  • ViewHolder为什么要声明为静态类
  • 在Activity中如何使用Handlder去除警告消息
  • 谈谈ListView中的MVC思想
  • ListView使用了哪些设计模式
  • 当ListView数据集改变之后如何更新ListView
  • ListView如何实现分页加载
  • ListView可以显示多类型的条目吗
  • ListView如何定位到指定位置
  • 如何在ScrollView中嵌套ListView
  • ScrollView嵌套listView方法除了设置高度外还有什么方法
  • ListView中如何优化图片
  • 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保持图片大小
    1. 在ListView中取图片的时候而不要直接取,应用用弱引用代替强引用
      • getView中图片转换的时候,变量要及时转换
  • 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("显示提示的图片");}

这样避免了显示的错乱

0 0
原创粉丝点击