面试中遇到的各种问题

2019-11-11  本文已影响0人  发光的老金

getHeight()和getMeasuredHeight()的区别

getMeasuredHeight()是获取测量宽度和高度,也就是 onMeasure 以后确定的值,相当于是通知了系统我的 View 应该是这么大,但是 View 最终的宽度和高度是在 layout 以后才确定的,也就是getHeight()的值。

Activity生命周期

正常情况:onCreate -> onStart -> onResume
点击home键回到桌面:onPause -> onStop
再次回到Activity时:onRestart -> onStart -> onResume
退出当前Activity时:onPause -> onStop -> onDestroy
1.启动一个Activity时,通常是Intent来完成。启动一个Activity首先执行回调函数onCreate。通常在代码中你需要在此函数中绑定布局,绑定控件,初始化数据等做一些初始化的动作。
2.即将执行Activity的onStart函数,执行之后Activity已经可见,但还没有出现在前台,无法与用户进行交流,这个时候通常Activity已经在后台准备好了,但是就差执行onResume函数出现在前台。
3.即将执行Activity的onResume函数,执行之后Activity不只可见而且还会出现在前台,可以与用户进行交互。
4.由于Activity执行了onResume函数,所以Activity出现在了前台,也就是Activity处于运行状态。
5.什么时候Activity会执行onPause方法呢?
(1)启动了一个新的Activity
(2)返回上一个Activity
可以理解为需要新的Activity,当前的Activity需要把手头的工作停下来,再把当前的界面交给下一个Activity,而onPause方法可以看作是一个转接工作的过程,因为屏幕空间只有那么一个 ,每次只允许一个Activity出现在前台进行工作,通常情况下onPause函数不会被单独执行,执行完onPause方法后会继续执行onStop方法才真正以为着当前Activity已经退出前台,存在于后台。
6.Activity即将执行onStop函数,之前说过,当Activity要从前台切换至后台的时候会执行,比如用户点击返回键,或用户切换至其他Activity等。
7.Activity即将执行onDestroy函数,代表这个Activity即将进入生命的终点,这是Activity生命周期函数中的最后一次回调,我们可以在onDestroy函数中,进行一些回收工作和资源的释放工作,如,广播接收器的注销工作。
8.执行完onDestroy方法的Activity接下来面对的是被GC回收,宣告生命终结。
9.很少情况下Activity才走“9”,(什么时候Activity只会调用onPause而不会调用onStop)只有当跳转的Activity是弹窗样式时,才会只执行onPause而不执行onStop。
10.当用户在其他Activity或桌面切换回这个Activity时,这个Activity就会先去执行onRestart函数,Restart有重新开始的意思,然后接下来执行onStart函数,接着执行onResume函数进入到运行状态。

onCreate

表示Activity正在被创建,这是Activity生命周期的第一个方法,通常在此做函数的初始化工作。

onStart

表示Activity正在被启动,这个时候Activity已经被创建好了,完全过了准备阶段,但是没有出现在前台,需要执行onResume函数才可以进入到前台与用户进行交互。

onResume

表示Activity已经可见了,并且Activity处于运行状态,也就是Activity不止出现在了前台,而且还可以让用户进行点击,滑动等操作与它进行交互。

onPause

表示Activity正在暂停,大多数情况下,Activity执行完onPause后会继续执行onStop,造成这种函数调用的原因是当前的Activity启动了另外一个Activity或者回切到上一个Activity,还有一种情况就是onPause被单独执行,并没有附带执行onStop方法,原因是启动了对话框样式的Activity。

onStop

表示Activity即将停止,在此方法中做一些不那么耗时的轻量级回收操作。

onDestroy

表示Activity要被销毁了,这是Activity生命周期函数的最后一个阶段,可以在onDestroy函数中做一些回收哦工作和资源释放,比如:广播接收器的注销等

onRestart

表示Activity正在重新启动,一般情况下,一个存在于后台不可见的Activity变为可见状态,都会去执行onRestart函数,然后会继续执行onStart函数,onResume函数出现在前台并且处于运行状态。

异常情况下的生命周期

情况1.资源相关的系统配置里发生改变导致Activity被杀死并重新创建

