Android知识点Android View - RomoteViews , SurfaceView,RecycleView,ListView

问题 : Activity-Window-View三者的差别

Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图) LayoutInflater像剪刀,Xml配置像窗花图纸。

  1. 在Activity中调用attach,创建了一个Window
  2. 创建的window是其子类PhoneWindow,在attach中创建PhoneWindow
  3. 在Activity中调用setContentView(R.layout.xxx)
  4. 其中实际上是调用的getWindow().setContentView()
  5. 调用PhoneWindow中的setContentView方法
  6. 创建ParentView:
作为ViewGroup的子类,实际是创建的DecorView(作为FramLayout的子类)
  7. 将指定的R.layout.xxx进行填充
通过布局填充器进行填充【其中的parent指的就是DecorView】
  8. 调用到ViewGroup
  9. 调用ViewGroup的removeAllView(),先将所有的view移除掉
  10. 添加新的view:addView()

1.RomoteViews


1.1  Android为了能让进程A显示进程B的View,设计了RmoteView。主要应用于两个场景 : 通知栏通知,桌面小程序

1.2  通知栏

RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout.layout_notification);

remoteViews.setTextViewText(R.id.msg, "chapter_5: " + sId);

remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);

PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this,0new Intent(this, DemoActivity_2.class),                                                                                                                 PendingIntent.FLAG_UPDATE_CURRENT);

remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);

1.3  桌面小部件
桌面小部件通过AppWidgetProvider来实现的,它的本质是一个广播。
AppWidgetProvider提供了几个主要的方法, onUpdate, onEnable, onDisable, onDeleted 以及 onReceive。
其中onReceive会根据广播的Action响应, 然后再调用其它方法。
当桌面小部件接收到用户的交互信息,则会通过onReceive传递, 用户通过重写onReceive方法,并判断intent.getAction()是否需要做相应的处理。 如果是更新界面,就需要通过RemoveView实现:

@Override
    public void onReceive(final Context context, Intent intent) {
        super.onReceive(context, intent);
        Log.i(TAG, "onReceive : action = " + intent.getAction());

        // 这里判断是自己的action,做自己的事情,比如小工具被点击了要干啥,这里是做一个动画效果
        if (intent.getAction().equals(CLICK_ACTION)) {
            Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Bitmap srcbBitmap = BitmapFactory.decodeResource(
                            context.getResources(), R.drawable.icon1);
                    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
                    for (int i = 0; i < 37; i++) {
                        float degree = (i * 10) % 360;
                        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
                        remoteViews.setImageViewBitmap(R.id.imageView1,rotateBitmap(context, srcbBitmap, degree));
                        Intent intentClick = new Intent();
                        intentClick.setAction(CLICK_ACTION);
                        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
                        remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
                        appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidgetProvider.class),remoteViews);
                        SystemClock.sleep(30);
                    }

                }
            }).start();
        }
    }

1.4 PendingIntent

PendingIntent表示一种处于Pending状态的意图, 即是一种特定,等待,即将发生的意思。

其它进程如果想要在你的app上做一些事情,如果还是传一个Intent,他们是没有执行权限的, 你必须给他们传PendingIntent, 他们才可以执行,因为PendingIntent包含了执行的权限。
上面通知的那段代码, PendingIntent的应用场景,就是给Remoteview设定一个点击的行为,打开DemoActivity_2这个activity。

2. RemoteView 原理

  1. 可以看到一个现象:RemoteView没有提供findViewById方法, 因此无法直接访问里面的view元素,而必须通过一系列的set方法(例如 setTextViewText(int viewId, CharSequence text))
  2. 上述的两个例子NotificationManager/AppWidgetmanager分别通过Binder和NotificationManagerService/AppWidgetService进程通信, 所以通知栏和桌面部件的界面也是在SystemService中被加载, 从而构成了跨进程通信的场景。
  3. RemoteView实现了Parcelable接口,会通过Binder传递到SystemServer进程。
  4. 如果View的操作通过Binder实现的话,view的方法太多成本较高,而且大量的IPC操作会影响效率。解决方法:Android系统提供了Action的概念,Action封装了具体的操作, 然后传输到远程SystemServer进程中。
  5. RemoteViews内部有主要的方法(apply,reapply)来实现Action的操作。

