本篇文章纲要

  • 零、先看效果
  • 一、准备
  • 开始动手
  • 1.添加依赖
  • 2.新建布局文件
  • 3、添加tabLayout和ViewPager布局
  • 4.添加一个activity页面
  • 5.按钮点击事件的处理


零、先看效果

Android recycleview grid 在第一个item 固定 放一个添加按钮 android gridlayout动态添加_android

一、准备

tab的使用我们经常能看到,在各大应用中也是颇受青睐,而对于Android开发而言,最常用的方式是tabLayout+ViewPager来实现这种需求。为了满足用户个性化的需求,越来越多的应用选择动态改变tab的数量。

Android recycleview grid 在第一个item 固定 放一个添加按钮 android gridlayout动态添加_移动开发_02

那么我们今天就模仿这个,利用viewpager+tablayout的方式来自己写一个。本文不会有太多原理性的东西,直接上代码。

开始动手

1.添加依赖

使用TabLayout,首先要在build.gradle中添加如下依赖

implementation 'com.google.android.material:material:1.2.1'

2.新建布局文件

添加如下四个按钮(Button)和两个(EditText)。

Android recycleview grid 在第一个item 固定 放一个添加按钮 android gridlayout动态添加_android_03


具体布局大家可以自己写,这里给出我的布局示例

(我的布局使用了约束布局ConstraintLayout+表格布局tableLayout,不过这都不是重点,接着往下看)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".row.sixth.viewpager.ViewPagerActivity">

    <LinearLayout
        android:id="@+id/button_area"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TableLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TableRow>

                <Button
                    android:id="@+id/add_last_page"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/button_add_last_page" />

                <Button
                    android:id="@+id/decrease_last_page"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/button_decrease_last_page" />

            </TableRow>

            <TableRow>

                <Button
                    android:id="@+id/add_first_page"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/button_add_first_page" />

                <Button
                    android:id="@+id/decrease_first_page"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/button_decrease_first_page" />

            </TableRow>

            <TableRow>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/text_pos" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/text_count" />

            </TableRow>

            <TableRow>

                <EditText
                    android:id="@+id/pos"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:inputType="phone"
                    android:maxLength="3" />

                <EditText
                    android:id="@+id/count"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:inputType="phone"
                    android:maxLength="3" />

            </TableRow>

            <TableRow>

                <Button
                    android:id="@+id/auto_add"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/text_auto_add" />

                <Button
                    android:id="@+id/auto_dec"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/text_auto_des" />

            </TableRow>

        </TableLayout>

    </LinearLayout>

    <!--这里待添加关键布局-->
    
</androidx.constraintlayout.widget.ConstraintLayout>
<string name="button_add_last_page">在最后增加一页</string>
    <string name="button_decrease_last_page">在最后减少一页</string>
    <string name="button_add_first_page">增加第一个</string>
    <string name="button_decrease_first_page">减少第一个</string>
    <string name="text_pos">位置</string>
    <string name="text_count">个数</string>
    <string name="text_auto_add">自由添加</string>
    <string name="text_auto_des">自由减少</string>

3、添加tabLayout和ViewPager布局

在上面的布局文件中
添加一个tabLayout

<com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_50"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/button_area"
        app:tabMode="scrollable" />

再在tablayout下面添加一个ViewPager

<androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tab_layout" />

4.添加一个activity页面

我们的主要逻辑都在这个页面里,tabLayout和ViewPager的基础使用方式为:

1.初始化tabLayout和viewPager;

private TabLayout mTabLayout;
private ViewPager mViewPager;
private List<String> mTabNameList = new ArrayList<>();
private List<String> mTabIdList = new ArrayList<>();
private List<TabFragment> mFragmentList = new ArrayList<>();
private int mCurrentSelect = 0;
……
mTabLayout = findViewById(R.id.tab_layout);
mViewPager = findViewById(R.id.view_pager);
……
initTab();
private void initTab() {
	//这里初始化一些数据
    for (int i = 0; i < 3; i++) {
        mTabNameList.add("tab" + i);
        mTabIdList.add(String.valueOf(i));
    }

    for (int i = 0; i < mTabNameList.size(); i++) {
        TextView textView = (TextView) LayoutInflater.from(this).inflate(R.layout.tab_layout, null);
        textView.setText(mTabNameList.get(i));
        TabLayout.Tab tab = mTabLayout.newTab().setCustomView(textView);
        mTabLayout.addTab(tab, (i == mCurrentSelect));

        TabFragment fragment = new TabFragment("页面" + i + ",\n" + "时间: " + System.currentTimeMillis());
        mFragmentList.add(fragment);
        }
    }

2.新建一个Adapter,我把它命名为TabViewPagerAdapter,这个Adapter继承FragmentPagerAdapter,adapter用于管理viewpager的每一个page;

