安卓ViewPager的使用+TabLayout属性


一、简介

ViewPager是Android中常用的一个控件,用于实现页面之间的切换效果,类似于滑动切换不同页面的功能。ViewPager通常用于实现轮播图、引导页、图片浏览等功能。

以下是ViewPager的一些特点和用法:
特点:

  • 允许用户通过手势(如滑动)或程序控制来切换页面。
  • 支持左右滑动切换页面,也可以自定义切换效果。
  • 可以容纳多个子视图(即多个页面),每个子视图对应一个页面。

1.ViewPager类直接继承了ViewGroup类,所以它是一个容器类,可以在其中添加其他的view类。
在这里插入图片描述

2.ViewPager类需要一个PagerAdapter适配器类给它提供数据。
在这里插入图片描述
在这里插入图片描述

3.ViewPager经常和Fragment一起使用,并且提供了专门的FragmentPagerAdapter和FragmentStatePagerAdapter类供Fragment中的ViewPager使用。
在这里插入图片描述


二、基础使用

2.1 ViewPager布局

ViewPager布局的xml编写

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/guide_vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

2.2 准备数据

使用四张ImageView作为素材放到集合中,待会添加到适配器中

    private int[] imgs = {R.drawable.y0, R.drawable.y1, R.drawable.y2, R.drawable.y3};
    private void initImgs() {
        ViewPager.LayoutParams params = new ViewPager.LayoutParams();
        imageViews = new ArrayList<ImageView>();
        for (int i = 0; i < imgs.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setLayoutParams(params);
            imageView.setImageResource(imgs[i]);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageViews.add(imageView);
        }
    }

2.3 ViewPager的适配器

自定义一个ViewPager的适配器,继承自PagerAdapter ,实现它的两个必写的抽象方法
getCount()和isViewFromObject(@NonNull View view, @NonNull Object object)
介绍一下里面最重要的四个方法:

  • int getCount()
    用途:获取PagerAdapter管理的子视图的数量。
    返回值:返回子视图的数量,通常是数据集合的大小。
    示例:
    @Override
    public int getCount() {
    return dataList.size();
    }
  • booleanisViewFromObject(View view, Object object)
    用途:判断instantiateItem()返回的对象是否与当前视图相关联。
    参数:
    view:当前视图。
    object:instantiateItem()返回的对象。
    返回值:如果view与object相关联,则返回true;否则返回false。
    示例:
    @Override
    public boolean isViewFromObject(View view, Object object) {
    return view == object;
    }
  • Object instantiateItem(ViewGroup container, int position)
    用途:实例化指定位置的子视图并添加到容器中。
    参数:
    container:ViewPager容器,子视图将被添加到此容器中。
    position:子视图在数据集合中的位置。
    返回值:返回实例化的子视图对象。
    示例:
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
    View view = LayoutInflater.from(context).inflate(R.layout.item_layout, container, false);
    container.addView(view);
    return view;
    }
  • void destroyItem(ViewGroup container, int position, Object object)
    用途:销毁指定位置的子视图。
    参数:
    container:ViewPager容器。
    position:要销毁的子视图在数据集合中的位置。
    object:要销毁的子视图对象,通常是通过instantiateItem()返回的对象。
    示例:
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
    container.removeView((View) object);
    }

用List包装ImageView作适配器的参数

public class GuideAdapter extends PagerAdapter {

    private final List<ImageView> imageViews;

    public GuideAdapter(List<ImageView> imageViews) {
        this.imageViews = imageViews;
    }

    /**
     * 获取当前要显示对象的数量
     */
    @Override
    public int getCount() {
        return imageViews.size();
    }

    /**
     * 判断是否用对象生成界面
     */
    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
        return view == o;
    }

    /**
     * 从ViewGroup中移除当前对象
     */
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView(imageViews.get(position));

    }

    /**
     * 当前要显示的对象
     */
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        container.addView(imageViews.get(position));
        return imageViews.get(position);
    }

}

2.4 添加适配器

    private ViewPager vp;
    private int[] imgs = {R.drawable.y0, R.drawable.y1, R.drawable.y2, R.drawable.y3};
    private GuideAdapter adapter;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_guide);
        vp = findViewById(R.id.guide_vp);
        //初始化图片
        initImgs();
        adapter = new GuideAdapter(imageViews);
        vp.setAdapter(adapter);
		......

2.5 监听器OnPageChangeListener