apply 方法

RemoteView中的核心方法apply,用来加载布局,更新界面,下面是主要的代码:

public class RemoteViews implements Parcelable, Filter {
    ......
    public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);
        View result;
        LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ......
        //加载布局
        result = inflater.inflate(rvToApply.getLayoutId(), parent, false);

        rvToApply.performApply(result, parent, handler);

        return result;
    }
     ......
}

可以看到,上述代码通过LayoutInflater加载布局,然后在performApply()对所有mActions中的Action都执行apply()操作:
1, 通过findViewById找到对应的view;
2, 通过Reflect机制,执行View实现类里的方法;

3. RemoteView的意义

可以看出,RemoteView主要是提供了进程间View更新的一种高效快速的解决方案, 主要应用在Notification 和 AppWidgetProvider中, 但是RemoteView并不支持所有的View:

布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout

组件:Button、ImageButton、ImageView、TextView、ListView、GridView、ViewStub等


链接:https://www.jianshu.com/p/dcbfad01e762


2  SurfaceView

1.什么是SurfaceView?

Surface意为表层、表面,顾名思义SurfaceView就是指一个在表层的View对象。

为什么说是在表层呢,这是因为它有点特殊跟其他View不一样,其他View是绘制在“表层”的上面,而它就是充当“表层”本身。

SDK的文档 说到:SurfaceView就是在窗口上挖一个洞,它就是显示在这个洞里,其他的View是显示在窗口上,所以View可以显式在 SurfaceView之上,你也可以添加一些层在SurfaceView之上。 

从API中可以看出SurfaceView属于View的子类 它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D和3D的效果都可以实现。

创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,比如:SurfaceView的改变 、SurfaceView的创建 、SurfaceView 销毁等,我们可以在相应的方法中做一些比如初始化的操作或者清空的操作等等。

Android系统提供了View进行绘图处理,我们通过自定义的View可以满足大部分的绘图需求,但是这有个问题就是我们通常自定义的View是用于主动更新情况的,用户无法控制其绘制的速度,由于View是通过invalidate方法通知系统去调用view.onDraw方法进行重绘,而Android系统是通过发出VSYNC信号来进行屏幕的重绘,刷新的时间是16ms,如果在16ms内View完成不了执行的操作,用户就会看着卡顿,比如当draw方法里执行的逻辑过多,需要频繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而导致画面卡顿。而SurfaceView相当于是另一个绘图线程,它是不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制。

2.如何使用SurfaceView?

首先SurfaceView也是一个View,它也有自己的生命周期。

因为它需要另外一个线程来执行绘制操作,所以我们可以在它生命周期的初始化阶 段开辟一个新线程,然后开始执行绘制,当生命周期的结束阶段我们插入结束绘制线程的操作。这些是由其内部一个SurfaceHolder对象完成的。  


SurfaceView它的绘制原理是绘制前先锁定画布(获取画布),然后等都绘制结束以后在对画布进行解锁 ,最后在把画布内容显示到屏幕上。       

通常情况下,使用以下步骤来创建一个SurfaceView的模板:

(1)创建SurfaceView

创建自定义的SurfaceView继承自SurfaceView,并实现两个接口:SurfaceHolder.Callback和Runnable.代码如下:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable

通过实现这两个接口,就需要在自定义的SurfaceView中实现接口的方法,对于SurfaceHolder.Callback方法,需要实现如下方法,其实就是SurfaceView的生命周期:

   @Override
    public void surfaceCreated(SurfaceHolder holder) {   }
   @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {   }

(2)初始化SurfaceView

在自定义的MySurfaceView的构造方法中,需要对SurfaceView进行初始化,包括SurfaceHolder的初始化、画笔的初始化等。在自定义的SurfaceView中,通常需要定义以下三个成员变量:

    private SurfaceHolder mHolder;
    private Canvas mCanvas;//绘图的画布
    private boolean mIsDrawing;//控制绘画线程的标志位
