第八章 性能优化 之 布局优化(三)

文章目录

第八章 性能优化 之 布局优化(三)

(一)消除卡顿

(1)16ms原则

Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).
为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.
就像是这样的:
在这里插入图片描述
这就意味着, 我们需要在16ms内完成下一次要刷新的界面的相关运算, 以便界面刷新更新. 然而, 如果我们无法在16ms内完成此次运算会怎样呢?
例如, 假设我们更新屏幕的背景图片, 需要24ms来做这次运算. 当系统在第一个16ms时刷新界面, 然而我们的运算还没有结束, 无法绘出图片. 当系统隔16ms再发一次VSYNC信息重绘界面时, 用户才会看到更新后的图片. 也就是说用户是32ms后看到了这次刷新(注意, 并不是24ms). 这就是传说中的丢帧(dropped frame):
在这里插入图片描述
丢帧给用户的感觉就是卡顿, 而且如果运算过于复杂, 丢帧会更多, 导致界面常常处于停滞状态, 卡到爆.

(2)卡顿原因及优化

1、过于复杂的布局

a.原因
界面性能取决于UI渲染性能. 我们可以理解为UI渲染的整个过程是由CPU和GPU两个部分协同完成的.
其中, CPU负责UI布局元素的Measure, Layout, Draw等相关运算执行. GPU负责栅格化(rasterization), 将UI元素绘制到屏幕上.
如果我们的UI布局层次太深, 或是自定义控件的onDraw中有复杂运算, CPU的相关运算就可能大于16ms, 导致卡顿.
b.优化
借助Hierarchy Viewer这个工具来分析布局. Hierarchy Viewer不仅可以以图形化树状结构的形式展示出UI层级, 还对每个节点给出了三个小圆点, 以指示该元素Measure, Layout, Draw的耗时及性能.并看下方Layout布局优化

2、过渡绘制(overdraw)

a.原因
Overdraw: 用来描述一个像素在屏幕上多少次被重绘在一帧上.通俗的说: 理想情况下, 每屏每帧上, 每个像素点应该只被绘制一次, 如果有多次绘制, 就是Overdraw, 过度绘制了.常见:
(1)绘制了多重背景
(2)绘制了不可见UI元素
b.优化
(1)删除不需要的背景
(2)去掉window默认背景

<item name="android:windowBackground">@null</item>

getWindow().setBackgroundDrawable(null);

3、UI线程复杂运算

4、频繁GC

a.原因
执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿.
导致频繁GC两个原因:
内存抖动(Memory Churn), 即大量的对象被创建又在短时间内马上被释放.
瞬间产生大量的对象会严重占用Young Generation的内存区域, 当达到阀值, 剩余空间不够的时候, 也会触发GC. 即使每次分配的对象需要占用很少的内存,但是他们叠加在一起会增加Heap的压力, 从而触发更多的GC.
b.优化
一般来说瞬间大量产生对象一般是因为我们在代码的循环中new对象, 或是在onDraw中创建对象等.

(二)Layout布局优化

(1)分析布局层级图工具Hierarchy Viewer

(2)优化方案

a.尽量减少布局层级和复杂度

1)尽量不要嵌套使用RelativeLayout.
2)尽量不要在嵌套的LinearLayout中都使用weight属性.
3)Layout的选择, 以尽量减少View树的层级为主.
4)去除不必要的父布局.
5)善用TextView的Drawable减少布局层级
如果H Viewer查看层级超过5层, 你就需要考虑优化下布局了~

b.善用Tag标签

1)使用include来重用布局.
2)使用merge来解决include或自定义组合ViewGroup导致的冗余层级问题.用一个merge标签来减少一级.
3)ViewStub

(三)自定义View优化

(1)onDraw优化

1、在onDraw()方法中你应该减少冗余代码,冗余代码会带来使你view不连贯的垃圾回收。
2、尽可能的不要频繁的调用它。大部分时候调用 onDraw()方法就是调用invalidate()的结果,所以减少不必要的调用invalidate()方法。有可能的,调用四种参数不同类型的invalidate(),而不是调用无参的版本。无参变量需要刷新整个view,而四种参数类型的变量只需刷新指定部分的view.这种高效的调用更加接近需求,也能减少落在矩形屏幕外的不必 要刷新的页面。

(2)requestLayout优化

