Android 高仿新浪微博底部导航栏,实现双击首页Tab,页面的ListView滚动、刷新

简介:

 现在很多APP,如微信、QQ、微博等等,它们的主页面都无一例外的选择使用底部Tab导航, 通过这种方式,可以很好的把页面层级分化,很好的提高用户体验。相信,很多Android开发者,都使用到过这种经典的设计,可是您你能保证您的设计真的没问题么?

 为啥我会有这个疑问呢? 因为我日前就遇到了这么一个情况,发现我做的APP导航页有问题。 具体可以参考这篇博客:【Android】保存Fragment切换状态 , 首先说明的是,我的项目是从之前就沿用下来的框架,页面底部tab的实现,就是采用前面博客提到的方式, 可是在测试的时候,竟然发现,使用这种方式来实现,经常会发生tab重叠情况: 比如,此刻选中的事“首页”tab,可是内容确实“活动”tab,尤其是在你的app在二级页面发生崩溃返回到一级页面时,这种情况经常发生! 当然,这篇博客的评论里面,也提到了这个问题,所以,最后大家建议大家采用的是;"推荐直接使用ViewPager,通过自定义ViewPager禁用掉左右滑动和自动销毁即可"

 

  在我接触的APP中,我觉得新浪微博的设计当然是最经典的,为啥呢?就因为它多了一个功能,“底部tab的双击,来实现列表滚动到最上方并刷新博客列表”,要知道,这样的设计,可以极大提高用户体验的(避免了用户手动滚动到最上方,然后下拉刷新...),接下来,将带着大家学习如何去实现吧。


先看效果图(尤其是日志):


1. 直接定义tab页面,一个ViewPager,四个RadioButton:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.lnyp.vf.ContainerViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="60dp" />

    <RadioGroup
        android:id="@+id/radiogroup"
        android:layout_width="fill_parent"
        android:layout_height="60dp"
        android:background="#ececec"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/radio_main"
            style="@style/navigation_style"
            android:checked="true"
            android:drawableTop="@drawable/selector_main_bottom_tab_first"
            android:paddingLeft="0dp"
            android:text="首页" />

        <RadioButton
            android:id="@+id/radio_projects"
            style="@style/navigation_style"
            android:checked="false"
            android:drawableTop="@drawable/selector_main_bottom_tab_second"
            android:paddingLeft="0dp"
            android:text="活动" />

        <RadioButton
            android:id="@+id/radio_studys"
            style="@style/navigation_style"
            android:checked="false"
            android:drawableTop="@drawable/selector_main_bottom_tab_third"
            android:paddingLeft="0dp"
            android:text="社区" />

        <RadioButton
            android:id="@+id/radio_user_center"
            style="@style/navigation_style"
            android:checked="false"
            android:drawableTop="@drawable/selector_main_bottom_tab_forth"
            android:paddingLeft="0dp"
            android:text="我的" />
    </RadioGroup>

</RelativeLayout>

2. 自定义PagerAdapter,为ViewPager添加布局(Fragment),要求可以实现Fragment切换,状态的保存:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
 * 为ViewPager添加布局(Fragment),绑定和处理fragments和viewpager之间的逻辑关系
 * 可保持Fragment切换状态
 */
public class FragmentViewPagerAdapter extends PagerAdapter implements MyViewPager.OnPageChangeListener {
    private List<Fragment> fragments; // 每个Fragment对应一个Page
    private FragmentManager fragmentManager;
    private ContainerViewPager viewPager; // viewPager对象
    private int currentPageIndex = 0; // 当前page索引(切换之前)

    private OnExtraPageChangeListener onExtraPageChangeListener; // ViewPager切换页面时的额外功能添加接口

    public FragmentViewPagerAdapter(FragmentManager fragmentManager, ContainerViewPager viewPager, List<Fragment> fragments) {
        this.fragments = fragments;
        this.fragmentManager = fragmentManager;
        this.viewPager = viewPager;
        this.viewPager.setAdapter(this);

        this.viewPager.setOnPageChangeListener(this);
    }

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

    @Override
    public boolean isViewFromObject(View view, Object o) {
        return view == o;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView(fragments.get(position).getView()); // 移出viewpager两边之外的page布局
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = fragments.get(position);
        if (!fragment.isAdded()) { // 如果fragment还没有added
            FragmentTransaction ft = fragmentManager.beginTransaction();
            ft.add(fragment, fragment.getClass().getSimpleName());
            ft.commit();
            /**
             * 在用FragmentTransaction.commit()方法提交FragmentTransaction对象后
             * 会在进程的主线程中,用异步的方式来执行。
             * 如果想要立即执行这个等待中的操作,就要调用这个方法(只能在主线程中调用)。
             * 要注意的是,所有的回调和相关的行为都会在这个调用中被执行完成,因此要仔细确认这个方法的调用位置。
             */
            fragmentManager.executePendingTransactions();
        }

        if (fragment.getView().getParent() == null) {
            container.addView(fragment.getView()); // 为viewpager增加布局
        }

        return fragment.getView();
    }

