android+手写布局,两年Android菜鸟手写RecyclerView布局——是时候该强行进阶一波了...

RecyclerView实现原理解析(自己手写RecycleView加深对RecycleView理解)

涉及知识点

回收池、scrollTo、scrollBy、onInterceptTouchEvent、onTouchEvent、onMeasure、onLayout、Stack、getScaledTouchSlop

回收池:复用同类型的Item,保证滑动展示大量数据Item时,内存占用不至于过大。永远都只使用回收池里几种类型Item

onInterceptTouchEvent、onTouchEvent:

监听手势滑动,RecycleView(自己实现的)继承ViewGroup,当手势滑动距离大于最小滑动距离时,ViewGroup本身自己处理滑动事件

Stack:回收池采用栈的方式管理,这样可以保证滑动RecycleView时,以最快的方式复用顶部滑出去的View为底部刚添加的View所用

getScaledTouchSlop:系统定义的一个区分手势点击或滑动的距离界限值

上述阐明了自己认为手写RecycleView需要关注的几个知识点,后续可能在继续补充

贴上RecycleView实现中心思想的代码

RecycleView.java

public class RecyclerView extends ViewGroup {

private static final String TAG = "RecyclerView";

// 最小滑动距离(通过此值判断滑动事件是否传递到子View)

private int touchSlop;

// 当前显示的View集合(无论后续上划,下划,最终展示使用的几个View集合)

private List viewList;

// 初始化 第一屏最慢

private boolean needRelayout;

// y偏移量

private int scrollY;

// item 高度之和

private int[] heights;

private int width;

private int height;

// 行数

private int rowCount;

// view的第一行 是占内容的几行

private int firstRow;

private Adapter adapter;

Recycler recycler;

public RecyclerView(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

private void init(Context context, AttributeSet attrs) {

ViewConfiguration configuration = ViewConfiguration.get(context);

this.touchSlop = configuration.getScaledTouchSlop();

this.viewList = new ArrayList<>();

this.needRelayout = true;

}

/**

* @param widthMeasureSpec

* @param heightMeasureSpec

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

final int widthSize = MeasureSpec.getSize(widthMeasureSpec);

final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

Log.i(TAG, "onMeasure widthSize:" + widthSize);

Log.i(TAG, "onMeasure heightSize:" + heightSize);

int h;

if (adapter != null) {

this.rowCount = adapter.getCount();

heights = new int[rowCount];

for (int i = 0; i < heights.length; i++) {

heights[i] = adapter.getHeight(i);

}

}

// 数据的高度(假设所有数据都滑动展示时,所有子ItemView高度之和)

int tmpH = sumArray(heights, 0, heights.length);

h = Math.min(heightSize, tmpH);

// 设置RecycleView的最终高度

// 在onMeasure方法中最终调用 setMeasuredDimension 方法来确定控件的大小

setMeasuredDimension(widthSize, h);

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

public void setAdapter(Adapter adapter) {

this.adapter = adapter;

if (adapter != null) {

// 保存复用RecycleView的Item回收池

recycler = new Recycler(adapter.getViewTypeCount());

scrollY = 0;

firstRow = 0;

needRelayout = true;

requestLayout(); // 1 onMeasure 2 onLayout

}

}

public Adapter getAdapter() {

return adapter;

}

// 初始化

@Override

protected void onLayout(boolean changed, int mLeft, int mTop, int mRight, int mBottom) {

Log.i(TAG, "onLayout changed:" + changed);

Log.i(TAG, "onLayout needRelayout:" + needRelayout);

if (needRelayout || changed) {

needRelayout = false;

viewList.clear();

removeAllViews();

if (adapter != null) {

// 摆放所有RecycleView的Item的实际高度和宽度

width = mRight - mLeft;

height = mBottom - mTop;

Log.i(TAG, "onLayout width:" + width);

Log.i(TAG, "onLayout height:" + height);

int top = 0, bottom;

for (int i = 0; i < rowCount && top < height; i++) {

bottom = top + heights[i];

// 生成一个View

View view = makeAndStep(i, 0, top, width, bottom);

viewList.add(view);

top = bottom; // 循环摆放需要展示的几个Item View

}

}

}

}

/**

* @param row

* @param left

* @param top

* @param right

* @param bottom

* @return

*/

private View makeAndStep(int row, int left, int top, int right, int bottom) {

View view = obtainView(row, right - left, bottom - top);

view.layout(left, top, right, bottom);

return view;

}

private View obtainView(int row, int width, int height) {

int itemType = adapter.getItemViewType(row);

// 通过指定 position 位置的View类型去回收池查找View,存在则复用,不存在则调用 onCreateViewHolder 创建

View recycleView = recycler.get(itemType);

View view;

if (recycleView == null) {

view = adapter.onCreateViewHolder(row, recycleView, this);

if (view == null) {

throw new RuntimeException("onCreateViewHolder 必须填充布局");

}

} else {

view = adapter.onBinderViewHolder(row, recycleView, this);

}

view.setTag(R.id.tag_type_view, itemType);

view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)

, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

addView(view, 0);

return view;

}

// -------------------------------------------------------------------------

// 当前滑动的y值

private int currentY;

@Override

public boolean onInterceptTouchEvent(MotionEvent event) {

Log.i(TAG, "onInterceptTouchEvent");

boolean intercept = false;

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN: {

currentY = (int) event.getRawY();

break;

}

case MotionEvent.ACTION_MOVE: {

// 当手指在屏幕上滑动距离大于 touchSlop 最小滑动距离时,则继承ViewGroup的 RecyclerView 拦截事件

int y2 = Math.abs(currentY - (int) event.getRawY());

if (y2 > touchSlop) {

intercept = true;

}

}

}

return intercept;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

Log.i(TAG, "onTouchEvent");

switch (event.getAction()) {

case MotionEvent.ACTION_MOVE: {

// 移动的距离 y方向

int y2 = (int) event.getRawY();

// 大于0表示 上滑

// 小于0表示 下滑

int diffY = currentY - y2;

// 画布移动 并不影响子控件的位置

scrollBy(0, diffY);

}

}

return super.onTouchEvent(event);

}

