【0080】【项目实战】-【智慧北京】-【02】【fragment-UI框架搭建】-【主页底栏标签开发】-【创建/填充5个标签页面】-【ViewPager的禁用滑动】-【侧边栏开启和禁用】-【使用Xutils】-【使用Gson解析Json】-【网络缓存】-【设置侧边栏数据】-【点击侧边栏切换菜单详情页】
【说明】
【1】项目的UI框架比较重要;在一开始就将MainActivity一分为二;
【2】主要使用的fragment,本项目中使用的Activity极少;
【3】创建一个基类BasePager,将View先加载好,然后对frameLayout的内容进行添加;
1.使用fragment搭建UI框架
1.1 fragment的认识
【fragment】【编程的思想】高内聚,低耦合;碎片化管理;在Android4.0之后大力推广的;
【fragment的生命周期】成对记忆:Activity存在的生命周期fragment都有,还多了三对,最关键的是记住;onCreateView();
【Activity生命周期6个】成对记忆:onCreate-onDestory;onStrat-onStop;onPause-onResume;

1.2 项目中使用fragment
【说明】根据布局,需要创建两个fragment,LeftMenuFragment和ContentFragment
【思路】创建一个共有的BaseFragment,写共有的方法,然后被继承;


【基类源码】【源码】/zhbj74/src/com/itheima/zhbj74/fragment/BaseFragment.java
1 package com.itheima.zhbj74.fragment; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.support.v4.app.Fragment; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 10 public abstract class BaseFragment extends Fragment { 11 12 public Activity mActivity;//这个activity就是MainActivity 13 14 // Fragment创建 15 @Override 16 public void onCreate(Bundle savedInstanceState) { 17 super.onCreate(savedInstanceState); 18 mActivity = getActivity();// 获取当前fragment所依赖的activity 19 } 20 21 // 初始化fragment的布局 22 @Override 23 public View onCreateView(LayoutInflater inflater, ViewGroup container, 24 Bundle savedInstanceState) { 25 View view = initView(); 26 return view; 27 } 28 29 // fragment所依赖的activity的onCreate方法执行结束 30 @Override 31 public void onActivityCreated(Bundle savedInstanceState) { 32 super.onActivityCreated(savedInstanceState); 33 // 初始化数据 34 initData(); 35 } 36 37 // 初始化布局, 必须由子类实现 38 public abstract View initView(); 39 40 // 初始化数据, 必须由子类实现 41 public abstract void initData(); 42 }
【左侧边栏布局】



【主页面布局】


2.【替换】本身已经具有侧边栏的效果,现在使用fragment替换掉

【替换的思路】将原来的内容删除掉,加载现在的fragment内容;
【主页面掏空】

【frameLayout】此布局不会出现自动增加布局的排列的内容;

【侧边栏掏空】


【效果如下】

【关联源码】引用库之后的关联源码的方法,增加一个配置文件;重启eclipse;

【替换源码】/zhbj74/src/com/itheima/zhbj74/MainActivity.java
1 package com.itheima.zhbj74; 2 3 import android.os.Bundle; 4 import android.support.v4.app.FragmentManager; 5 import android.support.v4.app.FragmentTransaction; 6 import android.view.Window; 7 8 import com.itheima.zhbj74.fragment.ContentFragment; 9 import com.itheima.zhbj74.fragment.LeftMenuFragment; 10 import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; 11 import com.jeremyfeinstein.slidingmenu.lib.app.SlidingFragmentActivity; 12 13 /** 14 * 主页面 15 * 16 * @author Kevin 17 * @date 2015-10-17 18 */ 19 public class MainActivity extends SlidingFragmentActivity { 20 21 private static final String TAG_LEFT_MENU = "TAG_LEFT_MENU"; 22 private static final String TAG_CONTENT = "TAG_CONTENT"; 23 24 @Override 25 public void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题, 28 // 必须在setContentView之前调用 29 setContentView(R.layout.activity_main); 30 31 // Utils.doSomthing(); 32 // R.drawable.p_10 33 setBehindContentView(R.layout.left_menu); 34 SlidingMenu slidingMenu = getSlidingMenu(); 35 slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);// 全屏触摸 36 slidingMenu.setBehindOffset(200);// 屏幕预留200像素宽度 37 38 initFragment(); 39 } 40 41 /** 42 * 初始化fragment 43 */ 44 private void initFragment() { 45 FragmentManager fm = getSupportFragmentManager(); 46 FragmentTransaction transaction = fm.beginTransaction();// 开始事务 47 transaction.replace(R.id.fl_left_menu, new LeftMenuFragment(), 48 TAG_LEFT_MENU);// 用fragment替换帧布局;参1:帧布局容器的id;参2:是要替换的fragment;参3:标记 49 transaction.replace(R.id.fl_main, new ContentFragment(), TAG_CONTENT); 50 transaction.commit();// 提交事务 51 // Fragment fragment = 52 // fm.findFragmentByTag(TAG_LEFT_MENU);//根据标记找到对应的fragment 53 }
【效果】可以使用fragment加载出主页面和侧边栏;
3.主页底栏标签开发
3.1 布局分析及完成
【说明】在上半部分的页面一次性加载五个页面,通过下面的点击进行切换;

3.2 布局完成
【去掉ViewButton的小圆圈】



【低栏灰色的背景的添加】




【文字上面的图片的添加】



【单个效果的完成】


【样式的抽取】名字和图片没有必要进行抽取;


【文字选中图片的改变】图片都会改变颜色,需要创建5个状态选择器,因为图片都不一致;



【文字选中的颜色的改变】修改一个状态选择器,只需要在样式中改变即可;


【布局源码】
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <com.itheima.zhbj74.view.NoScrollViewPager 8 android:id="@+id/vp_content" 9 android:layout_width="match_parent" 10 android:layout_height="0dp" 11 android:layout_weight="1" /> 12 13 <RadioGroup 14 android:id="@+id/rg_group" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" 17 android:background="@drawable/bottom_tab_bg" 18 android:gravity="center" 19 android:orientation="horizontal" > 20 21 <RadioButton 22 android:id="@+id/rb_home" 23 style="@style/Bottom_Tab_Style" 24 android:drawableTop="@drawable/tab_home_selector" 25 android:checked="true" 26 android:text="首页" /> 27 28 <RadioButton 29 android:id="@+id/rb_news" 30 style="@style/Bottom_Tab_Style" 31 android:drawableTop="@drawable/tab_news_selector" 32 android:text="新闻中心" /> 33 34 <RadioButton 35 android:id="@+id/rb_smart" 36 style="@style/Bottom_Tab_Style" 37 android:drawableTop="@drawable/tab_smart_selector" 38 android:text="智慧服务" /> 39 40 <RadioButton 41 android:id="@+id/rb_gov" 42 style="@style/Bottom_Tab_Style" 43 android:drawableTop="@drawable/tab_gov_selector" 44 android:text="政务" /> 45 46 <RadioButton 47 android:id="@+id/rb_setting" 48 style="@style/Bottom_Tab_Style" 49 android:drawableTop="@drawable/tab_setting_selector" 50 android:text="设置" /> 51 </RadioGroup> 52 53 </LinearLayout>
【效果】存在bug,五个可以同时选中;问题的原因:没有设置id,无法区分单个的button;

【增加id】

【效果】

【添加默认首页被选中的状态】

【效果】默认首页被选中,可以单独的切换5个页面;

4.创建5个标签页面
4.1 标签页面的结构分析
【说明】外面是大的线性布局,里面嵌套相对布局和帧布局;

4.2 【标题头布局的完成】

【源码】
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <RelativeLayout 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:background="@drawable/title_red_bg" > 11 12 <TextView 13 android:id="@+id/tv_title" 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:layout_centerHorizontal="true" 17 android:layout_centerVertical="true" 18 android:text="智慧北京" 19 android:textColor="#fff" 20 android:textSize="22sp" /> 21 22 <ImageButton 23 android:id="@+id/btn_menu" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:layout_alignParentLeft="true" 27 android:layout_centerVertical="true" 28 android:layout_marginLeft="10dp" 29 android:background="@null" 30 android:src="@drawable/img_menu" /> 31 </RelativeLayout> 32 33 </LinearLayout>
4.3 下半部分帧布局的添加

4.4 标题的抽取


4.5 标签基类的书写
【说明】因为存在不同的标签页,可以使用一个基类完成,然后继承;

【说明】此类就是一个单纯的java类,不存在与Activity对应的关系;

1 package com.itheima.zhbj74.base; 2 3 import android.app.Activity; 4 import android.view.View; 5 import android.view.View.OnClickListener; 6 import android.widget.FrameLayout; 7 import android.widget.ImageButton; 8 import android.widget.TextView; 9 10 import com.itheima.zhbj74.MainActivity; 11 import com.itheima.zhbj74.R; 12 import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; 13 14 /** 15 * 五个标签页的基类 16 * 17 * @author Kevin 18 * @date 2015-10-18 19 */ 20 public class BasePager { 21 22 public Activity mActivity; 23 24 public TextView tvTitle; 25 public ImageButton btnMenu; 26 public FrameLayout flContent;// 空的帧布局对象, 要动态添加布局 27 28 public View mRootView;// 当前页面的布局对象 29 30 public BasePager(Activity activity) { 31 mActivity = activity; 32 mRootView = initView(); 33 } 34 35 // 初始化布局 36 public View initView() { 37 View view = View.inflate(mActivity, R.layout.base_pager, null); 38 tvTitle = (TextView) view.findViewById(R.id.tv_title); 39 btnMenu = (ImageButton) view.findViewById(R.id.btn_menu); 40 flContent = (FrameLayout) view.findViewById(R.id.fl_content); 41 return view; 42 } 43 44 // 初始化数据 45 public void initData() { 46 47 } 48 }
4.6 子类的书写
【说明】子类的任务主要是在帧布局中动态的添加内容;



5.填充5个标签页
5.1 将Viewpager与fragment联系起来

【说明】找到需要添充内容的控件;

【说明】ViewPager的适配器

【说明】初始化Activity,并添加到Viewpager中;

【小技巧】将标题栏和帧布局的View对象添加到容器中;


【效果】可以进行滑动了,存在了5个标签页;但是帧布局的内容没有显示;

【修改】原因是initData方法没有被调用,设置的文字无法显示;

【效果】帧布局中的内容出现了滚动改变;

5.2 标题的改变
【说明】需要依次修改5处;

【效果】标题栏的内容随着滚动不停的改变;

6.ViewPager的禁用滑动
【说明】ViewPage此处需要禁止滑动,只容许点击生效;
【禁用原理】实现自定义的Viewpager,重写onTouchEvent方法,在此方法中什么都不做;



【效果】无法滑动了,低栏的图标的切换没有关联起来,没有效果;

6.点击标签切换页面
【说明】点击切换ImageButton可以切换Viewpager的内容;
【原理】设置ImageButton的监听事件;




【效果】存在动画

【去动画】将切换动画的【mViewPager.setCurrentItem(0, false);// 参2:表示是否具有滑动动画】第2个参数改为false;

7.性能优化
【存在的问题】会提前加载下一个Viewpager的页面,或者上一个页面,会浪费流量和机器的性能;
【修改1】

【方法1】繁琐,需要书写多次;

【方法2】推荐使用该方法;

【效果】刚开始默认的首页没有调用数据,需要手动加载调用;

【修改】手动加载第一页;

【效果】首页自动加载,点击切换功能完善,也不会提前加载下一个页面了;

8.侧边栏开启和禁用
8.1【说明】
【现在情形】但是现在在每个标签都存在侧边栏;

【实际使用的情形】只有在新闻中心、智慧服务、政府中可以滑动选择出来;

8.2 隐藏菜单按钮


【效果】


8.3 禁用侧边栏
【说明】判断是第一个或者是最后一个的时候禁用侧边栏;

【说明】禁用侧边栏需要拿到MainActivity的SlideMenu对象;


【效果】即使修改了仍然可以滑动出现侧边栏;

【原因及修改】该方法第一次没有被调用;

【效果】只有中间的三个标签可以调用;

9.使用Xutils访问服务器获取json
9.1 新闻中心的样式
【说明】所有的数据全部来源于网络;

9.2 服务器中的资源
【项目需要使用到的资源】

【项目资源在服务器上的位置】






【返回码】与响应码无关;

9.3 XUtils-请求服务器拿数据
【说明】在切换到新闻中心按钮的时候去拿服务器的数据;




【导入库】

【网络请求的使用】

【新建类:维护项目中的常量】优化:域名可能发生改变;域名不能说话那个localhost,因为手机与pc不在统一本地;
【使用模拟器预留的ip地址】10.0.2.2;问:真机该怎么办?


【域名的添加】

【增加权限】


【将请求的数据显示打印出来】

【请求数据的效果】将服务器中的数据顺利读取下来了;

10.使用Gson解析Json
【导入Gson的jar包】



【源码】/zhbj74/src/com/itheima/zhbj74/domain/NewsMenu.java
【说明】要与每个字段都一一对应;
1 package com.itheima.zhbj74.domain; 2 3 import java.util.ArrayList; 4 5 /** 6 * 分类信息封装 7 * 8 * 使用Gson解析时,对象书写技巧: 9 * 1. 逢{}创建对象, 逢[]创建集合(ArrayList) 10 * 2. 所有字段名称要和json返回字段高度一致 11 * 12 * @author Kevin 13 * @date 2015-10-18 14 */ 15 public class NewsMenu { 16 17 public int retcode; 18 public ArrayList<Integer> extend; 19 public ArrayList<NewsMenuData> data; 20 21 // 侧边栏菜单对象 22 public class NewsMenuData { 23 public int id; 24 public String title; 25 public int type; 26 27 public ArrayList<NewsTabData> children; 28 29 @Override 30 public String toString() { 31 return "NewsMenuData [title=" + title + ", children=" + children 32 + "]"; 33 } 34 } 35 36 // 页签的对象 37 public class NewsTabData { 38 public int id; 39 public String title; 40 public int type; 41 public String url; 42 43 @Override 44 public String toString() { 45 return "NewsTabData [title=" + title + "]"; 46 } 47 48 } 49 50 @Override 51 public String toString() { 52 return "NewsMenu [data=" + data + "]"; 53 } 54 55 }
【点击新闻按钮测试】

11.网络缓存
11.1服务器数据的缓存
【说明】数据的缓冲:将url的地址作为Key,将json数据作为value缓存之后封装起来;

【书写一个缓存工具类】/zhbj74/src/com/itheima/zhbj74/utils/CacheUtils.java
【说明】使用SP类;写缓存和读缓存;
1 package com.itheima.zhbj74.utils; 2 3 import android.content.Context; 4 5 /** 6 * 网络缓存工具类 7 * 8 * @author Kevin 9 * @date 2015-10-18 10 */ 11 public class CacheUtils { 12 13 /** 14 * 以url为key, 以json为value,保存在本地 15 * 16 * @param url 17 * @param json 18 */ 19 public static void setCache(String url, String json, Context ctx) { 20 //也可以用文件缓存: 以MD5(url)为文件名, 以json为文件内容 21 PrefUtils.setString(ctx, url, json); 22 } 23 24 /** 25 * 获取缓存 26 * 27 * @param url 28 * @param ctx 29 * @return 30 */ 31 public static String getCache(String url, Context ctx) { 32 //文件缓存: 查找有没有一个文件叫做MD5(url)的, 有的话,说明有缓存 33 return PrefUtils.getString(ctx, url, null); 34 } 35 }
【数据请求逻辑】如果缓存当中具有数据则直接解析json数据,否则请求数据在解析;

【写缓存】在服务器的数据请求成功之后写缓存;

【源码】
1 /** 2 * 从服务器获取数据 需要权限:<uses-permission android:name="android.permission.INTERNET" 3 * /> 4 */ 5 private void getDataFromServer() { 6 HttpUtils utils = new HttpUtils(); 7 utils.send(HttpMethod.GET, GlobalConstants.CATEGORY_URL, 8 new RequestCallBack<String>() { 9 10 @Override 11 public void onSuccess(ResponseInfo<String> responseInfo) { 12 // 请求成功 13 String result = responseInfo.result;// 获取服务器返回结果 14 System.out.println("服务器返回结果:" + result); 15 16 // JsonObject, Gson 17 processData(result); 18 19 // 写缓存 20 CacheUtils.setCache(GlobalConstants.CATEGORY_URL, 21 result, mActivity); 22 } 23 24 @Override 25 public void onFailure(HttpException error, String msg) { 26 // 请求失败 27 error.printStackTrace(); 28 Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT) 29 .show(); 30 } 31 }); 32 }
【测试】

【将应用中的sp存储文件的值查看】


【说明】下次打开的时候会自动出现上次缓存的内容;

11.2 优化
【说明】本地缓存的数据有可能与服务器当前更新的数据不一致,因此需要再判断同步更新数据;
【策略】【1】定时更新,定时一段时间,过了这段时间就更新;【2】不管什么情况下都会请求服务器的数据,在读缓存的同时继续调用服务器的数据;
【缓存存在的意义】在应用打开时可以先让用户看到数据,然后再更新数据;起到了一个临时数据的作用,防止用户不好的体验;


11.3 数据缓存的形式;
【说明】数据缓存的形式可以sp的形式缓存,也可以以文件的形式(谷歌电子市场项目就是以文字的形式)缓存;
12.设置侧边栏数据
12.1 侧边栏数据的说明
【说明】侧边栏显示的内容就是服务器返回的数据的字段;


12.2 基于主体框架解析数据的取回
【说明】需要将新闻中心的数据显示到侧边栏;

【思路】在新闻中心中通过MainActivity拿到LeftMenuFragment的对象,然后设置侧边栏的数据;

【MainActivity的获取】

【LeftMenuFragment的获取】


【侧边栏设置数据框架】




【侧边栏的布局】


【使用ViewUtils】通过注解的方式进行;




【原理】

【框架的完善】


【item的布局】




【适配器中的item加载和文字的设定】此处的View没有重用;


【效果】

【布局的调整】



12.3 侧边栏文字选中之后的文字的底色的改变
【效果】选中之后变为红色

【说明】因为listView不存在选中的点击的动作,只存在按下的的动作,因此使用的是enabled属性;



【临时测试】使用enabled的选项可以测试出该项是否起作用;
【默认】红色;可用是红色;



【效果】因为此时都可用,所以都是红色;

【定义当前被选中的逻辑】



【 切换逻辑-设置监听事件】


12.4 点击之后侧边框的收起



12.5 主页面按钮的点击事件
【说明】通过按钮的点击可以调出侧边栏;再次点击之后隐藏侧边栏;


13点击侧边栏切换菜单详情页
13.1【效果】

【分析】在点击侧边栏的按钮之后,此处修改的内容是新闻标签页中的framLayout标签页;

切换修改的界面总共是4个;



13.2 详情页的对象的封装
【说明】使用一个基类BaseMenuDetailPager类;

【新建基类】以为每个切换的标签页的布局和内容没有共同点,因此每个子类各自实现initView;


【子类】




13.3 frameLayout的内容的添加

【分析】现在是侧边栏需要拿到新闻中心的对象;

【获取MainActivity】

【获取主页面的fragment】


【ContentFragment获取新闻中心页】



【设置frameLayout的内容】


13.4【新闻中心页面的内容的添加】
【初始化新闻详情页】使用ArrayList进行封装;



【测试效果】点击新闻中心页之后,显示的文字会重叠;


【修改】清除之前的旧布局


13.5【bug1】刚开始默认加载的页面应该新闻菜单详情页,现在不是;

【正常的情况】



【效果】

13.6 标题的切换
【存在的问题】标题一直是新闻的字样,没有进行切换;



【效果】标题进行了切换;

13.7 BUG2
【侧边栏和详情页同步的问题】在点击为新闻中心的专题页面,然后再点击其他的页面之后再切换回来的时候,侧边栏显示的专题页面和详情页面不一致;

【修改-疑点】个人感觉此处不应该是赋值为初始位置,而应该记录之前的位置的值,而重新加载;


14.总结
【布局结构的框架】

【真机访问的线上的地址】


浙公网安备 33010602011771号