当Activity发生意外的情况的时候,Activity会被销毁,其onPause,onStop,onDestroy函数均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前的Activity状态。调用onSaveInstanceState的时机总会发生在onStop之前,至于会不会调用时机发生在onPause之前就不一定了。当Activity重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的 Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法。所以可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重新创建了,如果被重新创建了,那么就可以取出之前保存的数据并恢复,从时机上看,onRestoreInstanceState的调用时机发生在onStart之后。同时onSaveInstanceState和onRestoreInstanceState方法中,系统为我们做了一定的恢复工作,当Activity在异常情况下需要被重新创建时,系统会默认我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据。具体针对某一个特定的view系统,能为我们恢复哪些数据,可以查看view的源码,每一个view都有onSaveInstanceState和onRestoreInstanceState这两个方法。
关于保存和恢复view层次结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托window去保存数据,接着window会委托在它上面的顶级容器去保存数据,这样整个保存数据的过程就完成了。

情况2.资源内存不足导致低优先级的Activity被杀死。

优先级最高:与用户正在进行交互的Activity,即前台Activity
优先级中等:可见但非前台的Activity,比如:一个弹出对话框的Activity,可见但非在前台运行。
优先级最低:完全存在后台的Activity,比如执行了onStop。
当内存严重不足时,系统会按照上述优先级去kill掉目前Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件运行,那么这个进程将很快被系统杀死,因此后台工作不适合脱离四大组件运行,比较好的是放入Service中进行从而有一定的优先级,这样不会轻易被杀死。

总结:分析了系统的存储数据和恢复机制,在系统配置发生变化之后,Activity会被重新创建,如果系统不想被重新创建,可以给Activity指定configChanges属性。

Activity的横竖屏切换

与横竖屏生命周期函数有关的调用属性是“android:configChanges”,关于它的属性值设置影响如下:

orientation:消除横竖屏的影响

keyboardHidden:消除键盘的影响

screenSize:消除屏幕大小的影响

当我们设置Activity的android:configChanges属性为orientation或者orientation|keyboardHidden或者不设置这个属性的时候,它的生命周期会走如下流程

刚启动Activity : onCreate > onStart > onResume

竖屏切换横屏:onPause > onSaveInstanceState > onStop >onDestroy > onCreate > onStart > onRestoreInstanceState >onResume

横屏切换竖屏:onPause > onSaveInstanceState > onStop >onDestroy > onCreate > onStart > onRestoreInstanceState >onResume

当我们设置Activity的android:configChanges属性为orientation|screenSize或者orientation|screenSize|keyboardHidden的时候

刚启动Activity : onCreate > onStart > onResume

竖屏切换横屏:什么都没有被调用

横屏切换竖屏:什么都没有被调用

注意,设置了orientation|screenSize属性之后,进行横竖屏切换的时候调用的方法就是onConfigurationChanged,而不会调用Activity生命周期的各个函数。
可以屏蔽掉横竖屏切换的操作:
android:screenOrientation="portrait" 始终竖屏显示
android:screenOrientation="landscape"始终横屏显示

什么时候导致Activity的onDestroy不执行

当用户后台强杀应用程序时,当前返回栈仅有一个Activity实例时,强杀会执行onDestroy方法,当返回栈里存在多个Activity实例时,栈里的第一个没有被销毁的Activity会执行onDestroy方法,其他的不会执行。

进程的优先级

前台 > 可见 > 服务 > 后台 > 空

前台:与当前用户正在交互的Activity所在的进程。
可见:Activity可见但是没有在前台运行的进程。
服务:Activity在后台开启了Service服务所在的进程。
后台:Activity完全处于后台的进程。
空:没有任何Activity存在的进程,优先级别是最低的。

Activity任务栈

任务栈与Activity启动模式密不可分,它是用来存储Activity实例的一种数据结构,Activity的跳转和回跳都与这个任务栈有关。

Activity启动模式

Activity有哪些启动模式呢?四种,standard,singleTop ,singleTask,singleInstance

系统默认 standard

标准模式,每次启动一个Activity都会创建一个新的实例,这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity的任务栈中。

栈顶复用 singleTop

如果新的Activity位于栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息,这个时候,onCreate和onStart不会被调用,因为它并没有改变。

栈中复用 singleTask

在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会创建实例,和singleTop一样,系统会回调其onNewIntent。

单实例模式 singleInstance

加强singleTask模式,具有此种模式的Activity只能单独位于一个栈中。

Activity的启动过程(不要回答生命周期)

app启动的过程有两种情况
第一种是从桌面launcher上点击相应的应用图标
第二种是在activity中通过调用startActivity来启动一个新的activity。
我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activityInstrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

HttpClient与HttpUrlConnection的区别