1、减少耗时操作requestLayout()请求次数,任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。
2、尽量保持View的层级是扁平化的,这样对提高效率很有帮助。 如果你有一个复杂的UI,你应该写一个自定义的ViewGroup类来表现它的布局。不同于内置的 view类,你的自定义view能关于尺寸和它子控件的形状做出应用特定的假想,同时避免通过它子类来 计算尺寸。

(3)使用硬件加速

(4)初始化创建对象

不要在onDraw方法内创建绘制对象,一般都在构造函数里面初始化对象

(5)状态存储与恢复

如果内存不足,而恰好我们的Activity置于后台,不幸被重启,或者用户旋转屏幕造成Activity重启,我们的View应该也能尽可能的去保存自己的属性。

(四)ListView优化

(1)基本优化——adapter中getView()方法

1.convertView

主要优化加载布局的问题——减少getView方法每次调用LayoutInflater.inflate()方法

View view;
if(convertView == null){
view = LayoutInfalter.from(getContext()).inflate(resourceID,null)
}
else{
view = convertView
}

如果没有缓存就加载布局,如果有缓存就直接用convertView对象

2.viewHolder

主要优化加载控件问题——减少getView方法每次调用findViewById()方法

//getView核心代码
ViewHolder viewHolder;
if(convertView == null){
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewByID(R.id.fruit_image);
view.setTage(viewHolder);//讲ViewHolder存储在View中

}else{
view = convertView;
viewHolder = ViewHolder view.getTag();//重获取viewHolder
}
viewHolder.fruitImage.setImageResource(fruit.getIMageID);



//内部类
class ViewHolder{
ImageView fruitImage;
}

convertView为空时,viewHolder会将控件的实例放在ViewHolder中,然后用setTag方法将ViewHolder对象存储在View中
convertView不为空时,用getTag方法从View获取viewHolder对象

3.RecycleBinAndroid的GC机制

在这里插入图片描述
1.ListView上可见的itemView称为onScreenView(activeView),滚动后移除屏幕的itemView称为offScreenView(scrapView),RecycleBin会存储activeView和scrapView
2.滚动后,ListView会将需要显示的View从RecycleBin里面取出一个scrapView,将其作为convertView参数传递到Adapter的getView中,从而达到View的复用,不必每次都加载布局(LayoutInflater.inflate())
3.Google推荐的ListView优化方案

public View getView(int position, View convertView, ViewGroup parent) {
     Log.d("MyAdapter", "Position:" + position + "---"
             + String.valueOf(System.currentTimeMillis()));
     ViewHolder holder;
     if (convertView == null) {
         final LayoutInflater inflater = (LayoutInflater) mContext
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         convertView = inflater.inflate(R.layout.list_item_icon_text, null);
         holder = new ViewHolder();
         holder.icon = (ImageView) convertView.findViewById(R.id.icon);
         holder.text = (TextView) convertView.findViewById(R.id.text);
         convertView.setTag(holder);
     } else {
         holder = (ViewHolder) convertView.getTag();
     }
     holder.icon.setImageResource(R.drawable.icon);
     holder.text.setText(mData[position]);
     return convertView;
 }
  
 static class ViewHolder {
     ImageView icon;
  
     TextView text;

(2)真正意义上ListView性能优化

1.自定义Item中对图片的处理

1)异步加载(1、多线程/线程池2、AsyncTask)
由于UI线程(也可叫主线程)负责处理用户输入事件(TP事件,显示事件等),直接与用户交互,如果UI线程阻塞,直接会影响用户的体验效果,严重的会报ANR错误。所以我们需要把耗时操作(网络请求、图片加载)移出主线程,在子线程中进行处理
2)用软引用存储图片信息
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

View view = findViewById(R.id.button);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
    Drawable drawable = new BitmapDrawable(bitmap);
    SoftReference<Drawable> drawableSoftReference = 
                                                new SoftReference<Drawable>(drawable);
        Drawable bgdrawable = drawableSoftReference.get();
    if(bgdrawable != null) {
        view.setBackground(bgdrawable);
    }

3)图片压缩
4)释放滑出可见区域图片内存(不推荐)/图片缓存Cache(LruCache)
1.始终从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。如果缓存中不存在,那么启动一个AscynTask去加载
2.不推荐主动释放Bitmap内存,实现复杂
3. LruCache(LRU:最近最少使用算法)适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则更有可能会引起 java.lang.OutOfMemory 的异常,因为 LruCache 中的强引用不能被释放,而程序又需要内存。使用LruCache,需要重写sizeOf方法,返回占用的内存大小。

