![]() |
狂野的人字拖 · 美好教育的杭州实践丨杭州二中白马湖学校小学部 ...· 3 月前 · |
![]() |
安静的手链 · 8点1氪|官方通报“点读机女孩”手术视频为库 ...· 8 月前 · |
![]() |
眉毛粗的大蒜 · 我真没想抢男主啊快穿by漫天都是小星星-小说 ...· 1 年前 · |
![]() |
阳刚的牛排 · Java XMLDecoder反序列化分析 ...· 1 年前 · |
android开发 adapter tablayout viewpager |
https://naruto-1996.github.io/2021/06/25/android-tablayout-viewpager2-fragment-ni-ke-neng-yong-cuo-liao/ |
![]() |
健壮的爆米花
11 月前 |
tabLayout + viewPager2 + fragment 这是在app开发中非常常见的UI架构了 但是用不好的话 是会导致内存泄漏的
(
app
模板使用
android studio
创建一个就行 我只用
HomeFragment
)
之前我们都是使用viewPager 但是 viewPager 有两个毛病
不能关闭预加载
更新Adapter不生效
虽然能解决 但是很麻烦
viewPager 有个方法
offscreenPageLimit
用来设置 当前页前后预加载的数量 即使设置为0也不起任何作用 因为源码中默认值为1
先来看个图:
上面是
ViewPager
默认情况下的加载示意图,当切换到当前页面时,会默认预加载左右两侧的布局到
ViewPager
中,
尽管两侧的
View
并不可见的,我们称这种情况叫预加载;由于
ViewPager
对
offscreenPageLimit
设置了限制,
页面的预加载是不可避免
如果使用 一定要使用viewPager 也可以做到 fragment 懒加载 主要是对
fragment
做手脚 结合生命周期方法和setUserVisibleHint状态,控制数据延迟加载,而布局只能提前进入
1、 承载tabLayout 和 viewPager2 需要一个 fragment 或者 activity 这里我们使用 fragment
HomeFragment
如下:
fragment_home.xml
布局文件:
2、viewPager2的 FragmentStateAdapter 创建fragment时 我们 复用一个fragment
BlankFragment
如下:
fragment_blank.xml
如下:
至此 我们已经完成了 tabLayout + viewPager2 + fragment
setAdapter()
设置适配器
setOrientation()
设置布局方向
setCurrentItem()
设置当前Item下标
beginFakeDrag()
开始模拟拖拽
fakeDragBy()
模拟拖拽中
endFakeDrag()
模拟拖拽结束
setUserInputEnabled()
设置是否允许用户输入/触摸
setOffscreenPageLimit()
设置屏幕外加载页面数量
registerOnPageChangeCallback()
注册页面滑动监听回调
setPageTransformer()
设置页面滑动时的变换效果
one picture is worth a thousand words
:
viewPager2
默认开启预加载
如何关闭预加载、并设置缓存数量
常见设置如下
//设置应保留在当前可见页面两侧的页面数 viewPager2 . setOffscreenPageLimit ( 4 ) ; //关闭预加载 ( ( RecyclerView ) viewPager2 . getChildAt ( 0 ) ) . getLayoutManager ( ) . setItemPrefetchEnabled ( false ) ; //设置缓存数量,对应 RecyclerView 中的 mCachedViews,即屏幕外的视图数量 ( ( RecyclerView ) viewPager2 . getChildAt ( 0 ) ) . setItemViewCacheSize ( 4 ) ;
viewPager2
会默认缓存2个
ItemView
我们将
((RecyclerView)viewPager2.getChildAt(0)).setItemViewCacheSize(0);
设置为
0
即可 这样我们每次滑动一个新的
tab
时 会创建
新的
fragment
并销毁上一个
fragment
目前,
ViewPager2
对
Fragment
的支持只能使用
FragmentStateAdapter
,使用起来也是非常简单:
ViewPager2+TabLayout懒加载问题,Fragment被创建多次
ViewPager2
默认只加载当前页面,相当于官方处理了
Fragment
的懒加载问题,当你使用代码
此时当你滑动
ViewPager2
时,滑动到某个
Fragment
页面才会加载,执行onCreateView()方法,
但是当你手动点击
TabLayout
时,此时懒加载就会失效,
onCreateView()
会被执行多次,
原因就是…此时
ViewPager2
默认是平滑滚动的,滚动滑过的
Fragment
都会被加载,
只需修改代码:
new TabLayoutMediator ( tabLayout , viewPager , true , false , new TabLayoutMediator . TabConfigurationStrategy ( ) { & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; @Override & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; public void onConfigureTab ( @NonNull TabLayout . Tab tab , int position ) { & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; tab . setText ( titles . get ( position ) ) ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; & nbsp ; } } ) . attach ( ) ;
其中,第二个
boolean
参数为
smoothScroll
一定要填
false
这时点击 哪个
tab
就会创建 哪个
fragment
但是页面看起来没有那么平滑了 各有利弊吧
—————————————————————————————
如果 需要点击tab无动画 滑动页面时有动画 怎么办呢?
TabLayoutMediator 是final类 且smoothScroll字段是私有的、我们没法继承重写、但是可以copy源码修改、不会有问题
我们将 源码copy下来 稍作修改就可以用了 下面是修改好的代码:
import static androidx . viewpager2 . widget . ViewPager2 . SCROLL_STATE_DRAGGING ; import static androidx . viewpager2 . widget . ViewPager2 . SCROLL_STATE_IDLE ; import static androidx . viewpager2 . widget . ViewPager2 . SCROLL_STATE_SETTLING ; import androidx . annotation . NonNull ; import androidx . annotation . Nullable ; import androidx . recyclerview . widget . RecyclerView ; import androidx . viewpager2 . widget . ViewPager2 ; import com . google . android . material . tabs . TabLayout ; import java . lang . ref . WeakReference ; * A mediator to link a TabLayout with a ViewPager2. The mediator will synchronize the ViewPager2's * position with the selected tab when a tab is selected, and the TabLayout's scroll position when * the user drags the ViewPager2. TabLayoutMediator will listen to ViewPager2's OnPageChangeCallback * to adjust tab when ViewPager2 moves. TabLayoutMediator listens to TabLayout's * OnTabSelectedListener to adjust VP2 when tab moves. TabLayoutMediator listens to RecyclerView's * AdapterDataObserver to recreate tab content when dataset changes. * <p>Establish the link by creating an instance of this class, make sure the ViewPager2 has an * adapter and then call {@link #attach()} on it. Instantiating a TabLayoutMediator will only create * the mediator object, {@link #attach()} will link the TabLayout and the ViewPager2 together. When * creating an instance of this class, you must supply an implementation of {@link * com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy} in which you set the text of the tab, and/or perform any styling of the * tabs that you require. Changing ViewPager2's adapter will require a {@link #detach()} followed by * {@link #attach()} call. Changing the ViewPager2 or TabLayout will require a new instantiation of * TabLayoutMediator. public final class TabLayoutMediators { @NonNull private final TabLayout tabLayout ; @NonNull private final ViewPager2 viewPager ; private final boolean autoRefresh ; // 这里我们改为静态变量 以便于在静态方法中使用这个变量 private static boolean smoothScroll ; private final TabConfigurationStrategy tabConfigurationStrategy ; @Nullable private RecyclerView . Adapter < ? > adapter ; private boolean attached ; @Nullable private TabLayoutOnPageChangeCallback onPageChangeCallback ; @Nullable private TabLayout . OnTabSelectedListener onTabSelectedListener ; @Nullable private RecyclerView . AdapterDataObserver pagerAdapterObserver ; * A callback interface that must be implemented to set the text and styling of newly created * tabs. public interface TabConfigurationStrategy { * Called to configure the tab for the page at the specified position. Typically calls {@link * TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied. * @param tab The Tab which should be configured to represent the title of the item at the given * position in the data set. * @param position The position of the item within the adapter's data set. void onConfigureTab ( @NonNull TabLayout . Tab tab , int position ) ; public TabLayoutMediators ( @NonNull TabLayout tabLayout , @NonNull ViewPager2 viewPager , @NonNull TabConfigurationStrategy tabConfigurationStrategy ) { this ( tabLayout , viewPager , /* autoRefresh= */ true , tabConfigurationStrategy ) ; public TabLayoutMediators ( @NonNull TabLayout tabLayout , @NonNull ViewPager2 viewPager , boolean autoRefresh , @NonNull TabConfigurationStrategy tabConfigurationStrategy ) { this ( tabLayout , viewPager , autoRefresh , /* smoothScroll= */ true , tabConfigurationStrategy ) ; public TabLayoutMediators ( @NonNull TabLayout tabLayout , @NonNull ViewPager2 viewPager , boolean autoRefresh , boolean smoothScroll , @NonNull TabConfigurationStrategy tabConfigurationStrategy ) { this . tabLayout = tabLayout ; this . viewPager = viewPager ; this . autoRefresh = autoRefresh ; TabLayoutMediators . smoothScroll = smoothScroll ; this . tabConfigurationStrategy = tabConfigurationStrategy ; * Link the TabLayout and the ViewPager2 together. Must be called after ViewPager2 has an adapter * set. To be called on a new instance of TabLayoutMediator or if the ViewPager2's adapter * changes. * @throws IllegalStateException If the mediator is already attached, or the ViewPager2 has no * adapter. public void attach ( ) { if ( attached ) { throw new IllegalStateException ( "TabLayoutMediator is already attached" ) ; adapter = viewPager . getAdapter ( ) ; if ( adapter == null ) { throw new IllegalStateException ( "TabLayoutMediator attached before ViewPager2 has an " + "adapter" ) ; attached = true ; // Add our custom OnPageChangeCallback to the ViewPager onPageChangeCallback = new TabLayoutOnPageChangeCallback ( tabLayout ) ; viewPager . registerOnPageChangeCallback ( onPageChangeCallback ) ; // Now we'll add a tab selected listener to set ViewPager's current item onTabSelectedListener = new ViewPagerOnTabSelectedListener ( viewPager , smoothScroll ) ; tabLayout . addOnTabSelectedListener ( onTabSelectedListener ) ; // Now we'll populate ourselves from the pager adapter, adding an observer if // autoRefresh is enabled if ( autoRefresh ) { // Register our observer on the new adapter pagerAdapterObserver = new PagerAdapterObserver ( ) ; adapter . registerAdapterDataObserver ( pagerAdapterObserver ) ; populateTabsFromPagerAdapter ( ) ; // Now update the scroll position to match the ViewPager's current item tabLayout . setScrollPosition ( viewPager . getCurrentItem ( ) , 0f , true ) ; * Unlink the TabLayout and the ViewPager. To be called on a stale TabLayoutMediator if a new one * is instantiated, to prevent holding on to a view that should be garbage collected. Also to be * called before {@link #attach()} when a ViewPager2's adapter is changed. public void detach ( ) { if ( autoRefresh && adapter != null ) { adapter . unregisterAdapterDataObserver ( pagerAdapterObserver ) ; pagerAdapterObserver = null ; tabLayout . removeOnTabSelectedListener ( onTabSelectedListener ) ; viewPager . unregisterOnPageChangeCallback ( onPageChangeCallback ) ; onTabSelectedListener = null ; onPageChangeCallback = null ; adapter = null ; attached = false ; @SuppressWarnings ( "WeakerAccess" ) void populateTabsFromPagerAdapter ( ) { tabLayout . removeAllTabs ( ) ; if ( adapter != null ) { int adapterCount = adapter . getItemCount ( ) ; for ( int i = 0 ; i < adapterCount ; i ++ ) { TabLayout . Tab tab = tabLayout . newTab ( ) ; tabConfigurationStrategy . onConfigureTab ( tab , i ) ; tabLayout . addTab ( tab , false ) ; // Make sure we reflect the currently set ViewPager item if ( adapterCount > 0 ) { int lastItem = tabLayout . getTabCount ( ) - 1 ; int currItem = Math . min ( viewPager . getCurrentItem ( ) , lastItem ) ; if ( currItem != tabLayout . getSelectedTabPosition ( ) ) { tabLayout . selectTab ( tabLayout . getTabAt ( currItem ) ) ; * A {@link ViewPager2.OnPageChangeCallback} class which contains the necessary calls back to the * provided {@link TabLayout} so that the tab position is kept in sync. * <p>This class stores the provided TabLayout weakly, meaning that you can use {@link * ViewPager2#registerOnPageChangeCallback(ViewPager2.OnPageChangeCallback)} without removing the * callback and not cause a leak. private static class TabLayoutOnPageChangeCallback extends ViewPager2 . OnPageChangeCallback { @NonNull private final WeakReference < TabLayout > tabLayoutRef ; private int previousScrollState ; private int scrollState ; TabLayoutOnPageChangeCallback ( TabLayout tabLayout ) { tabLayoutRef = new WeakReference < > ( tabLayout ) ; reset ( ) ; @Override public void onPageScrollStateChanged ( final int state ) { // 根据 是滑动的viewPager还是 点击的 tab 去设置 是否需要平滑动画 if ( state == SCROLL_STATE_DRAGGING ) { // 如果是滑动就设置平滑动画 smoothScroll = true ; } else if ( state == SCROLL_STATE_IDLE ) { // 点击的tab就不设置平滑动画 smoothScroll = false ; previousScrollState = scrollState ; scrollState = state ; @Override public void onPageScrolled ( int position , float positionOffset , int positionOffsetPixels ) { TabLayout tabLayout = tabLayoutRef . get ( ) ; if ( tabLayout != null ) { // Only update the text selection if we're not settling, or we are settling after // being dragged boolean updateText = scrollState != SCROLL_STATE_SETTLING || previousScrollState == SCROLL_STATE_DRAGGING ; // Update the indicator if we're not settling after being idle. This is caused // from a setCurrentItem() call and will be handled by an animation from // onPageSelected() instead. boolean updateIndicator = ! ( scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE ) ; tabLayout . setScrollPosition ( position , positionOffset , updateText , updateIndicator ) ; @Override public void onPageSelected ( final int position ) { TabLayout tabLayout = tabLayoutRef . get ( ) ; if ( tabLayout != null && tabLayout . getSelectedTabPosition ( ) != position && position < tabLayout . getTabCount ( ) ) { // Select the tab, only updating the indicator if we're not being dragged/settled // (since onPageScrolled will handle that). boolean updateIndicator = scrollState == SCROLL_STATE_IDLE || ( scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE ) ; tabLayout . selectTab ( tabLayout . getTabAt ( position ) , updateIndicator ) ; void reset ( ) { previousScrollState = scrollState = SCROLL_STATE_IDLE ; * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back to the * provided {@link ViewPager2} so that the tab position is kept in sync. private static class ViewPagerOnTabSelectedListener implements TabLayout . OnTabSelectedListener { private final ViewPager2 viewPager ; // 注释掉这个局部变量 使用最上边的那个全局变量 //private final boolean smoothScroll; ViewPagerOnTabSelectedListener ( ViewPager2 viewPager , boolean smoothScroll ) { this . viewPager = viewPager ; // 这一行也注释掉 //this.smoothScroll = smoothScroll; @Override public void onTabSelected ( @NonNull TabLayout . Tab tab ) { viewPager . setCurrentItem ( tab . getPosition ( ) , smoothScroll ) ; @Override public void onTabUnselected ( TabLayout . Tab tab ) { // No-op @Override public void onTabReselected ( TabLayout . Tab tab ) { // No-op private class PagerAdapterObserver extends RecyclerView . AdapterDataObserver { PagerAdapterObserver ( ) { } @Override public void onChanged ( ) { populateTabsFromPagerAdapter ( ) ; @Override public void onItemRangeChanged ( int positionStart , int itemCount ) { populateTabsFromPagerAdapter ( ) ; @Override public void onItemRangeChanged ( int positionStart , int itemCount , @Nullable Object payload ) { populateTabsFromPagerAdapter ( ) ; @Override public void onItemRangeInserted ( int positionStart , int itemCount ) { populateTabsFromPagerAdapter ( ) ; @Override public void onItemRangeRemoved ( int positionStart , int itemCount ) { populateTabsFromPagerAdapter ( ) ; @Override public void onItemRangeMoved ( int fromPosition , int toPosition , int itemCount ) { populateTabsFromPagerAdapter ( ) ;具体用法为:
new TabLayoutMediators ( tabLayout , viewPager2 , new TabLayoutMediators . TabConfigurationStrategy ( ) { @Override public void onConfigureTab ( @NonNull TabLayout . Tab tab , int position ) { tab . setText ( tabTitles . get ( position ) ) ; } ) . attach ( ) ;和官方的用法一样 没啥差别
—————————————————————————————
如果即要求点击tab有动画 滑动页面也有动画 可以看下面这个 我们摒弃官方的 TabLayoutMediator 联动方法
我们自己来手动 让 tabLayout 和 viewPager2 联动起来
private void firstMethod ( ) { // 设置 tabLayout 标题 for ( int i = 0 ; i < tabTitles . size ( ) ; i ++ ) { tabLayout . addTab ( tabLayout . newTab ( ) . setText ( tabTitles . get ( i ) ) ) ; // 监听tabLayout事件 设置选中的viewPager2 tabLayout . addOnTabSelectedListener ( new TabLayout . OnTabSelectedListener ( ) { @Override public void onTabSelected ( TabLayout . Tab tab ) { viewPager2 . setCurrentItem ( tab . getPosition ( ) , false ) ; @Override public void onTabUnselected ( TabLayout . Tab tab ) { @Override public void onTabReselected ( TabLayout . Tab tab ) { } ) ; // viewPager2 滑动监听 设置tab选中 viewPager2 . registerOnPageChangeCallback ( new ViewPager2 . OnPageChangeCallback ( ) { @Override public void onPageSelected ( int position ) { tabLayout . selectTab ( tabLayout . getTabAt ( position ) ) ; } ) ;只有下面这四篇有帮助 感谢!!!
第二篇、 ViewPager2主要功能
第三篇、 解决ViewPager2+TabLayout懒加载问题,Fragment被创建多次
第四篇、 android ViewPager2+TabLayout、滑动效果相关问题!
LeakCanary –> 内存泄漏检测工具
使用非常简单
只需要 在build.gradle 中 添加这么一行就可以了 只会在debug模式下检测 不会影响到 正式环境
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
记一次被钓鱼网站盗走ETH的经历我本来是要研究NFT(非同质化代币)的, 在研究这个过程中 有几个在线的交易平台Nifty Gateway、OpenSea、MakersPlace等网站可以查看一些商品属性啥的,有助于了解到底啥是NFT