首先HttpClientHttpUrlConnection这两种方式都支持Https协议,都是以流的形式进行上传或者下载数据,也可以说是以流的形式进行数据的传输,还有ipv6,以及连接池等功能。HttpClient这个拥有非常多的API,所以如果想要进行扩展的话,并且不破坏它的兼容性的话,很难进行扩展,也就是这个原因,GoogleAndroid6.0的时候,直接就弃用了这个HttpClient.
HttpUrlConnection相对来说就是比较轻量级了,API比较少,容易扩展,并且能够满足Android大部分的数据传输。比较经典的一个框架volley,在Android 2.3版本以前都是使用Android HttpClient,在Android 2.3以后就使用了HttpUrlConnection

java虚拟机和Dalvik虚拟机的区别

Java虚拟机:

1、java虚拟机基于栈。
基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多更多。
2、java虚拟机运行的是java字节码。
java类会被编译成一个或多个字节码.class文件.

Dalvik虚拟机:

1、dalvik虚拟机是基于寄存器的
2、Dalvik运行的是自定义的.dex字节码格式。
java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据.
3、常量池已被修改为只使用32位的索引,以 简化解释器。
4、一个应用,一个虚拟机实例,一个进程
所有android应用的线程都是对应一个linux线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个android dalvik应用程序都被赋予了一个独立的linux PID(app_*)

讲解一下Context

Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。
Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。
不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。

Context数量 = Activity数量 + Service数量 + 1 (1为Application)

四种LaunchMode及其使用场景

此处延伸:栈(First In Last Out)与队列(First In First Out)的区别

栈与队列的区别:

队列先进先出,栈先进后出
对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
遍历数据速度不同
standard 模式
这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。使用场景:大多数Activity。

singleTop 模式
如果在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的onNewIntent()),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类App的内容页面。

singleTask 模式
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

singleInstance 模式
在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent())。其效果相当于多个应用共享一个应用,不管谁激活该Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。

怎样退出终止App

建立一个基类Activity,在Activity创建时添加到全局表中,在Activity销毁时移除全局表,调用全局退出时,对全局表Activity进行全部退出,达到完全退出的效果。

ANR出现的场景及解决方案

在Android中,应用的响应性被活动管理器(Activity Manager)和窗口管理器(Window Manager)这两个系统服务所监视。当用户触发了输入事件(如键盘输入,点击按钮等),如果应用5秒内没有响应用户的输入事件,那么,Android会认为该应用无响应,便弹出ANR对话框。而弹出ANR异常,也主要是为了提升用户体验。
解决方案是对于耗时的操作,比如访问网络、访问数据库等操作,需要开辟子线程,在子线程处理耗时的操作,主线程主要实现UI的操作

java中equals,hashcode的区别和联系

hashCode:hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
equals:equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。

描述下java的Error和Exception的区别,试着举例几种RuntimeException?

从概念角度分析:
Error:程序无法处理的系统错误,编译器不做检查;
Exception:程序可以处理的异常,捕获后可能恢复;
总结:前者是程序无法处理的错误,后者是可以处理的异常
从责任角度分析:
Error:属于JVM需要负担的责任;
Exception:RuntimeException(非受检异常)是程序应该负担的责任;
Checked Exception (受检异常)可检查异常时Java编译器应该负担的责任。
RuntimeException:NullPointerException,ClassCastException,IndexOutOfBoundsException,NumberFormatException

讲解java克隆的作用以及深浅克隆的原理区别

为什么要克隆?
答案是:克隆的对象可能包含一些已经修改过的属性,保留着你想克隆对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值。即当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)。


WX20200227-022828.png

浅克隆