private TabViewPagerAdapter mAdapter;
……
mAdapter = new TabViewPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, mFragmentList);
……
private class TabViewPagerAdapter extends FragmentPagerAdapter {
    private FragmentManager mFragmentManager;
    private List<TabFragment> mFragmentList = new ArrayList<>();

    public TabViewPagerAdapter(@NonNull FragmentManager fm, int behavior, List<TabFragment> fragmentList) {
        super(fm, behavior);
        mFragmentManager = fm;
        updateFragmentList(fragmentList);
    }

    public void updateFragmentList(List<TabFragment> fragmentList) {
        if (!mFragmentList.isEmpty()) {
            mFragmentList.clear();
        }
        mFragmentList.addAll(fragmentList);
        notifyDataSetChanged();
        mViewPager.setOffscreenPageLimit(mFragmentList.size());
    }

    @NonNull
    @Override
    //……覆写的一系列函数

}

3.将tabLayout和viewPager相互绑定,并设置监听

mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(mFragmentList.size());
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        mCurrentSelect = position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }
});
mTabLayout.setupWithViewPager(mViewPager);
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        mViewPager.setCurrentItem(tab.getPosition(), false);
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }
});

4.重点:在TabViewPagerAdapter中重写fragmentViewPagerAdapter的以下几个方法:

/**
		** 作用:根据位置(position),返回当前的fragment
		**/
		@Override
        public Fragment getItem(int position) {}

		/**
		** 作用:返回当前viewpager的page个数
		**/
        @Override
        public int getCount() {}

		/**
		**作用:根据位置(position),返回当前fragment的id
		**/
        @Override
        public long getItemId(int position) {}

		/**
		**作用:根据位置(position),返回当前fragment的标题
		**/
        @Override
        public CharSequence getPageTitle(int position) {}
        
        /**
		**作用:当fragment被创建的时候执行
		**/
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {}

 		/**
		**作用:当fragment被销毁的时候执行
		**/
        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {}

		/**
		**作用:当主视图视图确定当前item的位置是否有更改
		**/
        @Override
        public int getItemPosition(@NonNull Object object) {}

具体的覆写如下:
三个常用的覆写函数就不做过多描述:

@NonNull
        @Override
        public Fragment getItem(int position) {
            return mFragmentList.get(position);
        }

        @Override
        public int getCount() {
            return mFragmentList.size();
        }

        @Nullable
        @Override
        public CharSequence getPageTitle(int position) {
            return mTabNameList.get(position);
        }

实现tab个viewpager的重点在于如下覆写的五个函数,
其中instantiateItem()和destroyItem()函数覆写参考了这篇博客,见注释[^1]。

//将每一个item的id指定为fragment的hasCode,确保不会变更
@Override
public long getItemId(int position) {
    return mFragmentList.get(position).hashCode();
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    Fragment instantiateItemFragment = (Fragment) super.instantiateItem(container, position);
    Fragment itemFragment = mFragmentList.get(position);
    //如果集合中对应下标的fragment和fragmentManager中的对应下标的fragment对象一致,则直接返回该fragment
    if (instantiateItemFragment == itemFragment) {
        return instantiateItemFragment;
    } else {
        //如果集合中对应下标的fragment和fragmentManager中的对应下标的fragment对象不一致,那么就是新添加的,所以自己add进入;
        mFragmentManager.beginTransaction().add(container.getId(), itemFragment).commit();
        return itemFragment;
    }
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment) object;
    //如果getItemPosition中的值为PagerAdapter.POSITION_NONE,就执行该方法。
    if (mFragmentList.contains(fragment)) {
        super.destroyItem(container, position, object);
        return;
    }
    //自己执行移除。因为mFragments在删除的时候就把某个fragment对象移除了,所以一般都得自己移除在fragmentManager中的该对象。
    if (!getSupportFragmentManager().isStateSaved()) {
        mFragmentManager.beginTransaction().remove(fragment).commit();
    }
}

@Override
public int getItemPosition(@NonNull Object object) {
	//如果fragment还没有添加过,或者没有包含在里面,则返回没有找到
    if (!((Fragment) object).isAdded() || !mFragmentList.contains(object)) {
        return POSITION_NONE;
    }
    //否则就返回列表中的位置
    return mFragmentList.indexOf(object);
 }

5.按钮点击事件的处理

增加tab和viewpager的逻辑

/**
     * 
     * @param pos :需要在哪个位置开始增加
     * @param count :增加的个数
     */