ViewPager监听器OnPageChangeListener对象,实现了页面切换时的监听和处理。OnPageChangeListener接口中包含了三个方法,分别是onPageScrolledonPageSelectedonPageScrollStateChanged

方法用途参数解释
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)用途:当页面在滑动过程中被调用。position:当前页面的索引,即正在被滑动的页面。            positionOffset:当前页面偏移的百分比,取值范围为[0, 1)。         positionOffsetPixels:当前页面偏移的像素值。
onPageSelected(int position):用途:当新的页面被选中时被调用。position:新选中页面的索引。
onPageScrollStateChanged(int state):用途:当页面滑动状态改变时被调用。state:当前页面滑动状态,有三个取值:SCROLL_STATE_IDLE(空闲)、SCROLL_STATE_DRAGGING(拖动中)、SCROLL_STATE_SETTLING(固定中)。

当新的页面被选中的时候,选中不同的圆标,最后一个图片被选中时,Button可见。

        vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                for (int i = 0; i < dotViews.length; i++) {
                    if (position == i) {
                        dotViews[i].setImageResource(R.drawable.guide_selector);
                    } else {
                        dotViews[i].setImageResource(R.drawable.guide_white);
                    }

                    if (position == dotViews.length - 1) {
                        btn.setVisibility(View.VISIBLE);
                    } else {
                        btn.setVisibility(View.GONE);
                    }
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

看一下基础效果:
在这里插入图片描述


三、另外两个适配器

基础的适配器讲完了,另外的两个适配器,FragmentPagerAdapterFragmentStatePagerAdapter,更专注于每一页是Fragment的情况,而这两个子类适配器使用情况也是有区别的。FragmentPagerAdapter适用于页面比较少的情况,FragmentStatePagerAdapter适用于页面比较多的情况。为什么?简单分析下两个适配器的源码就可以知道了。

3.1 FragmentStatePagerAdapter:

   @Override
  public Object instantiateItem(ViewGroup container, int position) {
      // If we already have this item instantiated, there is nothing
      // to do.  This can happen when we are restoring the entire pager
      // from its saved state, where the fragment manager has already
      // taken care of restoring the fragments we previously had instantiated.
      if (mFragments.size() > position) {
          Fragment f = mFragments.get(position);//fragment被释放后这里得到的null值
          if (f != null) {
              return f;
          }
      }

      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }

      Fragment fragment = getItem(position);//fragment被释放后或者是初次进入页面拿到新的Fragment实例
      if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
      if (mSavedState.size() > position) {
          Fragment.SavedState fss = mSavedState.get(position);
          if (fss != null) {
              fragment.setInitialSavedState(fss);
          }
      }
      while (mFragments.size() <= position) {
          mFragments.add(null);
      }
      fragment.setMenuVisibility(false);
      fragment.setUserVisibleHint(false);
      mFragments.set(position, fragment);
      mCurTransaction.add(container.getId(), fragment);//新的Fragment实例 是add上去的

      return fragment;
  }

 @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      Fragment fragment = (Fragment) object;

      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }
      if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
              + " v=" + ((Fragment)object).getView());
      while (mSavedState.size() <= position) {
          mSavedState.add(null);
      }
      mSavedState.set(position, fragment.isAdded()
              ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
      mFragments.set(position, null);//真正释放了fragment实例

      mCurTransaction.remove(fragment);
  }

3.2 FragmentPagerAdapter:

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }

      final long itemId = getItemId(position);

      // Do we already have this fragment?
      String name = makeFragmentName(container.getId(), itemId);
      Fragment fragment = mFragmentManager.findFragmentByTag(name);
      if (fragment != null) {
          if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
          mCurTransaction.attach(fragment);//因为fragment实例没有被真正释放,所以可以直接attach效率高
      } else {
          fragment = getItem(position);//初始化页面的时候拿到fragment的实例
          if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
          mCurTransaction.add(container.getId(), fragment,
                  makeFragmentName(container.getId(), itemId));//add上去
      }
      if (fragment != mCurrentPrimaryItem) {
          fragment.setMenuVisibility(false);
          fragment.setUserVisibleHint(false);
      }

      return fragment;
  }

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }
      if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
              + " v=" + ((Fragment)object).getView());
      mCurTransaction.detach((Fragment)object);//并没有真正释放fragment对象只是detach
  }