SurfaceHolder,顾名思义,它里面保存了一个对Surface对象的引用,而我们执行绘制方法本质上就是操控Surface。
SurfaceHolder因为保存了对Surface的引用,所以使用它来处理Surface的生命周期。
(说到底 SurfaceView的生命周期其实就是Surface的生命周期)例如使用 SurfaceHolder来处理生命周期的初始化。

初始化代码如下:

 public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }
 public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
    }
    private void initView() {
        mHolder = getHolder();//获取SurfaceHolder对象
        mHolder.addCallback(this);//注册SurfaceHolder的回调方法
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }
(3)使用SurfaceView

通过SurfaceHolder对象的lockCanvans()方法,我们可以获取当前的Canvas绘图对象。

接下来的操作就和自定义View中的绘图操作一样了。

需要注意的是这里获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。

因此,之前的绘图操作都会被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。

绘制的时候,在surfaceCreated()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停的进行绘制,在绘制的逻辑中通过lockCanvas()方法获取Canvas对象进行绘制,通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。

@Override  
    public void surfaceCreated(SurfaceHolder holder) {  
        mIsDrawing = true;  
        new Thread(this).start();  
}  
@Override  
    public void surfaceChanged(SurfaceHolder holder,  
                               int format, int width, int height) {  
    }  
@Override  
    public void surfaceDestroyed(SurfaceHolder holder) {  
        mIsDrawing = false;  
    }  
@Override  
    public void run() {  
        while (mIsDrawing) {  
            draw();  
        }  
    }  
    //绘图操作  
    private void draw() {  
        try {  
            mCanvas = mHolder.lockCanvas();  
            // draw sth绘制过程  
        } catch (Exception e) {  
        } finally {  
            if (mCanvas != null)  
                mHolder.unlockCanvasAndPost(mCanvas);//保证每次都将绘图的内容提交  
        }  
    }  
这里说一个优化的地方,这就是在run方法中。

在我们的draw()方法每一次更新所耗费的时间是不确定的。举个例子 比如第一次循环draw() 耗费了1000毫秒 ,第二次循环draw() 耗时2000毫秒。很明显这样就会造成运行刷新时间时快时慢,可能出现卡顿现象。为此最好保证每次刷新的时间是相同的,这样可以保证整体画面过渡流畅。

