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

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值