Imitate zhihu wrote a set of UI interface, combined with the knowledge learned before, one day out, in fact, there is nothing, is some places did not touch the pit.

Results show





Results show

Knowledge points involved


  1. Basic viewpager writing can be seen in this super simple Viewpager control implementation Demo
  2. Tablayout + ViewPager creates a TAB page
  3. The realization of RecycleView can see this introduction to the use of RecyclerView
  4. Viewpager needs to add the head layout of RecycleView to slide up the recycleView, but the recycleView does not add the head layout method, the specific implementation is by loading different items. See RecycleView I wrote to load different types of items

Page analysis





Page analysis

Implementation approach


  1. The outer viewpager page, by clicking the RadioButton at the bottom of the page to switch the page (forbidding the viewpager to respond to sliding events), with the fragment implementation.
  2. The viewPager page in the middle layer is bound to tabLayout and fragment to implement the top TAB.
  3. The top-level viewpager page is just a simple rotograph that you don’t want to write. The viewPagerIndicator open source library is used at the bottom to realize the small dot indicator.
  4. Add depend on the compile ‘com. Android. Support: design: 24.2.0’ compile ‘com. Android. Support: cardview – v7:24.2.0’ compile ‘com. Android. Support: recyclerview – v7:24.2.0’ viewpagerindicator open source library can be downloaded from my projects

1. Layout implementation of the outer ViewPager


  • Layout code implementation

    <?xml version="1.0" encoding="utf-8"? >
    <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical">
    
      <zhj.viewpagerdemo.view.NoScrollViewPager
          android:id="@+id/vp_main"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_weight="1"/>
    
      <RadioGroup
          android:id="@+id/rg_group"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:orientation="horizontal"
          android:paddingTop="5dp">
    
          <RadioButton
              android:id="@+id/rb_home"
              style="@style/BottomTabStyle"
              android:checked="true"
              android:drawableBottom="@drawable/btn_tab_home_selector"/>
    
          <RadioButton
              android:id="@+id/rb_news"
              style="@style/BottomTabStyle"
              android:drawableBottom="@drawable/btn_tab_news_selector"/>
    
          <RadioButton
              android:id="@+id/rb_service"
              style="@style/BottomTabStyle"
              android:drawableBottom="@drawable/btn_tab_service_selector"/>
    
          <RadioButton
              android:id="@+id/rb_gov"
              style="@style/BottomTabStyle"
              android:drawableBottom="@drawable/btn_tab_gov_selector"/>
    
          <RadioButton
              android:id="@+id/rb_setting"
              style="@style/BottomTabStyle"
              android:drawableBottom="@drawable/btn_tab_setting_selector"/>
      </RadioGroup>
    </LinearLayout>Copy the code
  • Disallow sliding viewpager implementations and inherit viewpager

    public class NoScrollViewPager extends ViewPager {
      public NoScrollViewPager(Context context) {
          super(context);
      }
    
      public NoScrollViewPager(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
    
      If false is returned, the event is not intercepted
      @Override
      public boolean onInterceptTouchEvent(MotionEvent ev) {
          return false;
      }
    
      // Override the onTouchEvent event to do nothing
      @Override
      public boolean onTouchEvent(MotionEvent ev) {
          return false; }}Copy the code

2. Outer viewpager code implementation


public class MainActivity extends AppCompatActivity {
    private MyFragmentPagerAdapter myFragmentPagerAdapter;
    private RadioGroup rgGroup;
    private List<Fragment> fragments;
    private ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);  // Hide the original navigation bar
        setContentView(R.layout.activity_main);
        mViewPager = (ViewPager) findViewById(R.id.vp_main);
        // Create a Fragment collection object and pass it to the FragmentPagerAdapter
        fragments=new ArrayList<Fragment> (); fragments.add(new Fragment1());
        fragments.add(new Fragment2());
        fragments.add(new Fragment3());
        fragments.add(new Fragment4());
        fragments.add(new Fragment5());
        myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(),fragments);
        mViewPager.setAdapter(myFragmentPagerAdapter);

        rgGroup = (RadioGroup) findViewById(R.id.rg_group);
        rgGroup.check(R.id.rb_home);
        // Switch pages when the bottom button is clicked
        rgGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup radioGroup, int i) {
                if (i == R.id.rb_home) {
                    mViewPager.setCurrentItem(0.false);// Remove the animation for switching pages
                } else if (i == R.id.rb_news) {
                    mViewPager.setCurrentItem(1.false);
                } else if (i == R.id.rb_service) {
                    mViewPager.setCurrentItem(2.false);
                } else if (i == R.id.rb_gov) {
                    mViewPager.setCurrentItem(3.false);
                } else if (i == R.id.rb_setting) {
                    mViewPager.setCurrentItem(4.false); }}});// Prevent frequent view destruction
        mViewPager.setOffscreenPageLimit(4); }}Copy the code

