ViewPager+Fragment+ViewPager+Fragment
最近一段时间,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; @Override public 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 void setSharedPreferencesList(String tag, List 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 List getSharedPreferencesList(String tag) { List datas = new ArrayList<>(); String string = sharedPreferences.getString(tag, null); if (null==string) { return datas; } Gson gson = new Gson(); datas = gson.fromJson(string,new TypeToken>(){}.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 fragments = new ArrayList<>(); private List pageTitle = new ArrayList<>(); @Override protected 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 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和fragment for (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() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { } }); } private void initTab() { tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); } class ActivityFragmentAdapter extends FragmentPagerAdapter { public ActivityFragmentAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public 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 newsDatas;//子fragment对应的tab集合 @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(getLayoutId(), container, false); } protected abstract int getLayoutId(); @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); newsDatas = new SharedPreferencesHelper(BaseApplication.getContext(), "newsDatasSave").getSharedPreferencesList("newsDatas"); isPrepareView = true; } @Override public 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(); @Override public 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; @Override protected int getLayoutId() { return R.layout.fragment_main; } @Override public 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() { @Override public void onTabSelected(TabLayout.Tab tab) { } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); } private void initPager() { final List 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()) { @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public int getCount() { return fragments.size(); } // @Override// public CharSequence getPageTitle(int position) {// return newsDatas.get(position);// } }); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { } }); } @Override protected 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 newsDatasValue = new ArrayList<>();//子fragment内获取的新闻内容 private RecyclerView recyclerView; private NewsAdapter newsAdapter = new NewsAdapter(); private int position; @Override protected int getLayoutId() { return R.layout.fragment_main_news; } @Override public 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); } @Override protected 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() { @Override public void onError(Call call, Exception e, int id) { } @Override public 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 { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(BaseApplication.getContext()).inflate(R.layout.item_news, parent, false)); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.tv.setText(newsDatasValue.get(position).getTitle()); } @Override public 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前端都是核心技术人员,唯独移动开发像个打杂的,现在妹子都不联系了就想找个好平台【害羞......】
更多相关文章
- 【阿里云镜像】切换阿里巴巴开源镜像站镜像——Debian镜像
- Android屏幕分辨率正确获取及PX,DPI,DP,SP等的对应关系
- android 获取唯一标识
- android拍照与读取相册
- Android(安卓)热点开关状态的判断和获取热点ssid
- Android软键盘适配问题
- Android(安卓)P SystemUI之StatusBar UI布局status_bar.xml解析
- Android--SoLoader,android动态加载so库
- AIR Native Extension的使用(Android)一 : 打包ane