@Override

public void scrollBy(int x, int y) {

// scrollY表示 第一个可见Item的左上顶点 距离屏幕的左上顶点的距离

scrollY += y;

scrollY = scrollBounds(scrollY);

// scrollY

if (scrollY > 0) {

// 上滑正 下滑负 边界值

while (scrollY > heights[firstRow]) {

// 1 上滑移除 2 上划加载 3下滑移除 4 下滑加载

removeView(viewList.remove(0));

scrollY -= heights[firstRow];

firstRow++;

}

// 上滑添加

while (getFillHeight() < height) {

int addLast = firstRow + viewList.size();

View view = obtainView(addLast, width, heights[addLast]);

viewList.add(viewList.size(), view);

}

} else if (scrollY < 0) {

// 下滑加载

while (scrollY < 0) {

int firstAddRow = firstRow - 1;

View view = obtainView(firstAddRow, width, heights[firstAddRow]);

viewList.add(0, view);

firstRow--;

scrollY += heights[firstRow + 1];

}

// 下滑移除

while (sumArray(heights, firstRow, viewList.size()) - scrollY - heights[firstRow + viewList.size() - 1] >= height) {

removeView(viewList.remove(viewList.size() - 1));

}

}

repositionViews();

}

private int scrollBounds(int scrollY) {

// 上滑

if (scrollY > 0) {

} else {

// 极限值 会取零 非极限值的情况下 scroll

scrollY = Math.max(scrollY, -sumArray(heights, 0, firstRow));

}

return scrollY;

}

/**

* @return 数据的高度 -scrollY

*/

private int getFillHeight() {

return sumArray(heights, firstRow, viewList.size()) - scrollY;

}

/**

* @param array

* @param firstIndex

* @param count

* @return 计算所有组合Item的总高度 与 RecycleView高度对比

*/

private int sumArray(int array[], int firstIndex, int count) {

int sum = 0;

count += firstIndex;

for (int i = firstIndex; i < count; i++) {

sum += array[i];

}

return sum;

}

private void repositionViews() {

int left, top, right, bottom, i;

top = -scrollY;

i = firstRow;

for (View view : viewList) {

bottom = top + heights[i++];

view.layout(0, top, width, bottom);

top = bottom;

}

}

@Override

public void removeView(View view) {

super.removeView(view);

int key = (int) view.getTag(R.id.tag_type_view);

recycler.put(view, key);

}

// -------------------------------------------------------------------------

interface Adapter {

View onCreateViewHolder(int position, View convertView, ViewGroup parent);

View onBinderViewHolder(int position, View convertView, ViewGroup parent);

// Item的类型

int getItemViewType(int row);

// Item的类型数量

int getViewTypeCount();

int getCount();

int getHeight(int index);

}

}

Recycler.java(回收池代码)

public class Recycler {

private Stack[] views;

public Recycler(int typeNumber) {

views = new Stack[typeNumber];

for (int i = 0; i < typeNumber; i++) {

views[i] = new Stack();

}

}

public void put(View view, int type) {

views[type].push(view);

}

public View get(int type) {

try {

return views[type].pop();

} catch (Exception e) {

return null;

}

}

}

MainAct.java(同学们用这个代码测试一下)

public class MainAct extends Activity {

private static final String TAG = "MainAct";

private RecyclerView recyclerView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main_act);

recyclerView = findViewById(R.id.table);

recyclerView.setAdapter(new RecyclerView.Adapter() {

@Override

public View onCreateViewHolder(int position, View convertView, ViewGroup parent) {

convertView = MainAct.this.getLayoutInflater()

.inflate(R.layout.item_table, parent, false);

TextView textView = convertView.findViewById(R.id.text1);

textView.setText("RecycleView Item " + position);

Log.i(TAG, "onCreateViewHolder: " + convertView.hashCode());

return convertView;

}

@Override

public View onBinderViewHolder(int position, View convertView, ViewGroup parent) {

TextView textView = convertView.findViewById(R.id.text1);

textView.setText("RecycleView Item " + position);

Log.i(TAG, "onBinderViewHolder: " + convertView.hashCode());

return convertView;

}

@Override

public int getItemViewType(int row) {

return 0;

}

@Override

public int getViewTypeCount() {

return 1;

}

@Override

public int getCount() {

// 通过改动这里的数字来模拟实际使用中RecycleView展示Item总数

return 100;

}

@Override

public int getHeight(int index) {

return 100;

}

});

}

}

item_table.xml

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center"

android:orientation="vertical" >

android:id="@+id/text1"

android:background="@color/colorAccent"

android:textColor="@color/colorPrimaryDark"

android:gravity="center"

android:layout_width="match_parent"

android:layout_height="100dp" />

ids.xml(相关的几个调试代码文件我都贴下吧)

main_act.xml我就不贴了,就是一个手写RecycleView的布局,如果这个你也不想写,这种惰性是学不好知识的,哈哈

PS:手写RecycleView滑动到最后一个Item程序会Crash,这里实现只帮助大家加深对RecycleView实现原理了解

看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。

另外更有Android一线大厂面试完整考点、资料更新在我的Gitee,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!

Gitee地址:【老陈的Gitee】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值