Most of the time, ViewPager and Fragment will appear together in a project; each ViewPager page is a Fragment. Android provides some special adapters to make ViewPager working with fragments, namely FragmentPagerAdapter and FragmentStatePagerAdapter.

FragmentPagerAdapter inherits from PagerAdapter and is used to display multiple Fragment pages, and each Fragment is stored in the Fragment Manager. FragmentPagerAdapter is best suited for small and relatively static pages, such as a few TAB pages. Every fragment accessed by a user is stored in memory, although its view hierarchy may be destroyed when it is not visible. This can lead to a lot of memory because fragment instances can have any number of states. Set for more pages, it is recommended to use more FragmentStatePagerAdapter. When using FragmentPagerAdapter, the corresponding ViewPager must have a valid set of ids. The derived classes of FragmentPagerAdapter need only implement getItem(int) and getCount().

  • FragmentPagerAdapter Implementation of the adapter

    public class MyFragmentPagerAdapter extends FragmentPagerAdapter{
      private List<Fragment> fragments;
      public MyFragmentPagerAdapter(FragmentManager fm,List<Fragment> fragments) {
          super(fm);
          this.fragments=fragments;
      }
    
      @Override
      public Fragment getItem(int position) {
          return fragments.get(position);
      }
    
      @Override
      public int getCount(a) {
          return fragments.size(a); }}Copy the code

    Several fragments inside are simple layout, I will not paste. I’ll focus on Fragment1, because it contains two other viewPagers.

3. Middle layer layout implementation of viewPager


  • A simple tabLayout is used with viewPager to set the tabLayout style.
<? xmlversion="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">
    <android.support.design.widget.TabLayout
        android:id="@+id/tab_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#1e8ae8"
        app:tabGravity="fill"
        app:tabIndicatorColor="#fff"
        app:tabIndicatorHeight="4dp"
        app:tabMode="fixed"
        app:tabSelectedTextColor="#fff"
        app:tabTextColor="#97c8f4"
        >
    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_menu_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </android.support.v4.view.ViewPager>

</LinearLayout>Copy the code

4. Middle layer viewpager code implementation


public class Fragment1 extends Fragment {

    private ViewPager mViewPager;
    private MyTabFragmentPagerAdapter mMyTabFragmentPagerAdapter;
    private List<Fragment> fragments;
    private TabLayout.Tab one;
    private TabLayout.Tab two;
    private TabLayout.Tab three;
    private TabLayout.Tab four;
    private TabLayout mTabLayout;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        mTabLayout = (TabLayout) view.findViewById(R.id.tab_main);
        mViewPager = (ViewPager) view.findViewById(R.id.vp_menu_pager);
        initdata();
        return view;
    }

    // Initialize the data
    private void initdata() {
        fragments=new ArrayList<Fragment> (); fragments.add(new TabFragment1());
        fragments.add(new TabFragment2());
        fragments.add(new TabFragment3());
        fragments.add(new TabFragment4());
        mMyTabFragmentPagerAdapter = new MyTabFragmentPagerAdapter(getActivity().getSupportFragmentManager()
        ,fragments);
        mViewPager.setOffscreenPageLimit(4);
        mViewPager.setAdapter(mMyTabFragmentPagerAdapter);
        // Bind TabLayout and ViewPager together so that changes made by each side can directly affect the other side, freeing developers to monitor changes made by both sides
        mTabLayout.setupWithViewPager(mViewPager);

        // Specify the Tab position
        one = mTabLayout.getTabAt(0);
        two = mTabLayout.getTabAt(1);
        three = mTabLayout.getTabAt(2);
        four = mTabLayout.getTabAt(3); }}Copy the code
  • The FragmentPagerAdapter adapter of the viewPager in the middle tier

    public class MyTabFragmentPagerAdapter extends FragmentPagerAdapter {
      private String[] mTitles = new String[]{"Recommended"."Round table"."Hot"."Collection"};
      private List<Fragment> fragments;
    
      public MyTabFragmentPagerAdapter(FragmentManager fm,List<Fragment> fragments) {
          super(fm);
          this.fragments=fragments;
      }
    
      @Override
      public Fragment getItem(int position) {
          return fragments.get(position);
      }
    
      @Override
      public int getCount(a) {
          return mTitles.length;
      }
    
      // Set the TAB title
      @Override
      public CharSequence getPageTitle(int position) {
          returnmTitles[position]; }}Copy the code

