/* * Created by LuaView. * Copyright (c) 2017, Alibaba Group. All rights reserved. * * This source code is licensed under the MIT. * For the full copyright and license information,please view the LICENSE file in the root directory of this source tree. */ package com.taobao.luaview.userdata.list; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.taobao.luaview.global.Constants; import com.taobao.luaview.userdata.constants.UDPinned; import com.taobao.luaview.util.LuaUtil; import com.taobao.luaview.view.LVRecyclerView; import com.taobao.luaview.view.LVRefreshRecyclerView; import com.taobao.luaview.view.recyclerview.LVRecyclerViewHolder; import org.luaj.vm2.Globals; import org.luaj.vm2.LuaValue; import org.luaj.vm2.Varargs; /** * UDBaseRecyclerView 封装 * * @author song * @date 15/10/23 */ public abstract class UDBaseRecyclerView<T extends ViewGroup> extends UDBaseListOrRecyclerView<T> { private static final int DEFAULT_MAX_SPAN = 1; private SparseIntArray mSpanSize; // 用于添加吸顶视图的容器 private ViewGroup mPinnedContainer; private View mCurrentPinnedView; private int mCurrentPinnedPosition = -1; private boolean mHasPinnedCell = false; // 缓存真实的Pinned Cell Name private SparseArray<String> mPinnedPositionCellId = new SparseArray<String>(); // 缓存被Pinned标记的position public SparseBooleanArray mIsPinnedSparseArray = new SparseBooleanArray(); // 缓存被pinned标记的viewType, position public SparseIntArray mPinnedViewTypePosition = new SparseIntArray(); // 缓存被pinned标记的position,Holder public SparseArray<LVRecyclerViewHolder> mPinnedPositionHolder = new SparseArray<LVRecyclerViewHolder>(); public UDBaseRecyclerView(T view, Globals globals, LuaValue metaTable, Varargs initParams) { super(view, globals, metaTable, initParams); } public abstract LVRecyclerView getLVRecyclerView(); /** * mPinnedViewTypePosition是onCreateViewHolder时缓存的,不能清除 */ private void restore() { mHasPinnedCell = false; mIsPinnedSparseArray.clear(); mPinnedPositionCellId.clear(); mPinnedViewTypePosition.clear(); } /** * notify data changed (section, row) in java * * @param section * @param row * @return */ @Override public UDBaseRecyclerView reload(Integer section, Integer row) { restore(); final LVRecyclerView recyclerView = getLVRecyclerView(); if (recyclerView != null) { final RecyclerView.Adapter adapter = recyclerView.getLVAdapter(); if (adapter != null) { int diffSectionCount = getDiffSectionCount(); if (section == null || diffSectionCount != 0) {//如果 section无值,或者section数量变动则更新所有 refreshState(recyclerView); adapter.notifyDataSetChanged(); } else {//如果传了section,row,则表示要更新部分数据 int diffTotalCount = getDiffTotalCount();//total count diff boolean isChanged = diffTotalCount == 0;//数据变更,数量未变更 boolean isInserted = diffTotalCount > 0;//数量增加 boolean isRemoved = diffTotalCount < 0;//数量减少 if (row == null) {//row is null, notify whole section int start = getPositionBySectionAndRow(section, 0); int currentRowCount = getRowCount(section); if (isChanged) {//更新整个section,count不变,数据变 refreshState(recyclerView); adapter.notifyItemRangeChanged(start, currentRowCount); } else if (isInserted) {//更新整个section,count增加 int newRowCount = getRawRowCount(section); int count = Math.abs(newRowCount - currentRowCount);//新增count refreshState(recyclerView); adapter.notifyItemRangeInserted(start, count); } else if (isRemoved) {//更新整个section,count减少 int newRowCount = getRawRowCount(section); int count = Math.abs(newRowCount - currentRowCount);//新增count refreshState(recyclerView); adapter.notifyItemRangeRemoved(start, count); } } else {//row not null, notify row int pos = getPositionBySectionAndRow(section, row); refreshState(recyclerView); if (isChanged) {//更新某个元素 adapter.notifyItemChanged(pos); } else if (isInserted) {//插入一个元素 adapter.notifyItemInserted(pos); } else if (isRemoved) {//减少一个元素 adapter.notifyItemRemoved(pos); } } } } } return this; } /** * 更新列表状态,重新更新数据,元素间隔等 * * @param recyclerView */ private void refreshState(LVRecyclerView recyclerView) { init();//重新初始化数据 recyclerView.updateMaxSpanCount(); } @Override public void initOnScrollCallback(final T view) { if (view instanceof LVRecyclerView) { final LVRecyclerView lvRecyclerView = (LVRecyclerView) view; if (LuaUtil.isValid(mCallback) || mLazyLoad) { lvRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int scrollState) { updateAllChildScrollState(recyclerView, scrollState); if (LuaUtil.isValid(mCallback)) { switch (scrollState) { case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: { final int itemPosition = lvRecyclerView.getFirstVisiblePosition(); final int section = getSectionByPosition(itemPosition); final int row = getRowInSectionByPosition(itemPosition); LuaUtil.callFunction(LuaUtil.getFunction(mCallback, "ScrollBegin", "scrollBegin"), LuaUtil.toLuaInt(section), LuaUtil.toLuaInt(row)); break; } case AbsListView.OnScrollListener.SCROLL_STATE_FLING: break; case AbsListView.OnScrollListener.SCROLL_STATE_IDLE: { final int itemPosition = lvRecyclerView.getFirstVisiblePosition(); final int section = getSectionByPosition(itemPosition); final int row = getRowInSectionByPosition(itemPosition); LuaUtil.callFunction(LuaUtil.getFunction(mCallback, "ScrollEnd", "scrollEnd"), LuaUtil.toLuaInt(section), LuaUtil.toLuaInt(row)); break; } } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (LuaUtil.isValid(mCallback)) { final int itemPosition = lvRecyclerView.getFirstVisiblePosition(); final int section = getSectionByPosition(itemPosition); final int row = getRowInSectionByPosition(itemPosition); LuaUtil.callFunction(LuaUtil.getFunction(mCallback, "Scrolling", "scrolling"), LuaUtil.toLuaInt(section), LuaUtil.toLuaInt(row), valueOf(lvRecyclerView.getVisibleItemCount())); } pinned(lvRecyclerView); } }); } } } // TODO: 11/15/16 处理itemView之前有spacing的情况 private void pinned(LVRecyclerView lvRecyclerView) { if (!mHasPinnedCell) return; if (mPinnedContainer == null) { mPinnedContainer = new FrameLayout(lvRecyclerView.getContext()); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); ViewGroup parent = (ViewGroup) lvRecyclerView.getParent(); if (parent instanceof LVRefreshRecyclerView) { // RefreshCollectionView params.leftMargin = (int) parent.getX(); params.topMargin = (int) parent.getY(); ((ViewGroup) parent.getParent()).addView(mPinnedContainer, params); } else { // CollectionView params.leftMargin = (int) lvRecyclerView.getX(); params.topMargin = (int) lvRecyclerView.getY(); parent.addView(mPinnedContainer, params); } } int firstVisiblePosition = findFirstVisiblePosition(lvRecyclerView.getLayoutManager()); // 从firstVisiblePosition位置开始递减查找上一个pinned position int pinnedViewPosition = findPinnedViewPositionDecrease(firstVisiblePosition); if (pinnedViewPosition >= 0 && mCurrentPinnedPosition != pinnedViewPosition) { ViewGroup itemView = (ViewGroup) mPinnedPositionHolder.get(pinnedViewPosition).itemView; View child = itemView.getChildAt(0); if (child != null) { // 从itemView移除child之前,先设置其与child一样的宽高占位。 itemView.getLayoutParams().width = child.getLayoutParams().width; itemView.getLayoutParams().height = child.getLayoutParams().height; itemView.removeView(child); mPinnedContainer.addView(child); if (mCurrentPinnedView != null) { mCurrentPinnedView.setVisibility(View.GONE); } mCurrentPinnedView = child; } else { // 从(pinnedViewPosition + 1)位置开始递增查找下一个pinned position int nextPinnedPosition = findPinnedViewPositionIncrease(pinnedViewPosition + 1); ViewGroup parentItemView = (ViewGroup) mPinnedPositionHolder.get(nextPinnedPosition).itemView; View pinnedView = mPinnedContainer.getChildAt(mPinnedContainer.getChildCount() - 1); mPinnedContainer.removeView(pinnedView); parentItemView.addView(pinnedView); mCurrentPinnedView = mPinnedContainer.getChildAt(mPinnedContainer.getChildCount() - 1); if (mCurrentPinnedView != null) { mCurrentPinnedView.setVisibility(View.VISIBLE); } } mCurrentPinnedPosition = pinnedViewPosition; } // 第一个吸顶视图被移除的情况,亦即列表恢复没有吸顶视图的状态。 if (pinnedViewPosition == -1 && mCurrentPinnedPosition != -1) { View subview = mPinnedContainer.getChildAt(mPinnedContainer.getChildCount() - 1); mPinnedContainer.removeView(subview); // 从position 0开始找第一个pinned标记的itemView,并把最后一个吸顶视图添加回到它的原本位置 int firstPinnedPosition = findPinnedViewPositionIncrease(0); ViewGroup parentItemView = (ViewGroup) mPinnedPositionHolder.get(firstPinnedPosition).itemView; parentItemView.addView(subview); // 列表恢复没有吸顶视图的状态 mCurrentPinnedPosition = -1; mCurrentPinnedView = null; } // 处理吸顶视图切换时的位移效果 if (mPinnedContainer != null && mCurrentPinnedPosition != -1) { View targetView = lvRecyclerView.findChildViewUnder(mPinnedContainer.getMeasuredWidth() / 2, mPinnedContainer.getMeasuredHeight() + 1); if (targetView != null) { boolean isPinned = ((Boolean) targetView.getTag(Constants.RES_LV_TAG_PINNED)).booleanValue(); if (isPinned && targetView.getTop() > 0) { if (pinnedViewPosition != -1) { int deltaY = targetView.getTop() - mPinnedContainer.getMeasuredHeight(); if (deltaY < (lvRecyclerView.getMiniSpacing() - mPinnedContainer.getMeasuredHeight())) { // 防止设置了spacing的时候,在这个范围内mPinnedContainer被位移到top之上,而itemView是空白的现象 mPinnedContainer.setTranslationY(0); } else { mPinnedContainer.setTranslationY(deltaY); } } } else { mPinnedContainer.setTranslationY(0); } } else { mPinnedContainer.setTranslationY(0); } } } /** * 从传入位置递增找出Pinned的位置 * * @param fromPosition * @return int */ private int findPinnedViewPositionIncrease(int fromPosition) { for (int position = fromPosition; position < this.getTotalCount(); position++) { // 位置递减,只要查到位置是Pinned标示,立即返回此位置 if (this.mIsPinnedSparseArray.get(position)) { return position; } } return -1; } /** * 从传入位置递减找出Pinned的位置 * * @param fromPosition * @return int */ private int findPinnedViewPositionDecrease(int fromPosition) { for (int position = fromPosition; position >= 0; position--) { // 位置递减,只要查到位置是Pinned标示,立即返回此位置 if (this.mIsPinnedSparseArray.get(position)) { return position; } } return -1; } /** * 找出第一个可见的Item的位置 * * @param layoutManager * @return */ private int findFirstVisiblePosition(RecyclerView.LayoutManager layoutManager) { int firstVisiblePosition = 0; if (layoutManager instanceof GridLayoutManager) { firstVisiblePosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition(); } else if (layoutManager instanceof LinearLayoutManager) { firstVisiblePosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(into); firstVisiblePosition = Integer.MAX_VALUE; for (int pos : into) { firstVisiblePosition = Math.min(pos, firstVisiblePosition); } } return firstVisiblePosition; } /** * 由于该函数的特殊性,有则获取无则生成。 * 对于生成,有Pinned.YES标记的Id,会先加后缀(".PINNED"+position),再存放到mIdCache中; * 对于获取,用mPinnedPositionCellId缓存的Lua定义的真实的Id。 * * @param position * @param section * @param row * @return */ @Override protected String getId(int position, int section, int row) { final String cacheId = mIdCache != null ? mIdCache.get(position) : null; if (cacheId != null) { if (this.mIsPinnedSparseArray.get(position)) { // 获取CellId的时候,要用lua定义的真正的Id return mPinnedPositionCellId.get(position); } return cacheId; } else { String id = null; Varargs args = LuaUtil.invokeFunction(mCellDelegate.get("Id"), LuaUtil.toLuaInt(section), LuaUtil.toLuaInt(row)); if (args != null) { if (args.narg() > 1) { if (args.arg(2).toint() == UDPinned.PINNED_YES) { mHasPinnedCell = true; mIsPinnedSparseArray.put(position, true); id = args.arg(1).optjstring(""); /** * 构造唯一的id,使得在lua用同一种Cell作为多个position的PinnedCell时,也会有不同的viewType. * 见 {@link UDBaseRecyclerView#getItemViewType(int)} */ mPinnedPositionCellId.put(position, id); id = new StringBuffer(id).append(".PINNED").append(position).toString(); } else { id = args.arg(1).optjstring(""); } } else { // 兼容旧版本的写法,只有一个String参数的情况 id = ((LuaValue) args).optjstring(""); } } if (mIdCache != null) { mIdCache.put(position, id); } return id; } } /** * 根据位置获取item的viewType * * @param position * @return */ @Override public int getItemViewType(int position) { int viewType = 0; String viewTypeName = mIdCache.get(position);//得到坑位类型名称 if (viewTypeName == null) { // 已经有该position的viewTypeName则直接用,没有则调用getId函数生成并存入mIdCache,并返回。 viewTypeName = getId(position); } if (this.mViewTypeMap != null) { if (!this.mViewTypeMap.containsKey(viewTypeName)) { final int index = this.mViewTypeMap.size(); this.mViewTypeMap.put(viewTypeName, index); this.mViewTypeNameMap.put(index, viewTypeName); viewType = index; } else { viewType = this.mViewTypeMap.get(viewTypeName); } } if (this.mIsPinnedSparseArray.get(position)) { this.mPinnedViewTypePosition.put(viewType, position); } return viewType; } /** * TODO 支持offset * * @param offset * @param animate 是否动画 * @return */ @Override public UDBaseListOrRecyclerView scrollToTop(int offset, boolean animate) { final LVRecyclerView lv = getLVRecyclerView(); if (lv != null) { if (animate) { if (lv.getFirstVisiblePosition() > 7) {//hack fast scroll lv.scrollToPosition(7); } lv.smoothScrollToPosition(0); } else { if (lv.getLayoutManager() instanceof LinearLayoutManager) { ((LinearLayoutManager) lv.getLayoutManager()).scrollToPositionWithOffset(0, offset); } else if (lv.getLayoutManager() instanceof StaggeredGridLayoutManager) { ((StaggeredGridLayoutManager) lv.getLayoutManager()).scrollToPositionWithOffset(0, offset); } else { lv.scrollToPosition(0); } } } return this; } /** * TODO 支持offset * * @param section * @param rowInSection * @param offset * @param animate * @return */ @Override public UDBaseListOrRecyclerView scrollToItem(int section, int rowInSection, int offset, boolean animate) { final LVRecyclerView recyclerView = getLVRecyclerView(); if (recyclerView != null) { if (animate) { recyclerView.smoothScrollToPosition(getPositionBySectionAndRow(section, rowInSection)); } else { if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) { ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(getPositionBySectionAndRow(section, rowInSection), offset); } else if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { ((StaggeredGridLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(getPositionBySectionAndRow(section, rowInSection), offset); } else { recyclerView.scrollToPosition(getPositionBySectionAndRow(section, rowInSection)); } } } return this; } @Override public UDBaseListOrRecyclerView setMiniSpacing(int spacing) { final LVRecyclerView view = getLVRecyclerView(); if (view != null) { view.setMiniSpacing(spacing); } return this; } @Override public int getMiniSpacing() { return getView() instanceof LVRecyclerView ? ((LVRecyclerView) getView()).getMiniSpacing() : 0; } /** * 获取最大的spanCount,默认为1 * 1. 先读取所有cell的size,找到最小cell,能放多少个该cell即为最大列数 * 2. 初始化每个item的spanSize * TODO 使用另外的算法:先找到最小的gap,然后算出spancount的方式。找最小gap的方式需要遍历一遍,找到每行的最小空隙,如果最小空隙为0则找最小的cell为最小的gap。(该方式的问题是填不满的行会有间隙) * * @return */ public int getMaxSpanCount() { if (getWidth() > 0) { final int maxWidth = getWidth(); final int maxSpanCount = Math.max(1, maxWidth); initSpanSize(maxSpanCount); return maxSpanCount; } return DEFAULT_MAX_SPAN; } /** * 初始化spanCount,并保存 * * @param maxSpanCount */ private void initSpanSize(int maxSpanCount) { mSpanSize = new SparseIntArray(); final int totalCount = getTotalCount(); for (int i = 0; i < totalCount; i++) { final int[] size = callCellSize(LuaValue.NIL, i); final int spanSize = Math.max(0, Math.min(maxSpanCount, size[0]));//0 <= spanSize <= maxSpanCount mSpanSize.put(i, spanSize); } } /** * get cached span size * * @param position * @return */ public int getSpanSize(int position) { if (mSpanSize != null) { return mSpanSize.get(position); } return DEFAULT_MAX_SPAN; } //------------------------------------has cell size--------------------------------------------- /** * 是否有size的定义 * * @param viewType * @return */ public boolean hasCellSize(int viewType) { final String id = getItemViewTypeName(viewType); if (id != null) { if (mHasPinnedCell && this.mPinnedViewTypePosition.get(viewType, -1) != -1) { // 获取CellId的时候,要用Lua层定义的正确的Id return hasCellFunction(mPinnedPositionCellId.get(mPinnedViewTypePosition.get(viewType)), "Size"); } return hasCellFunction(id, "Size"); } return false; } }