private void addFragment(int pos, int count) {
	//增加的位置超过现有的tab个数,无效
    if (pos > mFragmentList.size()) {
        pos = mFragmentList.size();
    }
    //位置小于0也无效
    if (pos < 0) {
        pos = 0;
    }
    //增加的数量小于0也无意义
    if (count <= 0) {
        count = 1;
    }
    for (int i = pos; i < pos + count; i++) {
        long time = System.currentTimeMillis();
        mTabIdList.add(i, "增加tabId" + time);
        mTabNameList.add(i, "tab名称" + time);
        TextView textView = (TextView) LayoutInflater.from(this).inflate(R.layout.tab_layout, null);
        textView.setText(mTabNameList.get(i));
        TabLayout.Tab tab = mTabLayout.newTab().setCustomView(textView);
        mTabLayout.addTab(tab, i);
        TabFragment tabFragment = new TabFragment("增加Fragment:" + "\n" + "时间戳:" + time);
        mFragmentList.add(pos, tabFragment);
    }
    if (mViewPager.getAdapter() instanceof TabViewPagerAdapter) {
        	//源数据有变更后,要及时更新
            ((TabViewPagerAdapter) mViewPager.getAdapter()).updateFragmentList(mFragmentList);
            }
        //再选中tab变更之前的tab
        mViewPager.post(new Runnable() {
            @Override
            public void run() {
                mTabLayout.selectTab(mTabLayout.getTabAt(mCurrentSelect));
            }
        });
}

减少tab和viewpager的逻辑

/**
     *
     * @param pos :需要在哪个位置开始减少
     * @param count :减少的个数
     */
private void decFragment(int pos, int count) {
    if (mFragmentList.size() < count) {
        if (count > 1) {
            ToastUtil.toastLong(getString(R.string.text_input_count_error));
        } else {
            ToastUtil.toastLong(getString(R.string.text_dec_tip));
        }
        return;
    } else if (mFragmentList.size() == count) {
        ToastUtil.toastLong(getString(R.string.text_dec_all_tip));
        return;
    }
    if (pos > mFragmentList.size() - 1) {
        pos = mFragmentList.size() - 1;
    }
    if (pos < 0) {
        pos = 0;
    }
    if (count <= 0) {
        count = 1;
    }

    for (int i = pos; i < pos + count; i++) {
        mTabIdList.remove(i);
        mTabNameList.remove(i);
        mTabLayout.removeTabAt(i);
        TabFragment tabFragment = (TabFragment) mFragmentList.get(i);
        mFragmentList.remove(i);
    }
    if (mViewPager.getAdapter() instanceof TabViewPagerAdapter) {
        ((TabViewPagerAdapter) mViewPager.getAdapter()).updateFragmentList(mFragmentList);
    }
    //再选中tab变更之前的tab
    mViewPager.post(new Runnable() {
        @Override
        public void run() {
            mTabLayout.selectTab(mTabLayout.getTabAt(mCurrentSelect), false);
        }
    });
}

剩余的按钮点击事件逻辑:

@Override
    public void onClick(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.add_last_page:
                addFragment(mFragmentList.size(), 1);
                break;
            case R.id.decrease_last_page:
                decFragment(mFragmentList.size() - 1, 1);
                break;
            case R.id.add_first_page:
                addFragment(0, 1);
                break;
            case R.id.decrease_first_page:
                decFragment(0, 1);
                break;
            case R.id.auto_add:
                if (TextUtils.isEmpty(mEditTextPos.getText().toString())) {
                    ToastUtil.toastLong(getString(R.string.text_pos_tip));
                    return;
                }
                if (TextUtils.isEmpty(mEditTextCount.getText().toString())) {
                    ToastUtil.toastLong(getString(R.string.text_count_tip));
                    return;
                }
                String countString1 = mEditTextCount.getText().toString();
                String posString1 = mEditTextPos.getText().toString();
                try {
                    int count = Integer.parseInt(countString1);
                    int pos = Integer.parseInt(posString1);
                    addFragment(pos, count);
                } catch (Exception e) {
                    ToastUtil.toastLong(getString(R.string.text_input_tip));
                }
                break;
            case R.id.auto_dec:
                if (TextUtils.isEmpty(mEditTextPos.getText().toString())) {
                    ToastUtil.toastLong(getString(R.string.text_pos_tip));
                    return;
                }
                if (TextUtils.isEmpty(mEditTextCount.getText().toString())) {
                    ToastUtil.toastLong(getString(R.string.text_count_tip));
                    return;
                }
                String countString2 = mEditTextCount.getText().toString();
                String posString2 = mEditTextPos.getText().toString();
                try {
                    int count = Integer.parseInt(countString2);
                    int pos = Integer.parseInt(posString2);
                    decFragment(pos, count);
                } catch (Exception e) {
                    ToastUtil.toastLong(getString(R.string.text_input_tip));
                }
                break;
            default:
                break;
        }
    }