5. Implementation of the top-level viewpager


  • The recycleView is added to the fragment, and the viewpager is added to the RecycleView as a header.

  • Here is the code for the header layout

    <?xml version="1.0" encoding="utf-8"? >
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
    
      <android.support.v4.view.ViewPager
          android:id="@+id/vp_tab_headview"
          android:layout_width="match_parent"
          android:layout_height="180dp">
      </android.support.v4.view.ViewPager>
    
      <RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_alignParentBottom="true"
          android:background="#fff">
    
          <RelativeLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_centerInParent="true">// Add viewPagerIndicator to the layout to set the appearance with custom properties<com.viewpagerindicator.CirclePageIndicator
                  android:id="@+id/indicator"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:padding="10dip"
                  app:fillColor="#f00"
                  app:pageColor="#e0e0e0"
                  app:radius="4dp"
                  app:strokeWidth="0dp"/>
          </RelativeLayout>
    
      </RelativeLayout>
    </LinearLayout>Copy the code
  • Here is the logical code for TabFragment1

    // Omit the adapter of the viewpager because it is not important
    public class TabFragment1 extends Fragment {
      private RecyclerView mRecyclerView;
      private List<String> mDatas;
      private ArrayList<ImageView> imageList;
    
      @Nullable
      @Override
      public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
          View view = inflater.inflate(R.layout.tab_fragment1, container, false);
          mRecyclerView = (RecyclerView) view.findViewById(R.id.recycle);
          mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL.false));
          initdata();// Initialize the data
          mRecyclerView.setAdapter(new MyAdapter(mDatas));
          return view;
      }
    
      private void initdata() {
          // Initialize recycleView data
          mDatas = new ArrayList<String> ();for (int i = 0; i < 45; i++) {
              mDatas.add("item" + i);
          }
          // Initialize the viewpager data
          int[] imageResIDs = {R.drawable.a, R.drawable.b, R.drawable.c};
          imageList = new ArrayList<ImageView> ();for (int i = 0; i < imageResIDs.length; i++) {
              ImageView image = new ImageView(getActivity()); image.setBackgroundResource(imageResIDs[i]); imageList.add(image); }}class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
          private List<String> mDatas;
          private static final int HEAD_VIEW = 0;/ / head layout
          private static final int BODY_VIEW = 1;// Content layout
          private MyPagerAdapter mPagerAdapter = new MyPagerAdapter(a);// Create a construct parameter to accept the data set
          public MyAdapter(List<String> datas) {
              this.mDatas = datas;
          }
    
          / / create the ViewHolder
          @Override
          public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              if (viewType == HEAD_VIEW) {
                  View view = LayoutInflater.from(parent.getContext())
                          .inflate(R.layout.headview_recycleview, parent, false);
                  MyHeadViewHolder viewHolder = new MyHeadViewHolder(view);
                  return viewHolder;
              }
              if (viewType == BODY_VIEW) {
                  // Load the layout file
                  View view = LayoutInflater.from(parent.getContext())
                          .inflate(R.layout.item_recycle, parent, false);
                  MyBodyViewHolder viewHolder = new MyBodyViewHolder(view);
                  return viewHolder;
              }
              return null;
          }
    
          / / bind ViewHolder
          @Override
          public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
              // Populate the view with data
              if (holder instanceof MyHeadViewHolder) {((MyHeadViewHolder) holder).mViewPager.setAdapter(mPagerAdapter);
                  ((MyHeadViewHolder) holder).indicator.onPageSelected(0);
                  ((MyHeadViewHolder) holder).indicator.setViewPager(((MyHeadViewHolder) holder).mViewPager);
                  ((MyHeadViewHolder) holder).indicator.setSnap(true);
              }
              if (holder instanceof MyBodyViewHolder) {((MyBodyViewHolder) holder).tv.setText(mDatas.get(position- 1)); }}@Override
          public int getItemCount() {
              return mDatas.size() + 1;
          }
    
          // If it is the first item, the header layout is loaded
          @Override
          public int getItemViewType(int position) {
              if (position == 0) {
                  return HEAD_VIEW;
              } else {
                  return BODY_VIEW; }}}// Viewholder for the header layout
      class MyHeadViewHolder extends RecyclerView.ViewHolder {
          ViewPager mViewPager;
          CirclePageIndicator indicator; / / define the indicator
    
          public MyHeadViewHolder(View itemView) {
              super(itemView);
              mViewPager = (ViewPager) itemView.findViewById(R.id.vp_tab_headview);
              indicator = (CirclePageIndicator) itemView.findViewById(R.id.indicator); }}class MyBodyViewHolder extends RecyclerView.ViewHolder {
          TextView tv;
    
          public MyBodyViewHolder(View itemView) {
              super(itemView);
              tv = (TextView) itemView.findViewById(R.id.recycle_tv); }}}Copy the code
  • Problems with using ViewPager+Fragment




The page is destroyed after switching

As shown, switching may cause the page to be destroyed.

  • Solution: To prevent frequent destruction of views, setOffscreenPageLimit(2) or override the destroyItem method of PagerAdaper to leave it empty.

Here is the project address.

Reference blog.csdn.net/never_cxb/a… www.open-open.com/lib/view/op… www.imooc.com/article/274…