public class Student implements Cloneable{
    private int age;
    private String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
try {
            Student student1 = new Student(20, "张三");
            Student student2 = (Student) student1.clone();
            student2.setAge(22);
            Log.e("student1",student1.toString());
            Log.e("student2",student2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
WX20200227-021536.png

但是浅克隆会遇到问题

public class Student implements Cloneable{
    private int age;
    private String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Teacher implements Cloneable{
    private String name;
    private Student student;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", student=" + student + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
}

      try {
            Student student1 = new Student(20, "张三");
            Teacher teacher1 = new Teacher();
            teacher1.setName("小赵老师");
            teacher1.setStudent(student1);
            Teacher teacher2 = (Teacher)teacher1.clone();
            Student s2 = teacher2.getStudent();
            s2.setName("李四");
            s2.setAge(30);

            Log.e("teacher1",teacher1.toString());
            Log.e("teacher2",teacher2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
WX20200227-022232.png

这样就可能会碰到数据对应错误的问题

深克隆

在上面的Teacher类里面做处理


public class Teacher implements Cloneable{
    private String name;
    private Student student;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", student=" + student + "]";
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        Teacher teacher = (Teacher)super.clone();
        teacher.setStudent((Student)teacher.getStudent().clone());
        return teacher;

    }
}

在clone()方法中,处理拷贝对象包含的引用指向的所有对象。

Dialog和PopupWindow的区别

(1)PopupWindow在显示之前必须设置宽高,Dialog无此限制

(2)PopupWindow默认不会响应物理键盘的back,除非显示设置了popup.setFocusable(true);而在点击back的时候,Dialog会消失

(3)PopupWindow不会给页面其他部分添加蒙层,而Dialog会

(4)PopupWindow没有标题,Dialog默认有标题,可以通过dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);取消标题

(5)二者显示的时候都要设置Gravity。如果不设置,Dialog默认是Gravity.CENTER

(6)二者都有默认的背景,都可以通过setBackGroundDrawable(new ColorDrawable(android.R.color.transparent));去掉
本质差别:

AlterDialog是非阻塞式对话框:AlterDialog弹出时,后台还可以做事情

PopupWindow是阻塞式对话框:PopupWindow弹出时,程序会等待,在PopupWindow退出前,程序一直等待,只有在调用dismiss方法后,PopupWindow退出,程序才会向下执行。

差别表现:

AlterDialog弹出时,背景是黑色的,但是当我们点击背景,AlterDialog会消失,证明程序不仅响应AlterDialog的操作,还响应其他操作,其他程序没有被阻塞,说明AlterDialog是非阻塞式对话框,PopupWindow弹出时,背景没有变化,但是当我们点击背景的时候,程序没有响应,只允许我们操作PopupWindow,其他操作被阻塞。

ListView和RecyclerView的区别

ListView:

  1. 继承重写BaseAdapter类;
  2. 自定义ViewHolder与convertView的优化(判断是否为null);

RecyclerView:

  1. 继承重写RecyclerView.Adapter与RecyclerView.ViewHolder
  2. 设置LayoutManager,以及layout的布局效果

区别:

  1. ViewHolder的编写规范化,ListView是需要自己定义的,而RecyclerView是规范好的;
  2. RecyclerView复用item全部搞定,不需要想ListView那样setTag()与getTag();
  3. RecyclerView多了一些LayoutManager工作,但实现了布局效果多样化;

布局效果:

ListView 的布局比较单一,只有一个纵向效果;
RecyclerView 的布局效果丰富, 可以在LayoutMananger中设置:线性布局(纵向,横向),表格布局,瀑布流布局
在RecyclerView 中,如果存在的LayoutManager不能满足需求,可以在LayoutManager的API中自定义Layout

空数据处理:

在ListView中有个setEmptyView() 用来处理Adapter中数据为空的情况;但是在RecyclerView中没有这个API,所以在RecyclerView中需要进行一些数据判断来实现数据为空的情况;

HeaderView 与 FooterView:

  1. 在ListView中可以通过addHeaderView() 与 addFooterView()来添加头部item与底部item,来当我们需要实现下拉刷新或者上拉加载的情况;而且这两个API不会影响Adapter的编写;
  2. 但是RecyclerView中并没有这两个API,所以当我们需要在RecyclerView添加头部item或者底部item的时候,我们可以在Adapter中自己编写,根据ViewHolder的Type与View来实现自己的Header,Footter与普通的item,但是这样就会影响到Adapter的数据,比如position,添加了Header与Footter后,实际的position将大于数据的position;

局部刷新

  1. 在ListView中通常刷新数据是用notifyDataSetChanged() ,但是这种刷新数据是全局刷新的(每个item的数据都会重新加载一遍),这样一来就会非常消耗资源;
  2. RecyclerView中可以实现局部刷新,例如:notifyItemChanged();
  3. 但是如果要在ListView实现局部刷新,依然是可以实现的,当一个item数据刷新时,我们可以在Adapter中,实现一个onItemChanged()方法,在方法里面获取到这个item的position(可以通过getFirstVisiblePosition()),然后调用getView()方法来刷新这个item的数据;

动画效果:

  1. 在RecyclerView中,已经封装好API来实现自己的动画效果;有许多动画API,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;如果我们需要淑贤自己的动画效果,我们可以通过相应的接口实现自定义的动画效果(RecyclerView.ItemAnimator类),然后调用RecyclerView.setItemAnimator() (默认的有SimpleItemAnimator与DefaultItemAnimator);
  2. 但是ListView并没有实现动画效果,但我们可以在Adapter自己实现item的动画效果;

Item点击事件:

  1. 在ListView中有onItemClickListener(), onItemLongClickListener(), onItemSelectedListener(), 但是添加HeaderView与FooterView后就不一样了,因为HeaderView与FooterView都会算进position中,这时会发现position会出现变化,可能会抛出数组越界,为了解决这个问题,我们在getItemId()方法(在该方法中HeaderView与FooterView返回的值是-1)中通过返回id来标志对应的item,而不是通过position来标记;但是我们可以在Adapter中针对每个item写在getView()中会比较合适;
  2. 而在RecyclerView中,提供了唯一一个API:addOnItemTouchListener(),监听item的触摸事件;我们可以通过RecyclerView的addOnItemTouchListener()加上系统提供的Gesture Detector来实现像ListView那样监听某个item某个操作方法;

嵌套滚动机制:

  1. 在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(Nested Scrolling)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制;
  2. ListView就没有实现嵌套滚动机制;

两者在加载多布局的时候也略有不同

  1. ListView在加载多布局的时候,需要实现两个方法getItemViewType(int position) 和getViewTypeCount()。getViewTypeCount()是返回需要加载的多布局的数量,如返回两个
@Override
    public int getViewTypeCount() {
        return 2;
    }

getItemViewType(int position) 表示每个item需要加载的布局的类型,这个是根据位置返回你需要展示的类型,比如说返回0说明我想用1号布局,返回1说明我想用2号布局。特别注意一点的这里返回的值必须从0开始,且不能大于你返回的类型总数。这里我让单双item显示不同的布局

 @Override
    public int getItemViewType(int position) {
        return position % 2 == 0 ? 0 : 1;
    }

之后则要根据不同的布局设置不同的ViewHolder,并在getView里进行判断

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder1 holder1;
        ViewHolder2 holder2;
        int itemViewType = getItemViewType(position);

        if (convertView == null) {
            switch (itemViewType){
                case 0:
                    convertView = from.inflate(R.layout.test_item, null);
                    holder1 = new ViewHolder1();
                    convertView.setTag(holder1);
                    break;

                case 1:
                    convertView = from.inflate(R.layout.test_item2, null);
                    holder2 = new ViewHolder2();
                    convertView.setTag(holder2);
                    break;
            }

        } else {
            switch (getItemViewType(position)){
                case 0:
                    holder1 = (ViewHolder1) convertView.getTag();
                    break;
                case 1:
                    holder2 = (ViewHolder2) convertView.getTag();
                break;
            }

        }
        return convertView;
    }

  1. RecyclerView则有所不同,首先要实现getItemViewType ( int position)方法,表示每个item需要加载的布局的类型,这里我同样让单双item显示不同的布局
 @Override
        public int getItemViewType ( int position){
            return position % 2 == 0 ? 0 : 1;
        }

之后编写具体的RecyclerView.ViewHolder子类,两个布局就要编写两个,别忘了要让ViewHolder继承Adapter的泛型RecyclerView.ViewHolder

public class TestRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 

然后重写RecyclerView.Adapter的onCreateViewHolder(ViewGroup parent,int viewType),这里的viewType参数就是getItemViewType ( int position)方法中返回的值,可以根据它来判断需要加载哪个布局

 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder holder = null;
        switch (viewType) {
            case 0:
                View test_item = from.inflate(R.layout.test_item, parent, false);
                holder = new TestHolder1(test_item);
                break;
            case 1:
                View test_item2 = from.inflate(R.layout.test_item2, parent, false);
                holder = new TestHolder2(test_item2);
                break;
        }
        return holder;
    }

最后在onBindViewHolder(RecyclerView.ViewHolder holder, final int position)方法中,需要判断传入的参数holder的类型,这时候要调用getItemViewType ( int position)方法来判断,之后作出对应的处理就可以了。

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        int itemViewType = getItemViewType(position);
        switch (itemViewType) {
            case 0:
                TestHolder1 testHolder1 = (TestHolder1) holder;
                break;
            case 1:
                TestHolder2 testHolder2 = (TestHolder2) holder;、
                break;
        }
    }

上一篇下一篇

猜你喜欢

热点阅读