private HashMap<String, SoftReference<Drawable>> cacheMap = null;
	private BlockingQueue<Runnable> queue = null;
	private ThreadPoolExecutor executor = null;
 
	public ImageAsyncLoader() {
		cacheMap = new HashMap<String, SoftReference<Drawable>>();
 
		queue = new LinkedBlockingQueue<Runnable>();
		/**
		 * 线程池维护线程的最少数量2 <br>
		 * 线程池维护线程的最大数量10<br>
		 * 线程池维护线程所允许的空闲时间180秒
		 */
		executor = new ThreadPoolExecutor(2, 10, 180, TimeUnit.SECONDS, queue);
	}
 
	public Drawable loadDrawable(final Context context, final String imageUrl, final ImageCallback imageCallback) {
		if (cacheMap.containsKey(imageUrl)) {
			SoftReference<Drawable> softReference = cacheMap.get(imageUrl);
			Drawable drawable = softReference.get();
			if (drawable != null) {
				return drawable;
			}
		}
 
		final Handler handler = new Handler() {
			public void handleMessage(Message message) {
				imageCallback.imageLoaded((Drawable) message.obj, imageUrl);
			}
		};
 
		// 将任务添加到线程池
		executor.execute(new Runnable() {
			public void run() {
				// 根据URL加载图片
				Drawable drawable = loadImageFromUrl(context, imageUrl);
 
				// 图片资源不为空是创建软引用
				if (null != drawable)
					cacheMap.put(imageUrl, new SoftReference<Drawable>(drawable));
 
				Message message = handler.obtainMessage(0, drawable);
				handler.sendMessage(message);
			}
		});
 
		return null;
	}
 
	// 网络图片先下载到本地cache目录保存,以imagUrl的图片文件名保存,如果有同名文件在cache目录就从本地加载
	public static Drawable loadImageFromUrl(Context context, String imageUrl) {
		Drawable drawable = null;
 
		if (imageUrl == null)
			return null;
		String fileName = "";
 
		// 获取url中图片的文件名与后缀
		if (imageUrl != null && imageUrl.length() != 0) {
			fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1);
		}
 
		// 根据图片的名称创建文件(不存在:创建)
		File file = new File(context.getCacheDir(), fileName);
 
		// 如果在缓存中找不到指定图片则下载
		if (!file.exists() && !file.isDirectory()) {
			try {
				// 从网络上下载图片并写入文件
				FileOutputStream fos = new FileOutputStream(file);
				InputStream is = new URL(imageUrl).openStream();
				int data = is.read();
				while (data != -1) {
					fos.write(data);
					data = is.read();
				}
				fos.close();
				is.close();
 
				drawable = Drawable.createFromPath(file.toString());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		// 如果缓存中有则直接使用缓存中的图片
		else {
			// System.out.println(file.isDirectory() + " " + file.getName());
			drawable = Drawable.createFromPath(file.toString());
		}
		return drawable;
	}
 
	public interface ImageCallback {
		public void imageLoaded(Drawable imageDrawable, String imageUrl);
	}

结合Glide图片加载框架——图片加载与缓存类库

2.内存泄露的预防

(1)避免在adapter中使用static 来定义全局静态变量,static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context
(2)Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题
(3)使用WeakReference代替强引用。比如可以使用WeakReferencemContextRef
(4)线程
线程产生内存泄露的主要原因在于线程生命周期的不可控。如果我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题
解决:
(1)将线程的内部类,改为静态内部类——不会悄悄引用所属类的实例,所以就不容易泄露。
(2)如果需要引用Acitivity,使用弱引用

3.分页加载

原理:自定义PullToRefreshView控件+okHttp异步加载网络数据
1、PullToRefreshListView是怎么布局的?
PullToRefreshListView是一个垂直线性布局,顶部是mHeaderView,中间是ListView,底部是mFooterView。
2、初始状态为何看不到Header?
通过将mHeaderView的marginTop设置为负数,将Header区域隐藏起来。

LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
        mHeaderViewHeight);
// 设置topMargin的值为负的header View高度,即将其隐藏在最上方
params.topMargin = -(mHeaderViewHeight);
// mHeaderView.setLayoutParams(params1);
addView(mHeaderView, params);

