最近一段时间,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和存储名,创建sharedPreferencesEditor然后存取文字一对方法,存取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前端都是核心技术人员,唯独移动开发像个打杂的,现在妹子都不联系了就想找个好平台【害羞......】

 

更多相关文章

  1. 【阿里云镜像】切换阿里巴巴开源镜像站镜像——Debian镜像
  2. Android屏幕分辨率正确获取及PX,DPI,DP,SP等的对应关系
  3. android 获取唯一标识
  4. android拍照与读取相册
  5. Android(安卓)热点开关状态的判断和获取热点ssid
  6. Android软键盘适配问题
  7. Android(安卓)P SystemUI之StatusBar UI布局status_bar.xml解析
  8. Android--SoLoader,android动态加载so库
  9. AIR Native Extension的使用(Android)一 : 打包ane

随机推荐

  1. 我的android 第15天 -使用SQLiteOpenHelp
  2. Android(安卓)4.4 webview 架构
  3. android ndk开发
  4. Android 日历提供器(二)
  5. 【Android】Could not find XXX.apk!的解
  6. Android中因为没有使用wifi模块 因此:将WI
  7. Android: 设置wifi设备名
  8. Android(安卓)开源项目推荐
  9. Android Telechips89xx背光控制流程
  10. Android framework修改----关屏动画效果