最近一段时间,Android行业大不如从前轻松,企业要求越来越高了,就算入职了很多时候Android这块也不太受重视,现在Android开发者还能经常接到新需求就算是很幸运的事了,我最近的工作也是没什么活,闲来无事就整理一下Android的一些东西,以免遗忘。

这次要做的类似于许多新闻资讯类app,底部tab切换不同的功能页面,首页中又可以切换不同的新闻类型页面,当切换到最后一个新闻fragment时,再向后滑则父viewpager滑动,具体实现是用的ViewPager+Fragment+ViewPager+Fragment,tab用的是desgin库中的TabLayout,fragment实现懒加载,数据源用的是聚合数据(https://www.juhe.cn/),网络框架用的是张鸿洋大神的OkhttpUtils,因为只是做一个效果,就不搭建Rxjava+Retrofit网络请求框架了,主要用到的三方依赖如下:

compile "com.android.support:design:25.+"
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.zhy:okhttputils:2.6.2'
compile 'com.android.support:recyclerview-v7:25+'

一、准备工作

1. BaseApplica

可以自定义一个Application,一是为了初始化OkhttpUtils,另一个是为了获取一个使用较为方便的Context,因为fragment嵌套过多,使得Context获取比较麻烦

public class BaseApplication extends Application {private static Context context;@Overridepublic void onCreate() {super.onCreate();context = getApplicationContext();OkHttpClient okHttpClient = new OkHttpClient.Builder()
//                .addInterceptor(new LoggerInterceptor("TAG")).connectTimeout(10000L, TimeUnit.MILLISECONDS).readTimeout(10000L, TimeUnit.MILLISECONDS)//其他配置.build();OkHttpUtils.initClient(okHttpClient);}public static Context getContext(){return context;}
}

2. SharedPreferencesHelper

定义一个SharedPreferences用户偏好存储主要是因为我想实现一个可以添减新闻类型的效果,不过因为昨晚感觉时间有点晚了,而且代码量对博客而言已经不少了,就给这个思路,有兴趣或者有这个需求的兄弟可以参考下这个思路完成。

public class SharedPreferencesHelper {private SharedPreferences sharedPreferences;private SharedPreferences.Editor editor;public SharedPreferencesHelper(Context context,String preferenceName){sharedPreferences = context.getSharedPreferences(preferenceName,Context.MODE_PRIVATE);editor = sharedPreferences.edit();}public void setSharedPreferencesString(String  tag,boolean isFirst) {editor.putBoolean(tag,isFirst);editor.commit();}public boolean getSharedPreferencesString(String tag) {return sharedPreferences.getBoolean(tag,false);}public <T> void setSharedPreferencesList(String tag, List<T> list){if (null==list||list.size()==0) {return;}Gson gson = new Gson();String s = gson.toJson(list);editor.clear();editor.putString(tag,s);editor.commit();}public <T> List<T> getSharedPreferencesList(String tag) {List<T> datas = new ArrayList<>();String string = sharedPreferences.getString(tag, null);if (null==string) {return datas;}Gson gson = new Gson();datas = gson.fromJson(string,new TypeToken<List<T>>(){}.getType());return datas;}
}

这个类很简单,初始化的时候传入Context和存储名,创建sharedPreferences和Editor,然后存取文字一对方法,存取list集合一对方法,因为SharedPreferences存储只能存一些基本类型,所以先用Gson将list转化为String类型再进行存储,取的时候再用Gson把数据转化为list。使用也很简单,在MainActivity中进行是否初次进入判断,初次进入赋给喜欢新闻类型的list集合初始值,当然一般app的设计都是有几个新闻类型是一定存在的,这个无所谓,实现上大同小异。然后在初始化浏览新闻页面时,获取新闻类型集合,根据集合的数量创建相应个数的fragment,每个fragment根据新闻类型再进行网络访问获取新闻内容,具体实现如下。

二、页面设计

1. MainActivity

MainActivity是承载整个ViewPager+Fragment+ViewPager+Fragment的载体,专业来说是他们的父布局,我对它的设计很简单,就是上边一个ViewPager滑动页卡,下边一个TabLayout存放tab,然后在viewPager中添加fragment,TabLayout中添加tab,最后将二者关联起来:

public class MainActivity extends AppCompatActivity {private TabLayout tabLayout;private ViewPager viewPager;private List<Fragment> fragments = new ArrayList<>();private List<String> pageTitle = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SharedPreferencesHelper isFirstSave = new SharedPreferencesHelper(this, "isFirstSave");if (!isFirstSave.getSharedPreferencesString("isFirst")) {isFirstSave.setSharedPreferencesString("isFirst",true);List<String> list = new ArrayList<>();list.add("头条");list.add("社会");list.add("科技");new SharedPreferencesHelper(BaseApplication.getContext(),"newsDatasSave").setSharedPreferencesList("newsDatas",list);}viewPager = (ViewPager) findViewById(R.id.activity_main_pager);tabLayout = (TabLayout) findViewById(R.id.activity_main_tab);initPager();initTab();tabLayout.setupWithViewPager(viewPager);//关联viewPager和fragmentfor (int i = 0; i < fragments.size(); i++) {tabLayout.getTabAt(i).setText(pageTitle.get(i)).setIcon(R.mipmap.ic_launcher);//设置tab显示文字和图片}}private void initPager() {fragments.add(new MainFragment());fragments.add(new MsgFragment());fragments.add(new MineFragment());pageTitle.add("首页");pageTitle.add("消息");pageTitle.add("我的");ActivityFragmentAdapter activityFragmentAdapter = new ActivityFragmentAdapter(getSupportFragmentManager());viewPager.setAdapter(activityFragmentAdapter);viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrollStateChanged(int state) {}});}private void initTab() {tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {@Overridepublic void onTabSelected(TabLayout.Tab tab) {}@Overridepublic void onTabUnselected(TabLayout.Tab tab) {}@Overridepublic void onTabReselected(TabLayout.Tab tab) {}});}class ActivityFragmentAdapter extends FragmentPagerAdapter {public ActivityFragmentAdapter(FragmentManager fm) {super(fm);}@Overridepublic Fragment getItem(int position) {return fragments.get(position);}@Overridepublic int getCount() {return fragments.size();}//        这里设置的文字值将会在tab上显示
//        @Override
//        public CharSequence getPageTitle(int position) {
//            return pageTitle.get(position);
//        }}
}

这里需要注意的一点事tab文字和图片的设置一定要放在viewpager和fragment关联之后或者在getPageTitle方法中仅设置显示文字,否则设置的文字不会显示。

2. BaseFragment

接下来就要设计Fragment,寿险我们要设计一个基类,用作实现懒加载。所谓懒加载就是只有当fragment显示且无数据才加载数据,当数据加载完毕,我们要将其显示到控件,所以此时控件必须已加载完毕,否则会报空指针,综上,我们共需要三个判断。

public abstract class BaseFragment extends Fragment {private boolean isInitData = false;//是否初始化数据private boolean isVisibleToUser = false;//是否可见private boolean isPrepareView = false;//view是否加载完成protected List<String> newsDatas;//子fragment对应的tab集合@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(getLayoutId(), container, false);}protected abstract int getLayoutId();@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);newsDatas = new SharedPreferencesHelper(BaseApplication.getContext(), "newsDatasSave").getSharedPreferencesList("newsDatas");isPrepareView = true;}@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);this.isVisibleToUser = isVisibleToUser;lazyInitData();}private void lazyInitData() {if(!isInitData&&isVisibleToUser&&isPrepareView){isInitData = true;initData();}}protected abstract void initData();@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);lazyInitData();}
}

onViewCreated方法执行我们可以获取到view已初始化完毕的信息,setUserVisibleHint方法获取的是此fragment是否呈现的信息,当呈现时我们可以试着做第一次懒加载,如果无数据获取则讲数据获取的值改为true,并进行数据获取。为了防止fragment第一次可见未加载数据,在onActivityCreated再次做一个加载判断。

由于我最初的设想是显示新闻内容的fragment个数是可变的,而且fragment只有一个类,所以要获取页卡是什么新闻类型,最简单的实现应该就是把这两个参数放到基类里面,父tab显示的新闻类型在基类中获取。

3. MainFragment

显示新闻页卡的父fragment,布局实现和MainActivity差不多

public class MainFragment extends BaseFragment {private TabLayout tabLayout;private ViewPager viewPager;@Overrideprotected int getLayoutId() {return R.layout.fragment_main;}@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);tabLayout = (TabLayout) view.findViewById(R.id.fragment_main_tab);viewPager = (ViewPager) view.findViewById(R.id.fragment_main_pager);initTab();initPager();tabLayout.setupWithViewPager(viewPager);for (int i = 0; i < newsDatas.size(); i++) {tabLayout.getTabAt(i).setText(newsDatas.get(i));}}private void initTab() {tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {@Overridepublic void onTabSelected(TabLayout.Tab tab) {}@Overridepublic void onTabUnselected(TabLayout.Tab tab) {}@Overridepublic void onTabReselected(TabLayout.Tab tab) {}});}private void initPager() {final List<Fragment> fragments = new ArrayList<>();for (int i = 0; i < newsDatas.size(); i++) {NewsFragment newsFragment = new NewsFragment();fragments.add(newsFragment);newsFragment.setPosition(i);}viewPager.setAdapter(new FragmentPagerAdapter(getChildFragmentManager()) {@Overridepublic Fragment getItem(int position) {return fragments.get(position);}@Overridepublic int getCount() {return fragments.size();}//            @Override
//            public CharSequence getPageTitle(int position) {
//                return newsDatas.get(position);
//            }});viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrollStateChanged(int state) {}});}@Overrideprotected void initData() {}
}

这里要注意的问题是新闻fragment集合fragments的初始化,fragment经常会出现突然多一块白色区域,嵌套时多了白色页卡或者页卡被挤在一旁等等,这些要注意的问题是fragment中view大小设置和viewpager的list个数变化,此demo中如果fragments在类中初始化话,方法initPager中就会不断添加list数据,导致视图被挤在一旁。

还有一点需要注意就是FragmentManager 的使用,总的来说三种获取方式:getFragmentManager()、getSupportFragmentManager()和getChildFragmentManager(),Android V4扩展包获取的FragmentManager 使用getSupportFragmentManager(),android.app获取使用getFragmentManager(),像这种嵌套的使用getChildFragmentManager()。我就直接说使用,具体区别有兴趣的可以去搜下。

4. NewsFragment

显示新闻的fragment,简单期间,我就直接用RecyclerView显示标题实现了

public class NewsFragment extends BaseFragment {private List<NewsBean.ResultBean.DataBean> newsDatasValue = new ArrayList<>();//子fragment内获取的新闻内容private RecyclerView recyclerView;private NewsAdapter newsAdapter = new NewsAdapter();private int position;@Overrideprotected int getLayoutId() {return R.layout.fragment_main_news;}@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);recyclerView = (RecyclerView) view.findViewById(R.id.fragment_main_news_recycler);recyclerView.setLayoutManager(new LinearLayoutManager(BaseApplication.getContext()));recyclerView.setAdapter(newsAdapter);}@Overrideprotected void initData() {String url = "";switch (newsDatas.get(position)) {case "头条":url = "http://v.juhe.cn/toutiao/index?type=top&key=28aa4ac2a689c881a4e6e51cf918d695";break;case "社会":url = "http://v.juhe.cn/toutiao/index?type=shehui&key=28aa4ac2a689c881a4e6e51cf918d695";break;case "科技":url = "http://v.juhe.cn/toutiao/index?type=keji&key=28aa4ac2a689c881a4e6e51cf918d695";break;default:url = "http://v.juhe.cn/toutiao/index?type=top&key=28aa4ac2a689c881a4e6e51cf918d695";break;}OkHttpUtils.get().url(url).build().execute(new StringCallback() {@Overridepublic void onError(Call call, Exception e, int id) {}@Overridepublic void onResponse(String response, int id) {NewsBean newsBean = new Gson().fromJson(response, NewsBean.class);if (newsBean.getError_code() == 0) {newsDatasValue = newsBean.getResult().getData();newsAdapter.notifyDataSetChanged();}}});}class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {return new ViewHolder(LayoutInflater.from(BaseApplication.getContext()).inflate(R.layout.item_news, parent, false));}@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {holder.tv.setText(newsDatasValue.get(position).getTitle());}@Overridepublic int getItemCount() {return newsDatasValue.size();}public class ViewHolder extends RecyclerView.ViewHolder {TextView tv;public ViewHolder(View itemView) {super(itemView);tv = (TextView) itemView.findViewById(R.id.item_news_tv);}}}//fragment所在页卡序号public void setPosition(int position){this.position = position;}
}

还是很简单的,主要就是要注意定义fragment页卡所在序号用来区分fragment。

三、总结

其实像ViewPager+Fragment+ViewPager+Fragment这种不像知识点,更像是思路,当有这个需求的时候,又没做过,可以参考一下这个思路,然后根据自身情况再修改,有想法的人应该会看着博客写,出来的东西却完全不一样,如果真的用到项目上,肯定要提取Adapter,甚至写出各种不同的Adapter基类,还要抽离网络框架,界面设计的更美,要考虑适配问题,可能会做很多性能优化,复用、压缩、缓存。。。。。。线程要注意线程池和信息传递这些,页面多了也会有很多问题,譬如上边说的fragment多出白色区域,还有newsPosition这样的传值问题,不过代码的乐趣就是这些吧,头脑风暴,陷入进去真的会上瘾,根本停不下来。

这是我CSDN有排名后的第一篇博客(150万+......),现在我确实是个菜鸡,但希望以后能快速的成长,也能写出更好的博客,同时也祝福看到这篇博客的码友们,工作顺利,跳槽的都能找到更好的平台,祝Android行业越来越好。不要如我现在,java后端、web前端都是核心技术人员,唯独移动开发像个打杂的,现在妹子都不联系了就想找个好平台【害羞......】

ViewPager+Fragment+ViewPager+Fragment相关推荐

  1. 友盟页面统计 - 关于Viewpager中的Fragment的生命周期

    Activity和Fragment各自理论上的生命周期 Activity的生命周期是较为经典也最清晰的,在此不表: Fragment从出现到广泛运用也有一段时间了,其标准生命周期也仅比Activity ...

  2. 多个ViewPager引发的Fragment未初始化

    本文主要介绍ViewPager FragmentPagerAdapter中Fragment缓存的规则. 可能需要在一个Activity中显示多个ViewPager,如GridView或ListView ...

  3. Android中Fragment+ViewPager的配合使用

    原本在上一篇博客中要讲解一个Fragment的综合应用,但是中间又想到了滑屏方式,所以就分类在总结了一下,(http://smallwoniu.blog.51cto.com/3911954/13089 ...

  4. android viewpager fragment传值,Android开发中如何解决Fragment +Viewpager滑动页面重复加载的问题...

    前言 之前在做一个Viewpager上面加载多个Fragment时总会实例化已经创建好的Fragmnet对象类似 viewPager.setAdapter(new FragmentPagerAdapt ...

  5. android中viewpager+fragment,ViewPager和Fragment一篇就够了

    ViewPager显示多Fragment使用问题 前言:每当使用ViewPager时,对于选用什么适配器,缓存多少页面,是否需要懒加载以及Fragment的数据刷新经常会有些疑问,网络上的答案很多,但 ...

  6. TabLayout+Fragment+ViewPager+FragmentStatePagerAdapter实现Tab标签

    首先来看下实现的效果吧: 最近在项目中实现这个效果的时候.尽管自己磕磕绊绊的实现了,可是知识确实模模糊糊的,今天天气异常的冷,在加上这个知识不太熟练,实在是没有心情进行接下来的计划,干脆借着这个时间, ...

  7. 低版本系统兼容的ActionBar(六)用Fragment+ViewPager+Tab实现快速导航

    Tab经常和Fragment结合使用,这一讲我们用3种方式来实现这种快捷导航. 0.重要的两个监听器 MyTabListener,这个我们之前已经接触过了 package com.kale.actio ...

  8. android listview动态添加viewpager,向 ViewPager 中添加 包含 ListView 的 Fragment

    对与fragment就不说什么了,直接看API手册吧,亲. 向 ViewPager 中添加 包含 ListView 的 Fragment 的过程比较麻烦.他所表现的效果就是新闻客户端的滑动翻页效果. ...

  9. 解决Fragment中使用ViewPager时,ViewPager里的Fragment错位和空白问题

    这两天开始在改OSChina的开源android客户端,打算用Fragment来分离Main这个Activity里的功能.用Fragment嵌套ViewPager+Fragment的时候发现问题. 红 ...

  10. 使用FragmentTabHost+Fragment+viewpager 实现滑动分页

    之前分页效果一直用TabActivity+TabHost,但是android3.0后就不在推荐使用了,而是推荐使用Fragment,经过研究加参考其他朋友代码实现了滑动分页的效果,代码简单附上. 主页 ...

最新文章

  1. 周志华等人新著!国内第一部AI本科专业教育培养体系出炉
  2. 【机器学习】神经网络浅讲:从神经元到深度学习
  3. jQuery 技术揭秘
  4. Openstack 小知识点
  5. numba 让python速度提升百倍
  6. 中国非动物胶市场来产销需求及发展潜力研究报告2022版
  7. leetcode-无重复字符的最长子串
  8. Vue 导入文件import、路径@和.的区别
  9. 分辨率到底是个什么概念?它和DPI之间是什么关系?
  10. 网络编程和反射的基本知识点的总结
  11. Notepad++离线安装使用Markdown插件
  12. MACOS-Can't-connect-to-local-MySQL-server-through-socket-'/tmp/mysql.sock'
  13. mysql 解决慢sql_MySQL被慢sql hang住了,用shell脚本快速清除不断增长的慢sql的办法...
  14. java 基础学习——基本技巧(一)
  15. HyperLPR车牌识别代码解读
  16. 晶闸管触发电路的基本要求
  17. IDA7.5 无法打开解决方法
  18. 任务管理器已被管理员禁用win10
  19. keytool 错误: java.lang.Exception: 密钥库文件不存在: keystore
  20. Centos7下安装VScode

热门文章

  1. 技嘉主板大部分型号开启WOL(Wake up-On-LAN)网络唤醒与定时开机
  2. java 三元运算符
  3. 计算机团体及知名科学家,2019年度ACM杰出科学家!拿下华人数学领域最高荣誉的上财陆品燕教授,又拿奖啦!...
  4. steam 好友网络无法访问解决方法
  5. 【LeetCode】1823. 找出游戏的获胜者 Find the Winner of the Circular Game
  6. 计算机在教育的应用,计算机技术在教育中的应用
  7. SAP ABAP PARAMETERS定义下拉列表
  8. c语言实现偶数阶乘,c语言实现阶乘的方法
  9. 51单片机(六)矩阵键盘和矩阵键盘密码锁
  10. 【洛谷月赛】洛谷三月月赛题解报告