3、下拉刷新过程描述
在onInterceptTouchEvent中判断当前位置是否在ListView的顶部(第一项的顶端位于屏幕顶端),如果是则拦截下面所有的事件交给onTouchEvent处理。

if (deltaY > 0) {
    View child = mAdapterView.getChildAt(0);
       if (mAdapterView.getFirstVisiblePosition() == 0 && child.getTop() == 0) {
        mPullState = PULL_DOWN_STATE;
        return true;
    }

onTouchEvent根据下拉的距离deltaY修改mHeaderView的marginTop并更新布局,滚动出mHeaderHeight区域。如果下拉的距离大于mHeaderHeight的高度(marginTop>=0完全显示头部区域),则松手刷新布局调用headerRefreshing方法,将mHeaderHeight的marginTop置0并更新状态,否则重置到初始态。

int newTopMargin = changingHeaderViewTopMargin(deltaY);
// 当header view的topMargin>=0时,说明已经完全显示出来了
if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {
    mHeaderState = RELEASE_TO_REFRESH;
} 

mHeaderState = REFRESHING;
setHeaderTopMargin(0);

4、 刷新状态异步加载数据,加载结束调用onHeaderRefreshComplete方法,将mHeaderHeight重新置位负数并更新状态。

setHeaderTopMargin(-mHeaderViewHeight);
mHeaderState = PULL_TO_REFRESH;

(五)RecycleView

1.五虎上将

(1)LayoutManager

负责Item视图的布局的显示管理,处理onMeasure()、onLayout()、onDraw()

(2)ItemDecoration

给每一项Item视图添加子View,例如可以进行画分隔线之类,本身是Drawable

(3)ItemAnimator

负责处理数据添加或者删除时候的动画效果,每一个item在视图改变情况(手动调用notifyXXX()时)下都会执行的动画

(4)Adapter

为每一项Item创建视图,并返回视图给RecyclerView作为其子布局

(5)ViewHolder

持有Item视图View的子布局

2.源码分析extends ViewGroup

(1)RecyclerView的绘制

a)onMeasure
由LayoutManager处理,直接根据测量值和模式返回最适大小
b)onLayout
不断从缓存池recycler取itemView并填充到RecyclerView,直到填满屏幕为止
c)onDraw
绘制itemView自身及其子控件及分割线

(2)RecyclerView滚动事件

a)onTouch(MotionEvent.ACTION_MOVE)
调用LinearLayoutManager.scrollBy()
b)dispatchNestedPreScroll() 用于处理嵌套逻辑
1.Android触摸事件分发机制
子View首先得到滑动事件处理权,一旦子View开始处理,下个手势前是不会传给父View;一旦父View对其拦截,则拦截了以后在下个手势前无法将事件再发给子View
2.NestedScrolling机制
在子View消耗掉滑动事件之前调用,给父View一个率先处理滑动事件的机会

3.Recycler缓存与回收机制

(1)四级缓存

1.屏幕内缓存mAttachedScrap、mChangedScrap
指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中
mChangedScrap表示数据已经改变的ViewHolder列表
mAttachedScrap未与RecyclerView分离的ViewHolder列表
2.屏幕外缓存mCachedViews
当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
3.自定义缓存mViewCacheExtension
可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。通常我们也不会去设置他,系统已经预先提供了两级缓存了,除非有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到他
4.缓存池mRecyclerPool
ViewHolder首先会缓存在mCachedViews中,当超过了2个(默认为2),就会添加到mRecyclerPool中。mRecyclerPool会根据ViewType把ViewHolder分别存储在不同的集合中,每个集合最多缓存5个ViewHolder。RecyclerViewPool可以被多个RecyclerView共享

通过了解RecyclerView的四级缓存,我们可以知道,RecyclerView最多可以缓存N(屏幕最多可显示的item数)+ 2 (屏幕外的缓存) + 5*M (M代表M个ViewType,缓存池的缓存(若为线性布局则M=1))

(2)缓存策略

RecyclerView 的内部维护了一个四级缓存,滑出界面的 ViewHolder 会暂时放到 mCachedViews 结构中,而从 mCachedViews 结构中移除的 ViewHolder,则会放到RecycledViewPool 的循环缓存池中。在LayoutManager执行layoutChildren()中获取子View的时候,会调用RecyclerView的getViewForPosition(),Recyclerview在获取ViewHolder时按四级缓存的顺序查找holder及其对应的视图View,找到后才调用bindViewHolder,如果没找到就创建createViewHolder

