对了,今天中秋节,先祝大家今天中秋节快乐! 1. 老规矩--先上效果图忽然发现很多新技术都没弄过,RecyclerView居然也没用过,于是乎就学习了一下RecyclerView的用法顺带大概看了一下RecyclerView源码,在看到RecyclerView 滑动时调用LayoutManager类的layoutChunk函数addView的时候,我好奇的看了一下,这货为什么滑动中addView不卡,于是我发现了核心。
未点击按钮时
点击按钮时改变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代码自己加),不启用局部刷新效果图见上文。
使用于某个ViewGroup宽高已定位置已定,该子view想改变宽高等场景。