    /**
     * 当前page索引(切换之前)
     *
     * @return
     */
    public int getCurrentPageIndex() {
        return currentPageIndex;
    }

    public OnExtraPageChangeListener getOnExtraPageChangeListener() {
        return onExtraPageChangeListener;
    }

    /**
     * 设置页面切换额外功能监听器
     *
     * @param onExtraPageChangeListener
     */
    public void setOnExtraPageChangeListener(OnExtraPageChangeListener onExtraPageChangeListener) {
        this.onExtraPageChangeListener = onExtraPageChangeListener;
    }

    @Override
    public void onPageScrolled(int i, float v, int i2) {
        if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口
            onExtraPageChangeListener.onExtraPageScrolled(i, v, i2);
        }
    }

    @Override
    public void onPageSelected(int i) {
        fragments.get(currentPageIndex).onPause(); // 调用切换前Fargment的onPause()
        if (fragments.get(i).isAdded()) {
            fragments.get(i).onResume(); // 调用切换后Fargment的onResume()
        }
        currentPageIndex = i;

        if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口
            onExtraPageChangeListener.onExtraPageSelected(i);
        }
    }

    @Override
    public void onPageScrollStateChanged(int i) {
        if (null != onExtraPageChangeListener) { // 如果设置了额外功能接口
            onExtraPageChangeListener.onExtraPageScrollStateChanged(i);
        }
    }

    /**
     * page切换额外功能接口
     */
    public static class OnExtraPageChangeListener {
        public void onExtraPageScrolled(int i, float v, int i2) {
        }

        public void onExtraPageSelected(int i) {
        }

        public void onExtraPageScrollStateChanged(int i) {
        }
    }
}


  注: 上述代码中,有个ContainerViewPager,该ContainerViewPager是继承ViewPager,主要是为了去除ViewPager左右滑动功能,大家可以再源码里直接看到。


3. 自定义四个(随便几个)Fragment, 每个Fragment就是ViewPager里要承载的View,负责显示每个Tab页面


4. 实现MainActivity.java,为ViewPager设置PageAdapter, 并且,在MainActivity中实现双击tab功能:

import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.RadioButton;

