NativeAdsExpress 强制 RecyclerView 滚动以使 NativeAd 在首次加载时完全可见
NativeAdsExpress forces RecyclerView to Scroll to have the NativeAd fully visible when first loaded
我有一个奇怪的 "problem" 或者可能是 "feature" 我只是不知道,每当 NativeAdExpress 加载到我的 RecyclerView 时,如果只有部分 NativeAd 可见,它强制 RecyclerView 滚动直到原生广告完全可见,此行为会导致列表在我滚动时不断跳跃。
我的布局主要是:
Activity > 带有 Tabs 和 ViewPager 的 AppBar > Pager 中的每个页面都包含 PullToRefresh 并且在其中有一个 RecyclerView,
RecyclerView 有两种类型的项目(文章和 NativeAdExpress)。
更新:我猜测为什么会发生这种情况主要是因为原生广告在 webview 中呈现,并且这个 webview 获得焦点然后这导致 RecyclerView 滚动到它,但这只是一个猜测
更新 2:显然这是支持库中的一个问题。 24.0.0,这就是太更新的代价:(
这是我的全部 XML/Layouts
<?xml version="1.0" encoding="utf-8"?>
<my.package.custom.views.CustomDrawerLayout
android:id="@+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:openDrawer="end">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
style="@style/AlertDialog.AppCompat.Light"
fontPath="fonts/fonts/DroidKufi-Regular.ttf"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="true"
app:elevation="5px"
app:headerLayout="@layout/nav_header"
app:itemIconTint="@color/colorPrimary"
app:itemTextColor="@color/contentColor"
app:actionLayout="@layout/nav_item_layout"
app:menu="@menu/drawer_menu"
app:theme="@style/NavDrawerStyle"
tools:openDrawer="end"
/>
</my.package.custom.views.CustomDrawerLayout>
其中"app_bar_main.xml"如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.activities.ArticlesListActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="?attr/colorPrimary"
android:gravity="end"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingEnd="14dp"
android:paddingStart="14dp">
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/ivCustomDrawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@color/transparent"
android:tint="@color/white"
/>
<TextView
android:id="@+id/view_title"
android:visibility="gone"
style="@style/SectionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
<android.support.v7.widget.AppCompatSpinner
android:id="@+id/sources_spinner"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:transitionName="@string/transition_splash_logo"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
tools:targetApi="lollipop"/>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/TabsStyle"
android:layout_width="match_parent"
android:layout_height="42dp"
android:layout_gravity="bottom"
android:layout_marginTop="0dp"
android:transitionGroup="true"
app:tabContentStart="0dp"
app:tabGravity="fill"
app:tabIndicatorColor="@color/white"
app:tabIndicatorHeight="3dp"
app:tabMode="scrollable"
app:tabPaddingBottom="0dp"
app:tabPaddingTop="0dp"
app:tabTextAppearance="@style/TextAppearance.RegularTextFont"
/>
</android.support.design.widget.AppBarLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".ui.activities.ArticlesListActivity"
tools:showIn="@layout/activity_newsitem_list">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
最后我有 2 个视图类型项目和 NativeAdExpress:
NativeAdExpress ViewHolder如下:
public class NativeExpressAdViewHolder extends BaseCardAdViewHolder {
private final NativeExpressAdView view;
private boolean loaded = false;
private AdListener adListener;
private WeakReference<Context> context;
public NativeExpressAdViewHolder(View itemView, String adId, Context context) {
super(itemView);
view = new NativeExpressAdView(context);
view.setAdUnitId(adId);
((LinearLayout) itemView.findViewById(R.id.express_ad_holder)).addView(view);
this.context = new WeakReference<>(context);
}
public void loadAd(float cardWidthInDips) {
if (!loaded && null != context.get() && !view.isLoading()) {
int width = cardWidthInDips > 0 ? (int) cardWidthInDips : 330;
if (view.getAdSize() == null) {
view.setAdSize(new AdSize(width, 330));
view.setAdListener(new AdListener() {
@Override
public void onAdLoaded() {
super.onAdLoaded();
loaded = true;
if (adListener != null) {
adListener.onAdLoaded();
}
}
@Override
public void onAdFailedToLoad(int i) {
super.onAdFailedToLoad(i);
if (adListener != null) {
adListener.onAdFailedToLoad(i);
}
}
@Override
public void onAdOpened() {
super.onAdOpened();
if (adListener != null) {
adListener.onAdOpened();
}
}
});
}
new Handler(context.get().getMainLooper()).post(new Runnable() {
@Override
public void run() {
view.loadAd(new AdRequest.Builder().build());
}
});
}
}
public NativeExpressAdView getView() {
return view;
}
public void setAdListener(AdListener adListener) {
this.adListener = adListener;
}
@Override
public void destroyAd() {
if (view != null) {
view.destroy();
loaded = false;
}
}
}
广告是使用自定义适配器创建的,如下所示:
private BaseCardViewHolder createNativeExpressAdViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.ad_express, parent, false);
final NativeExpressAdViewHolder viewHolder = new NativeExpressAdViewHolder(
view,
adManager.getNativeExpressAdId(),
context.get()
);
adViewHolders.add(viewHolder);
viewHolder.setAdListener(new AdListener() {
@Override
public void onAdFailedToLoad(int errorCode) {
Log.i("ADS", "onAdFailedToLoad " + errorCode);
}
@Override
public void onAdLoaded() {
super.onAdLoaded();
// Do something
}
});
viewHolder.loadAd(cardWidthInDips);
return viewHolder;
}
我不明白您在代码中实际实现 RecyclerView
的位置,但无论如何,这里可能有适合您的情况。
我有一个类似的问题,GridView
总是在 Fragment
第一次加载期间获得焦点,它还会自动向下直接滚动到 GridView
。
经过大量测试,我终于在 GridView
:
的父布局上用这一行停止了这种行为
android:descendantFocusability="blocksDescendants"
这一行基本上意味着父布局的所有后代将不再获得焦点。您可以在 RecyclerView
父级布局上尝试一下。
我在 RecyclerView 滚动方面遇到了类似的问题,不是专门针对 NativeAdExpress...但 Android 支持库似乎存在问题 24.0.0与此相关。 android:descendantFocusability="blocksDescendants" 对我不起作用。但是降级到 Android 支持库 23.4.0 使其再次开始工作。
我不知道这是否是一个问题,但 RecyclerView 似乎在某处将其行为从支持库 23.4.0 更改为 24.2.0。
现在不再默认为 descendantFocusability = beforeDescendants,而是默认为 afterDescendants,这会导致它为 children 提供焦点,而不是自己占据焦点。
当 child 获得焦点时,它最终会滚动以使其可见。
我通过将 android:descendantFocusability="beforeDescendants" 添加到 RecyclerView 来修复它,以恢复以前的行为,它保持焦点本身。
我有一个奇怪的 "problem" 或者可能是 "feature" 我只是不知道,每当 NativeAdExpress 加载到我的 RecyclerView 时,如果只有部分 NativeAd 可见,它强制 RecyclerView 滚动直到原生广告完全可见,此行为会导致列表在我滚动时不断跳跃。
我的布局主要是:
Activity > 带有 Tabs 和 ViewPager 的 AppBar > Pager 中的每个页面都包含 PullToRefresh 并且在其中有一个 RecyclerView, RecyclerView 有两种类型的项目(文章和 NativeAdExpress)。
更新:我猜测为什么会发生这种情况主要是因为原生广告在 webview 中呈现,并且这个 webview 获得焦点然后这导致 RecyclerView 滚动到它,但这只是一个猜测
更新 2:显然这是支持库中的一个问题。 24.0.0,这就是太更新的代价:(
这是我的全部 XML/Layouts
<?xml version="1.0" encoding="utf-8"?>
<my.package.custom.views.CustomDrawerLayout
android:id="@+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:openDrawer="end">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
style="@style/AlertDialog.AppCompat.Light"
fontPath="fonts/fonts/DroidKufi-Regular.ttf"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="true"
app:elevation="5px"
app:headerLayout="@layout/nav_header"
app:itemIconTint="@color/colorPrimary"
app:itemTextColor="@color/contentColor"
app:actionLayout="@layout/nav_item_layout"
app:menu="@menu/drawer_menu"
app:theme="@style/NavDrawerStyle"
tools:openDrawer="end"
/>
</my.package.custom.views.CustomDrawerLayout>
其中"app_bar_main.xml"如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.activities.ArticlesListActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="?attr/colorPrimary"
android:gravity="end"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingEnd="14dp"
android:paddingStart="14dp">
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/ivCustomDrawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@color/transparent"
android:tint="@color/white"
/>
<TextView
android:id="@+id/view_title"
android:visibility="gone"
style="@style/SectionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
<android.support.v7.widget.AppCompatSpinner
android:id="@+id/sources_spinner"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:transitionName="@string/transition_splash_logo"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
tools:targetApi="lollipop"/>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/TabsStyle"
android:layout_width="match_parent"
android:layout_height="42dp"
android:layout_gravity="bottom"
android:layout_marginTop="0dp"
android:transitionGroup="true"
app:tabContentStart="0dp"
app:tabGravity="fill"
app:tabIndicatorColor="@color/white"
app:tabIndicatorHeight="3dp"
app:tabMode="scrollable"
app:tabPaddingBottom="0dp"
app:tabPaddingTop="0dp"
app:tabTextAppearance="@style/TextAppearance.RegularTextFont"
/>
</android.support.design.widget.AppBarLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".ui.activities.ArticlesListActivity"
tools:showIn="@layout/activity_newsitem_list">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
最后我有 2 个视图类型项目和 NativeAdExpress:
NativeAdExpress ViewHolder如下:
public class NativeExpressAdViewHolder extends BaseCardAdViewHolder {
private final NativeExpressAdView view;
private boolean loaded = false;
private AdListener adListener;
private WeakReference<Context> context;
public NativeExpressAdViewHolder(View itemView, String adId, Context context) {
super(itemView);
view = new NativeExpressAdView(context);
view.setAdUnitId(adId);
((LinearLayout) itemView.findViewById(R.id.express_ad_holder)).addView(view);
this.context = new WeakReference<>(context);
}
public void loadAd(float cardWidthInDips) {
if (!loaded && null != context.get() && !view.isLoading()) {
int width = cardWidthInDips > 0 ? (int) cardWidthInDips : 330;
if (view.getAdSize() == null) {
view.setAdSize(new AdSize(width, 330));
view.setAdListener(new AdListener() {
@Override
public void onAdLoaded() {
super.onAdLoaded();
loaded = true;
if (adListener != null) {
adListener.onAdLoaded();
}
}
@Override
public void onAdFailedToLoad(int i) {
super.onAdFailedToLoad(i);
if (adListener != null) {
adListener.onAdFailedToLoad(i);
}
}
@Override
public void onAdOpened() {
super.onAdOpened();
if (adListener != null) {
adListener.onAdOpened();
}
}
});
}
new Handler(context.get().getMainLooper()).post(new Runnable() {
@Override
public void run() {
view.loadAd(new AdRequest.Builder().build());
}
});
}
}
public NativeExpressAdView getView() {
return view;
}
public void setAdListener(AdListener adListener) {
this.adListener = adListener;
}
@Override
public void destroyAd() {
if (view != null) {
view.destroy();
loaded = false;
}
}
}
广告是使用自定义适配器创建的,如下所示:
private BaseCardViewHolder createNativeExpressAdViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.ad_express, parent, false);
final NativeExpressAdViewHolder viewHolder = new NativeExpressAdViewHolder(
view,
adManager.getNativeExpressAdId(),
context.get()
);
adViewHolders.add(viewHolder);
viewHolder.setAdListener(new AdListener() {
@Override
public void onAdFailedToLoad(int errorCode) {
Log.i("ADS", "onAdFailedToLoad " + errorCode);
}
@Override
public void onAdLoaded() {
super.onAdLoaded();
// Do something
}
});
viewHolder.loadAd(cardWidthInDips);
return viewHolder;
}
我不明白您在代码中实际实现 RecyclerView
的位置,但无论如何,这里可能有适合您的情况。
我有一个类似的问题,GridView
总是在 Fragment
第一次加载期间获得焦点,它还会自动向下直接滚动到 GridView
。
经过大量测试,我终于在 GridView
:
android:descendantFocusability="blocksDescendants"
这一行基本上意味着父布局的所有后代将不再获得焦点。您可以在 RecyclerView
父级布局上尝试一下。
我在 RecyclerView 滚动方面遇到了类似的问题,不是专门针对 NativeAdExpress...但 Android 支持库似乎存在问题 24.0.0与此相关。 android:descendantFocusability="blocksDescendants" 对我不起作用。但是降级到 Android 支持库 23.4.0 使其再次开始工作。
我不知道这是否是一个问题,但 RecyclerView 似乎在某处将其行为从支持库 23.4.0 更改为 24.2.0。
现在不再默认为 descendantFocusability = beforeDescendants,而是默认为 afterDescendants,这会导致它为 children 提供焦点,而不是自己占据焦点。
当 child 获得焦点时,它最终会滚动以使其可见。
我通过将 android:descendantFocusability="beforeDescendants" 添加到 RecyclerView 来修复它,以恢复以前的行为,它保持焦点本身。