从源码中我们可以看出FragmentStatePagerAdapter中fragment实例在destroyItem的时候被真正释放,所以FragmentStatePagerAdapter省内存。FragmentPagerAdapter中的fragment实例在destroyItem的时候并没有真正释放fragment对象只是detach,所以FragmentPagerAdapter消耗更多的内存,带来的好处就是效率更高一些。所以得出这样的结论:FragmentPagerAdapter适用于页面比较少的情况,FragmentStatePagerAdapter适用于页面比较多的情况,因此不同的场合选择合适的适配器才是正确的做法。


四、进阶知识

4.1 PagerTransformer定制页面切换效果

ViewPager 默认的切换效果有些平淡,我们可以定制 ViewPager 的页面切换效果,只需用到 ViewPager 的一个方法setPageTransformer(boolean reverseDrawingOrder, @Nullable PageTransformer transformer),实现一个接口 PageTransformer
在这里插入图片描述

参考链接:
利用ViewPage的PagerTransformer定制页面切换效果
ViewPager一屏显示多个页面

4.2 ViewPager的缓存机制

viewPager.setOffscreenPageLimit(int limit);

这个方法默认值为1,Google在开发ViewPager时,考虑到如果滑动的时候才创建Fragment实例时会带来一定程度的卡顿,因此为ViewPager设置了缓存机制,上述函数则是设置缓存Fragment的数量。

默认情况下,limit的值代表着还要缓存当前Fragment左右各limit个Fragment,一共会创建2*limit+1个Fragment。超出这个limit范围的Fragment就会被销毁。

4.3 ViewPager的懒加载策略

Android的View绘制流程是最消耗时间的操作,尤其是在ViewPager缓存Fragment的情况下,如果在View绘建的同时还进行多个Fragment的数据加载,那用户体验简直是爆炸(不仅浪费流量,而且还造成不必要的卡顿)。。。因此,需要对Fragment们进行懒加载策略。什么是懒加载?就是被动加载,当Fragment页面可见时,才从网络加载数据并显示出来。那什么时候Fragment可见呢?Fragment之中有这样一个函数:

  @Override
    public void setUserVisibleHint(boolean isVisibleToUser) { 
   
        super.setUserVisibleHint(isVisibleToUser);
        loaddata();
    }

当Fragment的可见状态发生变化时就会调用这个函数,boolean参数isVisibleToUser代表当前的Fragment是否可见。
这里有一个巨坑,这个setUserVisibleHint函数是游离在Fragment生命周期之外的,它的执行有可能早于onCreate和onCreateView,那么要实现数据的加载,就必须要在onCreateView创建完视图过后才能使用,不然就会返回空指针崩溃,懒加载的重点也是在这儿,那么实行懒加载必须满足哪些条件呢?

4.3.1 核心思路

1.View视图加载完毕,即onCreateView()执行完成
2.当前Fragment可见,即setUserVisibleHint()的参数为true
3.初次加载,防止多次滑动重复加载

4.3.2 解决措施

可以通过几个标志位的判断来实现上述懒加载思路。

boolean mIsPrepare = false;		//视图还没准备好
boolean mIsVisible= false;		//不可见
boolean mIsFirstLoad = true;	//第一次加载

在onCreateView中确保了View已经准备好时,将mIsPrepare置为true,
在setUserVisibleHint中确保了当前可见时,mIsVisible置为true,
第一次加载完毕后则将mIsFirstLoad 置为false,避免重复加载。

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 
   
    super.onViewCreated(view, savedInstanceState);
    mIsPrepare = true;
    lazyLoad();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) { 
   
    super.setUserVisibleHint(isVisibleToUser);
    //isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见
    if (isVisibleToUser) { 
   
        mIsVisible = true;
        lazyLoad();
    } else { 
   
        mIsVisible = false;
    }
}

懒加载的lazyLoad()代码:

private void lazyLoad() { 
   
    //这里进行三个条件的判断,如果有一个不满足,都将不进行加载
    if (!mIsPrepare || !mIsVisible||!mIsFirstLoad) { 
   
    return;
    }
        loadData();
        //数据加载完毕,恢复标记,防止重复加载
        mIsFirstLoad = false;
    }
    
  private void loadData() { 
   
    //这里进行网络请求和数据装载
    }

如果Fragment销毁的话,还应该将三个标志位进行默认值初始化:

   @Override
    public void onDestroyView() { 
   
        super.onDestroyView();
        mIsFirstLoad=true;
        mIsPrepare=false;
        mIsVisible = false;
    }

在onDestroyView中进行而不是在onDestroy中进行,是因为之前提到Adapter的差异,onDestroy并不一定会调用。

