diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index 2e99275a2d9..71709238b85 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -198,8 +198,8 @@ dependencies { api fileTree(dir: 'libs', include: ['*.jar']) //noinspection GradleDependency implementation 'androidx.annotation:annotation:1.0.0' - //noinspection GradleDependency - implementation 'androidx.recyclerview:recyclerview:1.1.0' + api 'androidx.recyclerview:recyclerview:1.1.0' + api 'androidx.viewpager:viewpager:1.0.0' if (!INCLUDE_SUPPORT_UI.toBoolean()) { compileOnly('com.tencent.mtt:support-ui:99.2.3') } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java b/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java index fdfdc00ba1d..f0c22beebb9 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/EasyLinearLayoutManager.java @@ -17,145 +17,148 @@ package androidx.recyclerview.widget; import android.content.Context; +import android.util.AttributeSet; +import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.LayoutParams; import androidx.recyclerview.widget.RecyclerView.State; -import android.util.AttributeSet; -import android.view.View; import java.util.HashMap; +/** + * Created on 2021/3/16. + * Description + * 精确计算内容的offset + */ public class EasyLinearLayoutManager extends LinearLayoutManager { - /** - * 无效的高度 - */ - public static final int INVALID_HEIGHT = -1; - private static final LayoutParams ITEM_LAYOUT_PARAMS = new LayoutParams(0, 0); - /** - * 由于排版后才知道对应item的高度,topMargin和bottomMargin,所以这里缓存一下这些值 - */ - protected HashMap itemHeightMaps = new HashMap<>(); - protected HashMap itemTopMarginMaps = new HashMap<>(); - protected HashMap itemBottomMarginMaps = new HashMap<>(); - - public EasyLinearLayoutManager(Context context) { - super(context); - } - - public EasyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { - super(context, orientation, reverseLayout); - } + /** + * 无效的高度 + */ + public static final int INVALID_HEIGHT = -1; + private static final LayoutParams ITEM_LAYOUT_PARAMS = new LayoutParams(0, 0); + /** + * 由于排版后才知道对应item的高度,topMargin和bottomMargin,所以这里缓存一下这些值 + */ + protected HashMap itemHeightMaps = new HashMap<>(); + protected HashMap itemTopMarginMaps = new HashMap<>(); + protected HashMap itemBottomMarginMaps = new HashMap<>(); - public EasyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } + public EasyLinearLayoutManager(Context context) { + super(context); + } - /** - * 排版完成后,缓存view的排版信息 - */ - @Override - public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, - int bottom) { - super.layoutDecoratedWithMargins(child, left, top, right, bottom); - cacheItemLayoutParams(bottom - top, (LayoutParams) child.getLayoutParams(), getPosition(child)); - } + public EasyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { + super(context, orientation, reverseLayout); + } - /** - * 缓存Item排版信息,缓存信息有两种来源 一种是排版过程中的得到实际上屏的排版信息 {@link #layoutDecoratedWithMargins(View, int, int, - * int, int)} 一种是从adapter获取的item的预排版信息 {@link #getItemHeightFromAdapter(int)} - * - * @param height item的高度 - * @param layoutParams 排版参数 - * @param position 位置 - */ - private void cacheItemLayoutParams(int height, LayoutParams layoutParams, int position) { - itemBottomMarginMaps.put(position, layoutParams.bottomMargin); - itemTopMarginMaps.put(position, layoutParams.topMargin); - itemHeightMaps.put(position, height); - } + public EasyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - /** - * 从adapter 获取position对应的排版信息,这里不需要排版,是item预先指定的高度 - */ - int getItemHeightFromAdapter(int position) { - Adapter adapter = mRecyclerView.getAdapter(); - if (adapter instanceof IItemLayoutParams) { - IItemLayoutParams layoutInfo = (IItemLayoutParams) adapter; - resetLayoutParams(); - layoutInfo.getItemLayoutParams(position, ITEM_LAYOUT_PARAMS); - if (ITEM_LAYOUT_PARAMS.height >= 0) { - cacheItemLayoutParams(ITEM_LAYOUT_PARAMS.height, ITEM_LAYOUT_PARAMS, position); - return ITEM_LAYOUT_PARAMS.height + ITEM_LAYOUT_PARAMS.bottomMargin - + ITEM_LAYOUT_PARAMS.topMargin; - } + /** + * 排版完成后,缓存view的排版信息 + */ + @Override + public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, int bottom) { + super.layoutDecoratedWithMargins(child, left, top, right, bottom); + cacheItemLayoutParams(bottom - top, (LayoutParams) child.getLayoutParams(), getPosition(child)); } - return INVALID_HEIGHT; - } - /** - * 清除之前的缓存数据,ITEM_LAYOUT_PARAMS 不是最终用来排版的,只是一个参数的载体 - */ - private static void resetLayoutParams() { - ITEM_LAYOUT_PARAMS.height = 0; - ITEM_LAYOUT_PARAMS.topMargin = 0; - ITEM_LAYOUT_PARAMS.rightMargin = 0; - ITEM_LAYOUT_PARAMS.leftMargin = 0; - ITEM_LAYOUT_PARAMS.bottomMargin = 0; - } + /** + * 缓存Item排版信息,缓存信息有两种来源 + * 一种是排版过程中的得到实际上屏的排版信息 {@link #layoutDecoratedWithMargins(View, int, int, int, int)} + * 一种是从adapter获取的item的预排版信息 {@link #getItemHeightFromAdapter(int)} + * + * @param height item的高度 + * @param layoutParams 排版参数 + * @param position 位置 + */ + private void cacheItemLayoutParams(int height, LayoutParams layoutParams, int position) { + itemBottomMarginMaps.put(position, layoutParams.bottomMargin); + itemTopMarginMaps.put(position, layoutParams.topMargin); + itemHeightMaps.put(position, height); + } - /** - * 计算position以前(包含position)的高度,如果发现其中有一个没有缓存,那么得出来的值是无效的 - */ - int getHeightUntilPosition(int position) { - int totalHeight = 0; - for (int i = 0; i <= position; i++) { - Integer height = itemHeightMaps.get(i); - if (height == null) { - height = getItemHeightFromAdapter(i); - } - if (height != null && height != INVALID_HEIGHT) { - totalHeight += height; - } else { + /** + * 从adapter 获取position对应的排版信息,这里不需要排版,是item预先指定的高度 + */ + int getItemHeightFromAdapter(int position) { + Adapter adapter = mRecyclerView.getAdapter(); + if (adapter instanceof IItemLayoutParams) { + IItemLayoutParams layoutInfo = (IItemLayoutParams) adapter; + resetLayoutParams(); + layoutInfo.getItemLayoutParams(position, ITEM_LAYOUT_PARAMS); + if (ITEM_LAYOUT_PARAMS.height >= 0) { + cacheItemLayoutParams(ITEM_LAYOUT_PARAMS.height, ITEM_LAYOUT_PARAMS, position); + return ITEM_LAYOUT_PARAMS.height + ITEM_LAYOUT_PARAMS.bottomMargin + ITEM_LAYOUT_PARAMS.topMargin; + } + } return INVALID_HEIGHT; - } } - return totalHeight; - } - /** - * 由于父类的computeVerticalScrollOffset是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 - * 用第一个可见view的前面总的内容高度减去第一个可见view的底部位置 - * - * @return 当前的内容偏移,也就是被推出顶部以外的内容高度。 - */ - @Override - public int computeVerticalScrollOffset(State state) { - if (getChildCount() <= 0 || getItemCount() <= 0) { - return 0; + /** + * 清除之前的缓存数据,ITEM_LAYOUT_PARAMS 不是最终用来排版的,只是一个参数的载体 + */ + private static void resetLayoutParams() { + ITEM_LAYOUT_PARAMS.height = 0; + ITEM_LAYOUT_PARAMS.topMargin = 0; + ITEM_LAYOUT_PARAMS.rightMargin = 0; + ITEM_LAYOUT_PARAMS.leftMargin = 0; + ITEM_LAYOUT_PARAMS.bottomMargin = 0; } - int firstVisiblePosition = findFirstVisibleItemPosition(); - View firstVisibleView = findViewByPosition(firstVisiblePosition); - int heightUntilPosition = getHeightUntilPosition(firstVisiblePosition); - if (firstVisibleView != null && heightUntilPosition != INVALID_HEIGHT) { - return heightUntilPosition - mOrientationHelper.getDecoratedEnd(firstVisibleView); + + /** + * 计算position以前(包含position)的高度,如果发现其中有一个没有缓存,那么得出来的值是无效的 + */ + int getHeightUntilPosition(int position) { + int totalHeight = 0; + for (int i = 0; i <= position; i++) { + Integer height = itemHeightMaps.get(i); + if (height == null) { + height = getItemHeightFromAdapter(i); + } + if (height != null && height != INVALID_HEIGHT) { + totalHeight += height; + } else { + return INVALID_HEIGHT; + } + } + return totalHeight; + } + + /** + * 由于父类的computeVerticalScrollOffset是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 + * 用第一个可见view的前面总的内容高度减去第一个可见view的底部位置 + * + * @return 当前的内容偏移,也就是被推出顶部以外的内容高度。 + */ + @Override + public int computeVerticalScrollOffset(State state) { + if (getChildCount() <= 0 || getItemCount() <= 0) { + return 0; + } + int firstVisiblePosition = findFirstVisibleItemPosition(); + View firstVisibleView = findViewByPosition(firstVisiblePosition); + int heightUntilPosition = getHeightUntilPosition(firstVisiblePosition); + if (firstVisibleView != null && heightUntilPosition != INVALID_HEIGHT) { + return heightUntilPosition - mOrientationHelper.getDecoratedEnd(firstVisibleView); + } + return super.computeVerticalScrollOffset(state); } - return super.computeVerticalScrollOffset(state); - } - /** - * 由于父类的computeVerticalScrollRange是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 - * - * @return 内容的总高度 - **/ - @Override - public int computeVerticalScrollRange(State state) { - int heightUntilPosition = getHeightUntilPosition(getItemCount() - 1); - if (heightUntilPosition != INVALID_HEIGHT) { - return heightUntilPosition; + /** + * 由于父类的computeVerticalScrollRange是基于平均值计算高度。对于Item类型和高度不一样的情况,计算是有误差的。 + * + * @return 内容的总高度 + **/ + @Override + public int computeVerticalScrollRange(State state) { + int heightUntilPosition = getHeightUntilPosition(getItemCount() - 1); + if (heightUntilPosition != INVALID_HEIGHT) { + return heightUntilPosition; + } + return super.computeVerticalScrollRange(state); } - return super.computeVerticalScrollRange(state); - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java b/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java index 776c2a11444..b1934c3b57e 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/EasyRecyclerView.java @@ -18,216 +18,231 @@ import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.RecycledViewPool.ScrapData; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool.ScrapData; import java.lang.reflect.Field; import java.util.ArrayList; +/** + * Created on 2020/10/14. + */ + public class EasyRecyclerView extends RecyclerView { - protected OverPullHelper overPullHelper; - protected OverPullListener overPullListener; - protected VelocityTracker velocityTracker; - - public EasyRecyclerView(@NonNull Context context) { - super(context); - init(); - } - - public EasyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(); - } - - public EasyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - protected void init() { - } - - public int getOverPullState() { - if (overPullHelper != null) { - return overPullHelper.getOverPullState(); - } - return OverPullHelper.OVER_PULL_NONE; - } - - public boolean isOverPulling() { - int pullState = getOverPullState(); - return pullState == OverPullHelper.OVER_PULL_DOWN_ING - || pullState == OverPullHelper.OVER_PULL_UP_ING - || pullState == OverPullHelper.OVER_PULL_SETTLING; - } - - /** - * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离,顶部有空白 - */ - public int getOverPullUpOffset() { - if (overPullHelper != null) { - return overPullHelper.getOverPullUpOffset(); - } - return 0; - } - - /** - * 上拉的时候,返回值>0,表示底部被上拉了一部分距离,底部有空白 - */ - public int getOverPullDownOffset() { - if (overPullHelper != null) { - return overPullHelper.getOverPullDownOffset(); - } - return 0; - } - - public void setOverPullListener(OverPullListener listener) { - overPullListener = listener; - if (overPullHelper != null) { - overPullHelper.setOverPullListener(listener); - } - } - - public void setEnableOverPull(boolean enableOverDrag) { - if (enableOverDrag) { - if (overPullHelper == null) { - overPullHelper = new OverPullHelper(this); - } - overPullHelper.setOverPullListener(overPullListener); - } else { - overPullHelper = null; - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (overPullHelper != null && overPullHelper.onTouchEvent(event)) { - return true; - } - boolean handled = super.onTouchEvent(event); - if (overPullHelper != null) { - overPullHelper.handleEventUp(event); - } - return handled; - } - - @Override - public void requestLayout() { - super.requestLayout(); - } - - public void recycleAndClearCachedViews() { - mRecycler.recycleAndClearCachedViews(); - } - - public int getChildCountWithCaches() { - return getCachedViewHolderCount() + getChildCount(); - } - - public View getChildAtWithCaches(int index) { - ArrayList viewHolders = getCachedViewHolders(); - if (index < viewHolders.size()) { - return viewHolders.get(index).itemView; - } else { - return getChildAt(index - viewHolders.size()); - } - } - - private int getCachedViewHolderCount() { - int count = mRecycler.mAttachedScrap.size() + mRecycler.mCachedViews.size(); - for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { - ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); - count += scrapData.mScrapHeap.size(); - } - return count; - } - - public ArrayList getCachedViewHolders() { - ArrayList listViewHolder = new ArrayList<>(); - listViewHolder.addAll(mRecycler.mAttachedScrap); - listViewHolder.addAll(mRecycler.mCachedViews); - for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { - ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); - listViewHolder.addAll(scrapData.mScrapHeap); - } - return listViewHolder; - } - - public boolean didStructureChange() { - return mState.didStructureChange(); - } - - - public int getFirstChildPosition() { - return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(0) : null); - } - - public int getLashChildPosition() { - return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(getChildCount() - 1) : null); - } - - /** - * 通过位置获取一个ViewHolder,目前暂时提供给header使用 - */ - public ViewHolder getViewHolderForPosition(int position) { - View view = mRecycler.getViewForPosition(position); - if (view.getLayoutParams() instanceof LayoutParams) { - return ((LayoutParams) view.getLayoutParams()).mViewHolder; - } - return null; - } - - public ViewHolder getFistChildViewHolder() { - View view = getChildAt(0); - if (view != null && view.getLayoutParams() instanceof LayoutParams) { - return ((LayoutParams) view.getLayoutParams()).mViewHolder; - } - return null; - } - - /** - * 改成public接口,主要用于hippy业务的特殊需求 - */ - @Override - public void dispatchLayout() { - super.dispatchLayout(); - } - - @Override - public void invalidateGlows() { - super.invalidateGlows(); - } - - /** - * 反射获取滚动的VelocityTracker - */ - public VelocityTracker getVelocityTracker() { - if (velocityTracker == null) { - try { - Field velocityTrackerField = RecyclerView.class.getDeclaredField("mVelocityTracker"); - velocityTrackerField.setAccessible(true); - velocityTracker = (VelocityTracker) velocityTrackerField.get(this); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - return velocityTracker; - } - - /** - * recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter 的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, - * 由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 - * IndexOutOfBoundsException - */ - public boolean isDataChangedWithoutNotify() { - return getAdapter().getItemCount() != mState.getItemCount(); - } + protected OverPullHelper overPullHelper; + protected OverPullListener overPullListener; + protected VelocityTracker velocityTracker; + private boolean enableOverDrag; + + public EasyRecyclerView(@NonNull Context context) { + super(context); + init(); + } + + public EasyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public EasyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + protected void init() { + } + + public int getOverPullState() { + if (overPullHelper != null) { + return overPullHelper.getOverPullState(); + } + return OverPullHelper.OVER_PULL_NONE; + } + + public boolean isOverPulling() { + int pullState = getOverPullState(); + return pullState == OverPullHelper.OVER_PULL_DOWN_ING || pullState == OverPullHelper.OVER_PULL_UP_ING + || pullState == OverPullHelper.OVER_PULL_SETTLING; + } + + /** + * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离,顶部有空白 + */ + public int getOverPullUpOffset() { + if (overPullHelper != null) { + return overPullHelper.getOverPullUpOffset(); + } + return 0; + } + + /** + * 上拉的时候,返回值>0,表示底部被上拉了一部分距离,底部有空白 + */ + public int getOverPullDownOffset() { + if (overPullHelper != null) { + return overPullHelper.getOverPullDownOffset(); + } + return 0; + } + + public void setOverPullListener(OverPullListener listener) { + overPullListener = listener; + if (overPullHelper != null) { + overPullHelper.setOverPullListener(listener); + } + } + + public boolean isEnableOverDrag() { + return enableOverDrag; + } + + public void setEnableOverPull(boolean enableOverDrag) { + this.enableOverDrag = enableOverDrag; + if (enableOverDrag) { + if (overPullHelper == null) { + overPullHelper = new OverPullHelper(this); + } + overPullHelper.setOverPullListener(overPullListener); + } else { + if (overPullHelper != null) { + overPullHelper.destroy(); + } + overPullHelper = null; + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (overPullHelper != null && overPullHelper.onTouchEvent(event)) { + return true; + } + boolean handled = super.onTouchEvent(event); + if (overPullHelper != null) { + overPullHelper.handleEventUp(event); + } + return handled; + } + + @Override + public void requestLayout() { + super.requestLayout(); + } + + public void recycleAndClearCachedViews() { + mRecycler.recycleAndClearCachedViews(); + } + + public int getChildCountWithCaches() { + return getCachedViewHolderCount() + getChildCount(); + } + + public View getChildAtWithCaches(int index) { + ArrayList viewHolders = getCachedViewHolders(); + if (index < viewHolders.size()) { + return viewHolders.get(index).itemView; + } else { + return getChildAt(index - viewHolders.size()); + } + } + + private int getCachedViewHolderCount() { + int count = mRecycler.mAttachedScrap.size() + mRecycler.mCachedViews.size(); + for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { + ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); + count += scrapData.mScrapHeap.size(); + } + return count; + } + + public ArrayList getCachedViewHolders() { + ArrayList listViewHolder = new ArrayList<>(); + listViewHolder.addAll(mRecycler.mAttachedScrap); + listViewHolder.addAll(mRecycler.mCachedViews); + for (int i = 0; i < mRecycler.getRecycledViewPool().mScrap.size(); i++) { + ScrapData scrapData = mRecycler.getRecycledViewPool().mScrap.valueAt(i); + listViewHolder.addAll(scrapData.mScrapHeap); + } + return listViewHolder; + } + + public boolean didStructureChange() { + return mState.didStructureChange(); + } + + public void smoothScrollBy(int dx, int dy, int duration) { + smoothScrollBy(dx, dy, null, duration); + } + + public int getFirstChildPosition() { + return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(0) : null); + } + + public int getLashChildPosition() { + return getChildLayoutPosition(getChildCount() > 0 ? getChildAt(getChildCount() - 1) : null); + } + + /** + * 通过位置获取一个ViewHolder,目前暂时提供给header使用 + */ + public ViewHolder getViewHolderForPosition(int position) { + View view = mRecycler.getViewForPosition(position); + if (view.getLayoutParams() instanceof LayoutParams) { + return ((LayoutParams) view.getLayoutParams()).mViewHolder; + } + return null; + } + + public ViewHolder getFistChildViewHolder() { + View view = getChildAt(0); + if (view != null && view.getLayoutParams() instanceof LayoutParams) { + return ((LayoutParams) view.getLayoutParams()).mViewHolder; + } + return null; + } + + /** + * 改成public接口,主要用于hippy业务的特殊需求 + */ + @Override + public void dispatchLayout() { + super.dispatchLayout(); + } + + @Override + public void invalidateGlows() { + super.invalidateGlows(); + } + + /** + * 反射获取滚动的VelocityTracker + */ + public VelocityTracker getVelocityTracker() { + if (velocityTracker == null) { + try { + Field velocityTrackerField = RecyclerView.class.getDeclaredField("mVelocityTracker"); + velocityTrackerField.setAccessible(true); + velocityTracker = (VelocityTracker) velocityTrackerField.get(this); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return velocityTracker; + } + + /** + * recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter + * 的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, + * 由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 + * IndexOutOfBoundsException + */ + public boolean isDataChangedWithoutNotify() { + return getAdapter().getItemCount() != mState.getItemCount(); + } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java index be032212389..ae0fa98b93b 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyItemTypeHelper.java @@ -16,6 +16,7 @@ package androidx.recyclerview.widget; +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool; import androidx.recyclerview.widget.RecyclerView.RecycledViewPool.ScrapData; import androidx.recyclerview.widget.RecyclerView.Recycler; import androidx.recyclerview.widget.RecyclerView.ViewHolder; @@ -25,95 +26,100 @@ import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewHolder; import java.util.ArrayList; +/** + * Created on 2021/1/4. + * Description + * Hippy 前端如果发生Item类型的变化,终端的RecyclerView需要将所有的ViewHolder进行同步修改 + */ public class HippyItemTypeHelper { - HippyRecyclerViewBase recyclerView; - private Recycler recycler; - - public HippyItemTypeHelper(HippyRecyclerViewBase recyclerView) { - this.recyclerView = recyclerView; - this.recycler = recyclerView.mRecycler; - } + HippyRecyclerViewBase recyclerView; + private Recycler recycler; - /** - * 更新3层缓存的ViewHolder - * - * @param oldType 老的type - * @param newType 新的type - * @param listItemRenderNode 前端变化type的RenderNode - */ - public void updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode) { - int count = recyclerView.getChildCount(); - for (int i = 0; i < count; i++) { - final ViewHolder holder = recyclerView - .getChildViewHolder(recyclerView.getChildAt(i)); - if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { - return; - } + public HippyItemTypeHelper(HippyRecyclerViewBase recyclerView) { + this.recyclerView = recyclerView; + this.recycler = recyclerView.mRecycler; } - if (updateItemType(oldType, newType, listItemRenderNode, recycler.mAttachedScrap)) { - return; - } - - if (updateItemType(oldType, newType, listItemRenderNode, recyclerView.mRecycler.mCachedViews)) { - return; - } + /** + * 更新3层缓存的ViewHolder + * + * @param oldType 老的type + * @param newType 新的type + * @param listItemRenderNode 前端变化type的RenderNode + */ + public void updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode) { + int count = recyclerView.getChildCount(); + for (int i = 0; i < count; i++) { + final RecyclerView.ViewHolder holder = recyclerView + .getChildViewHolder(recyclerView.getChildAt(i)); + if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { + return; + } + } - updateTypeForRecyclerPool(oldType, newType, listItemRenderNode); - } + if (updateItemType(oldType, newType, listItemRenderNode, recycler.mAttachedScrap)) { + return; + } - private void updateTypeForRecyclerPool(int oldType, int newType, ListItemRenderNode renderNode) { - if (recycler.getRecycledViewPool() != null) { - SparseArray scrap = recycler.getRecycledViewPool().mScrap; - ScrapData scrapData = scrap.get(oldType); - if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { - for (ViewHolder holder : scrapData.mScrapHeap) { - if (changeTypeIfNeed(oldType, newType, renderNode, holder)) { - scrapData.mScrapHeap.remove(holder); - addNewType(newType, holder); + if (updateItemType(oldType, newType, listItemRenderNode, recyclerView.mRecycler.mCachedViews)) { return; - } } - } - } - } - /** - * 重新将viewHolder加入缓存池 - */ - private void addNewType(int newType, ViewHolder holder) { - holder.mItemViewType = newType; - SparseArray scrap = recycler.getRecycledViewPool().mScrap; - ScrapData newScrapData = scrap.get(newType); - if (newScrapData == null) { - newScrapData = new ScrapData(); - scrap.append(newType, newScrapData); + updateTypeForRecyclerPool(oldType, newType, listItemRenderNode); } - newScrapData.mScrapHeap.add(holder); - } - private boolean updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode, - ArrayList viewHolders) { - final int cacheSize = viewHolders.size(); - for (int i = 0; i < cacheSize; i++) { - final ViewHolder holder = viewHolders.get(i); - if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { - return true; - } + private void updateTypeForRecyclerPool(int oldType, int newType, ListItemRenderNode renderNode) { + if (recycler.getRecycledViewPool() != null) { + SparseArray scrap = recycler.getRecycledViewPool().mScrap; + RecycledViewPool.ScrapData scrapData = scrap.get(oldType); + if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { + for (RecyclerView.ViewHolder holder : scrapData.mScrapHeap) { + if (changeTypeIfNeed(oldType, newType, renderNode, holder)) { + scrapData.mScrapHeap.remove(holder); + addNewType(newType, holder); + return; + } + } + } + } } - return false; - } - private boolean changeTypeIfNeed(int oldType, int newType, ListItemRenderNode listItemRenderNode, - ViewHolder holder) { - if (holder.getItemViewType() == oldType && holder instanceof HippyRecyclerViewHolder) { - RenderNode holderNode = ((HippyRecyclerViewHolder) holder).bindNode; - if (holderNode == listItemRenderNode) { + /** + * 重新将viewHolder加入缓存池 + */ + private void addNewType(int newType, ViewHolder holder) { holder.mItemViewType = newType; - return true; - } + SparseArray scrap = recycler.getRecycledViewPool().mScrap; + ScrapData newScrapData = scrap.get(newType); + if (newScrapData == null) { + newScrapData = new ScrapData(); + scrap.append(newType, newScrapData); + } + newScrapData.mScrapHeap.add(holder); + } + + private boolean updateItemType(int oldType, int newType, ListItemRenderNode listItemRenderNode, + ArrayList viewHolders) { + final int cacheSize = viewHolders.size(); + for (int i = 0; i < cacheSize; i++) { + final RecyclerView.ViewHolder holder = viewHolders.get(i); + if (changeTypeIfNeed(oldType, newType, listItemRenderNode, holder)) { + return true; + } + } + return false; + } + + private boolean changeTypeIfNeed(int oldType, int newType, ListItemRenderNode listItemRenderNode, + RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() == oldType && holder instanceof HippyRecyclerViewHolder) { + RenderNode holderNode = ((HippyRecyclerViewHolder) holder).bindNode; + if (holderNode == listItemRenderNode) { + holder.mItemViewType = newType; + return true; + } + } + return false; } - return false; - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java index 24d738cf215..4a7affdf6f4 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerExtension.java @@ -26,90 +26,96 @@ import com.tencent.mtt.hippy.views.hippylist.NodePositionHelper; import java.util.ArrayList; +/** + * Created on 2021/1/4. + * Description + * + * 目的是精确通过renderNode命中缓存 + * RecyclerView的扩展的缓存,如果mAttachedScrap 和 mCachedViews 都没有命中,会在访问RecyclerPool之前 + * 先访问ViewCacheExtension。参看{@link Recycler#tryGetViewHolderForPositionByDeadline}的执行流程 + */ public class HippyRecyclerExtension extends RecyclerView.ViewCacheExtension { - private final HippyEngineContext hpContext; - private final NodePositionHelper nodePositionHelper; - private HippyRecyclerViewBase recyclerView; - private int currentPosition; - - public HippyRecyclerExtension(HippyRecyclerViewBase recyclerView, HippyEngineContext hpContext, - NodePositionHelper nodePositionHelper) { - this.nodePositionHelper = nodePositionHelper; - this.recyclerView = recyclerView; - this.hpContext = hpContext; - } + private final HippyEngineContext hpContext; + private final NodePositionHelper nodePositionHelper; + private HippyRecyclerViewBase recyclerView; + private int currentPosition; - public int getCurrentPosition() { - return currentPosition; - } + public HippyRecyclerExtension(HippyRecyclerViewBase recyclerView, HippyEngineContext hpContext, + NodePositionHelper nodePositionHelper) { + this.nodePositionHelper = nodePositionHelper; + this.recyclerView = recyclerView; + this.hpContext = hpContext; + } - @Override - public View getViewForPositionAndType(Recycler recycler, int position, int type) { - currentPosition = position; - View bestView = findInAttachedScrap(recycler, position, type); - if (bestView == null) { - bestView = findInCachedScrap(recycler, position, type); + public int getCurrentPosition() { + return currentPosition; } - return bestView; - } - private View findInCachedScrap(Recycler recycler, int position, int type) { - ViewHolder bestHolder = findBestHolder(recycler.mCachedViews, position, type); - if (bestHolder != null) { - recycler.mCachedViews.remove(bestHolder); - return bestHolder.itemView; + @Override + public View getViewForPositionAndType(Recycler recycler, int position, int type) { + currentPosition = position; + View bestView = findInAttachedScrap(recycler, position, type); + if (bestView == null) { + bestView = findInCachedScrap(recycler, position, type); + } + return bestView; } - return null; - } - protected View findInAttachedScrap(Recycler recycler, int position, int type) { - ViewHolder bestHolder = findBestHolder(recycler.mAttachedScrap, position, type); - if (bestHolder != null) { - bestHolder.unScrap(); - return bestHolder.itemView; + private View findInCachedScrap(Recycler recycler, int position, int type) { + ViewHolder bestHolder = findBestHolder(recycler.mCachedViews, position, type); + if (bestHolder != null) { + recycler.mCachedViews.remove(bestHolder); + return bestHolder.itemView; + } + return null; } - return null; - } - private ViewHolder findBestHolder(ArrayList viewHolders, int position, - int type) { - int scrapCount = viewHolders.size(); - for (int i = 0; i < scrapCount; i++) { - final ViewHolder holder = viewHolders.get(i); - if (isTheBestHolder(position, type, holder)) { - return holder; - } + protected View findInAttachedScrap(Recycler recycler, int position, int type) { + ViewHolder bestHolder = findBestHolder(recycler.mAttachedScrap, position, type); + if (bestHolder != null) { + bestHolder.unScrap(); + return bestHolder.itemView; + } + return null; } - return null; - } - /** - * 找到对应的bindNode,比对缓存池的holder是否正好是当前position位置对应的Holder - * - * @param position 要获取Holder的position - * @param type 节点类型 - * @param scrapHolder 缓存池的Holder - * @return - */ - protected boolean isTheBestHolder(int position, int type, ViewHolder scrapHolder) { - if (scrapHolder.getAdapterPosition() != position || scrapHolder.isInvalid() || scrapHolder - .isRemoved()) { - return false; + private RecyclerView.ViewHolder findBestHolder(ArrayList viewHolders, int position, + int type) { + int scrapCount = viewHolders.size(); + for (int i = 0; i < scrapCount; i++) { + final RecyclerView.ViewHolder holder = viewHolders.get(i); + if (isTheBestHolder(position, type, holder)) { + return holder; + } + } + return null; } - if (scrapHolder.getItemViewType() == type && scrapHolder instanceof HippyRecyclerViewHolder) { - RenderNode nodeOfPosition = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) - .getChildAt(nodePositionHelper.getRenderNodePosition(position)); - return isNodeEquals(((HippyRecyclerViewHolder) scrapHolder).bindNode, - (ListItemRenderNode) nodeOfPosition); + + /** + * 找到对应的bindNode,比对缓存池的holder是否正好是当前position位置对应的Holder + * + * @param position 要获取Holder的position + * @param type 节点类型 + * @param scrapHolder 缓存池的Holder + * @return + */ + protected boolean isTheBestHolder(int position, int type, ViewHolder scrapHolder) { + if (scrapHolder.getAdapterPosition() != position || scrapHolder.isInvalid() || scrapHolder.isRemoved()) { + return false; + } + if (scrapHolder.getItemViewType() == type && scrapHolder instanceof HippyRecyclerViewHolder) { + RenderNode nodeOfPosition = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) + .getChildAt(nodePositionHelper.getRenderNodePosition(position)); + return isNodeEquals(((HippyRecyclerViewHolder) scrapHolder).bindNode, (ListItemRenderNode) nodeOfPosition); + } + return false; } - return false; - } - public static boolean isNodeEquals(ListItemRenderNode node1, ListItemRenderNode node2) { - if (node1 == null || node2 == null) { - return false; + public static boolean isNodeEquals(ListItemRenderNode node1, ListItemRenderNode node2) { + if (node1 == null || node2 == null) { + return false; + } + return node1.equals(node2); } - return node1.equals(node2); - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java index db0d53dbf3c..e8c2fced40b 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerPool.java @@ -24,90 +24,97 @@ import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewHolder; import com.tencent.mtt.hippy.views.hippylist.NodePositionHelper; +/** + * Created on 2021/1/4. Description + * + * 继承RecycledViewPool,主要用于renderNode节点的精确命中,不能从RecycledViewPool里面随意取一个node + * 所有重写了getRecycledView方法;putRecycledView也需要检测缓存是否会抛弃viewHolder,如果抛弃需要把 + * 事件同步给RenderManager进行相应的节点删除。 + */ public class HippyRecyclerPool extends RecyclerView.RecycledViewPool { - private final View recyclerView; - private final HippyRecyclerExtension viewCacheExtension; - private final HippyEngineContext hpContext; - private final NodePositionHelper nodePositionHelper; - private IHippyViewAboundListener viewAboundListener; - - public HippyRecyclerPool(HippyEngineContext hpContext, View recyclerView, - HippyRecyclerExtension viewCacheExtension, NodePositionHelper nodePositionHelper) { - this.nodePositionHelper = nodePositionHelper; - this.hpContext = hpContext; - this.recyclerView = recyclerView; - this.viewCacheExtension = viewCacheExtension; - } - - public void setViewAboundListener(IHippyViewAboundListener viewAboundListener) { - this.viewAboundListener = viewAboundListener; - } + private final View recyclerView; + private final HippyRecyclerExtension viewCacheExtension; + private final HippyEngineContext hpContext; + private final NodePositionHelper nodePositionHelper; + private IHippyViewAboundListener viewAboundListener; - /** - * 从缓存池里面获取ViewHolder进行复用 1、精确命中相同的renderNode 2、命中相同Type的ViewHolder,并且对应的RenderNode是没有被前端删除的 - * 如果renderNode.isDelete为true,说明前端删除了RenderNode, 此时会调用 RenderManager框架的deleteChild, 所以view也不会存在了。 - * 即使找到了相同type的Holder,也不能复用了。 - */ - @Override - public ViewHolder getRecycledView(int viewType) { - ScrapData scrapData = mScrap.get(viewType); - if (scrapData == null) { - return null; + public HippyRecyclerPool(HippyEngineContext hpContext, View recyclerView, + HippyRecyclerExtension viewCacheExtension, NodePositionHelper nodePositionHelper) { + this.nodePositionHelper = nodePositionHelper; + this.hpContext = hpContext; + this.recyclerView = recyclerView; + this.viewCacheExtension = viewCacheExtension; } - ViewHolder delegateHolder = null; - for (ViewHolder holder : scrapData.mScrapHeap) { - if (isTheSameRenderNode((HippyRecyclerViewHolder) holder)) { - scrapData.mScrapHeap.remove(holder); - delegateHolder = holder; - break; - } - } - //没有精确命中,再看看缓存池里面有没有相同类型的viewType - if (delegateHolder == null) { - delegateHolder = super.getRecycledView(viewType); + + public void setViewAboundListener(IHippyViewAboundListener viewAboundListener) { + this.viewAboundListener = viewAboundListener; } - //检测对应的节点是否被删除 - if (delegateHolder instanceof HippyRecyclerViewHolder - && ((HippyRecyclerViewHolder) delegateHolder).isRenderDeleted()) { - return null; + + /** + * 从缓存池里面获取ViewHolder进行复用 1、精确命中相同的renderNode 2、命中相同Type的ViewHolder,并且对应的RenderNode是没有被前端删除的 + * 如果renderNode.isDelete为true,说明前端删除了RenderNode, 此时会调用 RenderManager框架的deleteChild, + * 所以view也不会存在了。 即使找到了相同type的Holder,也不能复用了。 + */ + @Override + public ViewHolder getRecycledView(int viewType) { + ScrapData scrapData = mScrap.get(viewType); + if (scrapData == null) { + return null; + } + ViewHolder delegateHolder = null; + for (ViewHolder holder : scrapData.mScrapHeap) { + if (isTheSameRenderNode((HippyRecyclerViewHolder) holder)) { + scrapData.mScrapHeap.remove(holder); + delegateHolder = holder; + break; + } + } + //没有精确命中,再看看缓存池里面有没有相同类型的viewType + if (delegateHolder == null) { + delegateHolder = super.getRecycledView(viewType); + } + //检测对应的节点是否被删除 + if (delegateHolder instanceof HippyRecyclerViewHolder + && ((HippyRecyclerViewHolder) delegateHolder).isRenderDeleted()) { + return null; + } + return delegateHolder; } - return delegateHolder; - } - /** - * putRecycledView 可能出现缓存已经超过最大值,会发生ViewHolder被抛弃, 抛弃需要后,需要同步修改 renderManager内部创建对应的view,这样 {@link - * com.tencent.mtt.hippy.views.hippylist.HippyRecyclerListAdapter#onCreateViewHolder(ViewGroup, - * int)},才能通过 {@link RenderNode#createViewRecursive()} 创建新的view, 否则createViewRecursive会返回null。 - * - * @param scrap - */ - @Override - public void putRecycledView(ViewHolder scrap) { - notifyAboundIfNeed(scrap); - super.putRecycledView(scrap); - } + /** + * putRecycledView 可能出现缓存已经超过最大值,会发生ViewHolder被抛弃, 抛弃需要后,需要同步修改 renderManager内部创建对应的view,这样 + * {@link com.tencent.mtt.hippy.views.hippylist.HippyRecyclerListAdapter#onCreateViewHolder( + *ViewGroup, int)},才能通过 {@link RenderNode#createViewRecursive()} 创建新的view, + * 否则createViewRecursive会返回null。 + * + * @param scrap + */ + @Override + public void putRecycledView(ViewHolder scrap) { + notifyAboundIfNeed(scrap); + super.putRecycledView(scrap); + } - private void notifyAboundIfNeed(ViewHolder scrap) { - int viewType = scrap.getItemViewType(); - ScrapData scrapData = this.mScrap.get(viewType); - if (scrapData != null && scrapData.mScrapHeap.size() >= scrapData.mMaxScrap) { - viewAboundListener.onViewAbound((HippyRecyclerViewHolder) scrap); + private void notifyAboundIfNeed(ViewHolder scrap) { + int viewType = scrap.getItemViewType(); + ScrapData scrapData = this.mScrap.get(viewType); + if (scrapData != null && scrapData.mScrapHeap.size() >= scrapData.mMaxScrap) { + viewAboundListener.onViewAbound((HippyRecyclerViewHolder) scrap); + } } - } - /** - * 是否是节点完全相等 - * - * @param scrapHolder 缓存池里面的Holder - */ - private boolean isTheSameRenderNode(HippyRecyclerViewHolder scrapHolder) { - if (scrapHolder.bindNode == null) { - return false; + /** + * 是否是节点完全相等 + * + * @param scrapHolder 缓存池里面的Holder + */ + private boolean isTheSameRenderNode(HippyRecyclerViewHolder scrapHolder) { + if (scrapHolder.bindNode == null) { + return false; + } + RenderNode nodeForCurrent = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) + .getChildAt(nodePositionHelper.getRenderNodePosition(viewCacheExtension.getCurrentPosition())); + return scrapHolder.bindNode.equals(nodeForCurrent); } - RenderNode nodeForCurrent = hpContext.getRenderManager().getRenderNode(recyclerView.getId()) - .getChildAt( - nodePositionHelper.getRenderNodePosition(viewCacheExtension.getCurrentPosition())); - return scrapHolder.bindNode.equals(nodeForCurrent); - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java index f68b9477781..5ff75b28a6d 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/HippyRecyclerViewBase.java @@ -18,58 +18,106 @@ import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.util.AttributeSet; -import androidx.recyclerview.widget.LinearLayoutManager; + +/** + * Created on 2020/10/14. + * + * 由于Hippy的特殊需求,需要看到更多的RecyclerVew的方法和成员,这里创建和系统RecyclerView同包名。 + */ public class HippyRecyclerViewBase extends EasyRecyclerView { - public HippyRecyclerViewBase(@NonNull Context context) { - super(context); - } - - public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - } - - /** - * @param position 从哪一个数据位置开始排版,将position的item置顶 - * @param offset 相对于RecyclerView底部的offset,offset>0:内容下移,offset<0:内容上移 - */ - public void scrollToPositionWithOffset(int position, int offset) { - if (mLayoutSuppressed) { - return; + private boolean isBatching; + + public HippyRecyclerViewBase(@NonNull Context context) { + super(context); + } + + public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public HippyRecyclerViewBase(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + /** + * @param position 从哪一个数据位置开始排版,将position的item置顶 + * @param offset 相对于RecyclerView底部的offset,offset>0:内容下移,offset<0:内容上移 + */ + public void scrollToPositionWithOffset(int position, int offset) { + if (mLayoutSuppressed) { + return; + } + stopScroll(); + if (this.mLayout == null) { + android.util.Log.e("RecyclerView", + "Cannot scroll to position a LayoutManager set. Call setLayoutManager with a non-null argument."); + } else { + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset); + } else { + this.mLayout.scrollToPosition(position); + } + this.awakenScrollBars(); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + //这里不调用super.onLayout,因为HippyListView的RenderNode的update会走onLayout,导致多余的排版 + //HippyListView的dispatchLayout统一走setListData函数 } - stopScroll(); - if (this.mLayout == null) { - android.util.Log.e("RecyclerView", - "Cannot scroll to position a LayoutManager set. Call setLayoutManager with a non-null argument."); - } else { - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager instanceof LinearLayoutManager) { - ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset); - } else { - this.mLayout.scrollToPosition(position); - } - this.awakenScrollBars(); + + @Override + public void dispatchLayout() { + if (!isBatching) { + super.dispatchLayout(); + } + //由于上面屏蔽了super.onLayout,这里需要对齐框架的代码,把mFirstLayoutComplete该为true + this.mFirstLayoutComplete = true; + } + + @Override + String exceptionLabel() { + return super.exceptionLabel() + ",state:" + getStateInfo(); } - } - @Override - String exceptionLabel() { - return super.exceptionLabel() + ",state:" + getStateInfo(); - } + public String getStateInfo() { + if (mState != null) { + return mState.toString(); + } + return null; + } + + public void onBatchStart() { + isBatching = true; + } + + public void onBatchComplete() { + isBatching = false; + } - public String getStateInfo() { - if (mState != null) { - return mState.toString(); + /** + * view 被Hippy的RenderNode 删除了,这样会导致View的child完全是空的,这个view是不能再被recyclerView复用了 + * 否则如果被复用,在adapter的onBindViewHolder的时候,view的实际子view和renderNode的数据不匹配,diff会出现异常 + * 导致item白条,显示不出来,所以被删除的view,需要把viewHolder.setIsRecyclable(false),刷新list后,这个view就 + * 不会进入缓存。 + */ + public void disableRecycle(View childView) { + ViewGroup.LayoutParams layoutParams = childView.getLayoutParams(); + if (layoutParams instanceof LayoutParams) { + ViewHolder viewHolder = ((LayoutParams) layoutParams).mViewHolder; + if (viewHolder != null) { + viewHolder.setIsRecyclable(false); + } + } } - return null; - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java b/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java index 0516c1488ff..401028b957b 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/IHippyViewAboundListener.java @@ -1,8 +1,27 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package androidx.recyclerview.widget; import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewHolder; +/** + * Created on 2021/1/12. + * Description + */ public interface IHippyViewAboundListener { - void onViewAbound(HippyRecyclerViewHolder viewHolder); + void onViewAbound(HippyRecyclerViewHolder viewHolder); } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java b/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java index 7193f98210a..55e9fe6f618 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/IItemLayoutParams.java @@ -2,7 +2,11 @@ import androidx.recyclerview.widget.RecyclerView.LayoutParams; +/** + * Created on 2021/3/17. + * Description + */ public interface IItemLayoutParams { - void getItemLayoutParams(int position, LayoutParams lp); + void getItemLayoutParams(int position, LayoutParams lp); } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java b/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java index 654f8ce3ec7..965833b254f 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullHelper.java @@ -20,323 +20,337 @@ import android.animation.Animator; import android.animation.ValueAnimator; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.animation.DecelerateInterpolator; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView.OnScrollListener; import com.tencent.mtt.nxeasy.recyclerview.helper.AnimatorListenerBase; +/** + * Created on 2021/3/15. + * Description + * 原生recyclerView是不支持拉到最顶部,还可以继续拉动,要实现继续拉动,并且松手回弹的效果 + * recyclerView上拉回弹和下拉回弹的效果实现 + */ public class OverPullHelper { - private static final int DURATION = 150; - protected float lastRawY = -1; - protected float downRawY = -1; - - private int overPullState = OVER_PULL_NONE; - public static final int OVER_PULL_NONE = 0; - public static final int OVER_PULL_DOWN_ING = 1; - public static final int OVER_PULL_UP_ING = 2; - public static final int OVER_PULL_NORMAL = 3; - public static final int OVER_PULL_SETTLING = 4; - - private ValueAnimator animator; - private boolean enableOverDrag = true; - private int lastOverScrollMode = -1; - private boolean isRollBacking = false; - private OverPullListener overPullListener = null; - private EasyRecyclerView recyclerView; - - public OverPullHelper(EasyRecyclerView recyclerView) { - this.recyclerView = recyclerView; - lastOverScrollMode = recyclerView.getOverScrollMode(); - recyclerView.addOnScrollListener(new OnScrollListener() { - @Override - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - rollbackToBottomOrTop(); - } - } - }); - } - - private int getTouchSlop() { - final ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); - return vc.getScaledTouchSlop(); - } - - public void setOverPullListener(OverPullListener overPullListener) { - this.overPullListener = overPullListener; - } - - private boolean isMoving(MotionEvent event) { - return lastRawY > 0 && Math.abs(event.getRawY() - downRawY) > getTouchSlop(); - } - - public boolean onTouchEvent(MotionEvent event) { - if (isRollBacking) { - return true; + private static final int DURATION = 150; + private final OnScrollListener listener; + protected float lastRawY = -1; + protected float downRawY = -1; + + private int overPullState = OVER_PULL_NONE; + public static final int OVER_PULL_NONE = 0; + public static final int OVER_PULL_DOWN_ING = 1; + public static final int OVER_PULL_UP_ING = 2; + public static final int OVER_PULL_NORMAL = 3; + public static final int OVER_PULL_SETTLING = 4; + + private ValueAnimator animator; + private boolean enableOverDrag = true; + private int lastOverScrollMode = -1; + private boolean isRollBacking = false; + private OverPullListener overPullListener = null; + private EasyRecyclerView recyclerView; + private int scrollState; + + public OverPullHelper(EasyRecyclerView recyclerView) { + this.recyclerView = recyclerView; + lastOverScrollMode = recyclerView.getOverScrollMode(); + listener = new OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (scrollState != newState && newState == RecyclerView.SCROLL_STATE_IDLE) { + rollbackToBottomOrTop(); + } + scrollState = newState; + } + }; + recyclerView.addOnScrollListener(listener); + } + + public void destroy() { + recyclerView.removeOnScrollListener(listener); + } + + private int getTouchSlop() { + final ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); + return vc.getScaledTouchSlop(); } - if (checkOverDrag(event)) { - return true; + + public void setOverPullListener(OverPullListener overPullListener) { + this.overPullListener = overPullListener; } - return false; - } - - /** - * 检测是否处于顶部过界拉取,或者顶部过界拉取 - */ - private boolean checkOverDrag(MotionEvent event) { - if (!enableOverDrag) { - return false; + + private boolean isMoving(MotionEvent event) { + return lastRawY > 0 && Math.abs(event.getRawY() - downRawY) > getTouchSlop(); + } + + public boolean onTouchEvent(MotionEvent event) { + if (isRollBacking) { + return true; + } + if (checkOverDrag(event)) { + return true; + } + return false; } - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - lastRawY = event.getRawY(); - downRawY = event.getRawY(); - break; - case MotionEvent.ACTION_MOVE: - boolean overPullDown = isOverPullDown(event); - boolean overScrollUp = isOverPullUp(event); - if ((overPullDown || overScrollUp)) { - recyclerView.setOverScrollMode(OVER_SCROLL_NEVER); - recyclerView.invalidateGlows(); - if (overPullDown) { - setOverPullState(OVER_PULL_DOWN_ING); - } else { - setOverPullState(OVER_PULL_UP_ING); - } - int deltaY = (int) (event.getRawY() - lastRawY) / 2; + + /** + * 检测是否处于顶部过界拉取,或者顶部过界拉取 + */ + private boolean checkOverDrag(MotionEvent event) { + if (!enableOverDrag) { + return false; + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + lastRawY = event.getRawY(); + downRawY = event.getRawY(); + break; + case MotionEvent.ACTION_MOVE: + boolean overPullDown = isOverPullDown(event); + boolean overScrollUp = isOverPullUp(event); + if ((overPullDown || overScrollUp)) { + recyclerView.setOverScrollMode(OVER_SCROLL_NEVER); + recyclerView.invalidateGlows(); + if (overPullDown) { + setOverPullState(OVER_PULL_DOWN_ING); + } else { + setOverPullState(OVER_PULL_UP_ING); + } + int deltaY = (int) (event.getRawY() - lastRawY) / 2; // if (deltaY > 0) { // //下拉的时候除以2,放慢拉动的速度,调节拉动的手感 // deltaY = deltaY / 2; // } - recyclerView.offsetChildrenVertical(deltaY); - if (overPullListener != null) { - overPullListener - .onOverPullStateChanged(overPullState, overPullState, getOverPullOffset()); - } - } else { - setOverPullState(OVER_PULL_NORMAL); + recyclerView.offsetChildrenVertical(deltaY); + if (overPullListener != null) { + overPullListener.onOverPullStateChanged(overPullState, overPullState, getOverPullOffset()); + } + } else { + setOverPullState(OVER_PULL_NORMAL); + } + lastRawY = event.getRawY(); + break; + default: + reset(); + } + if (overPullState == OVER_PULL_DOWN_ING || overPullState == OVER_PULL_UP_ING) { + return true; + } + return false; + } + + /** + * 在松开手后, + * 1、如果当前处于fling状态,scrollState的值是SCROLL_STATE_SETTLING,先不做rollbackToBottomOrTop + * 等到onScrollStateChanged 变成 IDLE的时候,再做rollbackToBottomOrTop + * 2、如果当前处于非fling状态,scrollState的值不是SCROLL_STATE_SETTLING,就立即做rollbackToBottomOrTop + */ + public void handleEventUp(MotionEvent event) { + if (isActionUpOrCancel(event)) { + revertOverScrollMode(); + if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_SETTLING) { + rollbackToBottomOrTop(); + } } - lastRawY = event.getRawY(); - break; - default: - reset(); } - if (overPullState == OVER_PULL_DOWN_ING || overPullState == OVER_PULL_UP_ING) { - return true; + + private void revertOverScrollMode() { + if (lastOverScrollMode != -1) { + recyclerView.setOverScrollMode(lastOverScrollMode); + } } - return false; - } - - /** - * 在松开手后, 1、如果当前处于fling状态,scrollState的值是SCROLL_STATE_SETTLING,先不做rollbackToBottomOrTop - * 等到onScrollStateChanged 变成 IDLE的时候,再做rollbackToBottomOrTop 2、如果当前处于非fling状态,scrollState的值不是SCROLL_STATE_SETTLING,就立即做rollbackToBottomOrTop - */ - public void handleEventUp(MotionEvent event) { - if (isActionUpOrCancel(event)) { - revertOverScrollMode(); - if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_SETTLING) { - rollbackToBottomOrTop(); - } + + private int getOverPullOffset() { + if (overPullState == OVER_PULL_DOWN_ING) { + return getOverPullDownOffset(); + } else if (overPullState == OVER_PULL_UP_ING) { + return getOverPullUpOffset(); + } + return 0; } - } - private void revertOverScrollMode() { - if (lastOverScrollMode != -1) { - recyclerView.setOverScrollMode(lastOverScrollMode); + void setOverPullState(int newOverPullState) { + if (overPullListener != null) { + overPullListener.onOverPullStateChanged(overPullState, newOverPullState, getOverPullOffset()); + } + overPullState = newOverPullState; } - } - private int getOverPullOffset() { - if (overPullState == OVER_PULL_DOWN_ING) { - return getOverPullDownOffset(); - } else if (overPullState == OVER_PULL_UP_ING) { - return getOverPullUpOffset(); + /** + * 因为可能出现越界拉取,松手后需要回退到原来的位置,要么回到顶部,要么回到底部 + */ + void rollbackToBottomOrTop() { + int distanceToTop = recyclerView.computeVerticalScrollOffset(); + if (distanceToTop < 0) { + //顶部空出了一部分,需要回滚上去 + rollbackTo(distanceToTop, 0); + } else { + //底部空出一部分,需要混滚下去 + int overPullUpOffset = getOverPullUpOffset(); + if (overPullUpOffset != 0) { + rollbackTo(overPullUpOffset, 0); + } + } } - return 0; - } - void setOverPullState(int newOverPullState) { - if (overPullListener != null) { - overPullListener.onOverPullStateChanged(overPullState, newOverPullState, getOverPullOffset()); + /** + * 计算底部被overPull的偏移,需要向下回滚的距离 + * 要么出现底部内容顶满distanceToBottom,要么出现顶部内容顶满distanceToTop,取最小的那一个 + * + * @return + */ + public int getOverPullUpOffset() { + int contentOffset = recyclerView.computeVerticalScrollOffset(); + int verticalScrollRange = recyclerView.computeVerticalScrollRange(); + int blankHeightToBottom = contentOffset + recyclerView.getHeight() - verticalScrollRange; + if (blankHeightToBottom > 0 && contentOffset > 0) { + return Math.min(blankHeightToBottom, contentOffset); + } + return 0; } - overPullState = newOverPullState; - } - - /** - * 因为可能出现越界拉取,松手后需要回退到原来的位置,要么回到顶部,要么回到底部 - */ - void rollbackToBottomOrTop() { - int distanceToTop = recyclerView.computeVerticalScrollOffset(); - if (distanceToTop < 0) { - //顶部空出了一部分,需要回滚上去 - rollbackTo(distanceToTop, 0); - } else { - //底部空出一部分,需要混滚下去 - int overPullUpOffset = getOverPullUpOffset(); - if (overPullUpOffset != 0) { - rollbackTo(overPullUpOffset, 0); - } + + private boolean isActionUpOrCancel(MotionEvent event) { + return event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL; } - } - - /** - * 计算底部被overPull的偏移,需要向下回滚的距离 要么出现底部内容顶满distanceToBottom,要么出现顶部内容顶满distanceToTop,取最小的那一个 - * - * @return - */ - public int getOverPullUpOffset() { - int contentOffset = recyclerView.computeVerticalScrollOffset(); - int verticalScrollRange = recyclerView.computeVerticalScrollRange(); - int blankHeightToBottom = contentOffset + recyclerView.getHeight() - verticalScrollRange; - if (blankHeightToBottom > 0 && contentOffset > 0) { - return Math.min(blankHeightToBottom, contentOffset); + + private void endAnimation() { + if (animator != null) { + animator.removeAllListeners(); + animator.removeAllUpdateListeners(); + animator.end(); + animator = null; + } + isRollBacking = false; } - return 0; - } - - private boolean isActionUpOrCancel(MotionEvent event) { - return event.getAction() == MotionEvent.ACTION_UP - || event.getAction() == MotionEvent.ACTION_CANCEL; - } - - private void endAnimation() { - if (animator != null) { - animator.removeAllListeners(); - animator.removeAllUpdateListeners(); - animator.end(); - animator = null; + + /** + * 回弹动画的接口 + */ + private void rollbackTo(int from, int to) { + endAnimation(); + animator = ValueAnimator.ofInt(from, to); + animator.setInterpolator(new DecelerateInterpolator()); + animator.addUpdateListener(new RollbackUpdateListener(from)); + animator.addListener(new AnimatorListenerBase() { + @Override + public void onAnimationEnd(Animator animation) { + setOverPullState(OVER_PULL_NONE); + isRollBacking = false; + } + }); + isRollBacking = true; + animator.setDuration(DURATION).start(); } - isRollBacking = false; - } - - /** - * 回弹动画的接口 - */ - private void rollbackTo(int from, int to) { - endAnimation(); - animator = ValueAnimator.ofInt(from, to); - animator.setInterpolator(new DecelerateInterpolator()); - animator.addUpdateListener(new RollbackUpdateListener(from)); - animator.addListener(new AnimatorListenerBase() { - @Override - public void onAnimationEnd(Animator animation) { + + private void reset() { + revertOverScrollMode(); + lastRawY = -1; + downRawY = -1; setOverPullState(OVER_PULL_NONE); - isRollBacking = false; - } - }); - isRollBacking = true; - animator.setDuration(DURATION).start(); - } - - private void reset() { - revertOverScrollMode(); - lastRawY = -1; - downRawY = -1; - setOverPullState(OVER_PULL_NONE); - } - - /** - * 顶部是否可以越界下拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度+1 - */ - private boolean isOverPullDown(MotionEvent event) { - //常规情况,内容在顶部offset为0,异常情况,内容被完全拉到最底部,看不见内容的时候,offset也为0 - int offset = recyclerView.computeVerticalScrollOffset(); - int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; - //不能把内容完全拉得看不见 - if (Math.abs(offset) + dy < recyclerView.getHeight()) { - return isMoving(event) && isPullDownAction(event) && !canOverPullDown(); } - return false; - } - - /** - * 底部是否可以越界上拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度的一般 - */ - private boolean isOverPullUp(MotionEvent event) { - int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; - //不能让内容完全被滚出屏幕,否则computeVerticalScrollOffset为0是一个无效的值 - int distanceToBottom = - recyclerView.computeVerticalScrollOffset() + recyclerView.getHeight() - recyclerView - .computeVerticalScrollRange(); - if (distanceToBottom + dy < recyclerView.getHeight()) { - return isMoving(event) && isPullUpAction(event) && !canOverPullUp(); + + /** + * 顶部是否可以越界下拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度+1 + */ + private boolean isOverPullDown(MotionEvent event) { + //常规情况,内容在顶部offset为0,异常情况,内容被完全拉到最底部,看不见内容的时候,offset也为0 + int offset = recyclerView.computeVerticalScrollOffset(); + int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; + //不能把内容完全拉得看不见 + if (Math.abs(offset) + dy < recyclerView.getHeight()) { + return isMoving(event) && isPullDownAction(event) && !canOverPullDown(); + } + return false; + } + + /** + * 底部是否可以越界上拉,拉出一段空白区域,越界的部分最多不能超过RecyclerView高度的一般 + */ + private boolean isOverPullUp(MotionEvent event) { + int dy = Math.abs((int) (event.getRawY() - lastRawY)) + 1; + //不能让内容完全被滚出屏幕,否则computeVerticalScrollOffset为0是一个无效的值 + int distanceToBottom = recyclerView.computeVerticalScrollOffset() + recyclerView.getHeight() - recyclerView + .computeVerticalScrollRange(); + if (distanceToBottom + dy < recyclerView.getHeight()) { + return isMoving(event) && isPullUpAction(event) && !canOverPullUp(); + } + return false; + } + + boolean isPullDownAction(MotionEvent event) { + return event.getRawY() - lastRawY > 0; } - return false; - } - - boolean isPullDownAction(MotionEvent event) { - return event.getRawY() - lastRawY > 0; - } - - boolean isPullUpAction(MotionEvent event) { - return event.getRawY() - lastRawY <= 0; - } - - /** - * 顶部还有内容,还可以向下拉到 - */ - boolean canOverPullDown() { - return recyclerView.canScrollVertically(-1); - } - - /** - * 底部还有内容,还可以向上拉动 - */ - boolean canOverPullUp() { - return recyclerView.canScrollVertically(1); - } - - public int getOverPullState() { - return overPullState; - } - - /** - * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离 - */ - public int getOverPullDownOffset() { - if (overPullState == OVER_PULL_DOWN_ING) { - return recyclerView.computeVerticalScrollOffset(); + + boolean isPullUpAction(MotionEvent event) { + return event.getRawY() - lastRawY <= 0; } - return 0; - } - private class RollbackUpdateListener implements ValueAnimator.AnimatorUpdateListener { + /** + * 顶部还有内容,还可以向下拉到 + */ + boolean canOverPullDown() { + return recyclerView.canScrollVertically(-1); + } + + /** + * 底部还有内容,还可以向上拉动 + */ + boolean canOverPullUp() { + return recyclerView.canScrollVertically(1); + } - int currentValue; - int totalConsumedY; + public int getOverPullState() { + return overPullState; + } - RollbackUpdateListener(int fromValue) { - currentValue = fromValue; + /** + * 下拉的时候,返回值<0,表示顶部被下拉了一部分距离 + */ + public int getOverPullDownOffset() { + if (overPullState == OVER_PULL_DOWN_ING) { + return recyclerView.computeVerticalScrollOffset(); + } + return 0; } - @Override - public void onAnimationUpdate(ValueAnimator animation) { - if (recyclerView.isDataChangedWithoutNotify()) { - //由于动画是一个异步操作,做动画的时候,recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter - //的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, - //由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 - //IndexOutOfBoundsException - return; - } - int value = (int) animation.getAnimatedValue(); - int[] consumed = new int[2]; - int dy = value - currentValue; - //dy>0 上回弹,列表内容向上滚动,慢慢显示底部的内容;dy<0 下回弹,列表内容向下滚动,慢慢显示顶部的内容 - recyclerView.scrollStep(0, dy, consumed); - int consumedY = consumed[1]; - totalConsumedY += consumedY; - - //consumedY是排版view消耗的Y的距离,没有内容填充,即consumedY为0,需要强行offsetChildrenVertical - int leftOffset = consumedY - dy; - if (leftOffset != 0) { - //leftOffset<0 向上回弹,leftOffset>0 向下回弹 - recyclerView.offsetChildrenVertical(leftOffset); - } - setOverPullState(OVER_PULL_SETTLING); - currentValue = value; + private class RollbackUpdateListener implements ValueAnimator.AnimatorUpdateListener { + + int currentValue; + int totalConsumedY; + + RollbackUpdateListener(int fromValue) { + currentValue = fromValue; + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (recyclerView.isDataChangedWithoutNotify()) { + //由于动画是一个异步操作,做动画的时候,recyclerView的adapter状态已经变化,但是没有进行notify,导致state和adapter + //的itemCount对不齐,比如hippy场景,直接把recyclerView的renderNode删除了,adapter的itemCount直接变为0, + //由于没有notifyDatSetChange,state的itemCount不为0,这样就会出现validateViewHolderForOffsetPosition报 + //IndexOutOfBoundsException + return; + } + int value = (int) animation.getAnimatedValue(); + int[] consumed = new int[2]; + int dy = value - currentValue; + //dy>0 上回弹,列表内容向上滚动,慢慢显示底部的内容;dy<0 下回弹,列表内容向下滚动,慢慢显示顶部的内容 + recyclerView.scrollStep(0, dy, consumed); + int consumedY = consumed[1]; + totalConsumedY += consumedY; + + //consumedY是排版view消耗的Y的距离,没有内容填充,即consumedY为0,需要强行offsetChildrenVertical + int leftOffset = consumedY - dy; + if (leftOffset != 0) { + //leftOffset<0 向上回弹,leftOffset>0 向下回弹 + recyclerView.offsetChildrenVertical(leftOffset); + } + setOverPullState(OVER_PULL_SETTLING); + currentValue = value; + } } - } } diff --git a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java b/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java index ad7eb67f3e2..913750531cd 100644 --- a/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java +++ b/android/sdk/src/main/java/androidx/recyclerview/widget/OverPullListener.java @@ -16,12 +16,16 @@ package androidx.recyclerview.widget; +/** + * Created on 2021/3/15. + * Description + */ public interface OverPullListener { - /** - * @param newState {@link OverPullHelper#OVER_PULL_DOWN_ING} - * @param offset - */ + /** + * @param newState {@link OverPullHelper#OVER_PULL_DOWN_ING} + * @param offset + */ - void onOverPullStateChanged(int oldState, int newState, int offset); + void onOverPullStateChanged(int oldState, int newState, int offset); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java index bfbdb84c6f6..2f2ba3be446 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/ControllerManager.java @@ -279,7 +279,13 @@ public void onBatchComplete(String className, int id) { hippyViewController.onBatchComplete(view); } } - + public void onBatchStart(String className, int id) { + HippyViewController hippyViewController = mControllerRegistry.getViewController(className); + View view = mControllerRegistry.getView(id); + if (view != null) { + hippyViewController.onBatchStart(view); + } + } public void deleteChildRecursive(ViewGroup viewParent, View child, int childIndex) { if (viewParent == null || child == null) { return; diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java index cdfe658478d..9caffbc809c 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/HippyViewController.java @@ -670,7 +670,9 @@ public void dispatchFunction(T view, String functionName, HippyArray var) { public void dispatchFunction(T view, String functionName, HippyArray params, Promise promise) { } + public void onBatchStart(T view){ + } public void onBatchComplete(T view) { } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RecyclerItemRenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RecyclerItemRenderNode.java new file mode 100644 index 00000000000..e57ebc5c008 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RecyclerItemRenderNode.java @@ -0,0 +1,47 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.mtt.hippy.uimanager; + +import android.view.View; +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.common.HippyMap; + +/** + * Created on 2021/4/25. + * Description + */ +public class RecyclerItemRenderNode extends ListItemRenderNode { + + public RecyclerItemRenderNode(int mId, HippyMap mPropsToUpdate, String className, HippyRootView mRootView, + ControllerManager componentManager, boolean isLazyLoad) { + super(mId, mPropsToUpdate, className, mRootView, componentManager, isLazyLoad); + } + + /** + * y值是前端传入的,前端没有复用的概念,所有y是整个list长度的y值,并不是recyclerView的排版的y。 + * 真正意义上面的y是排版到屏幕范围以内的y,也是子view相对于recyclerView的起始位置的y,也就是子view的top + * 系统的recyclerView在刷新list前,layoutManager会调用anchorInfo.assignFromView,取第一个view计算当前的 + * anchorInfo,如果整个地方把y值修改了,导致anchorInfo会取不对. + * 这里保证updateLayout不要改变已经挂在到RecyclerView的view的top + */ + @Override + public void updateLayout(int x, int y, int w, int h) { + View renderView = mComponentManager.mControllerRegistry.getView(mId); + y = renderView != null ? renderView.getTop() : 0; + super.updateLayout(x, y, w, h); + mY = y; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java index 080af324427..720429f9fb2 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java @@ -186,6 +186,10 @@ public void batch() { LogUtils.d("RenderManager", "do batch size " + mUIUpdateNodes.size()); // mContext.getGlobalConfigs().getLogAdapter().log(TAG,"do batch size " + mShouldUpdateNodes.size()); + for (int i = 0; i < mUIUpdateNodes.size(); i++) { + RenderNode uiNode = mUIUpdateNodes.get(i); + uiNode.batchStart(); + } for (int i = 0; i < mUIUpdateNodes.size(); i++) { mUIUpdateNodes.get(i).createView(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java index 9e59bf7a8bd..0445bd6292a 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/uimanager/RenderNode.java @@ -411,6 +411,11 @@ public boolean isDelete() { return mIsDelete; } + public void batchStart() { + if (!mIsLazyLoad && !mIsDelete) { + mComponentManager.onBatchStart(mClassName, mId); + } + } public void batchComplete() { if (!mIsLazyLoad && !mIsDelete) { mComponentManager.onBatchComplete(mClassName, mId); diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyListUtils.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyListUtils.java new file mode 100644 index 00000000000..51e64ff46e9 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyListUtils.java @@ -0,0 +1,96 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippylist; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.LayoutManager; +import com.tencent.mtt.hippy.HippyEngineContext; +import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +import com.tencent.mtt.hippy.uimanager.ListViewRenderNode; +import com.tencent.mtt.hippy.uimanager.RenderNode; + +/** + * HippyList一些公用的方法 + */ +public class HippyListUtils { + + private HippyListUtils() { + + } + + /** + * 是否垂直方向排版 + */ + public static boolean isVerticalLayout(RecyclerView recyclerView) { + LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager != null) { + return layoutManager.canScrollVertically(); + } + return false; + } + + /** + * 是否水平方向排版 + */ + public static boolean isHorizontalLayout(RecyclerView recyclerView) { + LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager != null) { + return layoutManager.canScrollHorizontally(); + } + return false; + } + + public static boolean isLinearLayout(RecyclerView recyclerView) { + return recyclerView.getLayoutManager() instanceof LinearLayoutManager; + } + + /** + * 凡是ListItemRenderNode的createView都是在ListView的onCreateView的时候创建 + * 这里将父节点下面的所有ListItemRenderNode设置懒加载,避免listItemView提前在这里创建 + * 否则会导致HippyListView无法createItemView + */ + public static void setListItemNodeLazy(RenderNode parentNode) { + for (int i = 0; i < parentNode.getChildCount(); i++) { + RenderNode childNode = parentNode.getChildAt(i); + if (childNode instanceof ListItemRenderNode) { + childNode.setLazy(true); + } else { + setListItemNodeLazy(childNode); + } + } + } + + /** + * 解决mFixedContentIndex不生效的问题,场景是viewPagerItem被摧毁了,再次滑动回来的场景。 + * ViewPagerItem的Node调用了updateViewRecursive,如果下面有HippyListView的节点,HippyListView的节点会触发 + * HippyListView的排版,但是此时的排版不会走bachComplete,导致HippyListView的setListData不会调用 + * 这样就无法实现mFixedContentIndex的生效,所以这里需要补一下setListData + */ + public static void updateListView(HippyEngineContext hippyContext, RenderNode parentNode) { + for (int i = 0; i < parentNode.getChildCount(); i++) { + RenderNode childNode = parentNode.getChildAt(i); + if (childNode instanceof ListViewRenderNode) { + hippyContext.getRenderManager().getControllerManager() + .onBatchComplete(childNode.getClassName(), childNode.getId()); + break; + } else { + updateListView(hippyContext, childNode); + } + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java index d4beb560fd0..898813530c4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java @@ -37,367 +37,386 @@ import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IStickyItemsProvider; import java.util.ArrayList; -public class HippyRecyclerListAdapter extends - Adapter - implements IRecycleItemTypeChange, IStickyItemsProvider, IItemLayoutParams { - - protected final HippyEngineContext hpContext; - protected final HRCV hippyRecyclerView; - protected final HippyItemTypeHelper hippyItemTypeHelper; - protected int positionToCreateHolder; - protected PullFooterEventHelper footerEventHelper; - protected PullHeaderEventHelper headerEventHelper; - protected PreloadHelper preloadHelper; - - public HippyRecyclerListAdapter(HRCV hippyRecyclerView, HippyEngineContext hpContext) { - this.hpContext = hpContext; - this.hippyRecyclerView = hippyRecyclerView; - hippyItemTypeHelper = new HippyItemTypeHelper(hippyRecyclerView); - preloadHelper = new PreloadHelper(hippyRecyclerView); - } - - /** - * 对于吸顶到RenderNode需要特殊处理 吸顶的View需要包一层ViewGroup,吸顶的时候,从ViewGroup把RenderNode的View取出来挂载到顶部 - * 当RenderNode的View已经挂载到Header位置上面,如果重新触发创建ViewHolder,renderView会创建失败, - * 此时就只返回一个空到renderViewContainer上去,等viewHolder需要显示到时候,再把header上面的View还原到这个 ViewHolder上面。 - */ - @NonNull - @Override - public HippyRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - ListItemRenderNode renderNode = getChildNodeByAdapterPosition(positionToCreateHolder); - boolean isViewExist = renderNode.isViewExist(); - boolean needsDelete = renderNode.needDeleteExistRenderView(); - View renderView = createRenderView(renderNode); - if (isPullHeader(positionToCreateHolder)) { - ((HippyPullHeaderView) renderView).setParentView(hippyRecyclerView); - initPullHeadEventHelper((PullHeaderRenderNode) renderNode, renderView); - return new HippyRecyclerViewHolder(headerEventHelper.getView(), renderNode); - } else if (isStickyPosition(positionToCreateHolder)) { - return new HippyRecyclerViewHolder(getStickyContainer(parent, renderView), renderNode); - } else { - if (renderView == null) { - throw new IllegalArgumentException("createRenderView error!" - + ",isDelete:" + renderNode.isDelete() - + ",isViewExist:" + isViewExist - + ",needsDelete:" + needsDelete - + ",className:" + renderNode.getClassName() - + ",isLazy :" + renderNode.isIsLazyLoad() - + ",itemCount :" + getItemCount() - + ",getNodeCount:" + getRenderNodeCount() - + ",notifyCount:" + hippyRecyclerView.renderNodeCount - + "curPos:" + positionToCreateHolder - + ",rootView:" + renderNode.hasRootView() - + ",parentNode:" + (renderNode.getParent() != null) - + ",offset:" + hippyRecyclerView.computeVerticalScrollOffset() - + ",range:" + hippyRecyclerView.computeVerticalScrollRange() - + ",extent:" + hippyRecyclerView.computeVerticalScrollExtent() - + ",viewType:" + viewType - + ",id:" + renderNode.getId() - + ",attachedIds:" + getAttachedIds() - + ",nodeOffset:" + hippyRecyclerView.getNodePositionHelper().getNodeOffset() - + ",view:" + hippyRecyclerView - ); - } - return new HippyRecyclerViewHolder(renderView, renderNode); - } - } - - String getAttachedIds() { - StringBuilder attachedIds = new StringBuilder(); - int childCount = hippyRecyclerView.getChildCount(); - for (int i = 0; i < childCount; ++i) { - View attachedView = hippyRecyclerView.getChildAt(i); - attachedIds.append("|p_" + hippyRecyclerView.getChildAdapterPosition(attachedView)); - attachedIds.append("_i_" + attachedView.getId()); - } - return attachedIds.toString(); - } - - - /** - * deleteExistRenderView 是异常情况,正常情况应该不会走这个逻辑, 本来在{@link HippyRecyclerView#onViewAbound(HippyRecyclerViewHolder)} - * 的地方应该已经调用了deleteExistRenderView,deleteExistRenderView这里不应该可能走到 如果是吸顶的View,shouldSticky为true的情况下,是本身就存在的,不应该去调用deleteExistRenderView - * - * @param renderNode - * @return - */ - protected View createRenderView(ListItemRenderNode renderNode) { - if (renderNode.needDeleteExistRenderView() && !renderNode.shouldSticky()) { - deleteExistRenderView(renderNode); - } - renderNode.setLazy(false); - View view = renderNode.createViewRecursive(); - renderNode.updateViewRecursive(); - return view; - } - - public void deleteExistRenderView(ListItemRenderNode renderNode) { - renderNode.setLazy(true); - RenderNode parentNode = getParentNode(); - if (parentNode != null) { - hpContext.getRenderManager().getControllerManager() - .deleteChild(parentNode.getId(), renderNode.getId()); - } - renderNode.setRecycleItemTypeChangeListener(null); - } - - private FrameLayout getStickyContainer(ViewGroup parent, View renderView) { - FrameLayout container = new FrameLayout(parent.getContext()); - if (renderView != null) { - container.addView(renderView, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - } - return container; - } - - private void initPullHeadEventHelper(PullHeaderRenderNode renderNode, View renderView) { - if (headerEventHelper == null) { - headerEventHelper = new PullHeaderEventHelper(hippyRecyclerView, renderNode); - } - headerEventHelper.setRenderNodeView(renderView); - } - - @Override - public String toString() { - return "HippyRecyclerAdapter: itemCount:" + getItemCount(); - } - - /** - * 绑定数据 对于全新的viewHolder,isCreated 为true,调用updateViewRecursive进行物理树的创建,以及数据的绑定 - * 对于非全新创建的viewHolder,进行view树的diff,然后在把数据绑定到view树上面 - * - * @param hippyRecyclerViewHolder position当前的viewHolder - * @param position 绑定数据的节点位置 - */ - @Override - public void onBindViewHolder(HippyRecyclerViewHolder hippyRecyclerViewHolder, int position) { - setLayoutParams(hippyRecyclerViewHolder.itemView, position); - RenderNode oldNode = hippyRecyclerViewHolder.bindNode; - ListItemRenderNode newNode = getChildNodeByAdapterPosition(position); - oldNode.setLazy(true); - newNode.setLazy(false); - if (oldNode != newNode) { - //step 1: diff - ArrayList patchTypes = DiffUtils.diff(oldNode, newNode); - //step:2 delete unUseful views - DiffUtils.deleteViews(hpContext.getRenderManager().getControllerManager(), patchTypes); - //step:3 replace id - DiffUtils.replaceIds(hpContext.getRenderManager().getControllerManager(), patchTypes); - //step:4 create view is do not reUse - DiffUtils.createView(patchTypes); - //step:5 patch the dif result - DiffUtils.doPatch(hpContext.getRenderManager().getControllerManager(), patchTypes); - } - newNode.setRecycleItemTypeChangeListener(this); - hippyRecyclerViewHolder.bindNode = newNode; - enablePullFooter(position, hippyRecyclerViewHolder.itemView); - } - - /** - * 检测最后一个item是否是footer,如果是,需要对这个itemView设置监控,footer显示就通知前端加载下一页 - */ - private void enablePullFooter(int position, View itemView) { - if (position == getItemCount() - 1) { - ListItemRenderNode renderNode = getChildNodeByAdapterPosition(position); - if (renderNode.isPullFooter()) { - if (footerEventHelper == null) { - footerEventHelper = new PullFooterEventHelper(hippyRecyclerView); +/** + * Created on 2020/12/22. + * Description RecyclerView的子View直接是前端的RenderNode节点,没有之前包装的那层RecyclerViewItem。 + * 对于特殊的renderNode,比如header和sticky的节点,我们进行了不同的处理。 + */ +public class HippyRecyclerListAdapter extends Adapter + implements IRecycleItemTypeChange, IStickyItemsProvider, IItemLayoutParams { + + protected final HippyEngineContext hpContext; + protected final HRCV hippyRecyclerView; + protected final HippyItemTypeHelper hippyItemTypeHelper; + protected int positionToCreateHolder; + protected PullFooterEventHelper footerEventHelper; + protected PullHeaderEventHelper headerEventHelper; + protected PreloadHelper preloadHelper; + + public HippyRecyclerListAdapter(HRCV hippyRecyclerView, HippyEngineContext hpContext) { + this.hpContext = hpContext; + this.hippyRecyclerView = hippyRecyclerView; + hippyItemTypeHelper = new HippyItemTypeHelper(hippyRecyclerView); + preloadHelper = new PreloadHelper(hippyRecyclerView); + } + + /** + * 对于吸顶到RenderNode需要特殊处理 + * 吸顶的View需要包一层ViewGroup,吸顶的时候,从ViewGroup把RenderNode的View取出来挂载到顶部 + * 当RenderNode的View已经挂载到Header位置上面,如果重新触发创建ViewHolder,renderView会创建失败, + * 此时就只返回一个空到renderViewContainer上去,等viewHolder需要显示到时候,再把header上面的View还原到这个 + * ViewHolder上面。 + */ + @NonNull + @Override + public HippyRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + ListItemRenderNode renderNode = getChildNodeByAdapterPosition(positionToCreateHolder); + boolean isViewExist = renderNode.isViewExist(); + boolean needsDelete = renderNode.needDeleteExistRenderView(); + View renderView = createRenderView(renderNode); + if (isPullHeader(positionToCreateHolder)) { + ((HippyPullHeaderView) renderView).setParentView(hippyRecyclerView); + initPullHeadEventHelper((PullHeaderRenderNode) renderNode, renderView); + return new HippyRecyclerViewHolder(headerEventHelper.getView(), renderNode); + } else if (isStickyPosition(positionToCreateHolder)) { + return new HippyRecyclerViewHolder(getStickyContainer(parent, renderView), renderNode); + } else { + if (renderView == null) { + throw new IllegalArgumentException("createRenderView error!" + + ",isDelete:" + renderNode.isDelete() + + ",isViewExist:" + isViewExist + + ",needsDelete:" + needsDelete + + ",className:" + renderNode.getClassName() + + ",isLazy :" + renderNode.isIsLazyLoad() + + ",itemCount :" + getItemCount() + + ",getNodeCount:" + getRenderNodeCount() + + ",notifyCount:" + hippyRecyclerView.renderNodeCount + + "curPos:" + positionToCreateHolder + + ",rootView:" + renderNode.hasRootView() + + ",parentNode:" + (renderNode.getParent() != null) + + ",offset:" + hippyRecyclerView.computeVerticalScrollOffset() + + ",range:" + hippyRecyclerView.computeVerticalScrollRange() + + ",extent:" + hippyRecyclerView.computeVerticalScrollExtent() + + ",viewType:" + viewType + + ",id:" + renderNode.getId() + + ",attachedIds:" + getAttachedIds() + + ",nodeOffset:" + hippyRecyclerView.getNodePositionHelper().getNodeOffset() + + ",view:" + hippyRecyclerView + ); + } + return new HippyRecyclerViewHolder(renderView, renderNode); + } + } + + String getAttachedIds() { + StringBuilder attachedIds = new StringBuilder(); + int childCount = hippyRecyclerView.getChildCount(); + for (int i = 0; i < childCount; ++i) { + View attachedView = hippyRecyclerView.getChildAt(i); + attachedIds.append("|p_" + hippyRecyclerView.getChildAdapterPosition(attachedView)); + attachedIds.append("_i_" + attachedView.getId()); } - footerEventHelper.enableFooter(itemView); - } else { - if (footerEventHelper != null) { - footerEventHelper.disableFooter(); + return attachedIds.toString(); + } + + + /** + * deleteExistRenderView 是异常情况,正常情况应该不会走这个逻辑, + * 本来在{@link HippyRecyclerView#onViewAbound(HippyRecyclerViewHolder)} + * 的地方应该已经调用了deleteExistRenderView,deleteExistRenderView这里不应该可能走到 + * 如果是吸顶的View,shouldSticky为true的情况下,是本身就存在的,不应该去调用deleteExistRenderView + * + * @param renderNode + * @return + */ + protected View createRenderView(ListItemRenderNode renderNode) { + if (renderNode.needDeleteExistRenderView() && !renderNode.shouldSticky()) { + deleteExistRenderView(renderNode); } - } - } - } - - /** - * 设置View的LayoutParams排版属性,宽高由render节点提供 - */ - protected void setLayoutParams(View itemView, int position) { - LayoutParams childLp = getLayoutParams(itemView); - ListItemRenderNode childNode = getChildNodeByAdapterPosition(position); - childLp.height = getRenderNodeHeight(position); - childLp.width = childNode.getWidth(); - itemView.setLayoutParams(childLp); - } - - - protected LayoutParams getLayoutParams(View itemView) { - ViewGroup.LayoutParams params = itemView.getLayoutParams(); - LayoutParams childLp = null; - if (params instanceof LayoutParams) { - childLp = (LayoutParams) params; - } - if (childLp == null) { - childLp = new LayoutParams(MATCH_PARENT, 0); - } - return childLp; - } - - @Override - public int getItemViewType(int position) { - //在调用onCreateViewHolder之前,必然会调用getItemViewType,所以这里把position记下来 - //用在onCreateViewHolder的时候来创建View,不然onCreateViewHolder是无法创建RenderNode到View的 - setPositionToCreate(position); - return getChildNodeByAdapterPosition(position).getItemViewType(); - } - - protected void setPositionToCreate(int position) { - positionToCreateHolder = position; - } - - /** - * 获取子节点,理论上面是不会返回空的,否则就是某个流程出了问题 - * - * @param position adapter实际的item位置 - */ - public ListItemRenderNode getChildNodeByAdapterPosition(int position) { - return getChildNode(hippyRecyclerView.getNodePositionHelper().getRenderNodePosition(position)); - } - - /** - * 获取前端的renderNode的子节点 - * - * @param position 前端的子节点的位置 - */ - public ListItemRenderNode getChildNode(int position) { - RenderNode parentNode = getParentNode(); - if (parentNode != null && position < parentNode.getChildCount() && position >= 0) { - return (ListItemRenderNode) parentNode.getChildAt(position); - } - return null; - } - - /** - * listItemView的数量 - */ - @Override - public int getItemCount() { - return getRenderNodeCount(); - } - - /** - * 返回前端的list的内容Item数目 - * - * @return - */ - public int getRenderNodeCount() { - RenderNode listNode = getParentNode(); - if (listNode != null) { - return listNode.getChildCount(); - } - return 0; - } - - /** - * 前端展示的内容的高度 - * - * @return - */ - public int getRenderNodeTotalHeight() { - int renderCount = getRenderNodeCount(); - int renderNodeTotalHeight = 0; - for (int i = 0; i < renderCount; i++) { - renderNodeTotalHeight += getRenderNodeHeight(i); - } - return renderNodeTotalHeight; - } - - public int getItemHeight(int position) { - return getRenderNodeHeight(position); - } - - public int getRenderNodeHeight(int position) { - ListItemRenderNode childNode = getChildNode(position); - if (childNode != null) { - if (childNode.isPullHeader()) { - if (headerEventHelper != null) { - return headerEventHelper.getVisibleHeight(); + renderNode.setLazy(false); + View view = renderNode.createViewRecursive(); + renderNode.updateViewRecursive(); + return view; + } + + public void deleteExistRenderView(ListItemRenderNode renderNode) { + renderNode.setLazy(true); + RenderNode parentNode = getParentNode(); + if (parentNode != null) { + hpContext.getRenderManager().getControllerManager() + .deleteChild(parentNode.getId(), renderNode.getId()); } + renderNode.setRecycleItemTypeChangeListener(null); + } + + private FrameLayout getStickyContainer(ViewGroup parent, View renderView) { + FrameLayout container = new FrameLayout(parent.getContext()); + if (renderView != null) { + container.addView(renderView, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } + return container; + } + + private void initPullHeadEventHelper(PullHeaderRenderNode renderNode, View renderView) { + if (headerEventHelper == null) { + headerEventHelper = new PullHeaderEventHelper(hippyRecyclerView, renderNode); + } + headerEventHelper.setRenderNodeView(renderView); + } + + @Override + public String toString() { + return "HippyRecyclerAdapter: itemCount:" + getItemCount(); + } + + /** + * 绑定数据 对于全新的viewHolder,isCreated 为true,调用updateViewRecursive进行物理树的创建,以及数据的绑定 + * 对于非全新创建的viewHolder,进行view树的diff,然后在把数据绑定到view树上面 + * + * @param hippyRecyclerViewHolder position当前的viewHolder + * @param position 绑定数据的节点位置 + */ + @Override + public void onBindViewHolder(HippyRecyclerViewHolder hippyRecyclerViewHolder, int position) { + setLayoutParams(hippyRecyclerViewHolder.itemView, position); + RenderNode oldNode = hippyRecyclerViewHolder.bindNode; + ListItemRenderNode newNode = getChildNodeByAdapterPosition(position); + oldNode.setLazy(true); + newNode.setLazy(false); + if (oldNode != newNode) { + //step 1: diff + ArrayList patchTypes = DiffUtils.diff(oldNode, newNode); + //step:2 delete unUseful views + DiffUtils.deleteViews(hpContext.getRenderManager().getControllerManager(), patchTypes); + //step:3 replace id + DiffUtils.replaceIds(hpContext.getRenderManager().getControllerManager(), patchTypes); + //step:4 create view is do not reUse + DiffUtils.createView(patchTypes); + //step:5 patch the dif result + DiffUtils.doPatch(hpContext.getRenderManager().getControllerManager(), patchTypes); + } + newNode.setRecycleItemTypeChangeListener(this); + hippyRecyclerViewHolder.bindNode = newNode; + enablePullFooter(position, hippyRecyclerViewHolder.itemView); + } + + /** + * 检测最后一个item是否是footer,如果是,需要对这个itemView设置监控,footer显示就通知前端加载下一页 + */ + private void enablePullFooter(int position, View itemView) { + if (position == getItemCount() - 1) { + ListItemRenderNode renderNode = getChildNodeByAdapterPosition(position); + if (renderNode.isPullFooter()) { + if (footerEventHelper == null) { + footerEventHelper = new PullFooterEventHelper(hippyRecyclerView); + } + footerEventHelper.enableFooter(itemView); + } else { + if (footerEventHelper != null) { + footerEventHelper.disableFooter(); + } + } + } + } + + /** + * 设置View的LayoutParams排版属性,宽高由render节点提供 + * 对于LinearLayout的排版,竖向排版,宽度强行顶满,横向排版,高度强行顶满 + */ + protected void setLayoutParams(View itemView, int position) { + LayoutParams childLp = getLayoutParams(itemView); + RenderNode childNode = getChildNodeByAdapterPosition(position); + if (HippyListUtils.isLinearLayout(hippyRecyclerView)) { + boolean isVertical = HippyListUtils.isVerticalLayout(hippyRecyclerView); + childLp.height = isVertical ? childNode.getHeight() : MATCH_PARENT; + childLp.width = isVertical ? MATCH_PARENT : childNode.getWidth(); + } else { + childLp.height = childNode.getHeight(); + childLp.width = childNode.getWidth(); + } + itemView.setLayoutParams(childLp); + } + + + protected LayoutParams getLayoutParams(View itemView) { + ViewGroup.LayoutParams params = itemView.getLayoutParams(); + LayoutParams childLp = null; + if (params instanceof LayoutParams) { + childLp = (LayoutParams) params; + } + if (childLp == null) { + childLp = new LayoutParams(MATCH_PARENT, 0); + } + return childLp; + } + + @Override + public int getItemViewType(int position) { + //在调用onCreateViewHolder之前,必然会调用getItemViewType,所以这里把position记下来 + //用在onCreateViewHolder的时候来创建View,不然onCreateViewHolder是无法创建RenderNode到View的 + setPositionToCreate(position); + return getChildNodeByAdapterPosition(position).getItemViewType(); + } + + protected void setPositionToCreate(int position) { + positionToCreateHolder = position; + } + + /** + * 获取子节点,理论上面是不会返回空的,否则就是某个流程出了问题 + * + * @param position adapter实际的item位置 + */ + public ListItemRenderNode getChildNodeByAdapterPosition(int position) { + return getChildNode(hippyRecyclerView.getNodePositionHelper().getRenderNodePosition(position)); + } + + /** + * 获取前端的renderNode的子节点 + * + * @param position 前端的子节点的位置 + */ + public ListItemRenderNode getChildNode(int position) { + RenderNode parentNode = getParentNode(); + if (parentNode != null && position < parentNode.getChildCount() && position >= 0) { + return (ListItemRenderNode) parentNode.getChildAt(position); + } + return null; + } + + /** + * listItemView的数量 + */ + @Override + public int getItemCount() { + return getRenderNodeCount(); + } + /** + * 返回前端的list的内容Item数目 + * + * @return + */ + public int getRenderNodeCount() { + RenderNode listNode = getParentNode(); + if (listNode != null) { + return listNode.getChildCount(); + } + return 0; + } + + /** + * 前端展示的内容的高度 + * + * @return + */ + public int getRenderNodeTotalHeight() { + int renderCount = getRenderNodeCount(); + int renderNodeTotalHeight = 0; + for (int i = 0; i < renderCount; i++) { + renderNodeTotalHeight += getRenderNodeHeight(i); + } + return renderNodeTotalHeight; + } + + public int getItemHeight(int position) { + Integer itemHeight = getRenderNodeHeight(position); + if (itemHeight != null) { + return itemHeight; + } + return 0; + } + + public int getRenderNodeHeight(int position) { + ListItemRenderNode childNode = getChildNode(position); + if (childNode != null) { + if (childNode.isPullHeader()) { + if (headerEventHelper != null) { + return headerEventHelper.getVisibleHeight(); + } + + return 0; + } + return childNode.getHeight(); + } + return 0; + } + + public int getItemWidth(int position) { + Integer renderNodeWidth = getRenderNodeWidth(position); + if (renderNodeWidth != null) { + return renderNodeWidth; + } return 0; - } - return childNode.getHeight(); - } - return 0; - } - - public int getItemWidth(int position) { - Integer renderNodeWidth = getRenderNodeWidth(position); - if (renderNodeWidth != null) { - return renderNodeWidth; - } - return 0; - } - - public int getRenderNodeWidth(int position) { - ListItemRenderNode childNode = getChildNode(position); - if (childNode != null) { - return childNode.getWidth(); - } - return 0; - } - - protected RenderNode getParentNode() { - return hpContext.getRenderManager().getRenderNode(getHippyListViewId()); - } - - private int getHippyListViewId() { - return ((View) hippyRecyclerView.getParent()).getId(); - } - - @Override - public void onRecycleItemTypeChanged(int oldType, int newType, ListItemRenderNode listItemNode) { - hippyItemTypeHelper.updateItemType(oldType, newType, listItemNode); - } - - @Override - public long getItemId(int position) { - return getChildNodeByAdapterPosition(position).getId(); - } - - /** - * 该position对于的renderNode是否是吸顶的属性 - */ - @Override - public boolean isStickyPosition(int position) { - if (position >= 0 && position < getItemCount()) { - return getChildNodeByAdapterPosition(position).shouldSticky(); - } - return false; - } - - /** - * 该position对于的renderNode是否是Header属性,值判断第一个节点 - */ - private boolean isPullHeader(int position) { - if (position == 0) { - return getChildNodeByAdapterPosition(0).isPullHeader(); - } - return false; - } - - /** - * 获取下拉刷新的事件辅助器 - */ - public PullHeaderEventHelper getHeaderEventHelper() { - return headerEventHelper; - } - - public PreloadHelper getPreloadHelper() { - return preloadHelper; - } - - public void setPreloadItemNumber(int preloadItemNumber) { - preloadHelper.setPreloadItemNumber(preloadItemNumber); - } - - @Override - public void getItemLayoutParams(int position, LayoutParams lp) { - if (lp == null) { - return; - } - lp.height = getRenderNodeHeight(position); - } + } + + public int getRenderNodeWidth(int position) { + ListItemRenderNode childNode = getChildNode(position); + if (childNode != null) { + return childNode.getWidth(); + } + return 0; + } + + protected RenderNode getParentNode() { + return hpContext.getRenderManager().getRenderNode(getHippyListViewId()); + } + + private int getHippyListViewId() { + return ((View) hippyRecyclerView.getParent()).getId(); + } + + @Override + public void onRecycleItemTypeChanged(int oldType, int newType, ListItemRenderNode listItemNode) { + hippyItemTypeHelper.updateItemType(oldType, newType, listItemNode); + } + + @Override + public long getItemId(int position) { + return getChildNodeByAdapterPosition(position).getId(); + } + + /** + * 该position对于的renderNode是否是吸顶的属性 + */ + @Override + public boolean isStickyPosition(int position) { + if (position >= 0 && position < getItemCount()) { + return getChildNodeByAdapterPosition(position).shouldSticky(); + } + return false; + } + + /** + * 该position对于的renderNode是否是Header属性,值判断第一个节点 + */ + private boolean isPullHeader(int position) { + if (position == 0) { + return getChildNodeByAdapterPosition(0).isPullHeader(); + } + return false; + } + + /** + * 获取下拉刷新的事件辅助器 + */ + public PullHeaderEventHelper getHeaderEventHelper() { + return headerEventHelper; + } + + public PreloadHelper getPreloadHelper() { + return preloadHelper; + } + + public void setPreloadItemNumber(int preloadItemNumber) { + preloadHelper.setPreloadItemNumber(preloadItemNumber); + } + + @Override + public void getItemLayoutParams(int position, LayoutParams lp) { + if (lp == null) { + return; + } + lp.height = getItemHeight(position); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java index 8ad707ffd50..1d3e9b353de 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerView.java @@ -34,342 +34,376 @@ import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IHeaderHost; import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.StickyHeaderHelper; +/** + * Created on 2020/12/22. Description + */ public class HippyRecyclerView extends HippyRecyclerViewBase - implements IHeaderAttachListener, IHippyViewAboundListener { - - protected HippyEngineContext hippyEngineContext; - protected ADP listAdapter; - protected boolean isEnableScroll = true; //使能ListView的滚动功能 - protected StickyHeaderHelper stickyHeaderHelper; //支持吸顶 - protected IHeaderHost headerHost; //用于pullHeader下拉刷新 - protected LayoutManager layoutManager; - protected RecyclerViewEventHelper recyclerViewEventHelper;//事件集合 - private NodePositionHelper nodePositionHelper; - protected int renderNodeCount = 0; - - public HippyRecyclerView(Context context) { - super(context); - } - - public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public ADP getAdapter() { - return listAdapter; - } - - @Override - public void setAdapter(@Nullable Adapter adapter) { - listAdapter = (ADP) adapter; - super.setAdapter(adapter); - } - - public NodePositionHelper getNodePositionHelper() { - if (nodePositionHelper == null) { - nodePositionHelper = new NodePositionHelper(); - } - return nodePositionHelper; - } - - public void setOrientation(LinearLayoutManager layoutManager) { - this.layoutManager = layoutManager; - } - - public void setHeaderHost(IHeaderHost headerHost) { - this.headerHost = headerHost; - } - - public void setHippyEngineContext(HippyEngineContext hippyEngineContext) { - this.hippyEngineContext = hippyEngineContext; - } - - public void initRecyclerView() { - setAdapter(new HippyRecyclerListAdapter(this, this.hippyEngineContext)); - intEventHelper(); - } - - - @Override - public boolean onTouchEvent(MotionEvent e) { - if (!isEnableScroll) { - return false; - } - return super.onTouchEvent(e); - } - - /** - * 刷新数据 - */ - public void setListData() { - LogUtils.d("HippyRecyclerView", "itemCount =" + listAdapter.getItemCount()); - listAdapter.notifyDataSetChanged(); - //notifyDataSetChanged 本身是可以触发requestLayout的,但是Hippy框架下 HippyRootView 已经把 - //onLayout方法重载写成空方法,requestLayout不会回调孩子节点的onLayout,这里需要自己发起dispatchLayout - renderNodeCount = getAdapter().getRenderNodeCount(); - dispatchLayout(); - } - - /** - * 内容偏移,返回recyclerView顶部被滑出去的内容 1、找到顶部第一个View前面的逻辑内容高度 2、加上第一个View被遮住的区域 - */ - public int getContentOffsetY() { - return computeVerticalScrollOffset(); - } - - /** - * 内容偏移,返回recyclerView被滑出去的内容 1、找到顶部第一个View前面的逻辑内容宽度 2、加上第一个View被遮住的区域 - */ - public int getContentOffsetX() { - int firstChildPosition = getFirstChildPosition(); - int totalWidthBeforePosition = getTotalWithBefore(firstChildPosition); - int firstChildOffset = - listAdapter.getItemWidth(firstChildPosition) - getVisibleWidth(getChildAt(0)); - return totalWidthBeforePosition + firstChildOffset; - } - - /** - * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 - */ - protected int getVisibleHeight(View firstChildView) { - return getViewVisibleRect(firstChildView).height(); - } - - /** - * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 - */ - protected int getVisibleWidth(View firstChildView) { - return getViewVisibleRect(firstChildView).width(); - } - - /** - * 获取view在父亲中的可视区域 - */ - private Rect getViewVisibleRect(View view) { - Rect rect = new Rect(); - if (view != null) { - view.getLocalVisibleRect(rect); - } - return rect; - } - - /** - * 获取position 前面的内容高度,不包含position自身的高度 - */ - public int getTotalHeightBefore(int position) { - int totalHeightBefore = 0; - for (int i = 0; i < position; i++) { - totalHeightBefore += listAdapter.getItemHeight(i); - } - return totalHeightBefore; - } - - /** - * 获取renderNodePosition前面的内容高度,不包含renderNodePosition自身的高度 - */ - public int getRenderNodeHeightBefore(int renderNodePosition) { - int renderNodeTotalHeight = 0; - for (int i = 0; i < renderNodePosition; i++) { - renderNodeTotalHeight += listAdapter.getRenderNodeHeight(i); - } - return renderNodeTotalHeight; - } - - - /** - * 获取position 前面的内容高度,不包含position自身的高度 - */ - public int getTotalWithBefore(int position) { - int totalWidthBefore = 0; - for (int i = 0; i < position; i++) { - totalWidthBefore += listAdapter.getItemWidth(i); - } - return totalWidthBefore; - } - - public RecyclerViewEventHelper getRecyclerViewEventHelper() { - return intEventHelper(); - } - - private RecyclerViewEventHelper intEventHelper() { - if (recyclerViewEventHelper == null) { - recyclerViewEventHelper = createEventHelper(); - } - return recyclerViewEventHelper; - } - - protected RecyclerViewEventHelper createEventHelper() { - return new RecyclerViewEventHelper(this); - } - - /** - * 设置recyclerView可以滚动 - */ - public void setScrollEnable(boolean enable) { - isEnableScroll = enable; - } - - public int getNodePositionInAdapter(int position) { - return position; - } - - public void scrollToIndex(int xIndex, int yPosition, boolean animated, int duration) { - int positionInAdapter = getNodePositionInAdapter(yPosition); - if (animated) { - doSmoothScrollY(duration, getTotalHeightBefore(positionInAdapter) - getContentOffsetY()); - postDispatchLayout(); - } else { - scrollToPositionWithOffset(positionInAdapter, 0); - //不能调用postDispatchLayout,需要立即调研dispatchLayout,否则滚动位置不对 - dispatchLayout(); - } - } - - /** - * @param xOffset 暂不支持 - * @param yOffset yOffset>0 内容向上移动,yOffset<0, 内容向下移动 - * @param animated 是否有动画 - * @param duration 动画的时间 - */ - public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, - int duration) { - if (!canScrollToContentOffset()) { - return; - } - int yOffsetInPixel = (int) PixelUtil.dp2px(yOffset); - int deltaY = yOffsetInPixel - getContentOffsetY(); - //增加异常保护 - if (animated) { - doSmoothScrollY(duration, deltaY); - } else { - scrollBy(0, deltaY); - } - } - - /** - * renderNodeCount是在setListData的时候更新,必须调用setListData后,确保renderNodeCount和 - * getAdapter().getRenderNodeCount()的值相等,才能进行滚动,否则scrollBy会出现IndexOutOfBoundsException - * 的问题,主要原因就是RecyclerView的内部状态没有通过setListData进行刷新,还是老的数据 - * RenderManager的batch方法应该把dispatchUIFunction放到batchComplete后面,但是这样改动太大 - * - * @return - */ - private boolean canScrollToContentOffset() { - return renderNodeCount == getAdapter().getRenderNodeCount(); - } - - private void doSmoothScrollY(int duration, int scrollToYPos) { - if (duration != 0) { - if (scrollToYPos != 0 && !didStructureChange()) { - smoothScrollBy(0, scrollToYPos, null, duration); - postDispatchLayout(); - } - } else { - smoothScrollBy(0, scrollToYPos); - postDispatchLayout(); + implements IHeaderAttachListener, IHippyViewAboundListener { + + protected HippyEngineContext hippyEngineContext; + protected ADP listAdapter; + protected boolean isEnableScroll = true; //使能ListView的滚动功能 + protected StickyHeaderHelper stickyHeaderHelper; //支持吸顶 + protected IHeaderHost headerHost; //用于pullHeader下拉刷新 + protected LayoutManager layoutManager; + protected RecyclerViewEventHelper recyclerViewEventHelper;//事件集合 + protected int renderNodeCount = 0; + private NodePositionHelper nodePositionHelper; + private ViewStickEventHelper viewStickEventHelper; + private boolean stickEventEnable; + + public HippyRecyclerView(Context context) { + super(context); + } + + public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public HippyRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public ADP getAdapter() { + return listAdapter; + } + + @Override + public void setAdapter(@Nullable Adapter adapter) { + listAdapter = (ADP) adapter; + super.setAdapter(adapter); + } + + public NodePositionHelper getNodePositionHelper() { + if (nodePositionHelper == null) { + nodePositionHelper = new NodePositionHelper(); + } + return nodePositionHelper; + } + + public void setOrientation(LinearLayoutManager layoutManager) { + this.layoutManager = layoutManager; } - } - private void postDispatchLayout() { - post(new Runnable() { - @Override - public void run() { + public void setHeaderHost(IHeaderHost headerHost) { + this.headerHost = headerHost; + } + + public void setHippyEngineContext(HippyEngineContext hippyEngineContext) { + this.hippyEngineContext = hippyEngineContext; + } + + public void initRecyclerView() { + setAdapter(new HippyRecyclerListAdapter(this, this.hippyEngineContext)); + intEventHelper(); + } + + + @Override + public boolean onTouchEvent(MotionEvent e) { + if (!isEnableScroll) { + return false; + } + return super.onTouchEvent(e); + } + + /** + * 刷新数据 + */ + public void setListData() { + LogUtils.d("HippyRecyclerView", "itemCount =" + listAdapter.getItemCount()); + listAdapter.notifyDataSetChanged(); + //notifyDataSetChanged 本身是可以触发requestLayout的,但是Hippy框架下 HippyRootView 已经把 + //onLayout方法重载写成空方法,requestLayout不会回调孩子节点的onLayout,这里需要自己发起dispatchLayout + renderNodeCount = getAdapter().getRenderNodeCount(); dispatchLayout(); - } - }); - } - - public void scrollToTop() { - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager.canScrollHorizontally()) { - smoothScrollBy(-getContentOffsetX(), 0); - } else { - smoothScrollBy(0, -getContentOffsetY()); - } - postDispatchLayout(); - } - - /** - * @param enable true :支持Item 上滑吸顶功能 - */ - public void setRowShouldSticky(boolean enable) { - if (enable) { - if (stickyHeaderHelper == null) { - stickyHeaderHelper = new StickyHeaderHelper(this, listAdapter, this, headerHost); - addOnScrollListener(stickyHeaderHelper); - } - } else { - if (stickyHeaderHelper != null) { - removeOnScrollListener(stickyHeaderHelper); - } - } - } - - /** - * 同步删除RenderNode对应注册的View,deleteChild是递归删除RenderNode创建的所有的view - */ - @Override - public void onViewAbound(HippyRecyclerViewHolder viewHolder) { - if (viewHolder.bindNode != null && !viewHolder.bindNode.isDelete()) { - getAdapter().deleteExistRenderView(viewHolder.bindNode); - } - } - - /** - * 当header被摘下来,需要对header进行还原或者回收对处理 遍历所有都ViewHolder,看看有没有收纳这个headerView都ViewHolder - * 如果没有,需要把aboundHeader进行回收,并同步删除render节点对应都view - * - * @param aboundHeader HeaderView对应的Holder - * @param currentHeaderView headerView的实体内容 - */ - @Override - public void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView) { - boolean findHostViewHolder = false; - for (int i = 0; i < getChildCountWithCaches(); i++) { - ViewHolder viewHolder = getChildViewHolder(getChildAtWithCaches(i)); - if (isTheSameRenderNode((HippyRecyclerViewHolder) aboundHeader, - (HippyRecyclerViewHolder) viewHolder)) { - findHostViewHolder = true; - fillContentView(currentHeaderView, viewHolder); - break; - } - } - //当header无处安放,抛弃view都同时,需要同步给Hippy进行View都删除,不然后续无法创建对应都View - if (!findHostViewHolder) { - onViewAbound((HippyRecyclerViewHolder) aboundHeader); - } - } - - private boolean fillContentView(View currentHeaderView, ViewHolder viewHolder) { - if (viewHolder != null && viewHolder.itemView instanceof ViewGroup) { - ViewGroup itemView = (ViewGroup) viewHolder.itemView; - if (itemView.getChildCount() <= 0) { - itemView.addView(currentHeaderView); - } - } - return false; - } - - private boolean isTheSameRenderNode(HippyRecyclerViewHolder aboundHeader, - HippyRecyclerViewHolder viewHolder) { - if (viewHolder.bindNode != null && aboundHeader.bindNode != null) { - return viewHolder.bindNode.getId() == aboundHeader.bindNode.getId(); - } - return false; - } - - public void setNodePositionHelper(NodePositionHelper nodePositionHelper) { - this.nodePositionHelper = nodePositionHelper; - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + "{renderNodeCount:" + renderNodeCount + ",state:" - + getStateInfo() - + "}"; - } + } + + /** + * 内容偏移,返回recyclerView顶部被滑出去的内容 1、找到顶部第一个View前面的逻辑内容高度 2、加上第一个View被遮住的区域 + */ + public int getContentOffsetY() { + return computeVerticalScrollOffset(); + } + + /** + * 内容偏移,返回recyclerView被滑出去的内容 1、找到顶部第一个View前面的逻辑内容宽度 2、加上第一个View被遮住的区域 + */ + public int getContentOffsetX() { + int firstChildPosition = getFirstChildPosition(); + int totalWidthBeforePosition = getTotalWithBefore(firstChildPosition); + int firstChildOffset = listAdapter.getItemWidth(firstChildPosition) - getVisibleWidth(getChildAt(0)); + return totalWidthBeforePosition + firstChildOffset; + } + + /** + * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 + */ + protected int getVisibleHeight(View firstChildView) { + return getViewVisibleRect(firstChildView).height(); + } + + /** + * 获取一个View的可视高度,并非view本身的height,有可能部分是被滑出到屏幕外部 + */ + protected int getVisibleWidth(View firstChildView) { + return getViewVisibleRect(firstChildView).width(); + } + + /** + * 获取view在父亲中的可视区域 + */ + private Rect getViewVisibleRect(View view) { + Rect rect = new Rect(); + if (view != null) { + view.getLocalVisibleRect(rect); + } + return rect; + } + + /** + * 获取position 前面的内容高度,不包含position自身的高度 + * 对于竖向排版,取ItemHeight求和,对于横向排版,取ItemWidth求和 + */ + public int getTotalHeightBefore(int position) { + int totalHeightBefore = 0; + boolean vertical = HippyListUtils.isVerticalLayout(this); + for (int i = 0; i < position; i++) { + totalHeightBefore += vertical ? listAdapter.getItemHeight(i) : listAdapter.getItemWidth(i); + } + return totalHeightBefore; + } + + /** + * 获取renderNodePosition前面的内容高度,不包含renderNodePosition自身的高度 + * 对于竖向排版,取RenderNodeHeight求和,对于横向排版,取RenderNodeWidth求和 + */ + public int getRenderNodeHeightBefore(int renderNodePosition) { + int renderNodeTotalHeight = 0; + boolean vertical = HippyListUtils.isVerticalLayout(this); + for (int i = 0; i < renderNodePosition; i++) { + renderNodeTotalHeight += vertical ? listAdapter.getRenderNodeHeight(i) : listAdapter.getRenderNodeWidth(i); + } + return renderNodeTotalHeight; + } + + + /** + * 获取position 前面的内容高度,不包含position自身的高度 + */ + public int getTotalWithBefore(int position) { + int totalWidthBefore = 0; + for (int i = 0; i < position; i++) { + totalWidthBefore += listAdapter.getItemWidth(i); + } + return totalWidthBefore; + } + + public RecyclerViewEventHelper getRecyclerViewEventHelper() { + return intEventHelper(); + } + + private RecyclerViewEventHelper intEventHelper() { + if (recyclerViewEventHelper == null) { + recyclerViewEventHelper = createEventHelper(); + } + return recyclerViewEventHelper; + } + + protected RecyclerViewEventHelper createEventHelper() { + return new RecyclerViewEventHelper(this); + } + + /** + * 设置recyclerView可以滚动 + */ + public void setScrollEnable(boolean enable) { + isEnableScroll = enable; + } + + public int getNodePositionInAdapter(int position) { + return position; + } + + public void scrollToIndex(int xIndex, int yPosition, boolean animated, int duration) { + int positionInAdapter = getNodePositionInAdapter(yPosition); + if (animated) { + doSmoothScrollY(duration, getTotalHeightBefore(positionInAdapter) - getContentOffsetY()); + postDispatchLayout(); + } else { + scrollToPositionWithOffset(positionInAdapter, 0); + //不能调用postDispatchLayout,需要立即调研dispatchLayout,否则滚动位置不对 + dispatchLayout(); + } + } + + /** + * @param xOffset 暂不支持 + * @param yOffset yOffset>0 内容向上移动,yOffset<0, 内容向下移动 + * @param animated 是否有动画 + * @param duration 动画的时间 + */ + public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, int duration) { + if (!canScrollToContentOffset()) { + return; + } + int yOffsetInPixel = (int) PixelUtil.dp2px(yOffset); + int deltaY = yOffsetInPixel - getContentOffsetY(); + //增加异常保护 + if (animated) { + doSmoothScrollY(duration, deltaY); + } else { + scrollBy(0, deltaY); + } + } + + /** + * renderNodeCount是在setListData的时候更新,必须调用setListData后,确保renderNodeCount和 + * getAdapter().getRenderNodeCount()的值相等,才能进行滚动,否则scrollBy会出现IndexOutOfBoundsException + * 的问题,主要原因就是RecyclerView的内部状态没有通过setListData进行刷新,还是老的数据, + * 这个比现的场景来自于QQ浏览器的搜索tab的切换。 + * RenderManager的batch方法应该把dispatchUIFunction放到batchComplete后面,但是这样改动太大 + * + * @return + */ + private boolean canScrollToContentOffset() { + return renderNodeCount == getAdapter().getRenderNodeCount(); + } + + private void doSmoothScrollY(int duration, int scrollToYPos) { + if (duration != 0) { + if (scrollToYPos != 0 && !didStructureChange()) { + smoothScrollBy(0, scrollToYPos, duration); + postDispatchLayout(); + } + } else { + smoothScrollBy(0, scrollToYPos); + postDispatchLayout(); + } + } + + private void postDispatchLayout() { + post(new Runnable() { + @Override + public void run() { + dispatchLayout(); + } + }); + } + + public void scrollToTop() { + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager.canScrollHorizontally()) { + smoothScrollBy(-getContentOffsetX(), 0); + } else { + smoothScrollBy(0, -getContentOffsetY()); + } + postDispatchLayout(); + } + + /** + * @param enable true :支持Item 上滑吸顶功能 + */ + public void setRowShouldSticky(boolean enable) { + if (enable) { + if (stickyHeaderHelper == null) { + stickyHeaderHelper = new StickyHeaderHelper(this, listAdapter, this, headerHost); + addOnScrollListener(stickyHeaderHelper); + } + } else { + if (stickyHeaderHelper != null) { + removeOnScrollListener(stickyHeaderHelper); + } + } + } + + /** + * 同步删除RenderNode对应注册的View,deleteChild是递归删除RenderNode创建的所有的view + */ + @Override + public void onViewAbound(HippyRecyclerViewHolder viewHolder) { + if (viewHolder.bindNode != null && !viewHolder.bindNode.isDelete()) { + getAdapter().deleteExistRenderView(viewHolder.bindNode); + } + } + + /** + * 当header被摘下来,需要对header进行还原或者回收对处理 + * 遍历所有都ViewHolder,看看有没有收纳这个headerView都ViewHolder + * 如果没有,需要把aboundHeader进行回收,并同步删除render节点对应都view + * + * @param aboundHeader HeaderView对应的Holder + * @param currentHeaderView headerView的实体内容 + */ + @Override + public void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView) { + boolean findHostViewHolder = false; + for (int i = 0; i < getChildCountWithCaches(); i++) { + ViewHolder viewHolder = getChildViewHolder(getChildAtWithCaches(i)); + if (isTheSameRenderNode((HippyRecyclerViewHolder) aboundHeader, (HippyRecyclerViewHolder) viewHolder)) { + findHostViewHolder = true; + fillContentView(currentHeaderView, viewHolder); + break; + } + } + //当header无处安放,抛弃view都同时,需要同步给Hippy进行View都删除,不然后续无法创建对应都View + if (!findHostViewHolder) { + onViewAbound((HippyRecyclerViewHolder) aboundHeader); + } + } + + private boolean fillContentView(View currentHeaderView, ViewHolder viewHolder) { + if (viewHolder != null && viewHolder.itemView instanceof ViewGroup) { + ViewGroup itemView = (ViewGroup) viewHolder.itemView; + if (itemView.getChildCount() <= 0) { + itemView.addView(currentHeaderView); + } + } + return false; + } + + private boolean isTheSameRenderNode(HippyRecyclerViewHolder aboundHeader, HippyRecyclerViewHolder viewHolder) { + if (viewHolder.bindNode != null && aboundHeader.bindNode != null) { + return viewHolder.bindNode.getId() == aboundHeader.bindNode.getId(); + } + return false; + } + + public void setNodePositionHelper(NodePositionHelper nodePositionHelper) { + this.nodePositionHelper = nodePositionHelper; + } + + public void enableStickEvent(boolean enable) { + stickEventEnable = enable; + } + + public void onAfterUpdateProps() { + if (stickEventEnable) { + ensureViewStickEventHelper(); + } else { + destoryViewStickEventHelper(); + } + } + + private void ensureViewStickEventHelper() { + if (viewStickEventHelper == null) { + viewStickEventHelper = new ViewStickEventHelper((View) this.getParent()); + } + if (stickyHeaderHelper != null) { + stickyHeaderHelper.setStickViewListener(viewStickEventHelper); + } + } + + private void destoryViewStickEventHelper() { + if (stickyHeaderHelper != null) { + stickyHeaderHelper.setStickViewListener(null); + } + viewStickEventHelper = null; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{renderNodeCount:" + renderNodeCount + ",state:" + getStateInfo() + + "}"; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java index e40d3af0d7a..edf615c8f6a 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewController.java @@ -17,9 +17,11 @@ package com.tencent.mtt.hippy.views.hippylist; import android.content.Context; +import android.view.ViewGroup; import androidx.recyclerview.widget.EasyLinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import android.view.View; +import androidx.recyclerview.widget.RecyclerView; import com.tencent.mtt.hippy.HippyInstanceContext; import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.annotation.HippyController; @@ -31,157 +33,193 @@ import com.tencent.mtt.hippy.uimanager.ListViewRenderNode; import com.tencent.mtt.hippy.uimanager.RenderNode; +/** + * Created on 2020/12/22. + */ + @HippyController(name = HippyRecyclerViewController.CLASS_NAME) -public class HippyRecyclerViewController extends - HippyViewController { - - public static final String CLASS_NAME = "RecyclerView"; - public static final String SCROLL_TO_INDEX = "scrollToIndex"; - public static final String SCROLL_TO_CONTENT_OFFSET = "scrollToContentOffset"; - public static final String SCROLL_TO_TOP = "scrollToTop"; - public static final String COLLAPSE_PULL_HEADER = "collapsePullHeader"; - public static final String EXPAND_PULL_HEADER = "expandPullHeader"; - - public HippyRecyclerViewController() { - - } - - @Override - public int getChildCount(HRW viewGroup) { - return viewGroup.getChildCountWithCaches(); - } - - @Override - public View getChildAt(HRW viewGroup, int index) { - return viewGroup.getChildAtWithCaches(index); - } - - @Override - public void onBatchComplete(HRW view) { - super.onBatchComplete(view); - view.setListData(); - } - - @Override - protected View createViewImpl(Context context) { - return createViewImpl(context, null); - } - - @Override - protected View createViewImpl(Context context, HippyMap iniProps) { - return new HippyRecyclerViewWrapper(context, - initDefault(context, iniProps, new HippyRecyclerView(context))); - } - - public static HippyRecyclerView initDefault(Context context, HippyMap iniProps, - HippyRecyclerView recyclerView) { - LinearLayoutManager layoutManager = new EasyLinearLayoutManager(context); - recyclerView.setItemAnimator(null); - boolean enableScrollEvent = false; - if (iniProps != null) { - if (iniProps.containsKey("horizontal")) { - layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); - } - enableScrollEvent = iniProps.getBoolean("onScroll"); - } - recyclerView.setLayoutManager(layoutManager); - recyclerView.setHippyEngineContext(((HippyInstanceContext) context).getEngineContext()); - recyclerView.initRecyclerView(); - recyclerView.getRecyclerViewEventHelper().setOnScrollEventEnable(enableScrollEvent); - return recyclerView; - } - - @Override - public RenderNode createRenderNode(int id, HippyMap props, String className, - HippyRootView hippyRootView, - ControllerManager controllerManager, - boolean lazy) { - return new ListViewRenderNode(id, props, className, hippyRootView, controllerManager, lazy); - } - - @HippyControllerProps(name = "rowShouldSticky") - public void setRowShouldSticky(HRW view, boolean enable) { - view.setRowShouldSticky(enable); - } - - @HippyControllerProps(name = "onScrollBeginDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setScrollBeginDragEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setScrollBeginDragEventEnable(flag); - } - - @HippyControllerProps(name = "onScrollEndDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setScrollEndDragEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setScrollEndDragEventEnable(flag); - } - - @HippyControllerProps(name = "onMomentumScrollBegin", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setMomentumScrollBeginEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setMomentumScrollBeginEventEnable(flag); - } - - @HippyControllerProps(name = "onMomentumScrollEnd", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setMomentumScrollEndEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setMomentumScrollEndEventEnable(flag); - } - - @HippyControllerProps(name = "exposureEventEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) - public void setExposureEventEnable(HRW view, boolean flag) { - view.getRecyclerViewEventHelper().setExposureEventEnable(flag); - } - - @HippyControllerProps(name = "scrollEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) - public void setScrollEnable(HRW view, boolean flag) { - view.setScrollEnable(flag); - } - - @HippyControllerProps(name = "scrollEventThrottle", defaultType = HippyControllerProps.NUMBER, defaultNumber = 30.0D) - public void setscrollEventThrottle(HRW view, int scrollEventThrottle) { - view.getRecyclerViewEventHelper().setScrollEventThrottle(scrollEventThrottle); - } - - @HippyControllerProps(name = "preloadItemNumber") - public void setPreloadItemNumber(HRW view, int preloadItemNumber) { - getAdapter(view).setPreloadItemNumber(preloadItemNumber); - } - - @Override - public void dispatchFunction(HRW view, String functionName, HippyArray dataArray) { - super.dispatchFunction(view, functionName, dataArray); - switch (functionName) { - case SCROLL_TO_INDEX: { - // list滑动到某个item - int xIndex = dataArray.getInt(0); - int yIndex = dataArray.getInt(1); - boolean animated = dataArray.getBoolean(2); - int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 - view.scrollToIndex(xIndex, yIndex, animated, duration); - break; - } - case SCROLL_TO_CONTENT_OFFSET: { - // list滑动到某个距离 - double xOffset = dataArray.getDouble(0); - double yOffset = dataArray.getDouble(1); - boolean animated = dataArray.getBoolean(2); - int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 - view.scrollToContentOffset(xOffset, yOffset, animated, duration); - break; - } - case SCROLL_TO_TOP: { - view.scrollToTop(); - break; - } - case COLLAPSE_PULL_HEADER: { - getAdapter(view).getHeaderEventHelper().onHeaderRefreshFinish(); - break; - } - case EXPAND_PULL_HEADER: { - getAdapter(view).getHeaderEventHelper().onHeaderRefresh(); - break; - } - } - } - - private HippyRecyclerListAdapter getAdapter(HRW view) { - return view.getRecyclerView().getAdapter(); - } +public class HippyRecyclerViewController extends HippyViewController { + + public static final String CLASS_NAME = "RecyclerView"; + public static final String SCROLL_TO_INDEX = "scrollToIndex"; + public static final String SCROLL_TO_CONTENT_OFFSET = "scrollToContentOffset"; + public static final String SCROLL_TO_TOP = "scrollToTop"; + public static final String COLLAPSE_PULL_HEADER = "collapsePullHeader"; + public static final String EXPAND_PULL_HEADER = "expandPullHeader"; + public static final String HORIZONTAL = "horizontal"; + + public HippyRecyclerViewController() { + + } + + @Override + public int getChildCount(HRW viewGroup) { + return viewGroup.getChildCountWithCaches(); + } + + @Override + public View getChildAt(HRW viewGroup, int index) { + return viewGroup.getChildAtWithCaches(index); + } + + /** + * view 被Hippy的RenderNode 删除了,这样会导致View的child完全是空的,这个view是不能再被recyclerView复用了 + * 否则如果被复用,在adapter的onBindViewHolder的时候,view的实际子view和renderNode的数据不匹配,diff会出现异常 + * 导致item白条,显示不出来,所以被删除的view,需要把viewHolder.setIsRecyclable(false),刷新list后,这个view就 + * 不会进入缓存。 + */ + @Override + protected void deleteChild(ViewGroup parentView, View childView) { + super.deleteChild(parentView, childView); + ((HRW) parentView).getRecyclerView().disableRecycle(childView); + } + + @Override + public void onBatchStart(HRW view) { + super.onBatchStart(view); + view.onBatchStart(); + } + + @Override + public void onBatchComplete(HRW view) { + super.onBatchComplete(view); + view.onBatchComplete(); + view.setListData(); + } + + @Override + protected View createViewImpl(Context context) { + return createViewImpl(context, null); + } + + @Override + protected View createViewImpl(Context context, HippyMap iniProps) { + return new HippyRecyclerViewWrapper(context, initDefault(context, iniProps, new HippyRecyclerView(context))); + } + + public static HippyRecyclerView initDefault(Context context, HippyMap iniProps, HippyRecyclerView recyclerView) { + LinearLayoutManager layoutManager = new EasyLinearLayoutManager(context); + recyclerView.setItemAnimator(null); + boolean enableScrollEvent = false; + if (iniProps != null) { + if (iniProps.containsKey(HORIZONTAL)) { + layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + } + enableScrollEvent = iniProps.getBoolean("onScroll"); + } + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHippyEngineContext(((HippyInstanceContext) context).getEngineContext()); + recyclerView.initRecyclerView(); + recyclerView.getRecyclerViewEventHelper().setOnScrollEventEnable(enableScrollEvent); + return recyclerView; + } + + @Override + public RenderNode createRenderNode(int id, HippyMap props, String className, HippyRootView hippyRootView, + ControllerManager controllerManager, + boolean lazy) { + return new ListViewRenderNode(id, props, className, hippyRootView, controllerManager, lazy); + } + + @HippyControllerProps(name = "rowShouldSticky") + public void setRowShouldSticky(HRW view, boolean enable) { + view.setRowShouldSticky(enable); + } + + @HippyControllerProps(name = "onScrollBeginDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setScrollBeginDragEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setScrollBeginDragEventEnable(flag); + } + + @HippyControllerProps(name = "onScrollEndDrag", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setScrollEndDragEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setScrollEndDragEventEnable(flag); + } + + @HippyControllerProps(name = "onMomentumScrollBegin", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setMomentumScrollBeginEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setMomentumScrollBeginEventEnable(flag); + } + + @HippyControllerProps(name = "onMomentumScrollEnd", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setMomentumScrollEndEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setMomentumScrollEndEventEnable(flag); + } + + @HippyControllerProps(name = "onScrollEnable", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setOnScrollEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setOnScrollEventEnable(flag); + } + + @HippyControllerProps(name = "exposureEventEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = false) + public void setExposureEventEnable(HRW view, boolean flag) { + view.getRecyclerViewEventHelper().setExposureEventEnable(flag); + } + + @HippyControllerProps(name = "scrollEnabled", defaultType = HippyControllerProps.BOOLEAN, defaultBoolean = true) + public void setScrollEnable(HRW view, boolean flag) { + view.setScrollEnable(flag); + } + + @HippyControllerProps(name = "scrollEventThrottle", defaultType = HippyControllerProps.NUMBER, defaultNumber = 30.0D) + public void setscrollEventThrottle(HRW view, int scrollEventThrottle) { + view.getRecyclerViewEventHelper().setScrollEventThrottle(scrollEventThrottle); + } + + @HippyControllerProps(name = "preloadItemNumber") + public void setPreloadItemNumber(HRW view, int preloadItemNumber) { + getAdapter(view).setPreloadItemNumber(preloadItemNumber); + } + + @HippyControllerProps(name = "suspendViewListener", defaultType = HippyControllerProps.NUMBER, defaultNumber = 0) + public void setSuspendViewListener(final HRW viewWrapper, int open) { + viewWrapper.getRecyclerView().enableStickEvent(open == 1); + } + + @Override + public void onAfterUpdateProps(HRW viewWrapper) { + super.onAfterUpdateProps(viewWrapper); + viewWrapper.getRecyclerView().onAfterUpdateProps(); + } + + @Override + public void dispatchFunction(HRW view, String functionName, HippyArray dataArray) { + super.dispatchFunction(view, functionName, dataArray); + switch (functionName) { + case SCROLL_TO_INDEX: { + // list滑动到某个item + int xIndex = dataArray.getInt(0); + int yIndex = dataArray.getInt(1); + boolean animated = dataArray.getBoolean(2); + int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 + view.scrollToIndex(xIndex, yIndex, animated, duration); + break; + } + case SCROLL_TO_CONTENT_OFFSET: { + // list滑动到某个距离 + double xOffset = dataArray.getDouble(0); + double yOffset = dataArray.getDouble(1); + boolean animated = dataArray.getBoolean(2); + int duration = dataArray.getInt(3); //1.2.7 增加滚动时间 ms,animated==true时生效 + view.scrollToContentOffset(xOffset, yOffset, animated, duration); + break; + } + case SCROLL_TO_TOP: { + view.scrollToTop(); + break; + } + case COLLAPSE_PULL_HEADER: { + getAdapter(view).getHeaderEventHelper().onHeaderRefreshFinish(); + break; + } + case EXPAND_PULL_HEADER: { + getAdapter(view).getHeaderEventHelper().onHeaderRefresh(); + break; + } + } + } + + private HippyRecyclerListAdapter getAdapter(HRW view) { + return view.getRecyclerView().getAdapter(); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java index 3334e5f29b9..903f818698d 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewHolder.java @@ -21,19 +21,22 @@ import android.view.View; import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +/** + * Created on 2020/12/22. Description + */ public class HippyRecyclerViewHolder extends ViewHolder { - public ListItemRenderNode bindNode; + public ListItemRenderNode bindNode; - public HippyRecyclerViewHolder(@NonNull View itemView, ListItemRenderNode renderNode) { - super(itemView); - bindNode = renderNode; - } + public HippyRecyclerViewHolder(@NonNull View itemView, ListItemRenderNode renderNode) { + super(itemView); + bindNode = renderNode; + } - public boolean isRenderDeleted() { - if (bindNode != null) { - return bindNode.isDelete(); + public boolean isRenderDeleted() { + if (bindNode != null) { + return bindNode.isDelete(); + } + return false; } - return false; - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java index e8daa7d6202..c45903cc4a6 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerViewWrapper.java @@ -31,104 +31,117 @@ import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.IHeaderHost; -public class HippyRecyclerViewWrapper extends FrameLayout implements - HippyViewBase, - IHeaderHost { - - protected final HippyEngineContext hpContext; - protected HRCV recyclerView; - private NativeGestureDispatcher nativeGestureDispatcher; - - public HippyRecyclerViewWrapper(@NonNull Context context, HRCV recyclerView) { - super(context); - this.recyclerView = recyclerView; - addView(recyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - hpContext = ((HippyInstanceContext) context).getEngineContext(); - HippyRecyclerExtension cacheExtension = new HippyRecyclerExtension(recyclerView, hpContext, - recyclerView.getNodePositionHelper()); - recyclerView.setViewCacheExtension(cacheExtension); - recyclerView.setHeaderHost(this); - HippyRecyclerPool pool = new HippyRecyclerPool(hpContext, this, cacheExtension, - recyclerView.getNodePositionHelper()); - pool.setViewAboundListener(recyclerView); - recyclerView.setRecycledViewPool(pool); - - } - - @Override - public int computeVerticalScrollOffset() { - return recyclerView.computeVerticalScrollOffset(); - } - - @Override - public NativeGestureDispatcher getGestureDispatcher() { - return nativeGestureDispatcher; - } - - @Override - public void setGestureDispatcher(NativeGestureDispatcher dispatcher) { - nativeGestureDispatcher = dispatcher; - } - - public int getChildCountWithCaches() { - return recyclerView.getChildCountWithCaches(); - } - - public View getChildAtWithCaches(int index) { - return recyclerView.getChildAtWithCaches(index); - } - - public void setListData() { - recyclerView.setListData(); - } - - public RecyclerViewEventHelper getRecyclerViewEventHelper() { - return recyclerView.getRecyclerViewEventHelper(); - } - - public void setScrollEnable(boolean flag) { - recyclerView.setScrollEnable(flag); - } - - public void scrollToIndex(int xIndex, int yIndex, boolean animated, int duration) { - recyclerView.scrollToIndex(xIndex, yIndex, animated, duration); - } - - public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, - int duration) { - recyclerView.scrollToContentOffset(xOffset, yOffset, animated, duration); - } - - public void scrollToTop() { - recyclerView.scrollToTop(); - } - - public void setRowShouldSticky(boolean enable) { - recyclerView.setRowShouldSticky(enable); - } - - public HRCV getRecyclerView() { - return recyclerView; - } - - /** - * 将HeaderView放到RecyclerView到父亲View上面 - */ - @Override - public void attachHeader(View headerView, LayoutParams layoutParams) { - addView(headerView, layoutParams); - layout(getLeft(), getTop(), getRight(), getBottom()); - getViewTreeObserver().dispatchOnGlobalLayout(); - } - - @Override - public void addOnLayoutListener(OnGlobalLayoutListener listener) { - getViewTreeObserver().addOnGlobalLayoutListener(listener); - } - - @RequiresApi(api = VERSION_CODES.JELLY_BEAN) - @Override - public void removeOnLayoutListener(OnGlobalLayoutListener listener) { - getViewTreeObserver().removeOnGlobalLayoutListener(listener); - } +/** + * Description + * 这里搞一个RecyclerViewWrapper + * 其实是一个普通的FrameLayout,并不是RecyclerView,主要为吸顶的Header功能考虑, + * 系统RecyclerView做吸顶功能最简单的实现的是在RecyclerView的父亲覆盖一个View, + * 这样不会影响RecyclerView的Layout的排版,否则就需要重写LayoutManager,重新layoutManager也是后面要考虑的。 + */ +public class HippyRecyclerViewWrapper extends FrameLayout implements HippyViewBase, + IHeaderHost { + + protected final HippyEngineContext hpContext; + protected HRCV recyclerView; + private NativeGestureDispatcher nativeGestureDispatcher; + + public HippyRecyclerViewWrapper(@NonNull Context context, HRCV recyclerView) { + super(context); + this.recyclerView = recyclerView; + addView(recyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + hpContext = ((HippyInstanceContext) context).getEngineContext(); + HippyRecyclerExtension cacheExtension = new HippyRecyclerExtension(recyclerView, hpContext, + recyclerView.getNodePositionHelper()); + recyclerView.setViewCacheExtension(cacheExtension); + recyclerView.setHeaderHost(this); + HippyRecyclerPool pool = new HippyRecyclerPool(hpContext, this, cacheExtension, + recyclerView.getNodePositionHelper()); + pool.setViewAboundListener(recyclerView); + recyclerView.setRecycledViewPool(pool); + + } + + @Override + public int computeVerticalScrollOffset() { + return recyclerView.computeVerticalScrollOffset(); + } + + @Override + public NativeGestureDispatcher getGestureDispatcher() { + return nativeGestureDispatcher; + } + + @Override + public void setGestureDispatcher(NativeGestureDispatcher dispatcher) { + nativeGestureDispatcher = dispatcher; + } + + public int getChildCountWithCaches() { + return recyclerView.getChildCountWithCaches(); + } + + public View getChildAtWithCaches(int index) { + return recyclerView.getChildAtWithCaches(index); + } + + public void setListData() { + recyclerView.setListData(); + } + + public RecyclerViewEventHelper getRecyclerViewEventHelper() { + return recyclerView.getRecyclerViewEventHelper(); + } + + public void setScrollEnable(boolean flag) { + recyclerView.setScrollEnable(flag); + } + + public void scrollToIndex(int xIndex, int yIndex, boolean animated, int duration) { + recyclerView.scrollToIndex(xIndex, yIndex, animated, duration); + } + + public void scrollToContentOffset(double xOffset, double yOffset, boolean animated, int duration) { + recyclerView.scrollToContentOffset(xOffset, yOffset, animated, duration); + } + + public void scrollToTop() { + recyclerView.scrollToTop(); + } + + public void setRowShouldSticky(boolean enable) { + recyclerView.setRowShouldSticky(enable); + } + + public HRCV getRecyclerView() { + return recyclerView; + } + + /** + * 将HeaderView放到RecyclerView到父亲View上面 + */ + @Override + public void attachHeader(View headerView, LayoutParams layoutParams) { + addView(headerView, layoutParams); + layout(getLeft(), getTop(), getRight(), getBottom()); + getViewTreeObserver().dispatchOnGlobalLayout(); + } + + @Override + public void addOnLayoutListener(OnGlobalLayoutListener listener) { + getViewTreeObserver().addOnGlobalLayoutListener(listener); + } + + @RequiresApi(api = VERSION_CODES.JELLY_BEAN) + @Override + public void removeOnLayoutListener(OnGlobalLayoutListener listener) { + getViewTreeObserver().removeOnGlobalLayoutListener(listener); + } + + public void onBatchStart() { + recyclerView.onBatchStart(); + } + + public void onBatchComplete() { + recyclerView.onBatchComplete(); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java index b31f17fe124..0e1d687dfd4 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/NodePositionHelper.java @@ -16,40 +16,47 @@ package com.tencent.mtt.hippy.views.hippylist; +/** + * Created on 2021/2/3. + * Description + * 由于我们在 HippyRecyclerListAdapter 可能加一些native得Header或者footer, + * 这样HippyRecyclerListAdapter里面得position和前端的列表位置就无法对应了, + * 比如adapter的位置是1,对应前端列表的位置是0,这样就不能用1的位置去获取renderNode的节点,需要矫正位置。 + */ public class NodePositionHelper { - private int nodeOffset = 0; - - public NodePositionHelper() { - - } - - /** - * 如果前面加了NativeHeader,nodeOffset就加1 - */ - public void increaseOffset() { - this.nodeOffset++; - } - - /** - * @return 当前render节点的偏移 - */ - public int getNodeOffset() { - return nodeOffset; - } - - /** - * @param adapterPosition 是节点在adapter的位置,adapter上面可能不都是renderNode - * @return 返回adapterPosition对应前端的列表的node节点位置,减去前面的NativeHeader的位置 - */ - public int getRenderNodePosition(int adapterPosition) { - return adapterPosition - nodeOffset; - } - - /** - * 如果去掉nativeHeader,就减1 - */ - public void decreaseOffset() { - nodeOffset--; - } + private int nodeOffset = 0; + + public NodePositionHelper() { + + } + + /** + * 如果前面加了NativeHeader,nodeOffset就加1 + */ + public void increaseOffset() { + this.nodeOffset++; + } + + /** + * @return 当前render节点的偏移 + */ + public int getNodeOffset() { + return nodeOffset; + } + + /** + * @param adapterPosition 是节点在adapter的位置,adapter上面可能不都是renderNode + * @return 返回adapterPosition对应前端的列表的node节点位置,减去前面的NativeHeader的位置 + */ + public int getRenderNodePosition(int adapterPosition) { + return adapterPosition - nodeOffset; + } + + /** + * 如果去掉nativeHeader,就减1 + */ + public void decreaseOffset() { + nodeOffset--; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java index 4a6740231e7..80bf0220a70 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PreloadHelper.java @@ -6,49 +6,52 @@ import android.view.View; import com.tencent.mtt.hippy.uimanager.HippyViewEvent; +/** + * Created on 2021/1/15. Description 预加载的通知 + */ public class PreloadHelper extends RecyclerView.OnScrollListener { - protected HippyRecyclerView hippyRecyclerView; - protected int preloadItemNumber; - protected boolean isPreloading; + protected HippyRecyclerView hippyRecyclerView; + protected int preloadItemNumber; + protected boolean isPreloading; - public PreloadHelper(HippyRecyclerView hippyRecyclerView) { - this.hippyRecyclerView = hippyRecyclerView; - } + public PreloadHelper(HippyRecyclerView hippyRecyclerView) { + this.hippyRecyclerView = hippyRecyclerView; + } - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int itemCount = recyclerView.getAdapter().getItemCount(); - //频控,记录上次预加载的总条目数,相同就不再次触发预加载 - if (isPreloading) { - return; + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int itemCount = recyclerView.getAdapter().getItemCount(); + //频控,记录上次预加载的总条目数,相同就不再次触发预加载 + if (isPreloading) { + return; + } + if (hippyRecyclerView.getAdapter().getRenderNodeCount() > 0) { + View lastChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1); + int lastPosition = recyclerView.getChildAdapterPosition(lastChild); + if (lastPosition + preloadItemNumber >= itemCount) { + isPreloading = true; + sendReachEndEvent(recyclerView); + } + } } - if (hippyRecyclerView.getAdapter().getRenderNodeCount() > 0) { - View lastChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1); - int lastPosition = recyclerView.getChildAdapterPosition(lastChild); - if (lastPosition + preloadItemNumber >= itemCount) { - isPreloading = true; - sendReachEndEvent(recyclerView); - } + + public void sendReachEndEvent(RecyclerView recyclerView) { + new HippyViewEvent(EVENT_ON_END_REACHED).send((View) recyclerView.getParent(), null); } - } - - public void sendReachEndEvent(RecyclerView recyclerView) { - new HippyViewEvent(EVENT_ON_END_REACHED).send((View) recyclerView.getParent(), null); - } - - /** - * @param preloadItemNumber 提前多少条Item,通知前端加载下一页数据 - */ - public void setPreloadItemNumber(int preloadItemNumber) { - this.preloadItemNumber = preloadItemNumber; - hippyRecyclerView.removeOnScrollListener(this); - if (preloadItemNumber > 0) { - hippyRecyclerView.addOnScrollListener(this); + + /** + * @param preloadItemNumber 提前多少条Item,通知前端加载下一页数据 + */ + public void setPreloadItemNumber(int preloadItemNumber) { + this.preloadItemNumber = preloadItemNumber; + hippyRecyclerView.removeOnScrollListener(this); + if (preloadItemNumber > 0) { + hippyRecyclerView.addOnScrollListener(this); + } } - } - public void reset() { - isPreloading = false; - } + public void reset() { + isPreloading = false; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java index db8bfcf68a8..b7397e7c52e 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullFooterEventHelper.java @@ -21,41 +21,46 @@ import com.tencent.mtt.nxeasy.recyclerview.helper.footer.FooterExposureHelper; import com.tencent.mtt.nxeasy.recyclerview.helper.footer.IFooterLoadMoreListener; +/** + * Created on 2021/1/7. + * Description + * 监控footerView的显示状态,并通知前端onEndReached的事件 + */ class PullFooterEventHelper implements IFooterLoadMoreListener { - public static final String EVENT_ON_END_REACHED = "onLoadMore"; - private final HippyRecyclerView recyclerView; - private FooterExposureHelper footerExposureHelper; - private HippyViewEvent onEndReachedEvent; - - PullFooterEventHelper(HippyRecyclerView recyclerView) { - this.recyclerView = recyclerView; - } - - public void enableFooter(View itemView) { - disableFooter(); - footerExposureHelper = new FooterExposureHelper(); - footerExposureHelper.setFooterListener(this); - footerExposureHelper.setExposureView(itemView); - recyclerView.addOnScrollListener(footerExposureHelper); - } - - public void disableFooter() { - if (footerExposureHelper != null) { - recyclerView.removeOnScrollListener(footerExposureHelper); - footerExposureHelper = null; + public static final String EVENT_ON_END_REACHED = "onLoadMore"; + private final HippyRecyclerView recyclerView; + private FooterExposureHelper footerExposureHelper; + private HippyViewEvent onEndReachedEvent; + + PullFooterEventHelper(HippyRecyclerView recyclerView) { + this.recyclerView = recyclerView; + } + + public void enableFooter(View itemView) { + disableFooter(); + footerExposureHelper = new FooterExposureHelper(); + footerExposureHelper.setFooterListener(this); + footerExposureHelper.setExposureView(itemView); + recyclerView.addOnScrollListener(footerExposureHelper); } - } - protected HippyViewEvent getOnEndReachedEvent() { - if (onEndReachedEvent == null) { - onEndReachedEvent = new HippyViewEvent(EVENT_ON_END_REACHED); + public void disableFooter() { + if (footerExposureHelper != null) { + recyclerView.removeOnScrollListener(footerExposureHelper); + footerExposureHelper = null; + } } - return onEndReachedEvent; - } - @Override - public void onFooterLoadMore() { - getOnEndReachedEvent().send((View) recyclerView.getParent(), null); - } + protected HippyViewEvent getOnEndReachedEvent() { + if (onEndReachedEvent == null) { + onEndReachedEvent = new HippyViewEvent(EVENT_ON_END_REACHED); + } + return onEndReachedEvent; + } + + @Override + public void onFooterLoadMore() { + getOnEndReachedEvent().send((View) recyclerView.getParent(), null); + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java index d2bdf6ae17e..39c2412a860 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/PullHeaderEventHelper.java @@ -30,102 +30,102 @@ import com.tencent.mtt.nxeasy.recyclerview.helper.header.ILayoutRequester; public class PullHeaderEventHelper implements IHeaderRefreshListener, IHeaderRefreshView, - ILayoutRequester { - - public static final String EVENT_TYPE_HEADER_PULLING = "onHeaderPulling"; - public static final String EVENT_TYPE_HEADER_RELEASED = "onHeaderReleased"; - private final PullHeaderRenderNode renderNode; - private HippyRecyclerView recyclerView; - private View renderNodeView; - private LinearLayout headerContainer; - private LayoutParams contentLayoutParams; - private HeaderRefreshHelper headerRefreshHelper; - - PullHeaderEventHelper(HippyRecyclerView recyclerView, PullHeaderRenderNode renderNode) { - this.recyclerView = recyclerView; - this.renderNode = renderNode; - headerContainer = new LinearLayout(recyclerView.getContext()); - headerRefreshHelper = new HeaderRefreshHelper(); - headerRefreshHelper.setHeaderRefreshView(this); - headerRefreshHelper.setHeaderRefreshListener(this); - headerRefreshHelper.setLayoutRequester(this); - recyclerView.setOnTouchListener(headerRefreshHelper); - } - - public void setRenderNodeView(View renderNodeView) { - if (this.renderNodeView != renderNodeView) { - this.renderNodeView = renderNodeView; - headerContainer.removeAllViews(); - contentLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, renderNode.getHeight()); - contentLayoutParams.gravity = Gravity.BOTTOM; - headerContainer.addView(renderNodeView, contentLayoutParams); + ILayoutRequester { + + public static final String EVENT_TYPE_HEADER_PULLING = "onHeaderPulling"; + public static final String EVENT_TYPE_HEADER_RELEASED = "onHeaderReleased"; + private final PullHeaderRenderNode renderNode; + private HippyRecyclerView recyclerView; + private View renderNodeView; + private LinearLayout headerContainer; + private LayoutParams contentLayoutParams; + private HeaderRefreshHelper headerRefreshHelper; + + PullHeaderEventHelper(HippyRecyclerView recyclerView, PullHeaderRenderNode renderNode) { + this.recyclerView = recyclerView; + this.renderNode = renderNode; + headerContainer = new LinearLayout(recyclerView.getContext()); + headerRefreshHelper = new HeaderRefreshHelper(); + headerRefreshHelper.setHeaderRefreshView(this); + headerRefreshHelper.setHeaderRefreshListener(this); + headerRefreshHelper.setLayoutRequester(this); + recyclerView.setOnTouchListener(headerRefreshHelper); + } + + public void setRenderNodeView(View renderNodeView) { + if (this.renderNodeView != renderNodeView) { + this.renderNodeView = renderNodeView; + headerContainer.removeAllViews(); + contentLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, renderNode.getHeight()); + contentLayoutParams.gravity = Gravity.BOTTOM; + headerContainer.addView(renderNodeView, contentLayoutParams); + } + } + + + public View getView() { + return headerContainer; + } + + @Override + public void onStartDrag() { + + } + + @Override + public void onHeaderHeightChanged(int sumOffset) { + HippyMap params = new HippyMap(); + params.pushDouble("contentOffset", PixelUtil.px2dp(sumOffset)); + sendPullHeaderEvent(EVENT_TYPE_HEADER_PULLING, params); + } + + @Override + public void onRefreshing() { + + } + + @Override + public void onFolded() { + + } + + /** + * 松手后,触发的刷新回调,需要通知Hippy前端业务进行数据的刷新操作 + */ + @Override + public void onHeaderRefreshing(int refreshWay) { + sendPullHeaderEvent(EVENT_TYPE_HEADER_RELEASED, new HippyMap()); + } + + @Override + public int getContentHeight() { + return renderNode.getHeight(); + } + + protected void sendPullHeaderEvent(String eventName, HippyMap param) { + new HippyViewEvent(eventName).send(renderNodeView, param); + } + + /** + * Hippy前端业务通知数据已经刷新完毕,这里通知给headerRefreshHelper,进行header的收起功能 + */ + public void onHeaderRefreshFinish() { + headerRefreshHelper.onRefreshDone(); + } + + /** + * Hippy前端业务调用主动刷新功能,这款需要通知headerRefreshHelper进行自动下拉刷新 + */ + public void onHeaderRefresh() { + headerRefreshHelper.triggerRefresh(); + } + + public int getVisibleHeight() { + return headerRefreshHelper.getVisibleHeight(); + } + + @Override + public void requestLayout() { + recyclerView.dispatchLayout(); } - } - - - public View getView() { - return headerContainer; - } - - @Override - public void onStartDrag() { - - } - - @Override - public void onHeaderHeightChanged(int sumOffset) { - HippyMap params = new HippyMap(); - params.pushDouble("contentOffset", PixelUtil.px2dp(sumOffset)); - sendPullHeaderEvent(EVENT_TYPE_HEADER_PULLING, params); - } - - @Override - public void onRefreshing() { - - } - - @Override - public void onFolded() { - - } - - /** - * 松手后,触发的刷新回调,需要通知Hippy前端业务进行数据的刷新操作 - */ - @Override - public void onHeaderRefreshing(int refreshWay) { - sendPullHeaderEvent(EVENT_TYPE_HEADER_RELEASED, new HippyMap()); - } - - @Override - public int getContentHeight() { - return renderNode.getHeight(); - } - - protected void sendPullHeaderEvent(String eventName, HippyMap param) { - new HippyViewEvent(eventName).send(renderNodeView, param); - } - - /** - * Hippy前端业务通知数据已经刷新完毕,这里通知给headerRefreshHelper,进行header的收起功能 - */ - public void onHeaderRefreshFinish() { - headerRefreshHelper.onRefreshDone(); - } - - /** - * Hippy前端业务调用主动刷新功能,这款需要通知headerRefreshHelper进行自动下拉刷新 - */ - public void onHeaderRefresh() { - headerRefreshHelper.triggerRefresh(); - } - - public int getVisibleHeight() { - return headerRefreshHelper.getVisibleHeight(); - } - - @Override - public void requestLayout() { - recyclerView.dispatchLayout(); - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java index b9f6652e15e..715cab5a92f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java @@ -39,356 +39,364 @@ import com.tencent.mtt.hippy.views.list.HippyListItemView; import com.tencent.mtt.hippy.views.scroll.HippyScrollViewEventHelper; +/** + * Created on 2020/12/24. Description + * 各种事件的通知,通知前端view的曝光事件,用于前端的统计上报 + */ public class RecyclerViewEventHelper extends OnScrollListener implements OnLayoutChangeListener, - OnAttachStateChangeListener, OverPullListener { - - public static final String EVENT_ON_END_REACHED = "onEndReached"; - public static final String EVENT_ON_TOP_REACHED = "onTopReached"; - public static final String INITIAL_LIST_READY = "initialListReady"; - protected final HippyRecyclerView hippyRecyclerView; - private boolean scrollBeginDragEventEnable; - private boolean scrollEndDragEventEnable; - private HippyViewEvent onScrollDragEndedEvent; - private boolean momentumScrollBeginEventEnable; - private boolean momentumScrollEndEventEnable; - private HippyViewEvent onScrollFlingStartedEvent; - private HippyViewEvent onScrollFlingEndedEvent; - private int currentState; - protected boolean onScrollEventEnable = true; - private HippyViewEvent onScrollEvent; - private long lastScrollEventTimeStamp; - private int scrollEventThrottle; - private boolean exposureEventEnable; - private HippyViewEvent onScrollDragStartedEvent; - - //initialListReady event - private boolean isInitialListReadyNotified = false; - private ViewTreeObserver viewTreeObserver; - private OnPreDrawListener preDrawListener; - - - public RecyclerViewEventHelper(HippyRecyclerView recyclerView) { - this.hippyRecyclerView = recyclerView; - hippyRecyclerView.addOnScrollListener(this); - hippyRecyclerView.addOnAttachStateChangeListener(this); - hippyRecyclerView.addOnLayoutChangeListener(this); - preDrawListener = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - notifyInitialListReady(); - return true; - } - }; - } - - void notifyInitialListReady() { - if (canNotifyInit()) { - isInitialListReadyNotified = true; - viewTreeObserver.removeOnPreDrawListener(preDrawListener); - hippyRecyclerView.post(new Runnable() { - @Override - public void run() { - new HippyViewEvent(INITIAL_LIST_READY).send(getParentView(), null); - } - }); + OnAttachStateChangeListener, OverPullListener { + + public static final String INITIAL_LIST_READY = "initialListReady"; + protected final HippyRecyclerView hippyRecyclerView; + private boolean scrollBeginDragEventEnable; + private boolean scrollEndDragEventEnable; + private HippyViewEvent onScrollDragEndedEvent; + private boolean momentumScrollBeginEventEnable; + private boolean momentumScrollEndEventEnable; + private HippyViewEvent onScrollFlingStartedEvent; + private HippyViewEvent onScrollFlingEndedEvent; + private int currentState; + protected boolean onScrollEventEnable = true; + private HippyViewEvent onScrollEvent; + private long lastScrollEventTimeStamp; + private int scrollEventThrottle; + private boolean exposureEventEnable; + private HippyViewEvent onScrollDragStartedEvent; + + //initialListReady event + private boolean isInitialListReadyNotified = false; + private ViewTreeObserver viewTreeObserver; + private OnPreDrawListener preDrawListener; + private boolean isLastTimeReachEnd; + + + public RecyclerViewEventHelper(HippyRecyclerView recyclerView) { + this.hippyRecyclerView = recyclerView; + hippyRecyclerView.addOnScrollListener(this); + hippyRecyclerView.addOnAttachStateChangeListener(this); + hippyRecyclerView.addOnLayoutChangeListener(this); + preDrawListener = new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + notifyInitialListReady(); + return true; + } + }; } - } - protected View getParentView() { - return (View) hippyRecyclerView.getParent(); - } - - /** - * 是否满足initialListReady的通知条件,需要有真实view上屏才进行通知 - */ - private boolean canNotifyInit() { - return !isInitialListReadyNotified && hippyRecyclerView.getAdapter().getItemCount() > 0 - && hippyRecyclerView.getChildCount() > 0 - && viewTreeObserver.isAlive(); - } - - public void setScrollBeginDragEventEnable(boolean enable) { - scrollBeginDragEventEnable = enable; - } - - public void setScrollEndDragEventEnable(boolean enable) { - scrollEndDragEventEnable = enable; - } - - public void setMomentumScrollBeginEventEnable(boolean enable) { - momentumScrollBeginEventEnable = enable; - } - - public void setMomentumScrollEndEventEnable(boolean enable) { - momentumScrollEndEventEnable = enable; - } + void notifyInitialListReady() { + if (canNotifyInit()) { + isInitialListReadyNotified = true; + viewTreeObserver.removeOnPreDrawListener(preDrawListener); + hippyRecyclerView.post(new Runnable() { + @Override + public void run() { + new HippyViewEvent(INITIAL_LIST_READY).send(getParentView(), null); + } + }); + } + } - public void setOnScrollEventEnable(boolean enable) { - onScrollEventEnable = enable; - } + protected View getParentView() { + return (View) hippyRecyclerView.getParent(); + } - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, - int oldBottom) { - checkSendExposureEvent(); - } + /** + * 是否满足initialListReady的通知条件,需要有真实view上屏才进行通知 + */ + private boolean canNotifyInit() { + return !isInitialListReadyNotified && hippyRecyclerView.getAdapter().getItemCount() > 0 + && hippyRecyclerView.getChildCount() > 0 + && viewTreeObserver.isAlive(); + } - protected HippyViewEvent getOnScrollDragStartedEvent() { - if (onScrollDragStartedEvent == null) { - onScrollDragStartedEvent = new HippyViewEvent( - HippyScrollViewEventHelper.EVENT_TYPE_BEGIN_DRAG); + public void setScrollBeginDragEventEnable(boolean enable) { + scrollBeginDragEventEnable = enable; } - return onScrollDragStartedEvent; - } - // scroll - protected HippyViewEvent getOnScrollEvent() { - if (onScrollEvent == null) { - onScrollEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_SCROLL); + public void setScrollEndDragEventEnable(boolean enable) { + scrollEndDragEventEnable = enable; } - return onScrollEvent; - } - // start fling - protected HippyViewEvent getOnScrollFlingStartedEvent() { - if (onScrollFlingStartedEvent == null) { - onScrollFlingStartedEvent = new HippyViewEvent( - HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_BEGIN); + public void setMomentumScrollBeginEventEnable(boolean enable) { + momentumScrollBeginEventEnable = enable; } - return onScrollFlingStartedEvent; - } - // end drag event - protected HippyViewEvent getOnScrollDragEndedEvent() { - if (onScrollDragEndedEvent == null) { - onScrollDragEndedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_END_DRAG); + public void setMomentumScrollEndEventEnable(boolean enable) { + momentumScrollEndEventEnable = enable; } - return onScrollDragEndedEvent; - } - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - int oldState = currentState; - currentState = newState; - sendDragEvent(newState); - sendDragEndEvent(oldState, currentState); - sendFlingEvent(newState); - sendFlingEndEvent(oldState, currentState); - } + public void setOnScrollEventEnable(boolean enable) { + onScrollEventEnable = enable; + } - @Override - public void onScrolled(@NonNull final RecyclerView recyclerView, int dx, int dy) { - if (dx != 0 || dy != 0) { - checkSendOnScrollEvent(); + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, + int oldBottom) { + checkSendExposureEvent(); } - checkSendExposureEvent(); + protected HippyViewEvent getOnScrollDragStartedEvent() { + if (onScrollDragStartedEvent == null) { + onScrollDragStartedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_BEGIN_DRAG); + } + return onScrollDragStartedEvent; + } - if (!recyclerView.canScrollVertically(1)) { - sendOnEndReachedEvent(); + // scroll + protected HippyViewEvent getOnScrollEvent() { + if (onScrollEvent == null) { + onScrollEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_SCROLL); + } + return onScrollEvent; } - if (!recyclerView.canScrollVertically(-1)) { - sendOnTopReachedEvent(); + // start fling + protected HippyViewEvent getOnScrollFlingStartedEvent() { + if (onScrollFlingStartedEvent == null) { + onScrollFlingStartedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_BEGIN); + } + return onScrollFlingStartedEvent; } - } - protected void sendOnEndReachedEvent() { - new HippyViewEvent(EVENT_ON_END_REACHED).send(getParentView(), null); - } + // end drag event + protected HippyViewEvent getOnScrollDragEndedEvent() { + if (onScrollDragEndedEvent == null) { + onScrollDragEndedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_END_DRAG); + } + return onScrollDragEndedEvent; + } - protected void sendOnTopReachedEvent() { - new HippyViewEvent(EVENT_ON_TOP_REACHED).send(getParentView(), null); - } + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + int oldState = currentState; + currentState = newState; + sendDragEvent(newState); + sendDragEndEvent(oldState, currentState); + sendFlingEvent(newState); + sendFlingEndEvent(oldState, currentState); + } - private void checkSendOnScrollEvent() { - if (onScrollEventEnable) { - long currTime = System.currentTimeMillis(); - if (currTime - lastScrollEventTimeStamp >= scrollEventThrottle) { - lastScrollEventTimeStamp = currTime; - sendOnScrollEvent(); - } + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, int dx, int dy) { + if (scrollHappened(dx, dy)) { + checkSendOnScrollEvent(); + } + checkSendExposureEvent(); + if (scrollHappened(dx, dy)) { + checkSendReachEndEvent(); + } } - } - public void sendOnScrollEvent() { - getOnScrollEvent().send(getParentView(), generateScrollEvent()); - } + protected boolean scrollHappened(int dx, int dy) { + return dx != 0 || dy != 0; + } - private void observePreDraw() { - if (!isInitialListReadyNotified && viewTreeObserver == null) { - viewTreeObserver = hippyRecyclerView.getViewTreeObserver(); - viewTreeObserver.addOnPreDrawListener(preDrawListener); + /** + * 检查是否已经触底,发生onEndReached事件给前端 + * 如果上次是没有到底,这次滑动底了,需要发事件通知,如果上一次已经是到底了,这次到底不会发事件 + */ + private void checkSendReachEndEvent() { + boolean isThisTimeReachEnd; + if (HippyListUtils.isHorizontalLayout(hippyRecyclerView)) { + isThisTimeReachEnd = isHorizontalReachEnd(); + } else { + isThisTimeReachEnd = isVerticalReachEnd(); + } + if (!isLastTimeReachEnd && isThisTimeReachEnd) { + sendOnReachedEvent(); + } + isLastTimeReachEnd = isThisTimeReachEnd; } - } - protected void sendFlingEvent(int newState) { - if (momentumScrollBeginEventEnable && newState == SCROLL_STATE_SETTLING) { - getOnScrollFlingStartedEvent().send(getParentView(), generateScrollEvent()); + /** + * 竖向滑动,内容已经到达最下边 + */ + private boolean isVerticalReachEnd() { + return !hippyRecyclerView.canScrollVertically(1); } - } - protected void sendDragEndEvent(int oldState, int newState) { - if (scrollEndDragEventEnable && isReleaseDrag(oldState, newState) && !hippyRecyclerView - .isOverPulling()) { - getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + /** + * 水平滑动,内容已经到达最右边 + */ + private boolean isHorizontalReachEnd() { + return !hippyRecyclerView.canScrollHorizontally(1); } - } - private boolean isReleaseDrag(int oldState, int newState) { - return (oldState == SCROLL_STATE_DRAGGING && - (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_SETTLING)); - } + protected void sendOnReachedEvent() { + new HippyViewEvent(HippyScrollViewEventHelper.EVENT_ON_END_REACHED).send(getParentView(), null); + } - protected void sendFlingEndEvent(int oldState, int newState) { - if (momentumScrollEndEventEnable && oldState == SCROLL_STATE_SETTLING - && newState != SCROLL_STATE_SETTLING) { - getOnScrollFlingEndedEvent().send(getParentView(), generateScrollEvent()); + protected void checkSendOnScrollEvent() { + if (onScrollEventEnable) { + long currTime = System.currentTimeMillis(); + if (currTime - lastScrollEventTimeStamp >= scrollEventThrottle) { + lastScrollEventTimeStamp = currTime; + sendOnScrollEvent(); + } + } } - } - protected void sendDragEvent(int newState) { - if (scrollBeginDragEventEnable && newState == RecyclerView.SCROLL_STATE_DRAGGING) { - getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + public void sendOnScrollEvent() { + getOnScrollEvent().send(getParentView(), generateScrollEvent()); } - } - // end fling - protected HippyViewEvent getOnScrollFlingEndedEvent() { - if (onScrollFlingEndedEvent == null) { - onScrollFlingEndedEvent = new HippyViewEvent( - HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_END); + private void observePreDraw() { + if (!isInitialListReadyNotified && viewTreeObserver == null) { + viewTreeObserver = hippyRecyclerView.getViewTreeObserver(); + viewTreeObserver.addOnPreDrawListener(preDrawListener); + } } - return onScrollFlingEndedEvent; - } - public void setScrollEventThrottle(int scrollEventThrottle) { - this.scrollEventThrottle = scrollEventThrottle; - } + protected void sendFlingEvent(int newState) { + if (momentumScrollBeginEventEnable && newState == SCROLL_STATE_SETTLING) { + getOnScrollFlingStartedEvent().send(getParentView(), generateScrollEvent()); + } + } - public final HippyMap generateScrollEvent() { - HippyMap contentOffset = new HippyMap(); + protected void sendDragEndEvent(int oldState, int newState) { + if (scrollEndDragEventEnable && isReleaseDrag(oldState, newState) && !hippyRecyclerView.isOverPulling()) { + getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + } + } - float offsetX = hippyRecyclerView.getContentOffsetX(); - float offsetY = hippyRecyclerView.getContentOffsetY(); + private boolean isReleaseDrag(int oldState, int newState) { + return (oldState == SCROLL_STATE_DRAGGING && + (newState == SCROLL_STATE_IDLE || newState == SCROLL_STATE_SETTLING)); + } - if (offsetX != 0) { - offsetX = PixelUtil.px2dp(offsetX); + protected void sendFlingEndEvent(int oldState, int newState) { + if (momentumScrollEndEventEnable && oldState == SCROLL_STATE_SETTLING && newState != SCROLL_STATE_SETTLING) { + getOnScrollFlingEndedEvent().send(getParentView(), generateScrollEvent()); + } } - if (offsetY != 0) { - offsetY = PixelUtil.px2dp(offsetY); + protected void sendDragEvent(int newState) { + if (scrollBeginDragEventEnable && newState == RecyclerView.SCROLL_STATE_DRAGGING) { + getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + } } - contentOffset.pushDouble("x", offsetX); - contentOffset.pushDouble("y", offsetY); - HippyMap event = new HippyMap(); - event.pushMap("contentOffset", contentOffset); - return event; - } + // end fling + protected HippyViewEvent getOnScrollFlingEndedEvent() { + if (onScrollFlingEndedEvent == null) { + onScrollFlingEndedEvent = new HippyViewEvent(HippyScrollViewEventHelper.EVENT_TYPE_MOMENTUM_END); + } + return onScrollFlingEndedEvent; + } - public void setExposureEventEnable(boolean enable) { - exposureEventEnable = enable; - } + public void setScrollEventThrottle(int scrollEventThrottle) { + this.scrollEventThrottle = scrollEventThrottle; + } - /** - * 可视面积小于10%,任务view当前已经不在可视区域 - */ - private boolean isViewVisible(View view) { - if (view == null) { - return false; + public final HippyMap generateScrollEvent() { + HippyMap contentOffset = new HippyMap(); + contentOffset.pushDouble("x", PixelUtil.px2dp(0)); + contentOffset.pushDouble("y", PixelUtil.px2dp(hippyRecyclerView.getContentOffsetY())); + HippyMap event = new HippyMap(); + event.pushMap("contentOffset", contentOffset); + return event; } - Rect rect = new Rect(); - boolean visibility = view.getGlobalVisibleRect(rect); - if (!visibility) { - return false; - } else { - float visibleArea = rect.height() * rect.width(); //可见区域的面积 - float viewArea = view.getMeasuredWidth() * view.getMeasuredHeight();//当前view的总面积 - return visibleArea > viewArea * 0.1f; + + public void setExposureEventEnable(boolean enable) { + exposureEventEnable = enable; } - } - protected void checkExposureView(View view) { - if (view instanceof HippyListItemView) { - HippyListItemView itemView = (HippyListItemView) view; - if (isViewVisible(view)) { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_APPEAR) { - sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_APPEAR); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_APPEAR); + /** + * 可视面积小于10%,任务view当前已经不在可视区域 + */ + private boolean isViewVisible(View view) { + if (view == null) { + return false; } - } else { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { - sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_DISAPPEAR); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_DISAPPEAR); + Rect rect = new Rect(); + boolean visibility = view.getGlobalVisibleRect(rect); + if (!visibility) { + return false; + } else { + float visibleArea = rect.height() * rect.width(); //可见区域的面积 + float viewArea = view.getMeasuredWidth() * view.getMeasuredHeight();//当前view的总面积 + return visibleArea > viewArea * 0.1f; } - } } - } - protected void sendExposureEvent(View view, String eventName) { - if (eventName.equals(HippyListItemView.EXPOSURE_EVENT_APPEAR) || eventName - .equals(HippyListItemView.EXPOSURE_EVENT_DISAPPEAR)) { - new HippyViewEvent(eventName).send(view, null); + protected void checkExposureView(View view) { + if (view instanceof HippyListItemView) { + HippyListItemView itemView = (HippyListItemView) view; + if (isViewVisible(view)) { + if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_APPEAR) { + sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_APPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_APPEAR); + } + } else { + if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { + sendExposureEvent(view, HippyListItemView.EXPOSURE_EVENT_DISAPPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_DISAPPEAR); + } + } + } } - } - private void checkSendExposureEvent() { - if (!exposureEventEnable) { - return; - } - int childCount = hippyRecyclerView.getChildCount(); - for (int i = 0; i < childCount; i++) { - checkExposureView(findHippyListItemView((ViewGroup) hippyRecyclerView.getChildAt(i))); + protected void sendExposureEvent(View view, String eventName) { + if (eventName.equals(HippyListItemView.EXPOSURE_EVENT_APPEAR) || eventName + .equals(HippyListItemView.EXPOSURE_EVENT_DISAPPEAR)) { + new HippyViewEvent(eventName).send(view, null); + } } - } - /** - * 由于挂载到RecyclerView到子View可能不是HippyListItemView,比如对于stickyItem,我们会包一层 - * ViewGroup,所以这里拿HippyListItemView,需要做两层的判断 - */ - private View findHippyListItemView(ViewGroup viewGroup) { - if (viewGroup instanceof HippyListItemView) { - return viewGroup; - } - if (viewGroup.getChildCount() > 0) { - View child = viewGroup.getChildAt(0); - if (child instanceof HippyListItemView) { - return child; - } + protected void checkSendExposureEvent() { + if (!exposureEventEnable) { + return; + } + int childCount = hippyRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + checkExposureView(findHippyListItemView((ViewGroup) hippyRecyclerView.getChildAt(i))); + } } - return null; - } - @Override - public void onViewAttachedToWindow(View v) { - observePreDraw(); - } + /** + * 由于挂载到RecyclerView到子View可能不是HippyListItemView,比如对于stickyItem,我们会包一层 + * ViewGroup,所以这里拿HippyListItemView,需要做两层的判断 + */ + private View findHippyListItemView(ViewGroup viewGroup) { + if (viewGroup instanceof HippyListItemView) { + return viewGroup; + } + if (viewGroup.getChildCount() > 0) { + View child = viewGroup.getChildAt(0); + if (child instanceof HippyListItemView) { + return child; + } + } + return null; + } - @Override - public void onViewDetachedFromWindow(View v) { + @Override + public void onViewAttachedToWindow(View v) { + observePreDraw(); + } - } + @Override + public void onViewDetachedFromWindow(View v) { - @Override - public void onOverPullStateChanged(int oldState, int newState, int offset) { - LogUtils.d("QBRecyclerViewEventHelper", "oldState:" + oldState + ",newState:" + newState); - if (oldState == OverPullHelper.OVER_PULL_NONE && isOverPulling(newState)) { - sendOnEndReachedEvent(); - getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); } - if (isOverPulling(oldState) && isOverPulling(newState)) { - sendOnScrollEvent(); - } - if (newState == OverPullHelper.OVER_PULL_SETTLING - && oldState != OverPullHelper.OVER_PULL_SETTLING) { - getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + + @Override + public void onOverPullStateChanged(int oldState, int newState, int offset) { + LogUtils.d("QBRecyclerViewEventHelper", "oldState:" + oldState + ",newState:" + newState); + if (oldState == OverPullHelper.OVER_PULL_NONE && (isOverPulling(newState) + || newState == OverPullHelper.OVER_PULL_NORMAL)) { + getOnScrollDragStartedEvent().send(getParentView(), generateScrollEvent()); + } + if (isOverPulling(oldState) && isOverPulling(newState)) { + sendOnScrollEvent(); + } + if (newState == OverPullHelper.OVER_PULL_SETTLING && oldState != OverPullHelper.OVER_PULL_SETTLING) { + getOnScrollDragEndedEvent().send(getParentView(), generateScrollEvent()); + } } - } - private boolean isOverPulling(int newState) { - return newState == OverPullHelper.OVER_PULL_DOWN_ING - || newState == OverPullHelper.OVER_PULL_UP_ING; - } + private boolean isOverPulling(int newState) { + return newState == OverPullHelper.OVER_PULL_DOWN_ING || newState == OverPullHelper.OVER_PULL_UP_ING; + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/ViewStickEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/ViewStickEventHelper.java new file mode 100644 index 00000000000..78cbdb0b901 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippylist/ViewStickEventHelper.java @@ -0,0 +1,38 @@ +package com.tencent.mtt.hippy.views.hippylist; + +import android.view.View; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.uimanager.HippyViewEvent; +import com.tencent.mtt.nxeasy.recyclerview.helper.skikcy.StickViewListener; + +/** + * Created on 2021/8/24. + * Description + * 吸顶事件分发器 + */ +public class ViewStickEventHelper implements StickViewListener { + + public static final String ON_VIEW_SUSPEND_LISTENER = "onViewSuspendListener"; + public static final String IS_SHOW = "isShow"; + private View view; + + public ViewStickEventHelper(View view) { + this.view = view; + } + + @Override + public void onStickAttached(int stickyPosition) { + notifyStickEvent(true); + } + + @Override + public void onStickDetached(int stickyPosition) { + notifyStickEvent(false); + } + + private void notifyStickEvent(boolean isStickViewShown) { + HippyMap map = new HippyMap(); + map.pushBoolean(IS_SHOW, isStickViewShown); + new HippyViewEvent(ON_VIEW_SUSPEND_LISTENER).send(view, map); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPager.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPager.java new file mode 100644 index 00000000000..513fdb4a697 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPager.java @@ -0,0 +1,406 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Scroller; +import androidx.viewpager.widget.ViewPager; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.uimanager.HippyViewBase; +import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.hippypager.transform.VerticalPageTransformer; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPagerItem; +import com.tencent.mtt.supportui.views.ScrollChecker; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + + +public class HippyPager extends ViewPager implements HippyViewBase { + + private static final String TAG = "HippyViewPager"; + private final Handler handler = new Handler(Looper.getMainLooper()); + private NativeGestureDispatcher gestureDispatcher; + private boolean scrollEnabled = true; + private boolean firstUpdateChild = true; + private HippyPagerPageChangeListener pageListener; + private Promise callBackPromise; + private boolean isVertical = false; + private Scroller scroller; + private boolean ignoreCheck; + private Runnable measureAndLayout = new Runnable() { + @Override + public void run() { + measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + } + }; + + public HippyPager(Context context, boolean isVertical) { + super(context); + this.isVertical = isVertical; + init(context); + } + + public HippyPager(Context context) { + super(context); + init(context); + } + + private void init(Context context) { + pageListener = new HippyPagerPageChangeListener(this); + addOnPageChangeListener(pageListener); + setAdapter(createAdapter()); + initViewPager(); + initScroller(); + } + + public int getCurrentPage() { + return getCurrentItem(); + } + + + protected void initViewPager() { + if (isVertical) { + setPageTransformer(true, new VerticalPageTransformer()); + // The easiest way to get rid of the overscroll drawing that happens on the left and right + setOverScrollMode(OVER_SCROLL_NEVER); + } + } + + public int getPageCount() { + return getAdapter() == null ? 0 : getAdapter().getCount(); + } + + public Object getCurrentItemView() { + if (getAdapter() != null) { + return getAdapter().getCurrentItemObj(); + } + return null; + } + + public void setCallBackPromise(Promise promise) { + callBackPromise = promise; + } + + public Promise getCallBackPromise() { + return callBackPromise; + } + + protected HippyPagerAdapter createAdapter() { + return new HippyPagerAdapter(this); + } + + public void setInitialPageIndex(final int index) { + LogUtils.d(TAG, HippyPager.this.getClass().getName() + " " + "setInitialPageIndex=" + index); + setCurrentItem(index); + setDefaultItem(index); + } + + public void setChildCountAndUpdate(final int childCount) { + LogUtils.d(TAG, "doUpdateInternal: " + hashCode() + ", childCount=" + childCount); + getAdapter().setChildSize(childCount); + getAdapter().notifyDataSetChanged(); + triggerRequestLayout(); + if (firstUpdateChild) { + pageListener.onPageSelected(getCurrentItem()); + firstUpdateChild = false; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + //当viewPager重新挂着当时候,调用super.onAttachedToWindow 把mFirstLayout 设置为true + //这样滑动viewPager,放手后,不会触发自动滚动,原因是mFirstLayout为true,setCurrentItemInternal, + //走了requestLayout,这样会没有动画 + setFirstLayout(false); + } + + public void addViewToAdapter(HippyViewPagerItem view, int position) { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + adapter.addView(view, position); + } + } + + protected int getAdapterViewSize() { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + return adapter.getItemViewSize(); + } + return 0; + } + + protected void removeViewFromAdapter(HippyViewPagerItem view) { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + adapter.removeView(view); + } + } + + public View getViewFromAdapter(int currentItem) { + HippyPagerAdapter adapter = getAdapter(); + if (adapter != null) { + return adapter.getViewAt(currentItem); + } + return null; + } + + @Override + public HippyPagerAdapter getAdapter() { + return (HippyPagerAdapter) super.getAdapter(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + resetIgnoreCheck(ev); + if (!scrollEnabled) { + return false; + } + if (isVertical) { + boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); + swapXY(ev); // return touch coordinates to original reference frame for any child views + return intercepted; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + resetIgnoreCheck(ev); + if (!scrollEnabled) { + return false; + } + if (isVertical) { + return super.onTouchEvent(swapXY(ev)); + } + return super.onTouchEvent(ev); + } + + private void resetIgnoreCheck(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_UP + || ev.getAction() == MotionEvent.ACTION_CANCEL) { + ignoreCheck = false; + } + } + + public void switchToPage(int item, boolean animated) { + // viewpager的children没有初始化好的时候,直接设置mInitialPageIndex + if (getAdapter() == null || getAdapter().getCount() == 0) { + setInitialPageIndex(item); + } else { + if (getCurrentItem() != item) { + stopAnimationAndScrollToFinal(); + setCurrentItem(item, animated); + } else if (!firstUpdateChild) { + pageListener.onPageSelected(item); + } + } + } + + /** + * 如果仍然在滑动中,重置一下状态,abortAnimation ,getScrollX 会处于mFinalX的状态,直接scrollTo到mFinalX + */ + private void stopAnimationAndScrollToFinal() { + if (!scroller.isFinished()) { + invokeSetScrollingCacheEnabled(false); + if (scroller != null) { + scroller.abortAnimation(); + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = scroller.getCurrX(); + int y = scroller.getCurrY(); + if (oldX != x || oldY != y) { + scrollTo(x, y); + } + } + invokeSetScrollState(SCROLL_STATE_IDLE); + } + } + + public void setScrollEnabled(boolean scrollEnabled) { + this.scrollEnabled = scrollEnabled; + } + + @Override + public NativeGestureDispatcher getGestureDispatcher() { + return gestureDispatcher; + } + + @Override + public void setGestureDispatcher(NativeGestureDispatcher nativeGestureDispatcher) { + gestureDispatcher = nativeGestureDispatcher; + } + + public void triggerRequestLayout() { + //对象构造的时候,就会调用到这里,必须判空 + if (handler != null) { + handler.removeCallbacks(measureAndLayout); + handler.post(measureAndLayout); + } + } + + public void setOverflow(String overflow) { + //robinsli Android 支持 overflow: visible,超出容器之外的属性节点也可以正常显示 + if (!TextUtils.isEmpty(overflow)) { + switch (overflow) { + case "visible": + setClipChildren(false); //可以超出父亲区域 + break; + case "hidden": { + setClipChildren(true); //默认值是false + break; + } + } + } + invalidate(); + } + + public void onOverScrollSuccess() { + invokeSetIsUnableToDrag(false); + ignoreCheck = true; + } + + + /** + * viewpPager的孩子已经滚动到底了,已经不能继续滚动了,会触发通过onOverScroll事件,告诉 + * viewPager的进行继续,需要执行onOverScrollSuccess,让viewPager开始滚动 + * 会让mIsUnableToDrag设置为false,ignoreCheck 表示不在进行孩子的判断,有些孩子没有正确实现canScroll, + * 用ignoreCheck的值来忽略孩子的滚动,这是一种兼容老代码的逻辑,按道理来说,ignoreCheck应该不需要 + */ + public boolean onOverScroll(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, + int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { + if (isVertical) { + if (((scrollY == 0 && deltaY < 0) || (scrollY == scrollRangeY && deltaY > 0))) { + onOverScrollSuccess(); + } + } else { + if (((scrollX == 0 && deltaX < 0) || (scrollX == scrollRangeX && deltaX > 0))) { + onOverScrollSuccess(); + } + } + return true; + } + + /** + * ViewPager 在滚动的时候,他的树下的孩子节点可能也会滚动,这里需要check一下孩子是否可以滚动 + * ScrollChecker.canScroll 是QQ浏览器里面非标准的做法 + */ + @Override + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (ignoreCheck) { + return false; + } + return ScrollChecker.canScroll(v, checkV, isVertical, dx, x, y) || super.canScroll(v, checkV, dx, x, y); + } + + private MotionEvent swapXY(MotionEvent ev) { + float width = getWidth(); + float height = getHeight(); + float newX = (ev.getY() / height) * width; + float newY = (ev.getX() / width) * height; + ev.setLocation(newX, newY); + return ev; + } + + @Override + public void requestLayout() { + super.requestLayout(); + triggerRequestLayout(); + } + + /** + * hook 方法,不建议调用,这里只是为了兼容,目的是为了触发一次firstLayout恢复状态 + */ + private void setFirstLayout(boolean isFirstLayout) { + try { + Field field = ViewPager.class.getDeclaredField("mFirstLayout"); + field.setAccessible(true); + field.set(this, isFirstLayout); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * 也是Hack方法,设置初始化index + * + * @param position + */ + private void setDefaultItem(int position) { + try { + Field field = ViewPager.class.getDeclaredField("mCurItem"); + field.setAccessible(true); + field.setInt(this, position); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void initScroller() { + try { + Field velocityTrackerField = ViewPager.class.getDeclaredField("mScroller"); + velocityTrackerField.setAccessible(true); + scroller = (Scroller) velocityTrackerField.get(this); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void invokeSetIsUnableToDrag(boolean enabled) { + try { + Field field = ViewPager.class.getDeclaredField("mIsUnableToDrag"); + field.setAccessible(true); + field.set(this, enabled); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void invokeSetScrollingCacheEnabled(boolean enabled) { + try { + Method method = ViewPager.class.getDeclaredMethod("setScrollingCacheEnabled", Boolean.class); + method.setAccessible(true); + method.invoke(this, enabled); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void invokeSetScrollState(int state) { + try { + Method method = ViewPager.class.getDeclaredMethod("setScrollState", Integer.class); + method.setAccessible(true); + method.invoke(this, state); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerAdapter.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerAdapter.java new file mode 100644 index 00000000000..a04d6bd1001 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerAdapter.java @@ -0,0 +1,146 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.viewpager.widget.PagerAdapter; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPagerItem; +import java.util.ArrayList; +import java.util.List; + +/** + * Created on 2021/7/23. + */ + +public class HippyPagerAdapter extends PagerAdapter { + + protected final List views = new ArrayList<>(); + protected final HippyPager viewPager; + private int childSize = 0; + private Object currentItemObj = null; + + public HippyPagerAdapter(HippyPager viewPager) { + this.viewPager = viewPager; + } + + public void setChildSize(int size) { + childSize = size; + } + + public Object getCurrentItemObj() { + return currentItemObj; + } + + + protected void addView(HippyViewPagerItem view, int position) { + if (view != null && position >= 0) { + if (position >= views.size()) { + views.add(view); + } else { + views.add(position, view); + } + } + } + + protected void removeView(View view) { + int size = views.size(); + int index = -1; + for (int i = 0; i < size; i++) { + View curr = getViewAt(i); + if (curr == view) { + index = i; + break; + } + } + if (index >= 0) { + views.remove(index); + } + } + + protected View getViewAt(int index) { + if (index < 0 || index >= views.size()) { + return null; + } + return views.get(index); + } + + protected int getItemViewSize() { + return views.size(); + } + + @Override + public int getCount() { + return childSize; + } + + @Override + public int getItemPosition(Object object) { + if (views.isEmpty()) { + return POSITION_NONE; + } + int index = views.indexOf(object); + if (index < 0) { + return POSITION_NONE; + } + return index; + } + + @NonNull + @Override + public Object instantiateItem(ViewGroup container, int position) { + View pageItemView = getPageItemView(position); + if (pageItemView.getParent() == null) { + container.addView(pageItemView, new HippyPager.LayoutParams()); + viewPager.triggerRequestLayout(); + } + return pageItemView; + } + + protected View getPageItemView(int position) { + View pageItemView = null; + if (position >= 0 && position < views.size()) { + pageItemView = views.get(position); + } + if (pageItemView == null) { + throw new NullPointerException("Can not instantiateItem,position:" + position + ",size:" + views.size()); + } + return pageItemView; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (object instanceof View) { + View view = (View) object; + view.layout(0, 0, 0, 0); + container.removeView(view); + } + } + + @Override + public void setPrimaryItem(@NonNull ViewGroup container, int position, + @NonNull Object object) { + super.setPrimaryItem(container, position, object); + currentItemObj = object; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerController.java new file mode 100644 index 00000000000..f8ae03dd004 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerController.java @@ -0,0 +1,206 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import com.tencent.mtt.hippy.annotation.HippyController; +import com.tencent.mtt.hippy.annotation.HippyControllerProps; +import com.tencent.mtt.hippy.common.HippyArray; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.dom.node.NodeProps; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.uimanager.HippyViewController; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.viewpager.HippyViewPagerItem; + + +/** + * Created on 2021/7/23. + */ +@HippyController(name = HippyPagerController.CLASS_NAME) +public class HippyPagerController extends HippyViewController { + + public static final String CLASS_NAME = "ViewPager"; + + private static final String TAG = "HippyViewPagerController"; + + private static final String FUNC_SET_PAGE = "setPage"; + private static final String FUNC_SET_PAGE_WITHOUT_ANIM = "setPageWithoutAnimation"; + + private static final String FUNC_SET_INDEX = "setIndex"; + private static final String FUNC_NEXT_PAGE = "next"; + private static final String FUNC_PREV_PAGE = "prev"; + + @Override + protected View createViewImpl(Context context) { + return new HippyPager(context); + } + + @Override + protected View createViewImpl(Context context, HippyMap iniProps) { + boolean isVertical = false; + if (iniProps != null) { + if ((iniProps.containsKey("direction") && iniProps.getString("direction").equals("vertical")) + || iniProps.containsKey("vertical")) { + isVertical = true; + } + } + + return new HippyPager(context, isVertical); + } + + @Override + public View getChildAt(HippyPager hippyViewPager, int i) { + return hippyViewPager.getViewFromAdapter(i); + } + + @Override + public int getChildCount(HippyPager hippyViewPager) { + return hippyViewPager.getAdapter().getCount(); + } + + @Override + protected void addView(ViewGroup parentView, View view, int index) { + LogUtils.d(TAG, "addView: " + parentView.hashCode() + ", index=" + index); + if (parentView instanceof HippyPager && view instanceof HippyViewPagerItem) { + HippyPager hippyViewPager = (HippyPager) parentView; + hippyViewPager.addViewToAdapter((HippyViewPagerItem) view, index); + } else { + LogUtils.e(TAG, "add view got invalid params"); + } + } + + @Override + protected void deleteChild(ViewGroup parentView, View childView) { + LogUtils.d(TAG, "deleteChild: " + parentView.hashCode()); + if (parentView instanceof HippyPager && childView instanceof HippyViewPagerItem) { + ((HippyPager) parentView).removeViewFromAdapter((HippyViewPagerItem) childView); + } else { + LogUtils.e(TAG, "delete view got invalid params"); + } + } + + @Override + protected void onManageChildComplete(HippyPager viewPager) { + viewPager.setChildCountAndUpdate(viewPager.getAdapter().getItemViewSize()); + } + + @HippyControllerProps(name = "initialPage", defaultNumber = 0, defaultType = HippyControllerProps.NUMBER) + public void setInitialPage(HippyPager parent, int initialPage) { + parent.setInitialPageIndex(initialPage); + } + + @HippyControllerProps(name = "scrollEnabled", defaultBoolean = true, defaultType = HippyControllerProps.BOOLEAN) + public void setScrollEnabled(HippyPager viewPager, boolean value) { + viewPager.setScrollEnabled(value); + } + + @HippyControllerProps(name = "pageMargin", defaultNumber = 0, defaultType = HippyControllerProps.NUMBER) + public void setPageMargin(HippyPager pager, float margin) { + pager.setPageMargin((int) PixelUtil.dp2px(margin)); + } + + @HippyControllerProps(name = NodeProps.OVERFLOW, defaultType = HippyControllerProps.STRING, defaultString = "visible") + public void setOverflow(HippyPager pager, String overflow) { + pager.setOverflow(overflow); + } + + @Override + public void dispatchFunction(HippyPager view, String functionName, HippyArray var) { + if (view == null) { + return; + } + + int curr = view.getCurrentItem(); + + switch (functionName) { + case FUNC_SET_PAGE: + if (var != null) { + Object selected = var.get(0); + if (selected instanceof Integer) { + view.switchToPage((int) selected, true); + } + } + break; + case FUNC_SET_PAGE_WITHOUT_ANIM: + if (var != null) { + Object selected = var.get(0); + if (selected instanceof Integer) { + view.switchToPage((int) selected, false); + } + } + break; + case FUNC_SET_INDEX: + if (var != null && var.size() > 0) { + HippyMap paramsMap = var.getMap(0); + if (paramsMap != null && paramsMap.size() > 0 && paramsMap.containsKey("index")) { + int index = paramsMap.getInt("index"); + boolean animated = !paramsMap.containsKey("animated") || paramsMap.getBoolean("animated"); + view.switchToPage(index, animated); + } + } + break; + case FUNC_NEXT_PAGE: + int total = view.getAdapter().getCount(); + if (curr < total - 1) { + view.switchToPage(curr + 1, true); + } + break; + case FUNC_PREV_PAGE: + if (curr > 0) { + view.switchToPage(curr - 1, true); + } + break; + default: + break; + } + } + + @Override + public void dispatchFunction(HippyPager view, String functionName, HippyArray params, Promise promise) { + if (view == null) { + return; + } + + switch (functionName) { + case FUNC_SET_INDEX: + if (params != null && params.size() > 0) { + HippyMap paramsMap = params.getMap(0); + if (paramsMap != null && paramsMap.size() > 0 && paramsMap.containsKey("index")) { + int index = paramsMap.getInt("index"); + view.setCallBackPromise(promise); + boolean animated = !paramsMap.containsKey("animated") || paramsMap.getBoolean("animated"); + view.switchToPage(index, animated); + return; + } + } + + if (promise != null) { + String msg = "invalid parameter!"; + HippyMap resultMap = new HippyMap(); + resultMap.pushString("msg", msg); + promise.resolve(resultMap); + } + break; + default: + break; + } + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerPageChangeListener.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerPageChangeListener.java new file mode 100644 index 00000000000..109d06f5afe --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/HippyPagerPageChangeListener.java @@ -0,0 +1,117 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager; + +import android.view.View; +import androidx.viewpager.widget.ViewPager; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.modules.Promise; +import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageItemExposureEvent; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageScrollEvent; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageScrollStateChangedEvent; +import com.tencent.mtt.hippy.views.viewpager.event.HippyPageSelectedEvent; + +/** + * Created on 2021/7/23. + */ + +public class HippyPagerPageChangeListener implements ViewPager.OnPageChangeListener { + + public static final String IDLE = "idle"; + public static final String DRAGGING = "dragging"; + public static final String SETTLING = "settling"; + private HippyPageScrollEvent pageScrollEmitter; + private HippyPageScrollStateChangedEvent pageScrollStateChangeEmitter; + private HippyPageSelectedEvent pageSelectedEmitter; + private int lastPageIndex; + private int currPageIndex; + private HippyPager hippyPager; + + public HippyPagerPageChangeListener(HippyPager pager) { + hippyPager = pager; + pageScrollEmitter = new HippyPageScrollEvent(pager); + pageScrollStateChangeEmitter = new HippyPageScrollStateChangedEvent(pager); + pageSelectedEmitter = new HippyPageSelectedEvent(pager); + lastPageIndex = 0; + currPageIndex = 0; + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + pageScrollEmitter.send(position, positionOffset); + } + + @Override + public void onPageSelected(int position) { + currPageIndex = position; + pageSelectedEmitter.send(position); + if (hippyPager != null) { + View currView = hippyPager.getViewFromAdapter(currPageIndex); + HippyPageItemExposureEvent eventWillAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_WILL_APPEAR); + eventWillAppear.send(currView, currPageIndex); + View lastView = hippyPager.getViewFromAdapter(lastPageIndex); + HippyPageItemExposureEvent eventWillDisAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_WILL_DISAPPEAR); + eventWillDisAppear.send(lastView, lastPageIndex); + } + } + + private void onScrollStateChangeToIdle() { + if (hippyPager != null && currPageIndex != lastPageIndex) { + Promise promise = hippyPager.getCallBackPromise(); + if (promise != null) { + String msg = "on set index successful!"; + HippyMap resultMap = new HippyMap(); + resultMap.pushString("msg", msg); + promise.resolve(resultMap); + hippyPager.setCallBackPromise(null); + } + View currView = hippyPager.getViewFromAdapter(currPageIndex); + HippyPageItemExposureEvent eventWillAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_DID_APPEAR); + eventWillAppear.send(currView, currPageIndex); + View lastView = hippyPager.getViewFromAdapter(lastPageIndex); + HippyPageItemExposureEvent eventWillDisAppear = new HippyPageItemExposureEvent( + HippyPageItemExposureEvent.EVENT_PAGER_ITEM_DID_DISAPPEAR); + eventWillDisAppear.send(lastView, lastPageIndex); + lastPageIndex = currPageIndex; + } + } + + @Override + public void onPageScrollStateChanged(int newState) { + LogUtils.i("HippyPagerStateChanged", "onPageScrollStateChanged newState=" + newState); + String pageScrollState; + switch (newState) { + case ViewPager.SCROLL_STATE_IDLE: + pageScrollState = IDLE; + onScrollStateChangeToIdle(); + break; + case ViewPager.SCROLL_STATE_DRAGGING: + pageScrollState = DRAGGING; + break; + case ViewPager.SCROLL_STATE_SETTLING: + pageScrollState = SETTLING; + break; + default: + throw new IllegalStateException("Unsupported pageScrollState"); + } + pageScrollStateChangeEmitter.send(pageScrollState); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/transform/VerticalPageTransformer.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/transform/VerticalPageTransformer.java new file mode 100644 index 00000000000..6c352fae7ba --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/hippypager/transform/VerticalPageTransformer.java @@ -0,0 +1,36 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.hippypager.transform; + +import android.view.View; +import androidx.viewpager.widget.ViewPager; + +/** + * Created on 2021/7/23. + */ +public class VerticalPageTransformer implements ViewPager.PageTransformer { + + @Override + public void transformPage(View view, float position) { + if (position >= -1 && position <= 1) { + view.setTranslationX(view.getWidth() * -position); + float yPosition = position * view.getHeight(); + view.setTranslationY(yPosition); + } + } + +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyRecyclerItemViewController.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyRecyclerItemViewController.java new file mode 100644 index 00000000000..17f43b2aa83 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/list/HippyRecyclerItemViewController.java @@ -0,0 +1,49 @@ +/* Tencent is pleased to support the open source community by making Hippy available. + * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.mtt.hippy.views.list; + +import android.content.Context; +import android.view.View; +import com.tencent.mtt.hippy.HippyRootView; +import com.tencent.mtt.hippy.annotation.HippyController; +import com.tencent.mtt.hippy.common.HippyMap; +import com.tencent.mtt.hippy.uimanager.ControllerManager; +import com.tencent.mtt.hippy.uimanager.HippyViewController; +import com.tencent.mtt.hippy.uimanager.ListItemRenderNode; +import com.tencent.mtt.hippy.uimanager.RecyclerItemRenderNode; +import com.tencent.mtt.hippy.uimanager.RenderNode; + +/** + * Created by leonardgong on 2017/12/7 0007. + */ + +@HippyController(name = HippyRecyclerItemViewController.CLASS_NAME, isLazyLoad = true) +public class HippyRecyclerItemViewController extends HippyViewController { + + public static final String CLASS_NAME = "ListViewItem"; + + @Override + protected View createViewImpl(Context context) { + return new HippyListItemView(context); + } + + @Override + public RenderNode createRenderNode(int id, HippyMap props, String className, HippyRootView hippyRootView, + ControllerManager controllerManager, boolean lazy) { + return new RecyclerItemRenderNode(id, props, className, hippyRootView, controllerManager, lazy); + } +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java index ed4d30a36ba..efc12e3201f 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/refresh/RefreshWrapper.java @@ -25,6 +25,7 @@ import com.tencent.mtt.hippy.common.HippyMap; import com.tencent.mtt.hippy.uimanager.HippyViewEvent; import com.tencent.mtt.hippy.utils.PixelUtil; +import com.tencent.mtt.hippy.views.hippylist.HippyRecyclerViewWrapper; import com.tencent.mtt.hippy.views.view.HippyViewGroup; import com.tencent.mtt.supportui.views.recyclerview.RecyclerViewBase; @@ -104,6 +105,9 @@ float getCompactScrollY() { if (mContentView instanceof RecyclerViewBase) { return ((RecyclerViewBase) mContentView).getOffsetY(); } + if (mContentView instanceof HippyRecyclerViewWrapper) { + return ((HippyRecyclerViewWrapper) mContentView).computeVerticalScrollOffset(); + } return mContentView.getScrollY(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java index 4209586d1b3..2d1032ab8c5 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/hippy/views/scroll/HippyScrollViewEventHelper.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.tencent.mtt.hippy.views.scroll; import android.view.ViewGroup; @@ -25,68 +26,71 @@ @SuppressWarnings({"deprecation", "unused"}) public class HippyScrollViewEventHelper { - public static final long MOMENTUM_DELAY = 20; - public static final String EVENT_TYPE_BEGIN_DRAG = "onScrollBeginDrag"; - public static final String EVENT_TYPE_END_DRAG = "onScrollEndDrag"; - public static final String EVENT_TYPE_SCROLL = "onScroll"; - public static final String EVENT_TYPE_MOMENTUM_BEGIN = "onMomentumScrollBegin"; - public static final String EVENT_TYPE_MOMENTUM_END = "onMomentumScrollEnd"; - public static final String EVENT_TYPE_ANIMATION_END = "onScrollAnimationEnd"; - - public static void emitScrollEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_SCROLL); - } - - public static void emitScrollBeginDragEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_BEGIN_DRAG); - } - - public static void emitScrollEndDragEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_END_DRAG); - } - - public static void emitScrollMomentumBeginEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_MOMENTUM_BEGIN); - } - - public static void emitScrollMomentumEndEvent(ViewGroup view) { - emitScrollEvent(view, EVENT_TYPE_MOMENTUM_END); - } - - protected static void emitScrollEvent(ViewGroup view, String scrollEventType) { - if (view == null) { - return; + + public static final long MOMENTUM_DELAY = 20; + public static final String EVENT_TYPE_BEGIN_DRAG = "onScrollBeginDrag"; + public static final String EVENT_TYPE_END_DRAG = "onScrollEndDrag"; + public static final String EVENT_TYPE_SCROLL = "onScroll"; + public static final String EVENT_TYPE_MOMENTUM_BEGIN = "onMomentumScrollBegin"; + public static final String EVENT_TYPE_MOMENTUM_END = "onMomentumScrollEnd"; + public static final String EVENT_TYPE_ANIMATION_END = "onScrollAnimationEnd"; + public static final String EVENT_TYPE_REFRESH = "onRefresh"; + public static final String EVENT_ON_END_REACHED = "onEndReached"; + + public static void emitScrollEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_SCROLL); + } + + public static void emitScrollBeginDragEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_BEGIN_DRAG); + } + + public static void emitScrollEndDragEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_END_DRAG); + } + + public static void emitScrollMomentumBeginEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_MOMENTUM_BEGIN); } - HippyMap contentInset = new HippyMap(); - contentInset.pushDouble("top", 0); - contentInset.pushDouble("bottom", 0); - contentInset.pushDouble("left", 0); - contentInset.pushDouble("right", 0); - - HippyMap contentOffset = new HippyMap(); - contentOffset.pushDouble("x", PixelUtil.px2dp(view.getScrollX())); - contentOffset.pushDouble("y", PixelUtil.px2dp(view.getScrollY())); - - HippyMap contentSize = new HippyMap(); - contentSize.pushDouble("width", PixelUtil - .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getWidth() : view.getWidth())); - contentSize.pushDouble("height", PixelUtil - .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getHeight() : view.getHeight())); - - HippyMap layoutMeasurement = new HippyMap(); - layoutMeasurement.pushDouble("width", PixelUtil.px2dp(view.getWidth())); - layoutMeasurement.pushDouble("height", PixelUtil.px2dp(view.getHeight())); - - HippyMap event = new HippyMap(); - event.pushMap("contentInset", contentInset); - event.pushMap("contentOffset", contentOffset); - event.pushMap("contentSize", contentSize); - event.pushMap("layoutMeasurement", layoutMeasurement); - - if (view.getContext() instanceof HippyInstanceContext) { - HippyEngineContext context = ((HippyInstanceContext) view.getContext()).getEngineContext(); - context.getModuleManager().getJavaScriptModule(EventDispatcher.class) - .receiveUIComponentEvent(view.getId(), scrollEventType, event); + + public static void emitScrollMomentumEndEvent(ViewGroup view) { + emitScrollEvent(view, EVENT_TYPE_MOMENTUM_END); + } + + protected static void emitScrollEvent(ViewGroup view, String scrollEventType) { + if (view == null) { + return; + } + HippyMap contentInset = new HippyMap(); + contentInset.pushDouble("top", 0); + contentInset.pushDouble("bottom", 0); + contentInset.pushDouble("left", 0); + contentInset.pushDouble("right", 0); + + HippyMap contentOffset = new HippyMap(); + contentOffset.pushDouble("x", PixelUtil.px2dp(view.getScrollX())); + contentOffset.pushDouble("y", PixelUtil.px2dp(view.getScrollY())); + + HippyMap contentSize = new HippyMap(); + contentSize.pushDouble("width", PixelUtil + .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getWidth() : view.getWidth())); + contentSize.pushDouble("height", PixelUtil + .px2dp(view.getChildCount() > 0 ? view.getChildAt(0).getHeight() : view.getHeight())); + + HippyMap layoutMeasurement = new HippyMap(); + layoutMeasurement.pushDouble("width", PixelUtil.px2dp(view.getWidth())); + layoutMeasurement.pushDouble("height", PixelUtil.px2dp(view.getHeight())); + + HippyMap event = new HippyMap(); + event.pushMap("contentInset", contentInset); + event.pushMap("contentOffset", contentOffset); + event.pushMap("contentSize", contentSize); + event.pushMap("layoutMeasurement", layoutMeasurement); + + if (view.getContext() instanceof HippyInstanceContext) { + HippyEngineContext context = ((HippyInstanceContext) view.getContext()).getEngineContext(); + context.getModuleManager().getJavaScriptModule(EventDispatcher.class) + .receiveUIComponentEvent(view.getId(), scrollEventType, event); + } } - } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java index 62183c40b0a..ed17445ef5b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderAttachListener.java @@ -15,17 +15,21 @@ */ package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.view.View; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +/** + * Created by on 2021/1/12. + * Description + */ public interface IHeaderAttachListener { - /** - * header被摘下来,需要对header进行还原或者回收对处理 - * - * @param aboundHeader HeaderView对应的Holder - * @param currentHeaderView headerView的实体内容 - */ - void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView); + /** + * header被摘下来,需要对header进行还原或者回收对处理 + * + * @param aboundHeader HeaderView对应的Holder + * @param currentHeaderView headerView的实体内容 + */ + void onHeaderDetached(ViewHolder aboundHeader, View currentHeaderView); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java index 6a89970ff3f..6881fbf733b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderHost.java @@ -19,11 +19,16 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.FrameLayout.LayoutParams; +/** + * Created by on 2021/1/12. + * Description + * HeaderView的宿主,用于HeaderView的挂载,监听挂载的回调 + */ public interface IHeaderHost { - void attachHeader(View headerView, LayoutParams layoutParams); + void attachHeader(View headerView, LayoutParams layoutParams); - void addOnLayoutListener(OnGlobalLayoutListener listener); + void addOnLayoutListener(OnGlobalLayoutListener listener); - void removeOnLayoutListener(OnGlobalLayoutListener listener); + void removeOnLayoutListener(OnGlobalLayoutListener listener); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java index ddbd982a78e..8c562502887 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IHeaderViewFactory.java @@ -18,7 +18,11 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; +/** + * Created by on 2021/1/6. + * Description + */ public interface IHeaderViewFactory { - ViewHolder getHeaderForPosition(int position); + ViewHolder getHeaderForPosition(int position); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java index c90952b0716..585e20a32c0 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyAbleItem.java @@ -16,7 +16,11 @@ package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; +/** + * Created by on 2020/12/29. + * Description + */ public interface IStickyAbleItem { - boolean isStickyItem(); + boolean isStickyItem(); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java index 89076f338fe..9426d62e8c3 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/IStickyItemsProvider.java @@ -16,7 +16,11 @@ package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; +/** + * Created by on 2020/12/29. + * Description + */ public interface IStickyItemsProvider { - boolean isStickyPosition(int position); + boolean isStickyPosition(int position); } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickViewListener.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickViewListener.java new file mode 100644 index 00000000000..f205bb3b262 --- /dev/null +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickViewListener.java @@ -0,0 +1,12 @@ +package com.tencent.mtt.nxeasy.recyclerview.helper.skikcy; + +/** + * Created by on 2021/8/24. + * Description + */ +public interface StickViewListener { + + void onStickAttached(int stickyPosition); + + void onStickDetached(int stickyPosition); +} diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java index b02b693c595..6d9beb689bf 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyHeaderHelper.java @@ -19,224 +19,262 @@ import static androidx.recyclerview.widget.RecyclerView.HORIZONTAL; import static androidx.recyclerview.widget.RecyclerView.VERTICAL; -import androidx.recyclerview.widget.EasyRecyclerView; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout.LayoutParams; +import androidx.recyclerview.widget.EasyRecyclerView; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.OnScrollListener; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +/** + * Created on 2020/12/29. Description 一个RecyclerView的StickyHeader控制器, + * 通过recyclerView的onScroll事件,1、从列表中选择吸顶的View,2、挂载到顶部,3、设置吸顶View的OffSet + * 吸顶的headerView是从recyclerView里面的Item的子View抠出来挂载的,需要吸顶属性的ItemView包装一层ViewGroup + */ public class StickyHeaderHelper extends OnScrollListener implements - ViewTreeObserver.OnGlobalLayoutListener { - - private static final int INVALID_POSITION = -1; - private final IHeaderAttachListener headerAttachListener; - private EasyRecyclerView recyclerView; - private IStickyItemsProvider stickyItemsProvider; - private StickyViewFactory stickyViewFactory; - private ViewHolder headerOrgViewHolder; - private boolean orgViewHolderCanRecyclable = false; - private View currentHeaderView; - private int orientation; - private int currentStickPos = -1; - private IHeaderHost headerHost; - - public StickyHeaderHelper(final EasyRecyclerView recyclerView, - IStickyItemsProvider stickyItemsProvider, - IHeaderAttachListener headerAttachListener, IHeaderHost headerHost) { - this.recyclerView = recyclerView; - this.headerAttachListener = headerAttachListener; - this.stickyItemsProvider = stickyItemsProvider; - stickyViewFactory = new StickyViewFactory(recyclerView); - this.headerHost = headerHost; - orientation = recyclerView.getLayoutManager().canScrollVertically() ? VERTICAL : HORIZONTAL; - } - - /** - * 1、寻找stickyHeader 2、挂载stickyHeader 3、设置stickyHeader的偏移 - */ - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int newStickyPosition = getStickyItemPosition(); - if (currentStickPos != newStickyPosition) { - detachSticky(); - attachSticky(newStickyPosition); - } - offsetSticky(); - } - - /** - * 如果当前stickHolder和新的stickyHolder 不一样,那么把当前的stickyHolder删除掉,并还原HeaderView的Translation - */ - private void detachSticky() { - if (headerOrgViewHolder != null) { - removeViewFromParent(this.currentHeaderView); - currentHeaderView.setTranslationY(0); - currentHeaderView.setTranslationX(0); - returnHeaderBackToList(); - headerHost.removeOnLayoutListener(this); - } - currentStickPos = -1; - headerOrgViewHolder = null; - } - - /** - * 还原Header到List中去 1、ViewHolder正好是之前的ViewHolder,直接将headerView返回给headerOrgContainer - * 2、position相同,但是ViewHolder已经是不同了,出现在header的Item滑动到屏幕外,又滑回来,重新创建了一个ViewHolder - * 3、对于被顶出去的headView,是无法还原到list中的,需要把headView进行回收处理,如果不回收,Hippy场景无法重新创建View - */ - private void returnHeaderBackToList() { - headerOrgViewHolder.setIsRecyclable(orgViewHolderCanRecyclable); - if (headerAttachListener != null) { - headerAttachListener.onHeaderDetached(headerOrgViewHolder, currentHeaderView); - } else { - ViewHolder viewHolderToReturn = recyclerView - .findViewHolderForAdapterPosition(headerOrgViewHolder.getAdapterPosition()); - if (viewHolderToReturn != null && viewHolderToReturn.itemView instanceof ViewGroup) { - ViewGroup itemView = (ViewGroup) viewHolderToReturn.itemView; - //已经有孩子了,就不要加了,这个可能是新创建的ViewHolder已经有了内容 - if (itemView.getChildCount() <= 0) { - itemView.addView(this.currentHeaderView); - } - } - } - } - - /** - * 将stickyItemPosition对应的View挂载到RecyclerView的父亲上面 - */ - private void attachSticky(int newStickyPosition) { - if (newStickyPosition != INVALID_POSITION) { - headerOrgViewHolder = stickyViewFactory.getHeaderForPosition(newStickyPosition); - currentStickPos = newStickyPosition; - Log.d("returnHeader", "attachSticky:" + headerOrgViewHolder); - currentHeaderView = ((ViewGroup) headerOrgViewHolder.itemView).getChildAt(0); - removeViewFromParent(currentHeaderView); - //内容被取走了,不能被回收,避免view滑出屏幕,回收再利用,此时已经不能再被别人用了 - orgViewHolderCanRecyclable = headerOrgViewHolder.isRecyclable(); - headerOrgViewHolder.setIsRecyclable(false); - currentHeaderView.setVisibility(View.INVISIBLE); - LayoutParams layoutParams = new LayoutParams( - LayoutParams.MATCH_PARENT, 0); - ViewGroup.LayoutParams lp = headerOrgViewHolder.itemView.getLayoutParams(); - layoutParams.height = lp != null ? lp.height : LayoutParams.WRAP_CONTENT; - headerHost.addOnLayoutListener(this); - headerHost.attachHeader(currentHeaderView, layoutParams); - } - } - - /** - * 设置吸顶的View的偏移 在下一个吸顶view和当前吸顶的view交汇的时候,需要把当前吸顶view往上面移动,慢慢会把当前的吸顶view顶出屏幕 - */ - private void offsetSticky() { - if (headerOrgViewHolder != null) { - float offset = getOffset(findNextSticky(currentStickPos)); - if (orientation == VERTICAL) { - currentHeaderView.setTranslationY(offset); - } else { - currentHeaderView.setTranslationX(offset); - } - } - } - - /** - * 找到屏幕中,下一个即将吸顶的view,主要用于计算当前吸顶的HeaderView的Offset, 下一个即将吸顶的View会慢慢把当前正在吸顶的HeaderView慢慢顶出屏幕外 - */ - private View findNextSticky(int currentStickyPos) { - for (int i = 0; i < recyclerView.getChildCount(); i++) { - View nextStickyView = recyclerView.getChildAt(i); - int nextStickyPos = recyclerView.getChildLayoutPosition(nextStickyView); - if (nextStickyPos > currentStickyPos && stickyItemsProvider - .isStickyPosition(nextStickyPos)) { - return nextStickyView; - } - } - return null; - } - - /** - * 当nextStickyView和当前stickyView重叠的时候,是应该把当前的view移出屏幕外 支持水平排版和垂直排版 - */ - private float getOffset(View nextStickyView) { - float offset = 0; - View stickView = this.currentHeaderView; - if (stickView != null && nextStickyView != null) { - if (orientation == VERTICAL) { - if (nextStickyView.getY() < stickView.getHeight()) { - offset = nextStickyView.getY() - stickView.getHeight(); - } - } else { - if (stickView.isShown()) { - offset = -stickView.getWidth(); - } else if (nextStickyView.getX() < stickView.getWidth()) { - offset = stickView.getX() - stickView.getWidth(); - } - } - } - return offset; - } - - /** - * 高度确定后,设置HeaderView为可见状态,并且重新刷新offset的正确位置,解决下拉header上屏的闪烁问题 - */ - @Override - public void onGlobalLayout() { - if (currentHeaderView != null) { - currentHeaderView.setVisibility(View.VISIBLE); - offsetSticky(); - } - } - - /** - * 找到距离顶部最近的一个stickyItem的位置 - * - * @return INVALID_POSITION,没有找到stickyItem - */ - public int getStickyItemPosition() { - if (recyclerView.getChildCount() <= 0) { - return INVALID_POSITION; - } - int positionToSticky = INVALID_POSITION; - int startPosition = recyclerView.getFirstChildPosition(); - View firstView = recyclerView.getChildAt(0); - if (firstView.getY() >= 0) { - startPosition--;//当前view已经完全露出,需要往从前面一个开始寻找 - } - for (int i = startPosition; i >= 0; i--) { - if (stickyItemsProvider.isStickyPosition(i)) { - positionToSticky = i; - break; - } - } - if (positionToSticky != INVALID_POSITION) { - if (positionToSticky != recyclerView.getFirstChildPosition()) { - //positionToSticky已经被滑出屏幕,此时positionToSticky可以直接返回 + ViewTreeObserver.OnGlobalLayoutListener { + + private static final int INVALID_POSITION = -1; + private final IHeaderAttachListener headerAttachListener; + private EasyRecyclerView recyclerView; + private IStickyItemsProvider stickyItemsProvider; + private StickyViewFactory stickyViewFactory; + private ViewHolder headerOrgViewHolder; + private boolean orgViewHolderCanRecyclable = false; + private View currentHeaderView; + private int orientation; + private int currentStickPos = -1; + private IHeaderHost headerHost; + private StickViewListener stickViewListener; + private boolean isUpdateStickyHolderWhenLayout; + + public StickyHeaderHelper(final EasyRecyclerView recyclerView, + IStickyItemsProvider stickyItemsProvider, + IHeaderAttachListener headerAttachListener, IHeaderHost headerHost) { + this.recyclerView = recyclerView; + this.headerAttachListener = headerAttachListener; + this.stickyItemsProvider = stickyItemsProvider; + stickyViewFactory = new StickyViewFactory(recyclerView); + this.headerHost = headerHost; + orientation = recyclerView.getLayoutManager().canScrollVertically() ? VERTICAL : HORIZONTAL; + } + + /** + * 1、寻找stickyHeader 2、挂载stickyHeader 3、设置stickyHeader的偏移 + */ + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int newStickyPosition = getStickyItemPosition(); + if (currentStickPos != newStickyPosition) { + detachSticky(); + attachSticky(newStickyPosition); + } + offsetSticky(); + } + + public void setStickViewListener(StickViewListener stickViewListener) { + this.stickViewListener = stickViewListener; + } + + public void setUpdateStickyViewWhenLayout(boolean bindStickyHolderWhenLayout) { + isUpdateStickyHolderWhenLayout = bindStickyHolderWhenLayout; + } + + /** + * 如果当前stickHolder和新的stickyHolder 不一样,那么把当前的stickyHolder删除掉,并还原HeaderView的Translation + */ + private void detachSticky() { + if (headerOrgViewHolder != null) { + removeViewFromParent(this.currentHeaderView); + currentHeaderView.setTranslationY(0); + currentHeaderView.setTranslationX(0); + returnHeaderBackToList(); + headerHost.removeOnLayoutListener(this); + notifyStickDetached(); + } + currentStickPos = -1; + headerOrgViewHolder = null; + } + + private void notifyStickDetached() { + if (stickViewListener != null) { + stickViewListener.onStickDetached(currentStickPos); + } + } + + /** + * 还原Header到List中去 1、ViewHolder正好是之前的ViewHolder,直接将headerView返回给headerOrgContainer + * 2、position相同,但是ViewHolder已经是不同了,出现在header的Item滑动到屏幕外,又滑回来,重新创建了一个ViewHolder + * 3、对于被顶出去的headView,是无法还原到list中的,需要把headView进行回收处理,如果不回收,Hippy场景无法重新创建View + */ + private void returnHeaderBackToList() { + headerOrgViewHolder.setIsRecyclable(orgViewHolderCanRecyclable); + if (headerAttachListener != null) { + headerAttachListener.onHeaderDetached(headerOrgViewHolder, currentHeaderView); + } else { + ViewHolder viewHolderToReturn = recyclerView + .findViewHolderForAdapterPosition(headerOrgViewHolder.getAdapterPosition()); + if (viewHolderToReturn != null && viewHolderToReturn.itemView instanceof ViewGroup) { + ViewGroup itemView = (ViewGroup) viewHolderToReturn.itemView; + //已经有孩子了,就不要加了,这个可能是新创建的ViewHolder已经有了内容 + if (itemView.getChildCount() <= 0) { + itemView.addView(this.currentHeaderView); + } + } + } + } + + /** + * 将stickyItemPosition对应的View挂载到RecyclerView的父亲上面 + */ + private void attachSticky(int newStickyPosition) { + if (newStickyPosition != INVALID_POSITION) { + headerOrgViewHolder = stickyViewFactory.getHeaderForPosition(newStickyPosition); + currentStickPos = newStickyPosition; + Log.d("returnHeader", "attachSticky:" + headerOrgViewHolder); + currentHeaderView = ((ViewGroup) headerOrgViewHolder.itemView).getChildAt(0); + removeViewFromParent(currentHeaderView); + //内容被取走了,不能被回收,避免view滑出屏幕,回收再利用,此时已经不能再被别人用了 + orgViewHolderCanRecyclable = headerOrgViewHolder.isRecyclable(); + headerOrgViewHolder.setIsRecyclable(false); + currentHeaderView.setVisibility(View.INVISIBLE); + LayoutParams layoutParams = new LayoutParams( + LayoutParams.MATCH_PARENT, 0); + ViewGroup.LayoutParams lp = headerOrgViewHolder.itemView.getLayoutParams(); + layoutParams.height = lp != null ? lp.height : LayoutParams.WRAP_CONTENT; + headerHost.addOnLayoutListener(this); + headerHost.attachHeader(currentHeaderView, layoutParams); + notifyStickAttached(newStickyPosition); + } + } + + private void notifyStickAttached(int stickyPosition) { + if (stickViewListener != null) { + stickViewListener.onStickAttached(stickyPosition); + } + } + + /** + * 设置吸顶的View的偏移 在下一个吸顶view和当前吸顶的view交汇的时候,需要把当前吸顶view往上面移动,慢慢会把当前的吸顶view顶出屏幕 + */ + private void offsetSticky() { + if (headerOrgViewHolder != null) { + float offset = getOffset(findNextSticky(currentStickPos)); + if (orientation == VERTICAL) { + currentHeaderView.setTranslationY(offset); + } else { + currentHeaderView.setTranslationX(offset); + } + } + } + + /** + * 找到屏幕中,下一个即将吸顶的view,主要用于计算当前吸顶的HeaderView的Offset, 下一个即将吸顶的View会慢慢把当前正在吸顶的HeaderView慢慢顶出屏幕外 + */ + private View findNextSticky(int currentStickyPos) { + for (int i = 0; i < recyclerView.getChildCount(); i++) { + View nextStickyView = recyclerView.getChildAt(i); + int nextStickyPos = recyclerView.getChildLayoutPosition(nextStickyView); + if (nextStickyPos > currentStickyPos && stickyItemsProvider + .isStickyPosition(nextStickyPos)) { + return nextStickyView; + } + } + return null; + } + + /** + * 当nextStickyView和当前stickyView重叠的时候,是应该把当前的view移出屏幕外 支持水平排版和垂直排版 + */ + private float getOffset(View nextStickyView) { + float offset = 0; + View stickView = this.currentHeaderView; + if (stickView != null && nextStickyView != null) { + if (orientation == VERTICAL) { + if (nextStickyView.getY() < stickView.getHeight()) { + offset = nextStickyView.getY() - stickView.getHeight(); + } + } else { + if (stickView.isShown()) { + offset = -stickView.getWidth(); + } else if (nextStickyView.getX() < stickView.getWidth()) { + offset = stickView.getX() - stickView.getWidth(); + } + } + } + return offset; + } + + /** + * 高度确定后,设置HeaderView为可见状态,并且重新刷新offset的正确位置,解决下拉header上屏的闪烁问题 + */ + @Override + public void onGlobalLayout() { + if (currentHeaderView != null) { + currentHeaderView.setVisibility(View.VISIBLE); + offsetSticky(); + if (isUpdateStickyHolderWhenLayout) { + updateStickHolder(); + } + } + } + + /** + * 找到距离顶部最近的一个stickyItem的位置 + * + * @return INVALID_POSITION,没有找到stickyItem + */ + public int getStickyItemPosition() { + if (recyclerView.getChildCount() <= 0) { + return INVALID_POSITION; + } + int positionToSticky = INVALID_POSITION; + int startPosition = recyclerView.getFirstChildPosition(); + View firstView = recyclerView.getChildAt(0); + if (firstView.getY() >= 0) { + startPosition--;//当前view已经完全露出,需要往从前面一个开始寻找 + } + for (int i = startPosition; i >= 0; i--) { + if (stickyItemsProvider.isStickyPosition(i)) { + positionToSticky = i; + break; + } + } + if (positionToSticky != INVALID_POSITION) { + if (positionToSticky != recyclerView.getFirstChildPosition()) { + //positionToSticky已经被滑出屏幕,此时positionToSticky可以直接返回 + return positionToSticky; + } else { + //stickyItem和第一个孩子位置一样,如果完全和吸顶位置重合,不需要进行吸顶 + positionToSticky = + !headerAwayFromEdge(firstView) ? INVALID_POSITION : positionToSticky; + } + } return positionToSticky; - } else { - //stickyItem和第一个孩子位置一样,如果完全和吸顶位置重合,不需要进行吸顶 - positionToSticky = - !headerAwayFromEdge(firstView) ? INVALID_POSITION : positionToSticky; - } - } - return positionToSticky; - } - - /** - * headerToCopy == null表示,headerToCopy 已经完全移除屏幕外 headerToCopy != null ,getY()<0 部分移动屏幕外 - * - * @param headerToCopy 即将被选中吸顶的view - */ - private boolean headerAwayFromEdge(View headerToCopy) { - return headerToCopy != null && (orientation == VERTICAL ? headerToCopy.getY() < 0 - : headerToCopy.getX() < 0); - } - - private void removeViewFromParent(View view) { - if (view.getParent() instanceof ViewGroup) { - ((ViewGroup) view.getParent()).removeView(view); - } - } + } + + /** + * headerToCopy == null表示,headerToCopy 已经完全移除屏幕外 headerToCopy != null ,getY()<0 部分移动屏幕外 + * + * @param headerToCopy 即将被选中吸顶的view + */ + private boolean headerAwayFromEdge(View headerToCopy) { + return headerToCopy != null && (orientation == VERTICAL ? headerToCopy.getY() < 0 + : headerToCopy.getX() < 0); + } + + private void removeViewFromParent(View view) { + if (view.getParent() instanceof ViewGroup) { + ((ViewGroup) view.getParent()).removeView(view); + } + } + + private void updateStickHolder() { + if (headerOrgViewHolder != null) { + recyclerView.getAdapter().onBindViewHolder(headerOrgViewHolder, currentStickPos); + } + } } diff --git a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java index c81a1477e36..3131671464b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java +++ b/android/sdk/src/main/java/com/tencent/mtt/nxeasy/recyclerview/helper/skikcy/StickyViewFactory.java @@ -21,27 +21,29 @@ public final class StickyViewFactory implements IHeaderViewFactory { - private final EasyRecyclerView recyclerView; + private final EasyRecyclerView recyclerView; - public StickyViewFactory(EasyRecyclerView recyclerView) { - this.recyclerView = recyclerView; - } - - /** - * 根据position的位置,获取到一个实体到ViewHolder 1、先在已经上屏的view中,找到一个ViewHolder 2、第一步没有找到,就重新通过recyclerView去获取一个,这种获取的viewHolder可能是cache里面的,也可能 - * 是新创建的,不用再次进行bindViewHolder的操作 - * - * @param position 指定要获取到ViewHolder到位置 - * @return 返回对应到ViewHolder,不会返回Null - */ - public ViewHolder getHeaderForPosition(int position) { - if (position < 0) { - return null; + public StickyViewFactory(EasyRecyclerView recyclerView) { + this.recyclerView = recyclerView; } - ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position); - if (viewHolder == null) { - viewHolder = recyclerView.getViewHolderForPosition(position); + + /** + * 根据position的位置,获取到一个实体到ViewHolder + * 1、先在已经上屏的view中,找到一个ViewHolder + * 2、第一步没有找到,就重新通过recyclerView去获取一个,这种获取的viewHolder可能是cache里面的,也可能 + * 是新创建的,不用再次进行bindViewHolder的操作 + * + * @param position 指定要获取到ViewHolder到位置 + * @return 返回对应到ViewHolder,不会返回Null + */ + public ViewHolder getHeaderForPosition(int position) { + if (position < 0) { + return null; + } + ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position); + if (viewHolder == null) { + viewHolder = recyclerView.getViewHolderForPosition(position); + } + return viewHolder; } - return viewHolder; - } } \ No newline at end of file diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java index 4c158e9e7e6..6f225a87536 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/BaseLayoutManager.java @@ -728,7 +728,7 @@ else if (mOrientation == HORIZONTAL) } else { - //niuniuyang:如果内容小于屏幕高度了,如果mOffset不是原点,必须把list的offset重新回到原点 + //如果内容小于屏幕高度了,如果mOffset不是原点,必须把list的offset重新回到原点 int gap = mRecyclerView.mOffsetY - mOrientationHelper.getStartAfterPadding(); if (gap != 0) { diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java index 381acd018f2..3615f70a607 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/IBlockTouchListener.java @@ -1,7 +1,7 @@ package com.tencent.mtt.supportui.views.recyclerview; /** - * Created by niuniuyang on 2020-03-06. + * Created by on 2020-03-06. * Description */ public interface IBlockTouchListener { diff --git a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java index e3cfa952253..e67f239591b 100644 --- a/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java +++ b/android/sdk/src/main/java/com/tencent/mtt/supportui/views/recyclerview/RecyclerViewBase.java @@ -2215,7 +2215,7 @@ protected void dispatchLayout() } // mCount = mAdapter.getItemCount() + mAdapter.getHeaderViewCount() + // mAdapter.getFooterViewCount(); - //niuniuyang: 对于itemchanged,选中的item移动到屏幕外时,此时执行删除操作,item是不会做动画的。 + //对于itemchanged,选中的item移动到屏幕外时,此时执行删除操作,item是不会做动画的。 //也就无法通过onAnimationsFinished退出编辑态,这里需要加一个事件,通知出去,退出编辑态 if (animateChangesSimple && !mPostedAnimatorRunner) {