import java.util.ArrayList;
import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends FragmentActivity {

    public static final int TAB_HOME = 0;
    public static final int TAB_PROJECTS = 1;
    public static final int TAB_STUDYS = 2;
    public static final int TAB_USER_CENTER = 3;

    @Bind(R.id.viewpager)
    public ContainerViewPager viewPager;

    @Bind(R.id.radio_main)
    public RadioButton radioMain;

    @Bind(R.id.radio_projects)
    public RadioButton radioProjects;

    @Bind(R.id.radio_studys)
    public RadioButton radioStudys;

    @Bind(R.id.radio_user_center)
    public RadioButton radioUserCenter;

    FragmentMain fragmentMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);

        initView();
        addPageChangeListener();
    }

    private void initView() {

        List<Fragment> fragments = new ArrayList<Fragment>();

        fragmentMain = new FragmentMain();

        FragmentHuodong fragmentHuodong = new FragmentHuodong();

        FragmentShequ fragmentShequ = new FragmentShequ();

        FragmentMy fragmentMy = new FragmentMy();

        fragments.add(fragmentMain);
        fragments.add(fragmentHuodong);
        fragments.add(fragmentShequ);
        fragments.add(fragmentMy);

        this.viewPager.setOffscreenPageLimit(0);

        FragmentViewPagerAdapter adapter = new FragmentViewPagerAdapter(this.getSupportFragmentManager(), viewPager, fragments);

    }

    private void addPageChangeListener() {
        viewPager.setOnPageChangeListener(new MyViewPager.OnPageChangeListener() {

            @Override
            public void onPageSelected(int id) {
                switch (id) {
                    case TAB_HOME:
                        radioMain.setChecked(true);
                        break;
                    case TAB_PROJECTS:
                        radioProjects.setChecked(true);
                        break;
                    case TAB_STUDYS:
                        radioStudys.setChecked(true);
                        break;
                    case TAB_USER_CENTER:
                        radioUserCenter.setChecked(true);
                        break;
                }
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {

            }

            @Override
            public void onPageScrollStateChanged(int arg0) {

            }
        });
    }

    @OnClick({R.id.radio_main, R.id.radio_projects, R.id.radio_studys, R.id.radio_user_center})
    public void onClick(View v) {
        switch (v.getId()) {

            case R.id.radio_main:
                viewPager.setCurrentItem(TAB_HOME, false);
                doubleClick(v);
                break;
            case R.id.radio_projects:
                viewPager.setCurrentItem(TAB_PROJECTS, false);
                break;
            case R.id.radio_studys:
                viewPager.setCurrentItem(TAB_STUDYS, false);
                break;
            case R.id.radio_user_center:
                viewPager.setCurrentItem(TAB_USER_CENTER, false);
                break;
        }
    }

    long firstClickTime = 0;
    long secondClickTime = 0;

    public void doubleClick(View view) {

        if (firstClickTime > 0) {
            secondClickTime = SystemClock.uptimeMillis();
            if (secondClickTime - firstClickTime < 500) {
                fragmentMain.ScrollToTop();
            }
            firstClickTime = 0;
            return;
        }

        firstClickTime = SystemClock.uptimeMillis();

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                    firstClickTime = 0;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

  注:在处首页Tab的点击事件时,除了要设置viewPager.setCurrentItem(TAB_HOME, false);外,还需要设置doubleClick(v);它主要是处理了双击事件。


  通过以上四个步骤,已经可以实现tab导航,双击tab调用Fragment中方法了。接下来,让我们看下日志:仔细看下ViewPager+Fragment的生命周期:(我设置ViewPager取消了预加载功能)

1. 第一次进入到主页面:加载FragmentMain,执行生命周期方法



2. 分别点击其他的Tab:



3. 之后再点击FragmentMain,我们发现,并未在执行任何生命周期的方法;

4. 点击FragmentMain页面中的Button,进入新的Activity:


  我们发现,刚刚启动的几个Fragment(首页、活动、社区),都执行了onPause和onStop方法;


5. 返回到上一级页面,也就是首页:


  刚刚停止的几个Fragment(首页、活动、社区),执行了onStart和onResume方法。


6. 接着,测试下首页tab的双击事件:


  会调用我们在FragmentMain中定义ScrollToTop方法,在该方法中,我们可以处理一些相应的逻辑。


7. 退出APP,看下:




看到这里,不知道大家是否明白了如何定义使用Tab了,如果有疑问,可以再多看看源码,也欢迎一起讨论。


github源码地址:https://github.com/zuiwuyuan/ViewpagerFragmentTab


  如此这般,就OK啦!欢迎指正!
  如有疑问,欢迎进QQ群:487786925( Android研发村 )

相关文章
|
5月前
|
Android开发 容器
35. 【Android教程】视频页面:ViewPager
35. 【Android教程】视频页面:ViewPager
58 3
|
2月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
180 9
|
6月前
|
Android开发
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
|
6月前
|
Java Android开发
Android12 双击power键启动相机源码解析
Android12 双击power键启动相机源码解析
189 0
|
3月前
|
存储 安全 物联网
Android经典实战之跳转到系统设置页面或其他系统应用页面大全
本文首发于公众号“AntDream”,关注获取更多技巧。文章总结了Android开发中跳转至系统设置页面的方法,包括设备信息、Wi-Fi、显示与声音设置等,并涉及应用详情与电池优化页面。通过简单的Intent动作即可实现,需注意权限与版本兼容性。每日进步,尽在“AntDream”。
398 2
|
3月前
|
监控 安全 API
Android项目架构设计问题之保证线上用户不会进入到本地配置页面如何解决
Android项目架构设计问题之保证线上用户不会进入到本地配置页面如何解决
31 0
|
3月前
|
Android开发
Android项目架构设计问题之定义一个关闭当前页面的Action如何解决
Android项目架构设计问题之定义一个关闭当前页面的Action如何解决
17 0
|
5月前
|
API Android开发 开发者
`RecyclerView`是Android API 21引入的UI组件,用于替代ListView和GridView
【6月更文挑战第26天】`RecyclerView`是Android API 21引入的UI组件,用于替代ListView和GridView。它提供高效的数据视图复用,优化的布局管理,支持多种布局(如线性、网格),并解耦数据、适配器和视图。RecyclerView的灵活性、性能(如局部刷新和动画支持)和扩展性使其成为现代Android开发的首选,特别是在处理大规模数据集时。
64 2
|
5月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
5月前
|
传感器 Android开发 UED
Android统一设置页面竖屏
【6月更文挑战第4天】
145 8