From 2726c93059e0b60dda93cd2fa29343953687b48a Mon Sep 17 00:00:00 2001 From: iPel Date: Tue, 28 Nov 2023 17:56:43 +0800 Subject: [PATCH] feat(android): support appear events for ListView --- .../hippylist/HippyRecyclerListAdapter.java | 28 ++- .../hippylist/RecyclerViewEventHelper.java | 43 ++-- .../hippy/views/list/HippyListItemView.java | 210 ++++++++++-------- .../mtt/hippy/views/list/HippyListView.java | 21 +- .../renderer/node/ListItemRenderNode.java | 10 +- 5 files changed, 166 insertions(+), 146 deletions(-) diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java index 2814a1314f7..8cde8d1ccc8 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/HippyRecyclerListAdapter.java @@ -17,32 +17,24 @@ package com.tencent.mtt.hippy.views.hippylist; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static com.tencent.renderer.node.RenderNode.FLAG_LAZY_LOAD; -import android.view.MotionEvent; -import android.view.View.OnTouchListener; -import android.view.ViewParent; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.HippyItemTypeHelper; import androidx.recyclerview.widget.ItemLayoutParams; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.LayoutParams; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import com.tencent.renderer.node.ListItemRenderNode; -import com.tencent.renderer.node.PullFooterRenderNode; -import com.tencent.renderer.node.PullHeaderRenderNode; import com.tencent.mtt.hippy.uimanager.RenderManager; -import com.tencent.renderer.node.RenderNode; import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IStickyItemsProvider; import com.tencent.mtt.hippy.views.list.IRecycleItemTypeChange; import com.tencent.mtt.hippy.views.refresh.HippyPullFooterView; import com.tencent.mtt.hippy.views.refresh.HippyPullHeaderView; -import com.tencent.mtt.hippy.views.hippylist.recyclerview.helper.skikcy.IStickyItemsProvider; -import com.tencent.renderer.NativeRender; -import com.tencent.renderer.NativeRenderException; -import com.tencent.renderer.NativeRendererManager; +import com.tencent.renderer.node.ListItemRenderNode; +import com.tencent.renderer.node.PullFooterRenderNode; +import com.tencent.renderer.node.PullHeaderRenderNode; +import com.tencent.renderer.node.RenderNode; /** * Created on 2020/12/22. @@ -137,6 +129,12 @@ public void onViewAttachedToWindow(@NonNull HippyRecyclerViewHolder holder) { } } + @Override + public void onViewDetachedFromWindow(@NonNull HippyRecyclerViewHolder holder) { + holder.bindNode.onViewHolderDetached(); + super.onViewDetachedFromWindow(holder); + } + public void onFooterRefreshCompleted() { if (footerRefreshHelper != null) { footerRefreshHelper.onRefreshCompleted(); diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java index 849471f6a1f..cf17f7729f4 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/hippylist/RecyclerViewEventHelper.java @@ -72,6 +72,7 @@ public class RecyclerViewEventHelper extends OnScrollListener implements OnLayou private OnPreDrawListener preDrawListener; private boolean isLastTimeReachEnd; private int preloadItemNumber; + private Rect reusableExposureStateRect = new Rect(); public RecyclerViewEventHelper(HippyRecyclerView recyclerView) { this.hippyRecyclerView = recyclerView; @@ -328,42 +329,32 @@ public void setExposureEventEnable(boolean enable) { /** * 可视面积小于10%,任务view当前已经不在可视区域 */ - private boolean isViewVisible(View view) { + private int calculateExposureState(View view) { if (view == null) { - return false; + return HippyListItemView.EXPOSURE_STATE_INVISIBLE; } - Rect rect = new Rect(); - boolean visibility = view.getGlobalVisibleRect(rect); + boolean visibility = view.getGlobalVisibleRect(reusableExposureStateRect); if (!visibility) { - return false; + return HippyListItemView.EXPOSURE_STATE_INVISIBLE; + } + // visible area size of view + float visibleArea = reusableExposureStateRect.height() * reusableExposureStateRect.width(); + // total area size of view + float viewArea = view.getMeasuredWidth() * view.getMeasuredHeight(); + if (visibleArea >= viewArea) { + return HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE; + } else if (visibleArea > viewArea * 0.1f) { + return HippyListItemView.EXPOSURE_STATE_PART_VISIBLE; } else { - float visibleArea = rect.height() * rect.width(); //可见区域的面积 - float viewArea = view.getMeasuredWidth() * view.getMeasuredHeight();//当前view的总面积 - return visibleArea > viewArea * 0.1f; + return HippyListItemView.EXPOSURE_STATE_INVISIBLE; } } 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, EventUtils.EVENT_LIST_ITEM_APPEAR); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_APPEAR); - } - } else { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { - sendExposureEvent(view, EventUtils.EVENT_LIST_ITEM_DISAPPEAR); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_DISAPPEAR); - } - } - } - } - - protected void sendExposureEvent(View view, String eventName) { - if (eventName.equals(EventUtils.EVENT_LIST_ITEM_APPEAR) || eventName - .equals(EventUtils.EVENT_LIST_ITEM_DISAPPEAR)) { - new HippyViewEvent(eventName).send(view, null); + int newState = calculateExposureState(view); + itemView.moveToExposureState(newState); } } diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListItemView.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListItemView.java index b0613612819..b8032aab336 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListItemView.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListItemView.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.list; import android.content.Context; @@ -21,114 +22,139 @@ import android.graphics.Paint; import android.view.View; import android.view.ViewGroup; - import com.tencent.mtt.hippy.HippyRootView; import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.views.view.HippyViewGroup; +import com.tencent.renderer.utils.EventUtils; public class HippyListItemView extends HippyViewGroup { - private static final boolean VIEW_LEVEL_MONITOR_ENABLE = false; - private Paint mPaint; + private static final boolean VIEW_LEVEL_MONITOR_ENABLE = false; + private Paint mPaint; - public final static int EXPOSURE_STATE_WILL_APPEAR = 0; - public final static int EXPOSURE_STATE_APPEAR = 1; - public final static int EXPOSURE_STATE_DISAPPEAR = 2; - public final static int EXPOSURE_STATE_WILL_DISAPPEAR = 3; + public final static int EXPOSURE_STATE_FULL_VISIBLE = 1; + public final static int EXPOSURE_STATE_INVISIBLE = 2; + public final static int EXPOSURE_STATE_PART_VISIBLE = 3; - private int mExposureState = EXPOSURE_STATE_DISAPPEAR; + private int mExposureState = EXPOSURE_STATE_INVISIBLE; - public int getExposureState() { - return mExposureState; - } + @Deprecated + public int getExposureState() { + return mExposureState; + } - public void setExposureState(int state) { - mExposureState = state; - } + @Deprecated + public void setExposureState(int state) { + mExposureState = state; + } - public HippyListItemView(Context context) { - super(context); + public HippyListItemView(Context context) { + super(context); - if (VIEW_LEVEL_MONITOR_ENABLE) { - mPaint = new Paint(); - mPaint.setColor(Color.RED); - mPaint.setTextSize(PixelUtil.dp2px(16)); - mPaint.setTextAlign(Paint.Align.CENTER); + if (VIEW_LEVEL_MONITOR_ENABLE) { + mPaint = new Paint(); + mPaint.setColor(Color.RED); + mPaint.setTextSize(PixelUtil.dp2px(16)); + mPaint.setTextAlign(Paint.Align.CENTER); + } } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (VIEW_LEVEL_MONITOR_ENABLE) { - canvas.save(); - int selfLevel = calculateSelfLevel(); - int hippyLevel = calculateHippyLevel(); - int childLevel = calculateChildLevel(this); - canvas.drawText( - "总:" + (selfLevel + childLevel) + " , HP:" + (hippyLevel + childLevel) + " , 子:" - + childLevel, getWidth() / 2.0f, - getHeight() / 2.0f, mPaint); - canvas.restore(); + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (VIEW_LEVEL_MONITOR_ENABLE) { + canvas.save(); + int selfLevel = calculateSelfLevel(); + int hippyLevel = calculateHippyLevel(); + int childLevel = calculateChildLevel(this); + canvas.drawText( + "总:" + (selfLevel + childLevel) + " , HP:" + (hippyLevel + childLevel) + " , 子:" + + childLevel, getWidth() / 2.0f, + getHeight() / 2.0f, mPaint); + canvas.restore(); + } + } + + private int calculateSelfLevel() { + int level = 0; + View view = this; + while (true) { + if (view.getParent() != null && view.getParent() instanceof View) { + view = (View) view.getParent(); + ++level; + } else { + break; + } + } + return level; } - } - - private int calculateSelfLevel() { - int level = 0; - View view = this; - while (true) { - if (view.getParent() != null && view.getParent() instanceof View) { - view = (View) view.getParent(); - ++level; - } else { - break; - } + + private int calculateHippyLevel() { + int level = 0; + View view = this; + while (true) { + if (view.getParent() != null && view.getParent() instanceof View && !(view + .getParent() instanceof HippyRootView)) { + view = (View) view.getParent(); + ++level; + } else if (view.getParent() instanceof HippyRootView) { + ++level; + break; + } else { + break; + } + } + return level; } - return level; - } - - private int calculateHippyLevel() { - int level = 0; - View view = this; - while (true) { - if (view.getParent() != null && view.getParent() instanceof View && !(view - .getParent() instanceof HippyRootView)) { - view = (View) view.getParent(); - ++level; - } else if (view.getParent() instanceof HippyRootView) { - ++level; - break; - } else { - break; - } + + private int calculateChildLevel(View view) { + int level = 1; + if (view instanceof ViewGroup) { + int count = this.getChildCount(); + if (count != 0) { + int maxLevel = 0; + int currentLevel; + for (int i = 0; i < count; i++) { + currentLevel = calculateChildLevel(((ViewGroup) view).getChildAt(i)); + maxLevel = Math.max(maxLevel, currentLevel); + } + level = maxLevel + level; + } + } + return level; } - return level; - } - - private int calculateChildLevel(View view) { - int level = 1; - if (view instanceof ViewGroup) { - int count = this.getChildCount(); - if (count != 0) { - int maxLevel = 0; - int currentLevel; - for (int i = 0; i < count; i++) { - currentLevel = calculateChildLevel(((ViewGroup) view).getChildAt(i)); - maxLevel = Math.max(maxLevel, currentLevel); + + public void moveToExposureState(int state) { + if (state == mExposureState) { + return; + } + switch (state) { + case EXPOSURE_STATE_FULL_VISIBLE: + if (mExposureState == EXPOSURE_STATE_INVISIBLE) { + sendExposureEvent(EventUtils.EVENT_LIST_ITEM_WILL_APPEAR); + } + sendExposureEvent(EventUtils.EVENT_LIST_ITEM_APPEAR); + break; + case EXPOSURE_STATE_PART_VISIBLE: + if (mExposureState == EXPOSURE_STATE_FULL_VISIBLE) { + sendExposureEvent(EventUtils.EVENT_LIST_ITEM_WILL_DISAPPEAR); + } else { + sendExposureEvent(EventUtils.EVENT_LIST_ITEM_WILL_APPEAR); + } + break; + case EXPOSURE_STATE_INVISIBLE: + if (mExposureState == EXPOSURE_STATE_FULL_VISIBLE) { + sendExposureEvent(EventUtils.EVENT_LIST_ITEM_WILL_DISAPPEAR); + } + sendExposureEvent(EventUtils.EVENT_LIST_ITEM_DISAPPEAR); + break; + default: + break; } - level = maxLevel + level; - } + mExposureState = state; + } + + private void sendExposureEvent(String eventName) { + EventUtils.sendComponentEvent(this, eventName, null); } - return level; - } - - // public void setType(int type) - // { - // mType = type; - // } - - // public int getType() - // { - // return mType; - // } } diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java index 3ab91fcdd6a..18c15b3038a 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/list/HippyListView.java @@ -38,7 +38,6 @@ import com.tencent.mtt.hippy.utils.PixelUtil; import com.tencent.mtt.hippy.views.refresh.HippyPullFooterView; import com.tencent.mtt.hippy.views.refresh.HippyPullHeaderView; -import com.tencent.mtt.hippy.views.scroll.HippyScrollViewEventHelper; import com.tencent.mtt.supportui.views.recyclerview.BaseLayoutManager; import com.tencent.mtt.supportui.views.recyclerview.LinearLayoutManager; import com.tencent.mtt.supportui.views.recyclerview.RecyclerAdapter; @@ -562,30 +561,30 @@ protected void checkExposureView(View view, int visibleStart, int visibleEnd, in if (myEnd <= (visibleStart + correctingValueForDisappear) || myStart >= (visibleEnd - correctingValueForDisappear)) { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { - if (itemView.getExposureState() == HippyListItemView.EXPOSURE_STATE_APPEAR) { + if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_INVISIBLE) { + if (itemView.getExposureState() == HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE) { sendExposureEvent(view, EVENT_LIST_ITEM_WILL_DISAPPEAR, props); } sendExposureEvent(view, EVENT_LIST_ITEM_DISAPPEAR, props); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_DISAPPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_INVISIBLE); } } else if ((myStart < visibleStart && myEnd > visibleStart) || (myStart < visibleEnd && myEnd > visibleEnd)) { - if (currentExposureState == HippyListItemView.EXPOSURE_STATE_APPEAR) { + if (currentExposureState == HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE) { sendExposureEvent(view, EVENT_LIST_ITEM_WILL_DISAPPEAR, props); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_WILL_DISAPPEAR); - } else if (currentExposureState == HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_PART_VISIBLE); + } else if (currentExposureState == HippyListItemView.EXPOSURE_STATE_INVISIBLE) { sendExposureEvent(view, EVENT_LIST_ITEM_WILL_APPEAR, props); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_WILL_APPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_PART_VISIBLE); } } else if ((myStart >= visibleStart && myEnd <= visibleEnd) || (myStart <= visibleStart && myEnd > visibleEnd)) { - if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_APPEAR) { - if (itemView.getExposureState() == HippyListItemView.EXPOSURE_STATE_DISAPPEAR) { + if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE) { + if (itemView.getExposureState() == HippyListItemView.EXPOSURE_STATE_INVISIBLE) { sendExposureEvent(view, EVENT_LIST_ITEM_WILL_APPEAR, props); } sendExposureEvent(view, EVENT_LIST_ITEM_APPEAR, props); - itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_APPEAR); + itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE); } } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/ListItemRenderNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/ListItemRenderNode.java index 71a7f4a8f71..690943d35d8 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/ListItemRenderNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/ListItemRenderNode.java @@ -24,10 +24,9 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.tencent.mtt.hippy.uimanager.ControllerManager; import com.tencent.mtt.hippy.uimanager.RenderManager; -import com.tencent.mtt.hippy.utils.LogUtils; +import com.tencent.mtt.hippy.views.list.HippyListItemView; import com.tencent.mtt.hippy.views.list.IRecycleItemTypeChange; import com.tencent.renderer.NativeRenderException; import com.tencent.renderer.utils.MapUtils; @@ -94,6 +93,13 @@ public View onCreateViewHolder() throws NativeRenderException { return view; } + public void onViewHolderDetached() { + View hostView = getHostView(); + if (hostView instanceof HippyListItemView) { + ((HippyListItemView) hostView).moveToExposureState(HippyListItemView.EXPOSURE_STATE_INVISIBLE); + } + } + public void onViewHolderAbandoned() { setLazy(true); removeView();