4.4 ViewPager的卡顿及性能优化

Fragment的加载最为耗时的步骤主要有两个,一个是Fragment创建(尤其是创建View的过程),另一个就是读取数据填充到View上的过程。懒加载能够解决后者所造成的卡顿,但是针对前者来说,并没有效果。

4.4.1 优化一:设置缓存页面数

viewPager.setOffscreenPageLimit(int limit) 能够有效地一次性缓存多个Fragment,这样就能够解决在之后每次切换时不会创建实例对象,看起来也会流畅。但是这样的做法,最大的缺点就是容易造成第一次启动时非常缓慢!如果第一次启动时间满足要求的话,就使用这种简单地办法吧。

4.4.2 优化二:避免重复创建View

优化Viewpager和Fragment的方法就是尽可能地避免Fragment频繁创建,当然,最为耗时的都是View的创建。所以更加优秀的优化方案,就是在Fragment中缓存自身有关的View,防止onCreateView函数的频繁执行,直接上源码:

public class MyFragment extends Fragment { 
   
	View rootView;
	
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) { 
   
        if (rootView == null) { 
   
            rootView = inflater.inflate(R.layout.fragment_my, container, false);
        }
        return rootView;

   @Override
    public void onDestroyView() { 
   
        super.onDestroyView();
        Log.d(TAG, "onDestroyView: " + mParam1);
        mIsFirstLoad=true;
        mIsPrepare=false;
        mIsVisible = false;
        if (rootView != null) { 
   
            ((ViewGroup) rootView.getParent()).removeView(rootView);
        }
}

onCreateView中将会对rootView进行null判断,如果为null,说明还没有缓存当前的View,因此会进行过缓存,反之则直接利用。当然,最为重要的是需要在onDestroyView() 方法中及时地移除rootView,因为每一个View只能拥有一个Parent,如果不移除,将会重复加载而导致程序崩溃。


五、TabLayout+ViewPager+Fragment

很多app的主页面都是通过这种方式来实现的,当然现在可能使用jetpack的导航去实现了,不过知识嘛,仍然是嫌少不嫌多的。

整体思路:

创建存储多个Fragment实例的列表
创建PagerAdapter实例并关联到Viewpager中
将ViewPager关联到Tablayout中
根据需求改写Tablayout属性*

看一下mainFragment的布局

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:orientation="vertical">

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tab_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabGravity="fill"
                app:tabIndicatorColor="#F8C221"
                app:tabMode="fixed"
                app:tabSelectedTextColor="#FFC107"
                app:tabTextColor="#121212" />

            <androidx.viewpager.widget.ViewPager
                android:id="@+id/view_pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

        </LinearLayout>
    public void initFragments() {
        String[] title = {"漫画", "小说", "电影"};
        List<Fragment> fragmentlist = new ArrayList<>();
        fragmentlist.add(new ComicFragment());
        fragmentlist.add(new FictionFragment());
        fragmentlist.add(new MovieFragent());
        newTabAdapter = new ViewPageAdapter(getChildFragmentManager(), fragmentlist, title);
        viewPager.setAdapter(newTabAdapter);
        viewPager.setOffscreenPageLimit(3);
        tab_layout.setupWithViewPager(viewPager);
    }

注意ViewPageAdapter中使用了第一个参数使用了getChildFragmentManager,而不是常用的getFragmentManager() 或 getSupportFragmentManager()。为什么呢?
因为我的这块布局上面仍然是一块Fragment。

当在 Activity 中使用 ViewPager 时,一般会使用 getSupportFragmentManager() 方法来获取 Activity 的 FragmentManager。但在 Fragment 中,为了正确管理子 Fragment,应该使用 getChildFragmentManager() 来获取子 Fragment 管理器。
如果在 Fragment 中使用 getFragmentManager() 或 getSupportFragmentManager() 来获取父 Fragment 或 Activity 的 FragmentManager,可能会导致子 Fragment 的生命周期和父 Fragment 的生命周期出现不一致的情况。
因此,必须使用 getChildFragmentManager() 的情况是在 Fragment 中管理子 Fragment 的情况下。如果你没有子 Fragment,或者子 Fragment的生命周期不受父 Fragment 影响,那么你可以考虑使用 getFragmentManager() 或getSupportFragmentManager()。

看一下 ViewPageAdapter

public class ViewPageAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragmentList;
    private String[] titles;

    public ViewPageAdapter(@NonNull FragmentManager fm, List<Fragment> fragmentList, String[] titles) {
        super(fm);
        this.fragmentList = fragmentList;
        this.titles = titles;
    }


    @NonNull
    @Override
    public Fragment getItem(int position) {

        return fragmentList.get(position);
    }

    /**
     * fragment中的个数
     */
    @Override
    public int getCount() {
        return fragmentList.size();
    }

    /**
     * 返回当前的标题
     */
    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return titles[position];
    }
}