public void run() {  
    while (mIsRunning) {  
  
    /**取得更新之前的时间**/  
    long startTime = System.currentTimeMillis();  
  
    /**在这里加上线程安全锁**/  
    synchronized (mSurfaceHolder) {  
        /**拿到当前画布 然后锁定**/  
        mCanvas =mSurfaceHolder.lockCanvas();  
        draw();  
        /**绘制结束后解锁显示在屏幕上**/  
        mSurfaceHolder.unlockCanvasAndPost(mCanvas);  
    }  
  
    /**取得更新结束的时间**/  
    long endTime = System.currentTimeMillis();  
  
    /**计算出一次更新的毫秒数**/  
    int diffTime  = (int)(endTime - startTime);  
  
    /**确保每次更新时间为30帧**/  
    while(diffTime <=TIME_IN_FRAME) {  
        diffTime = (int)(System.currentTimeMillis() - startTime);  
        /**线程等待**/  
        Thread.yield();  
    }  
  
    }  


3.SurfaceView的使用实例
(1)正弦曲线

要绘制一个正弦曲线,只需要不断修改横纵坐标的值,并让他们满足正弦函数即可。因此,我们需要一个Path对象来保存正弦函数上的坐标点,在子线程的while循环中,不断改变横纵坐标值。代码如下:

public static final int TIME_IN_FRAME = 30;  
    @Override  
    public void run() {  
        long startTime = System.currentTimeMillis();  
         while(mIsDrawing){  
             draw();  
//             x+=1;  
//             y=(int)(100*Math.sin(x*2*Math.PI/180)+400);  
//             mPath.lineTo(x,y);  
         }  
          
        /**取得更新结束的时间**/  
        long endTime = System.currentTimeMillis();  
  
        /**计算出一次更新的毫秒数**/  
        int diffTime  = (int)(endTime - startTime);  
  
        /**确保每次更新时间为30帧**/  
        while(diffTime <=TIME_IN_FRAME) {  
            diffTime = (int)(System.currentTimeMillis() - startTime);  
            /**线程等待**/  
            Thread.yield();  
        }  
    } 


(2)画图板

我们也可以通过使用SurfaceView来实现一个简单的绘图板,绘图的方法与View中进行绘图所使用的方法一样,

也是通过Path对象记录手指滑动的路径来进行绘图。在SurfaceView的onTouchEvent()方法中记录Path路径,代码如下所示:

@Override  
   public boolean onTouchEvent(MotionEvent event) {  
       int x=(int)event.getX();  
       int y=(int)event.getY();  
       switch (event.getAction()){  
           case MotionEvent.ACTION_DOWN:  
               mPath.moveTo(x,y);  
               break;  
           case MotionEvent.ACTION_MOVE:  
               mPath.lineTo(x,y);  
               break;  
           case MotionEvent.ACTION_UP:  
               break;  
  
       }  
       return true;//表示此View拦截处理触摸事件  
   }

然后在draw 方法中进行绘制

private void draw() {  
       try{  
           mCanvas=mHolder.lockCanvas();//获取Canvas对象进行绘制  
           //SurfaceView背景  
           mCanvas.drawColor(Color.WHITE);  
           mCanvas.drawPath(mPath,mPaint);  
       }catch (Exception e){  
           e.printStackTrace();  
       }finally {  
           if (mCanvas!=null){  
               mHolder.unlockCanvasAndPost(mCanvas);//保证绘制的画布内容提交  
           }  
       }  
  }

4.SurfaceView和View的区别

总的归纳起来SurfaceView和View不同之处有:

1. SurfaceView允许其他线程更新视图对象(执行绘制方法)而View不允许这么做,它只允许UI线程更新视图对象。

2. SurfaceView是放在其他最底层的视图层次中,所有其他视图层都在它上面,所以在它之上可以添加一些层,而且它不能是透明的。

3. 它执行动画的效率比View高,而且你可以控制帧数。

4. SurfaceView在绘图时使用l了双缓冲机制,而View没有。

转 : http://blog.csdn.net/android_cmos/article/details/68955134


3. RecyclerView

1. RecyclerView 是一个增强版的ListView,不仅可以实现和ListView同样的效果,还优化了ListView中存在的各种不足之处

ResyslerView 能够实现横向滚动,这是ListView所不能实现的

目前官方更加推荐使用RecyclerView.


2. 实现竖直方向 

在   dependencies 中添加库的引用

------------------------------------------------------

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.0'
    compile 'com.android.support:recyclerview-v7:24.2.1'

}

----------------------------------------------------------------

添加布局文件:


    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
   

创建RecyclerView 适配器 BookBaseAdapter ,这个类继承 RecyclerView.Adapter 并将泛型指定为 BookBaseAdapter.ViewHolde  -》 和ListView基本一致

public class BookBaseAdapter extends RecyclerView.Adapter{  
    private List mBookList;  
    static class ViewHolder extends RecyclerView.ViewHolder{  
        ImageView bookImage;  
        TextView bookname;  
        public ViewHolder(View view) {  
            super(view);  
            bookImage = (ImageView) view.findViewById(R.id.book_iamge);  
            bookname = (TextView) view.findViewById(R.id.book_name);  
        }  
    }  
    public BookBaseAdapter(List mBookList) {  
        this.mBookList = mBookList;  
    }
    @Override  
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.book,parent,false);  
        ViewHolder holder = new ViewHolder(view);  
        return holder;  
    }  
    @Override  
    public void onBindViewHolder(ViewHolder holder, int position) {  
        Book book = mBookList.get(position);  
        holder.bookname.setText(book.getName());  
        holder.bookImage.setImageResource(book.getImageId());  
    }  
    @Override  
    public int getItemCount() {  
        return mBookList.size();  
    }  
}  

在Activity中调用

RecyclerView recyslerview = (RecyclerView) findViewById(R.id.recycler_view);  
 //创建LinearLayoutManager 对象 这里使用  
LinearLayoutManager layoutmanager = new LinearLayoutManager(this);  
//设置RecyclerView 布局  
recyslerview.setLayoutManager(layoutmanager);  
//设置Adapter  
BookBaseAdapter adapter = new BookBaseAdapter(mlsit);  
recyslerview.setAdapter(adapter);  

