Day02 01.昨天内容总结 ##
解决文字换行将图片顶高的问题
昨天处理的时候在styles中把padding完全删掉了,现在将Padding再加上
真正的问题是因为styles.xml中
<item name="android:layout_width">wrap_content</item>
写宽度时写的是0dp,一般权重是1,宽度就写0dp,它自动去计算
但这里写成0dp时,就会导致文字换行将图片顶高的问题
写0dp相当于交由系统完全分配宽高,系统分配时可能纯粹每个都完全一样,但‘新闻中心’和‘智慧服务’文字比‘首页’,‘政务’,‘设置’要多两个字,要适当稍微宽点,但系统强制完全等分,就会造成文字被换行问题
所以layout_width不一定每次都写0dp,适当去用wrap_content也可以,这个问题确实比较奇葩
但是写成0dp性能可能更好一些
运行程序,现在就正常了,相当于在分配宽度时,‘首页’还是这么宽,但‘新闻中心’它是这么宽
它根据包裹的内容去适配,不会强制完全等分成5等分
解决文字换行将图片顶高的问题有好多解决方式,不把styles.xml中0dp改成wrap_content也行
这些布局微调还是比较费劲的,有时为了让它完美展现得试很多方法调布局,如说少了一个像素,多了一个像素
其实大体上看起来没有什么问题就可以了
Day02 02.使用xutils注解方式初始化控件 ##
底栏的RadioButton已实现,接下来实现内容部分ViewPager
在ContentFragment中拿一个ViewPager对象,在ContentFragment中声明ViewPager
private ViewPager mViewPager;
这时就可以view.findViewById拿到mViewPager
mViewPager = (ViewPager)view.findViewById(R.id.vp_content);
这是ViewPager的一个对象,通常都是通过这样的方式实例化一个对象
控件很多时要写很多findViewById,可以用xUtils的ViewUtils
手机卫士中用xUtils的HttpUtils实现了文件的上传(参数)和下载(apk),使用的是jar包xUtils-2.6.14.jar的形式
Xutils也是gitHub上的一个开源项目
ViewUtils是对视图的封装,可以完全注解方式进行UI,资源和事件绑定
把它的源代码搞下来,叫做xUtils-master.zip,在这个文件里边有一个jar包叫做xUtils-2.6.14.jar
还有一个叫做library库文件,这里我们用库文件的形式,可以查看XUtils的所有源码
jar包没法查看源码
把xUtils-master.zip放在项目路径解压一下,选library把它导入进来后叫library,在这里点开可以看到它的所有源码,把它的名字重新命名为XUtilsLibrary,右键Properties,Android,Add,把XUtilsLibrary关联进来
怎么使用ViewUtils可以查看文档(刚才打开的.html),有ViewUtils使用方法
如果是一个Fragment必须在onCreateView中初始化view:
View view = inflater.inflate(R.layout.bitmap_fragment,container,false);
ViewUtils.inject(this,view)
activity是直接把当前对象传过去,fragment中是把当前的那个fragment的view对象也要注入进去
ContentFragment中拿到view对象,当前的是一个fragment,调一下ViewUtils.inject(this,view);
注入View和事件后不在去写findViewById了,在成员变量mViewPager处加一个注解@ViewInject(),把id传过来
@ViewInject(R.id.vp_content);
private ViewPager mViewPager;
java基础加强那块专门讲过怎么去注解,@Override就是一个系统原生注解,系统自带说明它是重写父类(BaseFragment)的方法
可以自定义注解,@ViewInject注解就是XUtils自己定义的注解
Day02 03.5个标签页面基类实现 ##
给ViewPager设置Adapter
class ContentAdapter extends PagerAdapter {
@Override
public int getCount() {
return mPagers.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
BasePager pager = mPagers.get(position);
View view = pager.mRootView;// 获取当前页面的根布局
// pager.initData();// 初始化数据, 不要再此处调用初始化数据方法, 因为此处会默认上一页,当前页和下一页,
// 浪费流量和性能
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
那有些方法是确定好的,isViewFromObject都是return arg0 == arg1;
destroyItem时container.removeView((View) object);把object移除出去,强转成view对象
getCount时计算页面数量,页面是多少个就应该是多少个
instantiateItem去初始化布局,
在做新手引导页时用一个一个的imageView是因为当时是写一个个引导页
直接在这个地方new一个imageView对象也不行,因为每个页面结构都不太一样,这时候就不能直接用一个ImageView,不能直接new了,这时应该是一个布局文件,5个页面每个都不一样,可以用一个基类的对象
com.itcast.zhxa02.base包中写一个基类BasePager,不需要继承任何东西,这是5个标签页的基类
在ContentFragment中用一个ArrayList集合把它们维护起来叫做mPagers
private ArrayList<BasePager> mPagers;
getCount中return mPagers.size();
mPager还没有初始化,在ContentFragment中重写BaseFragment的initData,专门来初始化页面数据
@Override
public void initData() {
super.initData();
}
这个super里边啥都没有,可以删掉,初始化5个标签页面应该把mPagers去new一个ArrayList,是一个BasePager,这个就是初始化mPagers
mPagers = new ArrayList<BasePager>();
然后初始化5个页面,因为5个页面都不同,所以不能搞一个for循环来new5个Pager
5个子类都有什么共同特点都放在父类BasePager中
页面分为两部分,标题栏和中间内容
标题栏基本是固定的,只不过文字在变,标题栏上的按钮隐藏或显示,不发生太大变化
中间内容是一个可变部分,变化比较大,用帧布局占位,以后动态添加布局
BasePager中写一个initView方法表示初始化布局
public void initView(){
}
写布局文件base_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include layout="@layout/title_bar" />
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</FrameLayout>
</LinearLayout>
title_bar.xml布局此处略
现在title_bar.xml就是一个独立的标题栏,在base_pager.xml中如何把title_bar.xml中的这个标题栏搞进来
手机卫士中讲过一些自定义组合控件,写一个View在这个自定义View中去加载布局,再通过一个自定义View方式去声明它
不去写那个view也可以,直接写<include>的一个标签,然后写一个layout="@layout/title_bar":
<include layout="@layout/title_bar">
有点像学html时include标签,它可以让a html把b html嵌套进来
在android中要实现多个布局文件嵌套可以用include标签实现嵌套效果
写完之后就可以在BasePager中初始化View了
在BasePager的initView中View.inflate(context,resource,root)布局
View view = View.inflate(mActivity, R.layout.base_pager, null);
context对象通过传参也可以,通过构造方法也可以把context传过来,在initView上边写一个构造方法
public BasePager(Activity activity) {
mActivity = activity;
mRootView = initView();
}
构造方法中传一个参数,不传context,传activity,actvity本身也是Context,它是context的子类,而且它用的api子类比父类的更多,用activity后边处理起来更方便一些,它的功能更多一些,在构造方法上边可以声明一下activity
public Activity mActivity;// MainActivity
这时就可以把这个mActivity传给View.inflate的context处
这时就可以把这个view返回回去了,在initView中return view;并且把initView的返回值void修改为View
希望布局一创建(构造)出来,马上调用initView去初始化布局,即在构造方法BasePager中去调一下initView
它返回的是一个View对象,把它叫做mRootView:
mRootView = initView();
把它搞成一个全局变量,是页面的根布局
private View mRootView;// 页面的根布局对象
Day02 04.5个标签页子类实现&禁用Viewpager滑动 ##
BasePager还没完全写完,在initVew中初始化布局,初始化标题栏上id为tvTitle的TextView,标题栏左边id为btn_menu的menu菜单按钮,内容区id为fl_content的帧布局FrameLayout
TextView tvTitle = view.findViewById(R.id.tv_title);
FrameLayout flcontent = (FrameLayout)view.findViewById(R.id.fl_content);
ImageButton btnMenu = (ImageButton) view.findViewById(R.id.btn_menu);
xutils其实没节省太多工作量,通过ViewUtils去初始化控件真不常用
布局已经由基类BasePager初始化好了,再写一个初始化数据的方法initData(),初始化数据应该由子类承担,把它抽取成abstract方法
/**
-
五个标签页的基类
*/
public abstract class BasePager {public Activity mActivity;// MainActivity public View mRootView;// 页面的根布局对象 public TextView tvTitle; public FrameLayout flContent; public ImageButton btnMenu; public ImageButton btnDisplay;// 组图切换按钮 public BasePager(Activity activity) { mActivity = activity; mRootView = initView(); } /**
-
初始化布局
*/
public View initView() {
View view = View.inflate(mActivity, R.layout.base_pager, null);
tvTitle = (TextView) view.findViewById(R.id.tv_title);
flContent = (FrameLayout) view.findViewById(R.id.fl_content);
btnMenu = (ImageButton) view.findViewById(R.id.btn_menu);
btnMenu.setOnClickListener(new OnClickListener() {@Override public void onClick(View v) { toggle(); } }); btnDisplay = (ImageButton) view.findViewById(R.id.btn_display); return view; } /**
-
打开或关闭侧边栏 如果当前是打开的状态, 就会关闭侧边栏;如果是关闭状态,就打开侧边栏
*/
protected void toggle() {
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
slidingMenu.toggle();// 如果当前是打开的状态, 就会关闭侧边栏;如果是关闭状态,就打开侧边栏
}/**
-
初始化数据, 必须由子类实现
*/
public abstract void initData();} 在com.itcast.zhxa02.base.impl包实现5个子类,HomePager继承BasePager表示首页,刚开始报错是没有实现父类构造方法,ctrl+1,实现父类构造方法即可,这个构造方法中的super不能删,因为super中有东西,即在BasePager的构造方法中写东西了 HomePager中标题叫做智慧北京,在initData中修改标题,父类BasePager中已经findViewById拿到tvtitle了,在这个地方可以直接给它setText,即:tvTitle.setText("智慧北京"); 其次中间有个‘首页’是TextView,在内容部分控件中,这个控件是一个帧布局,先把TextView初始化一下,可以直接在initData中new一个textView,即: TextView view = new TextView(mActivity); 给这个TextView去setText文字是‘首页’,文字颜色是红色,给它setTextColor,给它setTextSize文字大小为22像素,给它setGravity为居中 要把这个布局添加给FrameLayout,所以flContent应该addView,把view布局添加给帧布局 帧布局相当于容器,里边可以塞各种各样的孩子,现在塞了一个TextView,HomePager就这样写完了 /**
-
首页
*/
public class HomePager extends BasePager {
public HomePager(Activity activity) {
super(activity);
}
@Override
public void initData() {
System.out.println(“初始化首页数据…”);
//隐藏侧边栏按钮
btnMenu.setVisibility(View.GONE);
// 修改标题
tvTitle.setText(“智慧北京”);TextView view = new TextView(mActivity); view.setText("首页"); view.setTextColor(Color.RED); view.setTextSize(22); view.setGravity(Gravity.CENTER); // 将布局添加给帧布局 flContent.addView(view); } } 除了‘首页’HomePager,还应该有个‘新闻中心’页面NewsCenter,复制下HomePager将名字改为NewsCenter 把修改标题的setText设置的文字由‘智慧北京’改为‘新闻中心’,TextView也叫‘新闻中心’ 再来个SmartServicePager,修改标题为‘智慧服务’, TextView也叫‘智慧服务’ 再来个政务GovAffairsPager 再来个设置SettingPager 现在将5个标签页分别实现了,都属于ContentFragment的ViewPager 在ContentFragment已写了一个集合为5个标签页面的集合,对它进行数据填充 在ContentFragment的initData中add这5个页面 mPager.add(new HomePager(mActivity)); mPager.add(new NewsCenterPager(mActivity)); mPager.add(new SmartServicePager(mActivity)); mPager.add(new GovAffairsPager(mActivity)); mPager.add(new SettingPager(mActivity)); 这样的5个标签页面一上来就初始化好了,集合ArrayList<BasePager>中就有东西了 在ContentFragment的initData中给mViewPager去setAdapter设置数据 mViewPager.setAdapter(new ContentAdapter); 这时候它就去找ContentAdapter,其中getCount的mPagers.size等于5 然后在instantiateItem中初始化布局,每个页面的对象全都在这个集合mPagers中,首先通过这个集合拿到当前页面的对象,把position传过来,mPagers.get(position);,这是它当前页面的对象,把它叫做pager BasePager pager = mPagers.get(position); 它肯定是一个子类,我们用父类把它引用起来了,是一个多态概念,拿到这个页面之后,pager中有一个属性mRootView pager.mRootView; 这个mRootView就是BasePager.java中的构造方法中调了一下的initView,initView返回的是一个mRootView 而initView中返回的是inflate的View,即布局文件base_pager.xml,这个布局文件就是有一个标题栏还有一个FrameLayout的布局文件,所以现在就相当于拿了一个mRootView,这个view其实就是我们这一个页面的布局: View view = pager.mRootView; //获取页面的根布局 然后又把这个页面的布局添加给container对象: container.addView(view); 最后把这个view一定要return回去 在子类HomePager的initData中初始化数据时修改标题和设置内容 这个initData肯定要有人去调用它,就是在contentFragment的ContentAdapter中的instantiateItem中初始化item时顺便把这个数据初始化一下,: pager.initData();//初始化数据 运行程序,看能把这5个页面加载出来否 报Launch canceled!错误 1.有时候把项目刷新一下也能够将程序跑起来 2.不行就reset adb 3.还不行就关掉模拟器,ctrl+alt+del看任务管理器进程中有adb在运行就结束进程,重启模拟器 首页已运行起来,点新闻中心没做处理,但滑动可以跳转新闻中心,智慧服务 梳理思路 上边这个是ViewPager(标题栏+内容部分),ViewPager需要初始化每个页面布局,初始化布局的方法没有在ContentFragment的instantiateItem去写,而是交给每个子类实现 先写个父类BasePager,在父类的initView中inflate布局,这个声明放在mRootView中保存起来,public View mRootView就是根布局对象 然后在子类中年再给这个布局锦上添花,即在子类HomePager的initData方法中修改标题,给它帧布局里边再去塞一个TextView,这个初始化数据initData是一个锦上添花效果 最后在ContentFragment中再去初始化时,只需要在instantiateItem中拿到pager对象,再拿到pager对象的根布局mRootView,这个根布局刚刚已初始化好,直接把这个addView放在容器container中返回就行 同时再来个锦上添花,initData将每个页面数据都有了各自特点,把它去初始化一下就好了
自定义NoScrollViewPager(禁用ViewPager的滑动事件)
如何禁用掉ViewPager原生滑动事件 ViewPager没有提供相应方法把滑动事件禁掉,非得要禁用就要自定义ViewPager 这个没有以前自定义控件复杂,继承的就是ViewPager,对它有些方法重写就可以 在com.itcast.zhxa02.view包中新建NoScrollViewPager 要继承构造方法就右键选Source,cenerate Consructors from Superclass,从基类中继承两个构造方法 让你写ViewPager左右去滑动,你肯定要继承onTouchEvent判断左右还是偏移多少,与在新手指导页ViewPager中的指示器原理一样,ViewPager原生自己也重写过onTouchEvent方法,在这个地方再重写onTouchEvent方法 super.onTouchEvent(arg0)指的是ViewPager的onTouchEvent 这个地方绑ViewPager源码稍稍有点费劲,之前绑ViewPager源码是选中zhxa02项目,右键Properties,java Build Path,点击Libraries选项,把Android Dependencies直接remove掉 现在不能随便remove了,之前能把Android Dependencies直接remove掉是因为它里边只有一个jar包supportV4 现在点开Android Dependencies发现有slidingmenuLibrary.jar,xutilslibrary.jar,android.supportV4.jar,所以现在不能直接将Android Dependencies去remove掉,一删掉它,xutils和sldingmenu都找不到了 而且单独删掉也不行 有一个曲折的办法能达到,supportV4是SlidngMenuLibrary的supportV4,有一个配置文件android-support-v4.properties,打开看下,它手动指定了当前V4的jar包源码位置,将来就将src=后边的换成你自己的路径,记得斜杠是'/',如果要写反斜杠就必须写两个‘\’来转义 把这个文件拷贝在SlidingMenuLibrary的libs路径下,因为要用的v4包是这个libs下的v4,重启eclipse NoScrollViewPager中的onTouchEvent的super.onTouchEvent做了一大堆滑动处理 要禁用滑动相当于在滑动中啥都不做直接return一个true 什么意思呢?你看,我现在这个ViewPager继承的是它是吧,子类把父类事件重写了,ViewPager运行时onTouchEvent肯定优先去找子类NoScrollViewPager重写的onTouchEvent,此处什么都不做,从而实现禁用滑动 在fragment_content.xml中将原生ViewPager路径换成NoScrollViewPager全路径 运行程序,ViewPager滑动效果禁用掉了 //不能左右滑动的viewpager public class NoScrollViewPager extends ViewPager { public NoScrollViewPager(Context context, AttributeSet attrs) { super(context, attrs); } public NoScrollViewPager(Context context) { super(context); } //拦截事件 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return false;//不拦截子控件的事件,保证嵌套的子viewpager可以滑动 } @Override public boolean onTouchEvent(MotionEvent arg0) { //......................... //此处什么都不做, 从而实现禁用滑动效果 return true; } }
Day02 05.点击标签切换页面&页面数据初始化优化 ##
到ContentFragment中用ViewUtils注解方式初始化id为rg_group的RadioGroup
@ViewInject(R.id.rg_content_group)
private RadioGroup mRadioGroup;
在ContentFragment的initView中给rgGroup去set一个checkedChangeListener(listener):
rgGroup.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
}
});
表示RadioButton被点击的切换事件,导包导的是RadioButton
判断选中的checkedId
如果是R.id.rb_home
将ViewPager切到第首页 mViewPager.setCurrentItem(0,false);
如果是R.id.rb_news,
将ViewPager切到新闻中心 mViewPager.setCurrentItem(1,false);
如果是R.id.rb_smart
将ViewPager切到智慧服务 mViewPager.setCurrentItem(2,false);
如果是R.id.rb_gov
将ViewPager切到政务 mViewPager.setCurrentItem(3,false);
如果是R.id.rb_setting
将ViewPager切到设置 mViewPager.setCurrentItem(4,false);
运行程序,依次点新闻中心,智慧服务,政务,设置,都在切换
发现在切换时有个动画效果,如果不想要可以禁止掉
ViewPager禁用动画
ContentFragment的setOnCheckedChangeListener中
mViewPager在去setCurrentItem时还有另外一个方法
setCurrentItem(int item,boolean smoothScroll);
smoothScroll平滑滑动动画效果,只需要给它传一个false即可禁用掉动画
mViewPager.setCurrentItem(0, false);// 禁用页面切换的动画效果
这个true或false传给mViewPager了,ViewPager底层源码肯定对smoothScroll进行判断
源码底层肯定是true或false决定动画启用还是禁用
运行程序,每次切换都没有动画了
ViewPager性能优化(去掉加载上一页或下一页数据)
ViewPager会默认预加载下一页数据,为了节省流量, 只有切换到该页面时才加载当前页面数据
当每次切换时,看下初始化数据的initData
分别在5个页面初始化数据的initData中加打印语句
System.out.println("初始化首页数据...");
运行程序,打印了两条日志
初始化首页数据...
初始化新闻中心数据...
initData是在ContentFragment的instantiateItem中调的 pager.initData();//初始化数据
instantiateItem会默认加载当前页和上一页或下一页,但会浪费流量和性能
ViewPager同样没有提供禁用加载上一页或下一页的方法,此处不需要自定义ViewPager达到禁用功能了
不把初始化数据的方法pager.initData()放在instantiateItem就行,当页面切换到某一页后再去调
在ContentFragment的initView中给mViewPager去设置页面切换事件setOnPageChangeListener:
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mPagers.get(position).initData();// 只有当前页面被选中后才初始化数据
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
选v4包下的ViewPager导包,当某个页面onPageSelected被选中才调数据
mPagers.get(position)拿到页面对象后再调一下initData()去加载数据
运行程序,发现第一页首页数据没有初始化,因为根本没有切页面,刚进来根本就不会走onPageSelected
所以应该手动把第一页初始化一下
在ContentFragment的initView的setOnPageChangeListener的下边去写
mPager.get(0).initData(); //初始化首页数据
0代表是第一页的数据
运行程序奔溃了
Cased by: java.lang.NullPointerException
at com.itcast.zhxa02.fragment.ContentFragment.initView(ContentFragment.java)
空指针异常,ContentFragment中初始化布局initView空指针了
点击进去发现是mPager.get(0).initData()这行空指针了
有可能mPager空,mPagers是在初始化数据的initData中初始化的
一上来就是调用initView初始化布局,这时mPagers还没初始化,应该将初始化首页数据的代码放在initData中
为什么把初始化其他页面数据的方法放在initView的setOnPageChangeListener监听中没事
这个在滑动时才调,这时mPagers肯定已经初始化完了,自然就不会为空
运行程序,首页数据出来了,切换时每页数据都会出来
Day02 06.控制侧边栏可用&不可用 ##
实现"首页"和"设置"页面SlidingMenu不可用
侧边栏现在在任何页面都可以滑出来,实际上在‘首页’,‘设置’页面没有侧边栏
只有在新闻中心,智慧服务,政务页面才有侧边栏,可以在‘首页’时把侧边栏按钮隐藏掉
HomePager中,在initData中隐藏掉侧边栏按钮
btnMenu.setVisibility(View.GONE);//隐藏侧边栏按钮
SettingPager中,在initData中同样
btnMenu.setVisibility(View.GONE);//隐藏侧边栏按钮
把按钮隐藏了侧边栏还可以拉出来 按钮和侧边栏没啥关系
在MainActivity中拿到侧边栏对象 SlidingMenu slidingMenu = getSlidingMenu();
当时给它setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN)
TOUCHMODE_FULLSCREEN 全屏触摸都可滑出侧边栏
TOUCHMODE_MARGIN 边界触摸
TOUCHMODE_NONE 不触摸
怎么触摸侧边栏都不出来,可以通过这个控制侧边栏显示
ContentFragment的onCheckedChanged中当RadioButton切换到首页时应该禁用侧边栏
写个侧边栏是否可用的方法setSlidingMenuEnable(false),传一个false表示不可用
//设置侧边栏是否可用
protected void setSlidingMenuEnable(boolean enable) {
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
if (enable) {
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
} else {
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);
}
}
首先应该拿到侧边栏的对象,可以通过MainActivity拿到侧边栏对象SlidingMenu
如何在ContentFragment中拿到MainAcitivity对象
ContentFragment继承自BaseFragment,BaseFragment中有个mActivity
mActivity是在BaseFrgment的onCreate中通过getActivity()获取到的
getActivity拿的是BaseFragment所依赖的MainActivity
ContentFragment的setSlidingMenuEnable中把它叫做mainUI
MainActivity mainUI = (MainActivity) mActivity;
再通过mainUI去getSlidingMenu()
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
MainActivity本来就能拿到SlidingMenu对象,因为MainActivity继承的就是SlidingFragmentActivity,它本来就有getSlidingMenu()方法,即在MainActivity的onCreate中本来就有个
SlidingMenu slidngMenu = getSlidingMenu();
判断SlidingMenu是否可用,可用就设置TOUCHMODE_FULLSCREEN,否则设置TOUCHMODE_NONE
private void setSlidingMenuEnable(boolean enable) {
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
if (enable) {
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
} else {
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);
}
}
在ContentFragment的onCheckedChanged的每个页面调用setSlidingMenuEnable()设置参数为false
// 监听RadioGroup的选中事件,对页面进行切换
mRadioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb_home:
mViewPager.setCurrentItem(0, false);// 让ViewPager切换到第一个页面
setSlidingMenuEnable(false);
break;
case R.id.rb_news:
mViewPager.setCurrentItem(1, false);
setSlidingMenuEnable(true);
break;
case R.id.rb_service:
mViewPager.setCurrentItem(2, false);
setSlidingMenuEnable(true);
break;
case R.id.rb_gov:
mViewPager.setCurrentItem(3, false);
setSlidingMenuEnable(true);
break;
case R.id.rb_setting:
mViewPager.setCurrentItem(4, false);
setSlidingMenuEnable(false);
break;
default:
break;
}
}
});
运行程序,首页滑动侧边栏还能出来,因为刚进入程序时RadioButton根本就没有点,不会调setOnCheckedChangeListener的首页的setSlidingMenuEnable(false)禁用侧边栏
所以要在ContentFragment的initData中加如下一行,手动禁用首页侧边栏
// 禁用首页侧边栏
setSlidingMenuEnable(false);
运行程序,效果都实现了
Day02 07.xutils请求网络数据&gson解析 ##
实现新闻中心页面NewsCenterPager
xutils请求网络数据&gson解析
新闻中心侧边‘新闻’,‘专题’,‘组图’,‘互动’这些数据需要访问网络从服务器获取获取
day01资料中服务器中有zhbj的服务器代码,将zhbj文件夹拷贝到Tomcat的webapps文件夹的ROOT目录,相当于服务器中有zhbj文件了,zhbj有好多资源页面数据(10006,10007),接口categories.json
xutils请求网络数据
浏览器访问categories.json,地址栏输入localhost:8080/zhbj/categories.json,出来的便是categories.json的json数据,看到有一个大的根标签retcode,下边有根标签data,根标签extend
格式化json数据可以通过给浏览器加json插件,在线json校验网站地址,也可以用本地格式化json工具HiJson 2.1.2_jdk
用HiJson 2.1.2_jdk64工具,点击'格式化JSON字符串'会自动把JSON字符串格式化好
在com.itcast.zhxa02.global包下写GlobalConstants.java全局变量
分类信息接口 public static String CATEGORY_URL ="http://localhost:8080/zhbj/categories.json";
url常量要大写,选中category_url,ctrl+shift+X转换大写(ctrl+shift+Y是转换为小写)
这样写不规范,每个接口根地址都是http://localhost:8080/zhbj,专门抽取出来写一个地址SERVER_URL
public static String SERVER_URL = "http://localhost:8080/zhbj";
然后 public static String CATEGORY_URL = SERVER_URL + "/categories.json";
可防止以后服务器域名改变后,只需改SERVER_URL
SERVER_URL写个localhost用PC的浏览器请求没有问题,但用模拟器请求链接会有问题
模拟器是另外一个系统,请求localhost服务器,指的是模拟器里边的服务器地址,显然不对
把localhost改成192.168.1.100死ip,每次运行还得改成自己的ip
域名10.0.2.2是预留ip地址,专供模拟器访问PC服务器,用这个ip就不需改ip
但genymoton模拟器不支持此预留ip,只能写死ip
com.itcast.zhxa02.global
GlobalConstants.java
public class GlobalConstants {
// public static final String SERVER_URL =
// "http://zhihuibj.sinaapp.com/zhbj";//服务器线上前缀地址
// 服务器根地址, 10.0.2.2是预留的ip地址,专供模拟器访问PC的服务器使用
public static String SERVER_URL = "http://10.0.2.2:8080/zhbj";
// 分类信息的接口
public static String CATEGORY_URL = SERVER_URL + "/categories.json";
// 组图信息接口
public static String PHOTOS_URL = SERVER_URL + "/photos/photos_1.json";
}
有了这个地址后去请求链接,从服务器获取数据,使用过HTTPURLconnection,此处用xUtils的httpUtils请求
httpUtils连文件都能下载,请求服务器的链接很容易
到‘新闻中心’页面NewsCenterPager的initData中调用getDataFromServer(),并创建getDataFromServer()是从服务器获取数据的方法
getDataFromServer()中new一个HttpUtils,把它叫做utils
HttpUtils utils = new HttpUtils();
查看HttpUtils源码
点击HttpUtils查看源码,包名是package com.lidroid.xutils
让它去发送一个请求send(method,url,callBack)
参1.method:HttpMethod.GET,请求方式
点击HttpMethod查看源码,其实是枚举enum,申明了很多请求方式 GET,POST,PUT,HEAD,MOVE,COPY,DELETE,OPTIONS,TRACE,CONNECT
百分之九十用的都是GET和POST
参2.url:GlobalConstants.CATEGORY_URL
参3.callBack是回调,new RequestCallBack<String>(),手机卫士中下载的是apk文件,泛型是file
这里请求的是一个json字符串,泛型是String
utils.send(HttpMethod.GET, GlobalConstants.CATEGORY_URL,
new RequestCallBack<String>() {
});
鼠标放在RequestCallBack,按ctrl+1,选择Add unimplement methods,在大括号中自动生成访问成功的方法onSuccess,请求失败的方法onFailure
onFailure方法中返回参数
HttpException类型的错误信息error: 是一个,error.printStackTrace()打印日志
String类型的msg: 把错误信息提示给用户Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
onSuccess方法中返回参数
ResponseInfo<String>类型的responseInfo:responseInfo有个字段result就是返回结果,以字符串形式返回 String result = responseInfo.result;
System.out.println("请求结果:" + result);
运行前需要做的事情
1.请求网络需要添加权限:
android.permission.INTERNET
2.把服务器启动起来
3.在服务器中将zhbj服务器文件拷贝在webapp/ROOT下
运行程序,切到新闻中心时请求结果日志打印出来了
使用Gson解析json数据
onSuccess中调用processData();将服务器返回的数据result传给这个方法
processData(result);
并写解析数据的这个方法
protected void processData(String result) {
}
传统方式解析数据用的是jsonObject,解析时是一层一层解析,先拿一个data,再拿children,一点一点去解析
jsonObject适合解析简单数据
开源网络框架Gson是谷歌出的一款专门解析json的包,用它去解析非常容易
将gson-2.3.1.jar包导进来,拷贝到zhxa02项目的libs目录下就可以用它的api了
在NewsCenterPager的processData中new一个Gson对象
Gson gson = new Gson();
gson有个方法fromJson(json,classOfT)
参1 json:它是Sting,就是json串,就是请求成功,服务器返回的数据result
参2 classOfT:类文件,字节码文件
什么是字节码文字:
解析完后封装成对象,指要解析成什么对象
com.itcast.zhxa02.domain包中新建NewsMenuBean,这是网络分类信息的封装,对照json串写完后
在新闻中心NewsCenterPager中用Gson解析NewsMenuBean.class,会返回一个NewsMenuBean
NewsMenuBean newsMenuBean = gson.fromJson(result,NewsMenuBean.class);
它是一个泛型,传啥就把相应对象返回回来了,把它叫做newsMenuBean,打印对象:
Sytem.out.println("解析结果:"+ newsMenuBean);
直接这样解析打印出来是对象的地址, 要在NewsMenuBean中右键选择Source,Generate toString()...直接生成,retcode和extend不打印,只打印data,所以只勾选上data就行,生成toString方法
@Override
public String toString() {
return "NewsMenuBean [data=" + data + "]";
}
data中又是一个一个对象,NewsMenuData也要toString
在NewsMenuData方法中,id和type不打印,只打印title和children,所以只勾选上title和children就行
@Override
public String toString() {
return "NewsMenuData [title=" + title + ", children=" + children + "]";
}
children即页签对象NewsTabData中又要写它的toString
@Override
public String toString() {
return "NewsTabData [title=" + title + "]";
}
运行程序,点新闻中心,LogCat中打印出来请求结果和解析结果了,解析结果中就是刚才选择toString的data,title,children这些,新闻下边有北京,中国国际,体育,生活,旅游,科技,军事,汽车,专题,后边有组图,互动,组图和互动没有children,所以children打印的是null
NewsMenuBean写起来稍微有点费劲,别的非常简单,两行代码搞定
用Gson解析json注意事项:
什么叫Gson,因为Google出的,所以叫G
NewsMenuBean.java对象怎么写
1.对象里边逢{}创建对象,逢[]创建ArrayList集合
逢大括号就写对象,这个json串的最外边是一个特别大的大括号就写一个NewsMenuBean.java对象,json串下边一碰到大括号就要写一个对象
逢[]写一个ArrayList集合
对象和集合是套起来的,对象中有集合,它的写法是直接在所在的对象中声明集合,集合中有对象,它的写法是,集合ArrayList<>的泛型就写成对象
2.类(NewsMenuBean.java对象)中所有字段命名要和网络返回的字段(即在HiJson工具中格式化好的json串)一致
Gson解析时,接口中叫retcode的字段要给NewsMenuBean中字段名叫retcode的赋值
这样Gson才能相应字段对应起来
3.不需要的字段可以不解析,甚至不用在NewsMenuBean.java中声明
day02 08.给侧边栏设置网络数据 ##
给侧边栏设置网络数据
完整项目侧边栏背景是黑色,上边是新闻,专题,组图,互动的ListView
创建侧边栏布局fragment_left_menu.xml,此处略写
在LeftMenuFragment中用ViewUtils对listView初始化
在initView方法中
View view = View.inflate(mActivity, R.layout.fragment_left_menu, null);
ViewUtils.inject(this, view);
在成员变量lvList上边加注解,把id传过去
@ViewInject(R.id.lv_list)
private ListView lvList;
在LeftMenuFragment中写一个LeftMenuAdapter,继承自BaseAdapter
实现getCount,getItem,getItemId,getView
实现页面之间数据的传递
将data数据从新闻中心NewsCenterPager的解析json数据的方法processData的newsMenuBean传递给侧边栏对象LeftMenuFragment
UI框架图看到,LeftMenuFragment是侧边栏,NewsCenterPager是新闻中心,这两个离得挺远但是有联系
新闻中心NewsCenterPager属于主页面ContentFragment,ContentFragment属于MainActivity
新闻中心NewsCenterPager只要拿到MainActivity对象,MainActivity拿到侧边栏LeftMenuFragment对象
就相当于新闻中心NewsCenterPager和侧边栏LeftMenuFragment建立了连接
将数据从新闻中心NewsCenterPager传给侧边栏LeftMenuFragment
第一步:获取MainActivity
第二步:通过MainActivity获取LeftMenuFragment对象
第三步:将数据设置给LeftMenuFragment
新闻中心NewsCenterPager继承的是BasePager,BasePager的成员变量处有一个activity,即:
public Activity mActivity;
public BasePager(Activity activity) {
mActivity = activity;
mRootView = initView();
}
这个mActivity就是MainActivity
在构造方法BasePager去new时在参数中传了一个activity,在ContentFragment中new了5个对象,即:
// 初始化5个标签页面
mPagers = new ArrayList<BasePager>();
mPagers.add(new HomePager(mActivity));
mPagers.add(new NewsCenterPager(mActivity));
mPagers.add(new SmartServicePager(mActivity));
mPagers.add(new GovAffairsPager(mActivity));
mPagers.add(new SettingPager(mActivity));
初始化5个标签页时把mActivity以参数形式传过去了,点击NewsCenterPager中传的mActivity跟踪下去就是BaseFragment成员变量处的mActivity,即:
public Activity mActivity;//这个对象就是MainActivity
BaseFragment的mActivity就是MainActivity,所以它肯定是MainActivity
1)在新闻中心NewsCenterPager解析json数据的方法processData中获取MainActivity很简单,mActivity就是MainActivity, 即:
MainActivity mainUI = (MainActivity)mActivity;
2)接下来需要通过MainActivity拿到侧边栏LeftMenuFragment
在MainActivity的初始化Fragment的方法initFragment中,塞了一个LeftMenuFragment和ContentFragment,即:
transaction.replace(R.id.fl_left_menu, new LeftMenuFragment(),TAG_LEFT_MENU);
transaction.replace(R.id.fl_main, new ContentFragment(), TAG_CONENT);
当时设置了TAG的目的就是为了通过findFragmentByTag() 方便找到Fragment对象
在MainActivity的initFragment()下边写一个获取侧边栏Fragment对象的方法getLeftMenuFragment,即:
/*
-
获取侧边栏Fragment对象
*/
public LeftMenuFragment getLeftMenuFragment() {
FragmentManager fm = getSupportFragmentManager();// Fragment管理器
return (LeftMenuFragment) fm.findFragmentByTag(TAG_LEFT_MENU);
}首先拿到Fragment管理器fm,再通过fm去findFragmentByTag()传入TAG_LEFT_MENU返回的是一个Fragment对象,把它强转成LeftMenuFragment并return回去 在新闻中心NewsCenterPager用mainUI调用获取侧边栏Fragment对象的方法,即: LeftMenuFragment leftMenuFragment = mainUI.getLeftMenuFragment(); 新闻中心拿到侧边栏引用后需要将数据传递给它,侧边栏再接一下就可以了 LeftMenuFragment中写一个setMenuData方法接收侧边栏数据,此方法供新闻中心NewsCenterPager调用,即: public void setMenuData() { } 在新闻中心NewsCenterPager的processData方法中通过leftMenuFragment调setMenuData把数据set过去 通过传参方式把数据newsMenuBean中ArrayList类型的data数据传递给侧边栏,即: leftMenuFragment.setMenuData(newsMenuBean.data); 会因为传了参数newsMenuBean.data报错,选中报错的setMenuData,ctrl+1,Change method 'setMenuData()':Add parameter 'ArrayList<NewsMenuData>'去修改下LeftMenuFragment中刚写的方法,点击后自动跳转到LeftMenuFragment的这个方法并自动加了参数ArrayList<NewsMenuData>,即: //设置侧边栏数据,此方法供新闻中心NewsCenterPager页面调用 public void setMenuData(ArrayList<NewsMenuData> data) { } 面试问Android或项目中怎么传递数据 传递数据有很多种方式,这就是其中的一种最根本最简单的方式,通过获取对象的方式传递数据 在LeftMenuFragment中接收一下数据,全局变量处把侧边栏数据叫做mMenuData来声明一下,即: private ArrayList<NewsMenuData> mMenuData ; setMenuData中把参数data塞给mMenuData,即: mMenuData = data; 上边已经将LeftMenuAdapter写好了,在setMenuData方法中new一个LeftMenuAdapter对象把它声明成全局变量叫做mAdapter,即: mAdapter = new LeftMenuAdapter(); 在setMenuData方法中给listView去setAdapter设置数据,即: lvList.setAdapter(adapter); getCount方法: 是mMenuData.size(); getItem方法: 就是从集合中get一个position返回的是NewsMenuData,以后只要是调getItem就可以把NewsMenuData对象拿到手了,就不用从mMenuData集合去取了即: @Override public NewsMenuData getItem(int position) { return mMenuData.get(position); } getItemId方法: 一般用不上,返回一个position就行 getView方法:初始化一个View,需要将侧边栏的listview布局的Item布局画好,再写一个布局文件list_item_left_menu.xml 侧边栏listview中每个item有一个红色箭头,右边是TextView 箭头红色的叫menu_arr_select.png,白色的叫menu_arr_normal.png 创建ListView的item的布局list_item_left_menu.xml,此处略写 LeftMenuFragment中LeftMenuAdapter的getView中可以用View.inflate加载布局,即: View.inflate(context,resource,root); 参1:context是mActivity 参2:resource是R.layout.list_item_left_menu 参3:root是一个null 它返回一个View对象 View view = View.inflate(mActivity,R.layout.list_item_left_menu,null); 把view对象return回去: return view; 初始化textView控件,即: TextView tvMenu = (TextView) view.findViewById(R.id.tv_menu); 以前拿对象要mMenuData去get一个position才能拿到对象,即:mMenuData.get(position); 现在可以直接调getItem,即: NewsMenuData info = getItem(position); 然后给TextView设置title,即: tvMenu.setText(info.title); 此处数据不大,偷个懒就不对ListView进行优化了 运行程序,点新闻中心NewsCenterPager时初始化数据后调用processData解析,解析完后把数据传递给侧边栏,侧边栏通过adapter去设置数据 所以点击新闻中心后侧边栏就有数据了
Day02 09.菜单详情页对象创建 ##
完整项目在新闻中心页面的侧边栏中分别点新闻,专题,组图,互动,在新闻中心页面展现的是新闻,专题,组图,互动的详情页,点击时文字和箭头被选中后会从白色变为红色
1.实现文字和箭头被选中后从白色变为红色的状态选择器功能
“是否可用”的状态选择器
状态选择器有按下pressed,选中,可用不可用,颜色,图片
可用和不可用和之前自定义控件相似,RadioButton有勾选checked属性,但listview没有checked被选中属性,所以用可用和不可用控制
在color文件夹下创建txt_menu_selector.xml,state_enabled时是红色,默认是白色
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true" android:color="#f00"/>
<item android:color="#fff"/>
</selector>
到list_item_left_menu.xml中把文字TextView的textColor属性值改为
android:textColor="@color/txt_menu_selector"
给小箭头设置“可用不可用"的图片状态选择器
在drawable下创建btn_arrow_selector.xml,也是state_enabled
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/menu_arr_select" android:state_enabled="true"></item>
<item android:drawable="@drawable/menu_arr_normal"/>
</selector>
到list_item_menu.xml中把TextView的draableLeft属性值改为
android:drawableLeft="@drawable/btn_arrow_selector"
运行程序,点新闻中心,将侧边栏拉出来,发现新闻,专题,组图,互动,包括小箭头默认都是红色,因为它们都可用,需要控制它的可用不可用
代码中实现可用不可用功能
到LeftMenuFragment的getView中,让当前被选中的item是可用,剩下的都不可用
给它声明当前被选中的是第几个,0是默认第一个被选中,即:
private int mCurrentItem = 0;//当前被选中的菜单位置
getView中去判断,如果position刚好等于当前被选中条目,TextView应该setEnabled为true表示可用,变为红色,else是不可用,变为白色,即:
if (position == mCurrentItem) {
tvMenu.setEnabled(true);// 可用, 变为红色
} else {
tvMenu.setEnabled(false);// 不可用, 变为白色
}
能通过代码setEnabled来控制颜色可用和不可用,是因为设置了可用不可用的状态选择器,必须设置状态选择器后才能根据代码setEnabled去变颜色
运行程序,侧边栏拉开,第一个新闻是红色,剩下的专题,组图,互动是白色
LeftMenuFragment设置侧边栏数据的方法setMenuData中,给ListView设置点击事件监听
lvList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
}
});
onItemClick参数position就是当前被点击的position,把原来的mCurrentItem要变成当前被点击的position,即:
mCurrentItem = position;
变了之后要把ListView刷新一下,即: mAdapter.notifyDataSetChanged();//刷新listview
这样它就按照这个最新的mCurrentItem去做处理了
运行程序,再点专题,点组图,点互动,颜色都会变成红色,因为每点一下,listview都会刷新一下,当前被选中的页面就会更新一下
2.实现在新闻中心页面的侧边栏中分别点新闻,专题,组图,互动,在新闻中心页面展现的是新闻,专题,组图,互动的详情页
切换页面时标题中间文字在变,中间的是帧布局,内容部分一直在变
逻辑类似于主页面ContentFragment中用5个BasePager表示页面
新闻中心侧边栏中有4个菜单页面,也可以用4个对象分别表示对应页面
先写菜单详情页四个页面的基类BaseMenuDetailPager,和BasePager类似
basePager有
初始化布局的方法initView
初始化数据的方法initData
还有它的构造方法传一个activity
BaseMenuDetailPager也一样,构造方法BaseMenuDetailPager以参数形式传过来一个Activity对象
接收一下这个Activity并在成员变量处写一个Activity把它叫做mActivity
再写一个初始化View的方法,每个子类的View页面都不一样,必须加abstract由子类实现,并返回View对象
所以BaseMenuDedailPager这个类也要加abstract变成抽象类
再来一个初始化数据的方法initData,初始化数据子类可以实现也可以不实现,所以不用搞成抽象方法
这个initView也是在构造方法中一构造就马上调用,返回的是一个View对象叫做mRootView,它就是当前页面BaseMenuDetalPager的根布局,在成员变量处声明一下
/**
-
菜单详情页基类
*/
public abstract class BaseMenuDetailPager {public Activity mActivity; public View mRootView;// 页面的根布局 public BaseMenuDetailPager(Activity activity) { mActivity = activity; mRootView = initView(); } // 初始化view, 子类必须实现 public abstract View initView(); // 初始化数据 public void initData() { } } 针对BaseMenuDetailPager实现4个子类,在com.itcast.zhxa02.base.impl.menu包下新建 菜单详情页-新闻NewsCenterDetailPager继承BaseMenuDetailPager,实现父类构造方法和initView方法 到设置页面SettingPager中把initData中的TextView代码复制到NewsMenuDetailPager的initView中,最后把TextView返回去,即: @Override public View initView() { TextView view = new TextView(mActivity); view.setText("菜单详情页-新闻"); view.setTextColor(Color.RED); view.setTextSize(22); view.setGravity(Gravity.CENTER); return view; } 注意: 菜单详情页-新闻视频中叫NewsCenterDetailPager,笔记中叫NewsMenuDetailPager,以笔记为主 将剩下的3个页面也实现一下,分别创建 菜单详情页-专题 TopicMenuDetailPager 菜单详情页-组图 PhotoMenuDetailPager 菜单详情页-互动 InteractMenuDetailPager 在新闻中心页面切新闻,专题,组图,互动时都是在修改新闻中心NewsCenterPager的FrameLayout,所以这4个页面属于新闻中心页面 在新闻中心NewsCenterPager中网络数据已加载成功之后才能初始化这四个子页面, 即在新闻中心NewsCenterPager解析json数据的方法processData中初始化这4个菜单详情页 需要一个集合把这4个页面维护起来 在NewsCenterPager的成员变量处再搞一个ArrayList,泛型为BaseMenuDetailPager,叫做mMenuDetailPagers,这就是菜单详情页的集合 private ArrayList<BaseMenuDetailPager> mMenuDetailPagers;// 菜单详情页集合 在processData方法中初始化mMenuDetailPagers集合 mMenuDetailPagers = new ArrayList<BaseMenuDetailPager>(); 初始化4个页面就是去add4个对象 mMenuDetailPagers.add(new NewsMenuDetailPager(mActivity));//菜单详情页-新闻 mMenuDetailPagers.add(new TopicMenuDetailPager(mActivity));//菜单详情页-专题 mMenuDetailPagers.add(new PhotoMenuDetailPager(mActivity, btnDisplay));//菜单详情页-组图 mMenuDetailPagers.add(new InteractMenuDetailPager(mActivity));//菜单详情页-互动
Day02 10.点击侧边栏切换菜单详情页 ##
梳理UI框架
两个Fragment,主页面Fragment中是5个标签页
新闻中心NewsCenterPager页又分为4个菜单详情页 新闻,专题,组图,互动
侧边栏点击时要主动修改新闻中心页面
需要侧边栏LeftMenuFragment对象拿到新闻中心NewsCenterPager对象
刚才新闻中心拿到侧边栏给侧边栏塞数据比较简单
但让侧边栏拿新闻中心对象就比较绕了
侧边栏能拿到MainActivity,通过MainActivity拿到ContentFragment,通过ContentFragment能拿到新闻中心NewsCenterPager,通过这条线它两就能建立对话
在LeftMenuFragment的setOnItemClickListener的onItemClick中添加
setCurrentDetailPager(position);
把当前被点的位置position传给这个方法
在setCurrentDetailPager方法中拿"获取新闻中心对象NewsCenterPager"
1.先获取MainActivity,它有个mainUI就是mActivity
//设置当前要显示的详情页面
protected void setCurrentDetailPager(int position) {
MainActivity mainUI = (MainActivity) mActivity;
}
2.通过MainActivity获取主页ContentFragment
1)MainActivity中写个getContentFragment方法,把获取侧边栏Fragment的getLeftMenuFragment方法复改修改即可
public ContentFragment getContentFragment() {
FragmentManager fm = getSupportFragmentManager();// Fragment管理器
return (ContentFragment) fm.findFragmentByTag(TAG_CONENT);
}
2)LeftMenuFragment的setCurrentDetailPager方法中通过MainActivity去getContentFragment
protected void setCurrentDetailPager(int position) {
MainActivity mainUI = (MainActivity) mActivity;
ContentFragment contentFragment = mainUI.getContentFragment();//获取主页Fragment
}
3.通过主页ContentFragment获取新闻中心NewsCenterPager对象
1)ContentFragment中写个getNewsCenterPager方法获取新闻中心对象
ContentFragment的initData的mPagers中就有新闻中心对象,第二个就是新闻中心
//获取新闻中心对象
public NewsCenterPager getNewsCenterPager() {
NewsCenterPager pager = (NewsCenterPager) mPagers.get(1);
return pager;
}
2)LeftMenuFragment的setCurrentDetailPager方法中通过contentFragment去getNewsCenterPager()
//设置当前要显示的详情页面
protected void setCurrentDetailPager(int position) {
MainActivity mainUI = (MainActivity) mActivity;
ContentFragment contentFragment = mainUI.getContentFragment();//获取主页Fragment
NewsCenterPager newsCenterPager = contentFragment.getNewsCenterPager();//获取新闻中心页面
newsCenterPager.setCurrentDetailPager(position);//设置新闻中心的详情页面
}
4.调用NewsCenterPager方法更新菜单详情页
1)NewsCenterPager中写个setCurrentMenuDetailPager方法‘设置当前菜单详情页’
需要把position传过来,新闻中心要知道更新第几个页面
//设置当前详情页面
public void setCurrentDetailPager(int position) {
}
2)新闻中心NewsCenterPager中写个getNewsCenterPager方法
因为真正的切换要由新闻中心NewsCenterPager自己处理
在LeftMenuFragment的setCurrentDetailPager方法只需要调用新闻中心NewsCenterPager的setCurrentMenuDetailPager(positon)方法,把position传过去,让新闻中心自己去写方法切换
此处不写getNewsCenterPager了,将代码直接写在了setCurrentDetailPager
a.新闻中心setCurrentMenuDetailPager方法得到position后就可以从mMenuDetailPagers集合拿到对应菜单详情页
b.通过pager可以拿到它的根布局mRootView,返回一个View,叫做view
每个子页面initView时初始化的view,返回给父类BaseMenuDetailPager,父类构造方法中把initView的结果放到了mRootView,所以mRootView就是当前菜单详情页根布局
c.这个布局view拿到后应该addView塞给新闻中心的帧布局,它的帧布局就是父类BasePager的flContent
运行程序,点专题,发现专题页面切换到新闻中心页面了,压在新闻中心页面上边了,点击组图,就会又压了上去,再加直接就奔溃掉了
d.添加布局前应该先清除上一次的页面,NewsCenterPager的setCurrentMenuDetailPager方法中,frameLayout有一个方法removeAllViews是清除之前所有布局
e.每个菜单详情页都有初始化数据的方法initData,每次切换完数据后都要加载数据
到NewsCenterPager的setCurrentMenuDetailPager方法调用pager.initData()方法初始化菜单详情页数据
//设置当前详情页面
public void setCurrentDetailPager(int position) {
BaseMenuDetailPager pager = mMenuDetailPagers.get(position);//获取当前被选中的菜单详情页
View view = pager.mRootView;
flContent.removeAllViews();// 清除之前所有的布局
flContent.addView(view);// 添加当前详情页的布局
pager.initData();// 初始化数据
}
运行程序,点专题,专题页面切换到新闻中心页面了,点组图,组图页面就切换到新闻中心页面了,之前专题页面就被清除了
梳理思路:
一点击新闻中心侧边栏菜单后,立马将位置position传递给了新闻中心对象NewsCenterPager的setCurrentDetailPager方法
新闻中心马上调用removeAllView将View删除掉,再把当前被点击的页面布局addView塞给帧布局,最后调用initData加载数据
最核心思路是通过侧边栏拿到新闻中心对象后修改新闻中心对象的帧布局内容
Day02 11.切换菜单详情页细节处理 ##
智慧北京项目UI框架比市面项目还要复杂,就在一个MainActivity中玩各种对象,通过对象去塞布局,掌握了这个项目的思路你会发现市面上很多结构都很简单好做
1.实现点击新闻中心侧边栏item后侧边栏自动收起的功能
每次点新闻中心侧边栏item后,还得手动点空白处侧边栏才收起来,希望一点item马上收起来
LeftMenuFragment的setMenuData方法中onItemClick中,调用打开关闭侧边栏的方法toggle,然后创建这个toggle方法
//打开或关闭侧边栏 如果当前是打开的状态, 就会关闭侧边栏;如果是关闭状态,就打开侧边栏
protected void toggle(){
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
slidingMenu.toggle();// 如果侧边栏打开,则关闭;如果关闭,则打开
}
通过MainActivity拿到侧边栏对象,并调用侧边栏的api方法toggle
toggle是开关意思,按下打开侧边栏,再按下关闭侧边栏
运行程序,点新闻中心,侧边栏拉出来是打开状态,点击专题后侧边栏就会主动关闭了
2.实现点击新闻中心页的侧边栏按钮打开侧边栏
新闻中心继承的是BasePager,BasePager中btnMenu就是菜单按钮
在BasePager的initView方法给btnMenu设置OnClickListener就可以了,在这个地方也要调toggle()方法
btnMenu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
toggle();
}
});
然后将刚才在LeftMenuFragment中写的toggle方法复制一份放在BasePager中
运行程序,点击新闻中心将侧边栏滑出来,点击专题侧边栏会自动收起,点击侧边栏按钮侧边栏也出来,再点侧边栏按钮侧边栏又会收起
3.新闻中心页面切换时更新对应标题
NewsCenterPager的setCurrentMenuDetailPager方法中更新页面标题
TextView叫做tvTitle,在BasePager中已findViewById拿到它了,给它setText设置文字
tvTitle.setText(“”);
文字的网络数据在新闻中心解析json数据的processData方法的newsMenuBean中能拿到,把newsMenuBean搞成全局,在新闻中心setCurrentDetailPager的初始化数据代码后边再添加如下代码
// 更新页面标题
String title = mNewsMenuBean.data.get(position).title;
tvTitle.setText(title);
这是更新页面标题的逻辑
运行程序,点击专题,新闻中心的标题栏就马上变成'专题'了,点击组图,同样会变'组图'
4.实现新闻中心默认展示菜单详情页-新闻功能
第一次进到新闻中心,内容部分应该展示菜单详情页-新闻页
完整项目中新闻中心本身没有页面,全都是侧边栏新闻专题组图互动页面,默认是新闻这个item页面
NewsCenterPager的processData方法初始化数据时,手动调setCurrentMenuDetailPager()方法,把0传过去
// 新闻菜单详情页为默认页面
setCurrentMenuDetailPager(0);
运行程序,新闻中心内容部分展示的是'菜单详情页-新闻'
切换智慧服务,政务,设置页面后,再回到新闻中心标签页时,新闻中心内容部分会有短暂重合
5.解决切回新闻中心时内容部分出现的短暂重合
新闻中心一旦切过来马上调initData方法,在initData中先把刚写的TextView文字添加到帧布局,再去从网络获取数据
获取完数据后去新闻中心调processData的setCurrentMenuDetailPager(0)第0个页面
setCurrentMenuDetailPager中把数据removeAllViews删掉再重新addView添加,所以会看到瞬间的重叠效果
NewsCenterPager中initData的TextView数据没任何意义了,所以这个页面可以完全删除注释掉
6.解决新闻中心变为新闻的问题
运行程序,新闻中心内容部分不会再出现短暂文字重叠,但新闻中心标题突然变成新闻了
NewsCenterPager的initData方法中设置了'新闻中心',又在setCurrentMenuDetailPager方法中设置了新闻
在initData中设置的'新闻中心'也用不上了,注释掉就行了
7.解决标题文字从‘标题’突然变成了‘新闻’的问题
因为标题栏本来有一个默认文字,在BasePager的initView的base_Pager.xml布局文件的include中有个title_bar,title_bar中写了个‘标题’文字
如果‘新闻’还没有赋值,默认显示的是‘标题’,只有等网络加载完成后,才把‘标题’切换为‘新闻’,如果网速比较慢,用户就会感觉奇怪,用户会看到‘标题’突然切换为‘新闻’的闪动
在title_bar.xml中将‘标题’文字删掉,不需要默认值也行
8.解决再次切回“新闻中心"时,item没有切回新闻的问题
选组图item,当前选择的组图,随便点个标签,再切到‘新闻中心’标签,内容部分恢复到‘菜单详情页-新闻’了,但是侧边栏选中的还是组图
页面是‘菜单详情页-新闻’是因为切到这个页面后在NewsCenterPager的processData中把‘新闻’设置为初始页面
// 新闻菜单详情页为默认页面
setCurrentMenuDetailPager(0);
原因是侧边栏listView没有更新
LeftMenuFragment的setMenuData中给listView设置数据时
因为mCurrentItem = position而且mCurrentItem = 0,所以position默认是0
它现在没有更新过来,还是上次选的那个值,在这要强制把它归零
LeftMenuFragment的setMenuData中强制把当前position归零,即:
mCurrentItem = 0;//当前位置默认是第一个页面
运行程序,就不会出现上边问题
因为一旦切到新闻中心,新闻中心的initData马上调用getDataFromServer()
拿到数据后调用解析json数据的方法processData
processData中调用侧边栏setMenuData设置数据,在setMenuData中设置数据时就会顺便执行上边那行代码把位置重新归零,重新设置成第1个页面
Day02 12.网络缓存 ##
市面应用默认会对数据缓存,可以从缓存取数据,不用每次都调用服务器加载数据,速度会更快
此处的缓存指的是对服务器接口返回数据的缓存
后边还会提到图片缓存,有时在页面中要加载很多网络上的图片,图片缓存是另外一个机制,要把图片以文件形式缓存在本地,后边会介绍
写一个缓存工具类CacheUtils
需要写缓存setCache和读缓存getCache
市面上绝大多数写的是网络返回的json数据,因为写json数据最好处理,也最好维护
如果是写对象,先封装成对象,再把对象序列化到本地数据库,很费劲
以url为key,json为value写到sp中,当然也可以保存在数据库,本地文件中
setCache中要传String url,String json
用PrefUtils去put一个String时还需要context,所以setCache还要传Context
public static void setCache(String url, String json, Context ctx) {
//缓存也可以写在本地文件中: 以md5(url)作为文件名, 以json作为文件内容写进去
PrefUtils.putString(ctx, url, json);
}
以链接作为key是因为只要链接确定了,它的json就基本确定了
在getCache方法中从sp中读出缓存
public static String getCache(Context ctx, String key) {
return SharePreferenceUtils.getString(ctx, key, null);
}
defValue默认值传个null就可以
放在sp中比较简单,一句话搞定,写在本地文件还得写输出流,输入流,但写本地有好处
因为sp中通常放的是一些配置信息,比如开关打开,选择状态true或flase
往sp中放一大堆缓存容易导致sp可读性非常差,但它用起来确实方便,所以就这样做了
如果想写在文件中,可以以url作为文件名,json作为文件内容写进去,下次找这个本地文件就找一下有没有文件名是此url,有就说明有缓存,说明以前写过这样的缓存,如果没有这个文件就说明没有缓存
写在数据库也行,都是以一个url为它的key,以json为value保存起来
面试:
怎么做网络缓存,都是以url为key,以它的json为value去保存,这是最传统,最简洁,也是最通用的一种方式
当然这个url有时会比较复杂,比如说有时url中还可以带参数,带个问号,name等于zhangsan,然后再来一个age等于1
要记这个缓存的话一定要把这个参数也作为url的一部分作为key去保存,因为参数不一样,返回的结果可能不一样,要把参数也加进来
----------------------------------------------------------------
用CacheUtils 给项目添加缓存数据功能
NewsCenterPager的getDataFromServer中result已经拿到,这个result就是json数据
在getDataFromServer调CacheUtils去setCache设置或更新缓存,即:
// 写缓存
CacheUtils.setCache(GlobalConstants.CATEGORY_URL,result, mActivity);
在NewsCenterPager的initData方法中在调网络getDataFromServer之前要读缓存,读之前要判断缓存是否为空
不为空的话解析从缓存中取出的json数据
即:
// 先从缓存中读取数据并展示
String cache = CacheUtils.getCache(GlobalConstants.CATEGORY_URL,mActivity);
if (!TextUtils.isEmpty(cache)) {
processData(cache);
}
// 请求服务器数据, 读了缓存之后,仍然继续访问网络,保证获取到最新的数据
getDataFromServer();
打印日志'发现缓存'
有缓存还有必要在initData中调用getDataFromServer()从服务器请求?
可以写个else放在它里边请求服务器,但缓存的数据不是最新,要继续请求服务器保证数据最新
新闻客户端,网易新闻,微信,不管有没有网都有内容看,微信不管有网没网聊天数据都可以看
网易新闻客户端不管有网没网都有新闻,新闻是旧的,后台自动刷刷出最新数据
缓存作用是为了让用户不要过于长时间等待,如果网络比较慢,还可以看缓存
如果项目经理或产品经理说,有缓存就不要去调网络了,那就写个else中调用getDataFromServer()方法
如果项目经理说,不管有没有缓存都要调网络,读缓存后仍要继续访问网络,保证获取最新数据,就要按上边的写,不能写else放在其中
运行程序,点新闻中心,从服务器获取数据的方法getDataFromServer中已经调了CacheUtils.setCache()写了一下缓存
看下这个缓存有没有写,打开DDMS,在data/data下,找到智慧西安com.itcast.zhxa02,点击shared_prefs文件夹下的config.xml,导出来看到已经写进来了,写进来之后下次再请求时,如果已有缓存就会打印"发现缓存",即在NewsCenterPager的initData的System.out.println("发现缓存");
看下日志,打印发现缓存了,发现缓存后马上解析,就会接着打印解析结果,还要请求网路,所以会接着打请求结果,请求完了之后又要解析,所以后边会接着打印解析结果
因为我们是既调缓存,又调网络,所以打印了这么多日志
Day02 13.总结 ##