实现效果:
在这里插入图片描述


六、TabLayout属性

属性描述
app:tabTextColor=“”设置tab未被选中时候文本的颜色。
app:tabSelectedTextColor=“”设置tab选中时候文本的颜色。
app:tabIndicatorColor=“”设置指示器的颜色,即文本下方的横条。
app:tabIndicatorHeight=“10dp”设置指示器的高度,即横条的高度。
app:tabBackground=“@color/color_00ff00”设置TabLayout组件的背景色,注意该值需要是attr,也就是资源引用,无法输入类似”#123456”的具体颜色值。
app:tabTextAppearance=“@style/YourStyle”设置style.xml文件中样式引用,一般是关于文本的属性设置,并且文本大小的设置只有这一种方式。默认的字体大小使用的是design_tab_text_size,自己在dimens.xml中覆写上就可以替换系统的设置。
app:tabMode=“”有三个值【auto、fixed、scrollable】默认是fixed,表示item默认填充满且平均分布,当item过多的时候,越文本多的item字体会越小进而导致不规范,而scrollable则是表示该TabLayout表示可滑动,很多新闻类应用上方的滑动切换标签都是这样的。
app:tabGravity=“”有三个值【fill、center、start】默认是fill,并且需要tabMode为fixed时才有效,center其实是按照wrap_content的形式居中显示。和tabMaxWidth、tabMinWidth属性不能共用。
tabMinWidth=“”设置Tab的最小宽度,和app:tabGravity属性互斥。
tabMaxWidth=“”设置Tab的最大宽度,和app:tabGravity属性互斥。
tabContentStart官方说设置TabLayout开始位置的偏移量,但是小空没用过,不知道具体啥样。
tabPadding,tabPaddingStart,tabPaddingEnd,tabPaddingTop,tabPaddingBottom设置tab项的内边距。
app:tabIconTint=“”设置文本上面的图标颜色,结合tabIconTintMode使用,这个颜色设置后其实是控制的图标的一个方形区域内所有的颜色,即使这个图标是透明PNG里面内容没有宽高,最终的展示效果受tabIconTintMode影响。你可以找一个红心图片,该属性设置为绿色,tabIconTintMode设置为add就能看出来了。
app:tabIconTintMode=“”用来设置TabItem的图标颜色和tabIconTint设置的颜色是什么样的混合模式。共有6个值:src_atop、src_over、add、screen、src_in、multiply(更多详情可查询资料xfermode,涉及的内容很多不是一两句话说的清的)。
app:tabIndicatorFullWidth设置指示器是否沾满整个Item的宽度,当true的时候每个item会根据自身的宽度设置指示器的宽度,默认为true。如果是false表示指示器的宽度一般会随着item中文本的宽度而设定。
app:tabIndicatorGravity=“bottom”设置指示器所在的位置,有四个值【top、bottom、center、stretch】。Top表示在最上方,也就是图片上方,center表示指示器在图片和文字的中间,stretchj表示占满整个item,默认是bottom,也就是在底部。
app:tabIndicatorAnimationDuration=“3000”表示设置指示器的动画时间。
app:tabIndicatorAnimationMode=“”表示设置动画模式,有两个值【linear、elastic】,elastic表示是弹性的,也就是在切换过程中会逐渐拉长然后收缩为正常;而linear表示指示器一直是默认长度。
tabInlineLabel=“”表示设置图标和文件的方向(默认为false),false表示图片在上文本在下,true表示图片在左文本在右。
app:tabRippleColor=“@color/color_00ff00”表示设置点击item的时候水波纹的颜色。
app:tabUnboundedRipple=“true”表示水波纹是否有边界,默认是false代表有边界,点击的时候会看到看方形背景,设置为true则无边框,背景为圆。

参考链接:
TabLayout+ViewPager+Fragment实现切页展示「建议收藏」
利用ViewPage的PagerTransformer定制页面切换效果
ViewPager一屏显示多个页面

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值