3.实现横向滚

layoutmanager.setOrientation(LinearLayoutManager.HORIZONTAL);


4. 工作原理 :

RecyclerView 主要的5个类 

类名 作用
RecyclerView.LayoutManager 负责Item视图的布局的显示管理
RecyclerView.ItemDecoration 给每一项Item视图添加子View,例如可以进行画分隔线之类
RecyclerView.ItemAnimator 负责处理数据添加或者删除时候的动画效果
RecyclerView.Adapter 为每一项Item创建视图
RecyclerView.ViewHolder 承载Item视图的子布局

LayoutManager

java.lang.Object  
   ↳ android.view.View  
        ↳ android.view.ViewGroup  
            ↳ android.support.v7.widget.RecyclerView

首先是 RecyclerView 继承关系,可以看到,与 ListView 不同,他是一个 ViewGroup。既然是一个 View,那么就不可少的要经历 onMeasure()onLayout()onDraw() 这三个方法。 实际上,RecyclerView 就是将onMeasure()onLayout() 交给了 LayoutManager 去处理,因此如果给 RecyclerView 设置不同的 LayoutManager 就可以达到不同的显示效果,因为onMeasure()onLayout()都不同了嘛。

temDecoration 工作原理

ItemDecoration 是为了显示每个 item 之间分隔样式的。它的本质实际上就是一个 Drawable。当 RecyclerView 执行到 onDraw() 方法的时候,就会调用到他的 onDraw(),这时,如果你重写了这个方法,就相当于是直接在 RecyclerView 上画了一个 Drawable 表现的东西。 而最后,在他的内部还有一个叫getItemOffsets()的方法,从字面就可以理解,他是用来偏移每个 item 视图的。当我们在每个 item 视图之间强行插入绘画了一段 Drawable,那么如果再照着原本的逻辑去绘 item 视图,就会覆盖掉 Decoration 了,所以需要getItemOffsets()这个方法,让每个 item 往后面偏移一点,不要覆盖到之前画上的分隔样式了。

ItemAnimator

每一个 item 在特定情况下都会执行的动画。说是特定情况,其实就是在视图发生改变,我们手动调用notifyxxxx()的时候。通常这个时候我们会要传一个下标,那么从这个标记开始一直到结束,所有 item 视图都会被执行一次这个动画。

Adapter

首先是适配器,适配器的作用都是类似的,用于提供每个 item 视图,并返回给 RecyclerView 作为其子布局添加到内部。
但是,与 ListView 不同的是,ListView 的适配器是直接返回一个 View,将这个 View 加入到 ListView 内部。而 RecyclerView 是返回一个 ViewHolder 并且不是直接将这个 holder 加入到视图内部,而是加入到一个缓存区域,在视图需要的时候去缓存区域找到 holder 再间接的找到 holder 包裹的 View。

ViewHolder

每个 ViewHolder 的内部是一个 View,并且 ViewHolder 必须继承自RecyclerView.ViewHolder类。 这主要是因为 RecyclerView 内部的缓存结构并不是像 ListView 那样去缓存一个 View,而是直接缓存一个 ViewHolder ,在 ViewHolder 的内部又持有了一个 View。既然是缓存一个 ViewHolder,那么当然就必须所有的 ViewHolder 都继承同一个类才能做到了。


关于缓存机制:

RecyclerView 的内部维护了一个二级缓存,滑出界面的 ViewHolder 会暂时放到 cache 结构中,而从 cache 结构中移除的 ViewHolder,则会放到一个叫做 RecycledViewPool 的循环缓存池中。
顺带一说,RecycledView 的性能并不比 ListView 要好多少,它最大的优势在于其扩展性。但是有一点,在 RecycledView 内部的这个第二级缓存池 RecycledViewPool 是可以被多个 RecyclerView 共用的,这一点比起直接缓存 View 的 ListView 就要高明了很多,但也正是因为需要被多个 RecyclerView 公用,所以我们的 ViewHolder 必须继承自同一个基类(即RecyclerView.ViewHolder)。

