忽然发现很多新技术都没弄过,RecyclerView居然也没用过,于是乎就学习了一下RecyclerView的用法顺带大概看了一下RecyclerView源码,在看到RecyclerView 滑动时调用LayoutManager类的layoutChunk函数addView的时候,我好奇的看了一下,这货为什么滑动中addView不卡,于是我发现了核心。
1. 老规矩--先上效果图
未点击按钮时
点击按钮时改变view高度
未使用局部刷新的Log 以及 性能效果图
使用了局部刷新的Log 以及 性能效果图
局部刷新的效果居然这么明显!!!震惊了有木有
2. 来自于RecyclerView的原理
RecyclerView addView调用的时候ViewGroup addView的源码,源码如下(如无特殊说明,以下源码均为api 25)
public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this + " addView"); } if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child's request // will be blocked at our level requestLayout(); invalidate(true); addViewInner(child, index, params, false); }
也就是会调用requestLayout,这是一个全局刷新的函数,也就是说整个界面将会被刷新,有全局刷新在View层级较多较复杂的时必然存在卡顿
那么RecyclerView 如何做到滑动时addView时不卡的呢,也就是说RecyclerView addView时候为什么没有引起RecyclerView 的onMeasure触发呢,答案就是RecyclerView 以下代码
@Override public void requestLayout() { if (mEatRequestLayout == 0 && !mLayoutFrozen) { super.requestLayout(); } else { mLayoutRequestEaten = true; } }
尼玛还有这种操作??!复写requestLayout不向上报告,自己做内部处理,内部处理详见LayoutManager类的layoutChunk函数这里不细说。
好了,得到了黑科技的样本,接下来我就来实现一个黑科技的demo,改变View宽高时局部刷新界面。
3. 黑科技的应用
MainActivity布局如下
<?xml version="1.0" encoding="utf-8"?><com.zjw.appmethodtime.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.zjw.appmethodtime.MainActivity"> <com.zjw.appmethodtime.MyLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.zjw.appmethodtime.MyTextView android:id="@+id/text_view" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/colorPrimary" android:text="Click Me" android:gravity="center" android:textSize="25sp" android:textStyle="bold"/> </com.zjw.appmethodtime.MyLayout></com.zjw.appmethodtime.MyRelativeLayout>
自定义一个RelativeLayout 用以看是否局部刷新是否生效,如果局部刷新无效则顶层onMeasure会调用(因为改变了控件大小嘛全局刷新肯定会调用到处于顶层的onMeasure)
public class MyRelativeLayout extends RelativeLayout { public MyRelativeLayout(Context context) { super(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}
自定义一个MyLayout 继承自LinearLayout ,这里只是demo你想用什么ViewGroup可以自己改。
这里MyLayout 就类似于RecyclerView 了,该子View宽高改变时会调用requestLayout,MyLayout 这里做拦截,然后自行处理。
public class MyLayout extends LinearLayout { private int mWidthMeasureSpec; private int mheightMeasureSpec; private int mLeft; private int mTop; private int mRight; private int mBottom; public static boolean shouldLocalIinvalidate = false; public MyLayout(Context context) { this(context, null); } public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mLeft = l; mTop = t; mRight = r; mBottom = b; super.onLayout(changed, l, t, r, b); } @Override public void requestLayout() { if (shouldLocalIinvalidate) { localRequestLayout(); } else { super.requestLayout(); } } @SuppressLint("WrongCall") void localRequestLayout() { onMeasure(mWidthMeasureSpec, mheightMeasureSpec); onLayout(true, mLeft, mTop, mRight, mBottom); invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidthMeasureSpec = widthMeasureSpec; mheightMeasureSpec = widthMeasureSpec; }}
以下代码就是在MainActivity里使用改变子View宽高局部刷新界面
package com.zjw.appmethodtime;import android.content.res.Resources;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.TypedValue;import android.view.View;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener { protected MyRecycleView mListView; protected TextView mTextView; private float value; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.activity_main); initView(); } private void initView() { Resources resources = this.getResources(); value = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, resources.getDisplayMetrics()); mTextView = (TextView) findViewById(R.id.text_view); mTextView.setOnClickListener(MainActivity.this); } @Override public void onClick(View view) { if (view.getId() == R.id.text_view) { view.getLayoutParams().height += value; //shouldLocalIinvalidate 为true 表示开启局部刷新 否则为关闭(MyLayout shouldLocalIinvalidate 默认为false) ((MyLayout) view.getParent()).shouldLocalIinvalidate = true; view.requestLayout(); view.invalidate(); //局部刷新完成及时恢复成可以全局刷新的状态 ((MyLayout) view.getParent()).shouldLocalIinvalidate = false; } }}
上面MainActivity 的onClick代码中开启了局部刷新(log代码自己加),效果图参见上文。
把上面的((MyLayout) view.getParent()).shouldLocalIinvalidate = true;
这句去掉,这就是相当于不启用局部刷新,然后看MyRelativeLayout 的onMeasure方法log(log代码自己加),不启用局部刷新效果图见上文。
4. 应用场景
使用于某个ViewGroup宽高已定位置已定,该子view想改变宽高等场景。
共同学习,写下你的评论
评论加载中...
作者其他优质文章