4.基本使用

(1)设置布局

recyclerview.setLayoutManager(new LinearLayoutManager(
this, LinearLayoutManager.VERTICAL, false));
a)LayoutManager类型
LinearLayoutManager 、 GridLayoutManager 、 StaggeredGridLayoutManager线性显示、网格显示、瀑布流显示
RecyclerView实现瀑布流,可以通过一句话设置:recycler.setLayoutManager(new StaggeredGridLayoutManager(2, VERTICAL))就可以了。
recyclerview.setLayoutManager(new LinearLayoutManager(
this, LinearLayoutManager.VERTICAL, false));
b)LayoutManager方法
findFirstVisibleItemPosition() 返回当前第一个可见 Item 的 position
findFirstCompletelyVisibleItemPosition() 返回当前第一个完全可见 Item 的 position
findLastVisibleItemPosition() 返回当前最后一个可见 Item 的 position
findLastCompletelyVisibleItemPosition() 返回当前最后一个完全可见 Item 的 position.
scrollBy() 滚动到某个位置

(2)设置适配器

继承RecyclerView.Adapter类,实现三个抽象方法,创建一个静态的 ViewHolder

(3)设置监听

a)onItemTouchListener
b)ViewHolder上设置点击事件

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView tvDate;             
    public ViewHolder(View itemView) {
        super(itemView);        
        tvDate = (TextView) itemView.findViewById(R.id.tv_date);
        itemView.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        if(listener!=null){
            listener.onItemClick(getPosition(),mList.get(getPosition()));
        }
    }
}
//我们回调的自定义接口
public  interface  onListItemClickListener{     
    void onItemClick(int position,TrackHistory mTrackHistory);
}

5.RecycleView与ListView比较(替代?)

(1)ViewHolder封装

RecyclerView:已封装好ViewHolder的回收利用
ListView:自己调用ViewHolder与setTag

(2)布局管理器管理子View位置

RecyclerView支持多种布局效果:LinearLayoutManager(线性布局效果)、GridLayoutManager(网格布局效果)、StaggeredGridLayoutManager(瀑布流布局效果)或自定义LayoutManager

(3)局部刷新

ListView:notifyDataSetChanged()重绘每个 Item

// 数据发生了改变,那调用这个方法,传入改变对象的位置。
public final void notifyItemChanged(int position);
// 可以刷新从positionStart开始itemCount数量的item了
public final void notifyItemRangeChanged(int positionStart, int itemCount);
// 添加,传入对象的位置。
public final void notifyItemInserted(int position);
// 删除,传入对象的位置。
public final void notifyItemRemoved(int position);
// 对象从fromPosition移动到toPosition 
public final void notifyItemMoved(int fromPosition, int toPosition); 
//批量添加 
public final void notifyItemRangeInserted(int positionStart, int itemCount);
//批量删除
public final void notifyItemRangeRemoved(int positionStart, int itemCount);

(4)动画效果ItemAnimation

在添加或删除了数据后,RecyclerView 还提供了一个默认的动画效果,来改变显示。同时,你也可以定制自己的动画效果:模仿 DefaultItemAnimator 或直接继承这个类,实现自己的动画效果,并调用recyclerview.setItemAnimator(new DefaultItemAnimator()); 设置上自己的动画。

(5)嵌套滚动机制

ListView不支持嵌套滚动
RecycerView支持嵌套滚动
CollapsingToolbarLayout 嵌套RecyclerView ,当然 RecyclerView 向上滑动时,CollapsingToolbarLayout 能够同时网上收缩,直到只剩下顶部的 Toolbar。若换成ListView则只能滑动ListView

(6)缓存机制

(7)适配器adapter

ListView适配器直接返回View,并加入ListView
RecycleView适配器返回一个ViewHolder并将这个holder先加入缓存区。当视图需要的时候取缓存区找holder及其内部的View
缺点:

(7)空数据处理

ListView 提供了 setEmptyView 这个 API 来让我们处理 Adapter 中数据为空的情况,RecycleView需要自行处理

(8)HeaderView和FooterView

在 ListView 的设计中,存在着 HeaderView 和 FooterView 两种类型的视图,并且系统也提供了相应的 API 来让我们设置,RecyclerView需要自行处理

(9)Item监听事件

ListView 为我们准备了几个专门用于监听 Item 的回调接口,如单击、长按、选中某个 Item 等,RecyclerView需要自行处理