默认的情况下,cache 缓存 2 个 holder,RecycledViewPool 缓存 5 个 holder。对于二级缓存池中的 holder 对象,会根据 viewType 进行分类,不同类型的 viewType 之间互不影响。


回收与复用

Recycler -> 进行回收

private ArrayList mAttachedScrap  

private ArrayList mChangedScrap与RecyclerView分离的ViewHolder列表。

private ArrayList mCachedViewsViewHolder缓存列表。

private ViewCacheExtension mViewCacheExtension开发者控制的ViewHolder缓存

private RecycledViewPool mRecyclerPool提供复用ViewHolder池。

public void bindViewToPosition(View view, int position)  将某个View绑定到Adapter的某个位置。

public View getViewForPosition(int position)

获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为:  

(1)检查mChangedScrap,若匹配到则返回相应holder  

(2)检查mAttachedScrap,若匹配到且holder有效则返回相应holder

(3)检查mViewCacheExtension,若匹配到则返回相应holder  

(4)检查mRecyclerPool,若匹配到则返回相应holder  

(5)否则执行Adapter.createViewHolder(),新建holder实例  

(6)返回holder.itemView  

(7)注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)

4.ListView

ListView使用很频繁,就不再赘述了。关于一些问题进行总结

4.1 ListView的优化

    增加优化一:convertView的使用,主要优化加载布局问题

   增加优化二:内部类ViewHolder的使用。

4.2 ListView图片加载错乱的原理和解决方案

图片错位原理: 
如果我们只是简单显示list中数据,而没用convertview的复用机制和异步操作,就不会产生图片错位;重用convertview但没用异步,也不会有错位现象。但我们的项目中list一般都会用,不然会很卡。 
在上图中,我们能看到listview中整屏刚好显示7个item,当向下滑动时,显示出item8,而item8是重用的item1,如果此时异步网络请求item8的图片,比item1的图片慢,那么item8就会显示item1的image。当item8下载完成,此时用户向上滑显示item1时,又复用了item8的image,这样就导致了图片错位现象(item1和item8是用的同一块内存哦)。

解决方法: 
对imageview设置tag,并预设一张图片。 

向下滑动后,item8显示,item1隐藏。但由于item1是第一次进来就显示,所以一般情况下,item1都会比item8先下载完,但由于此时可见的item8的tag,和隐藏了的item1的url不匹配,所以就算item1的图片下载完也不会显示到item8中,因为tag标识的永远是可见图片中的url。

4.3 Recycleview和ListView的区别

一、两者的缓存机制上的区别

    先来说一样的地方,ListView与RecyclerView缓存机制原理大致一样,滑动的时候,离开屏幕的ItemView被回收到缓存,新的itemView加在优先获取的缓存中的,这是正常的两种类似的缓存机制。

而不同的地方在于,两者的缓存层级不同,ListView只有两层,RecycleView有四级缓存。

    1. mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;

    2. mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用;

    3. RecyclerView的优势在于: a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用; b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

    同时,两者的缓存不同,RecycleView缓存在ViewHolder,可以看作是:View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);

    而ListView缓存在View上,listView的mScrapViews是根据pos获取相应的缓存,但不直接用,是重新getView(这样会重新bindView)。所以ListView中通过pos获取的是View。

mRecycleView在获取缓存的时候,是通过匹配pos获得目标的位置的缓存,这么做可以不用重新bindView,所以RecycleVIew中通过pos获取的是viewholder,是pos-->(view,holder,flag),flag是判断view是否需要重新bindView。

二、局部刷新的区别

    RecycleView中的notifyItemRemoved(position)方法,最终会调用requestLayout()方法,使整个View重新绘制,为:onMeasure()-->onLayout()-->onDraw()

    其中的onLayout分三步: 

        1.dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算;

        2.dispathLayoutStep2():真正测量布局大小,位置,核心函数为layoutChildren();

        3.dispathLayoutStep3():计算布局前后各个ItemView的状态,如Remove,Add,Move,Update等,如有必要执行相应的动画.

当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag。当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次。

    需要指出,ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。

参考 : http://blog.csdn.net/qq_28851933/article/details/54889921



 


你可能感兴趣的:(Android,Android开发艺术探索学习,面试点整理)