总结:

RecyclerView灵活,扩展性强,可实现不同的布局排版,且对Adapter做了很好的封装。可以很好地代替ListView

6.RecycleView通过ViewType显示不同样式的item

需求:(实战)
自己的商品显示状态、侧滑删除与详情
他人的商品显示发布人、侧滑详情
在这里插入图片描述

  1. 注册多个委托Adapter对象,每个Adapter处理一种样式,并定义同一接口;不同样式(viewType)通过不同委托集合的index标识
    (1)委托接口
public interface IDelegateAdapter {
    // 查找委托时调用的方法,根据商品的类型选择样式
    boolean isForViewType(Goods goods);
    // 用于委托Adapter的onCreateViewHolder方法
    RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
    // 用于委托Adapter的onBindViewHolder方法
    void onBindViewHolder(RecyclerView.ViewHolder holder, int position, Goods goods);
}

(2)定义委托实现接口方法

public class GoodsOfOthersDelegateAdapter implements IdelegateAdapter
public class GoodsOfMineDelegateAdapter implements IDelegateAdapter

(3)简化后(不同viewType交给委托处理)适配器
3.1getItemView根据当前位置的数据判断样式并遍历所有委托adapter返回对应viewType
3.2根据viewType找到对应的委托Adapter,通过Adapter处理onCreateViewHolder和onBindViewHolder

public class GoodsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private List<Goods> goodslist = new ArrayList<>();
    private List<IDelegateAdapter> delegateAdapters = new ArrayList<>();
    private Integer currentType = 0;

    public void setDataItems(List<Goods> goodslist){
        this.goodslist = goodslist;
        notifyDataSetChanged();
    }

    public void addDelegate(IDelegateAdapter delegateAdapter){
        delegateAdapters.add(delegateAdapter);
    }

    @Override
    public int getItemViewType(int position) {
        Goods goods = goodslist.get(position);
        for(IDelegateAdapter delegateAdapter : delegateAdapters){
            if(delegateAdapter.isForViewType(goods))
                currentType = goods.getGoodsType();
        }
        return currentType;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 找到对应的委托Adapter
        IDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);
        // 把onCreateViewHolder交给委托Adapter去处理
        RecyclerView.ViewHolder viewHolder = delegateAdapter.onCreateViewHolder(parent, viewType);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int viewType = holder.getItemViewType();
        // 找到对应的委托Adapter
        IDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);
        // 把onCreateViewHolder交给委托Adapter去处理
        delegateAdapter.onBindViewHolder(holder,position,goodslist.get(position));
    }

    @Override
    public int getItemCount() {
        return goodslist.size();
    }
}

(4)使用适配器获得数据源

recyclerView = (RecyclerView)findViewById(R.id.chy_recycleView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
goodsAdapter = new GoodsAdapter();
goodsAdapter.setDataItems(goodsList);
goodsAdapter.addDelegate(new GoodsOfMineDelegateAdapter());
goodsAdapter.addDelegate(new GoodsOfOthersDelegateAdapter());
recyclerView.setAdapter(goodsAdapter);

7.RecycleView优化

一个带横向滚动列表的垂直滚动列表。这是通过在一个recyclerView中嵌套另一个recyclerView来实现的。当用户滚动横向列表的时候,inner RecyclerView可以流畅的滚动。但是当垂直滚动的时候, inner RecyclerView 中的每个view再次inflated了一遍,从而感觉很卡顿。这是因为每个嵌套的 RecyclerViews 都有各自的 view pool
我们可以为所有 inner RecyclerView 设置一个单一的 view pool

	public OuterRecyclerViewAdapter(List<Item> items) {
	    //Constructor stuff
	    viewPool = new RecyclerView.RecycledViewPool();
	}

	@Override
	public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
	    //Create viewHolder etc
	    holder.innerRecyclerView.setRecycledViewPool(viewPool);
	}

(六)新控件:ExpandListView——显示二级节点的listView

ExpandableListView主要由组与子元素组成。ExpandableListView主要有setOnGroupClickListener,setOnGroupExpandListener,
setOnGroupCollapseListener,setOnChildClickListener这四个监听事件,分别表示组元素点击事件,组元素展开,组收缩,子元素点击事件。

发布了74 篇原创文章 · 获赞 15 · 访问量 6255

猜你喜欢

转载自blog.csdn.net/qq_29966203/article/details/90473675