Day03 01.前几天内容总结##
Day03 02.页签详情页实现 ##
实现菜单详情页-新闻NewsMenuDetailPager模块
上边是自定义控件 中间是内容部分,内容部分最大的是一个一个标签,能够来回切换
下边这个可以左右滑动,就是一个ViewPager
实现左右滑动的ViewPager功能
这个页面以前写的TextView不要了,在layout中创建布局pager_news_menu_detail.xml
先不管上边那个,下边这个肯定是一个ViewPager
SlidingMenuLibrary库中的android-support-v4.jar下的view中找到ViewPager,抄下全路径复制到pager_news_menu_detail.xml控件名称上,给它加id为vp_news_detail
pager_news_menu_detail.xml布局此处略
NewsMenuDetailPager的initView加载布局,并把view返回回去
用ViewUtils注解方式初始化ViewPager
activity页面,需要将activity对象传过去,用的是inject(Activity activity)
不是activity页面,需要将当前view传过去,用的是inject(Object handler,View view)
handler: 当前的对象,用this
view: 上边inflate布局时返回的view
@ViewInject(R.id.vp_news_detail)
private ViewPager mViewPager;
@Override
public View initView() {
View view = View.inflate(mActivity, R.layout.news_menu_detail, null);
ViewUtils.inject(this, view); // 把当前的View对象注入到xUtils框架中
return view;
}
给ViewPager写适配器NewsMenuAdapter,entends的pagerAdapter
实现方法:getPageTitle,getCount,isViewFromObject,instantiateItem,destroyItem
isViewFromObject中通常都是 return view == object;
destroyItem中通常都是 container.removeView((View) object);
给adapter的每个页面进行填充
思路和最开始主页面的标签页ViewPager思路类似
当时写一个RadioButton,用一个BasePager把整个页面描述起来
在com.itcast.zhxa02.base.impl包下创建一个页签详情页TabDetailPager继承BaseMenuDetailPager专门描述内容部分ViewPager页
BaseMenuDetailPager是侧边栏四个页面的基类
TabDetailPager单独给它写一个父类,父类中和BaseMenuDetailPager的完全一样,也是initView,initData方法
所以可以让TabDetailPager和菜单详情页4个页面共用一个父类
页签详情页TabDetailPager的initView中实现它的view
中间布局viewPager,先用一个TextView表现页面中的内容,在initView中写一个TextView,从SettingPager中抄一个TextView过来即可
页签详情页TabDetailPager写好后,在菜单详情页-新闻NewsMenuDetailPager中去初始化这一堆页签
应该初始化多少个页签是由接口返回决定
接口data中的新闻模块的childern有多少个元素就应该写多少个页签
网络数据是在新闻中心NewsCenterPager拿到的,要将网络数据传递给菜单详情页-新闻NewsMenuDetailPager
这个很好给,菜单详情页4个页面本就属于新闻中心,新闻中心很容易拿到菜单详情页-新闻对象
在新闻中心NewsCenterPager解析json数据的processData中,初始化了菜单详情页这4个页面数据
里边就有菜单详情页-新闻NewsMenuDetailPager,网络数据mNewsMenuBean也刚好在这个方法中
通过构造方法将数据从新闻中心传递给菜单详情页-新闻
// 初始化详情页数据
mMenuDetailPagers = new ArrayList<BaseMenuDetailPager>();
mMenuDetailPagers.add(new NewsMenuDetailPager(mActivity, mNewsMenuBean.data.get(0).children));
mMenuDetailPagers.add(new TopicMenuDetailPager(mActivity));
mMenuDetailPagers.add(new PhotosMenuDetailPager(mActivity));
mMenuDetailPagers.add(new InteractMenuDetailPager(mActivity));
这时报错,把构造方法修改一下就可以,选中NewsMenuDetailPager然后ctrl+1,选择change constructor 'NewsMenuDetailPager(Activity)':Add parameter 'ArrayList<NewsTabData>'
自动修改菜单详情页-新闻的构造方法并来到菜单详情页-新闻中,接收并在全局变量处声明出来,叫做mTabList
private ArrayList<NewsTabData> mTabList;// 页签网络数据
public NewsMenuDetailPager(Activity activity,ArrayList<NewsTabData> children) {
super(activity);
mTabList = children;
}
把数据设置给NewsMenuAdapter
getCount方法中:return mTabList.size();
初始化布局的方法instantiateItem中,初始化布局不能直接从网络数据mTabList去取,应该从TabDetailPager去做
在NewsMenuDetailPager中实现初始化布局的方法initdata
每一个页签都是一个TabDetailPager对象,应该维护一个集合
到NewsMenuDetailPager的initData初始化数据,children下有12个页签,应该初始化12个页签页面对象
写for循环,int i = 0,i小于网络数据的大小mTabList.size(),然后i++,for循环中new一个TabDetailPager
成员变量处写一个集合,把它维护在集合中,所以泛型为TabDetailPager,叫做mPagers,这是页签页面对象的集合
在初始化数据的initData方法中去new这个泛型为TabDetailPager的集合
初始化一个页面后就add给mPagers
最后NewsMenuDetailPager中代码如下:
private ArrayList<TabDetailPager> mPagers;// 页签页面对象集合
@Override
public void initData() {
mPagers = new ArrayList<TabDetailPager>();
// 初始化12个页签页面对象
for (int i = 0; i < mTabList.size(); i++) {
TabDetailPager pager = new TabDetailPager(mActivity,mTabList.get(i));
mPagers.add(pager);
}
这样就将十二个页签对象全部放在mPagers集合中了,以后要从ViewPager展现布局或对象时,从mPagers去取
现在要在NewsMenuDetailPager的instantiateItem中初始化item
先拿到对象的pager,即mPagers.get(position)拿到对应pager对象
Pager对象中的mRootView就是它的根布局
把根布局addView塞给container
把view最后return回去
然后pager.initData()初始化数据
最后NewsMenuDetailPager中的instantiateItem方法如下:
@Override
public Object instantiateItem(ViewGroup container, int position) {
TabDetailPager pager = mPagers.get(position);
View view = pager.mRootView;
pager.initData();// 初始化数据
container.addView(view);
return view;
}
这个逻辑和首页逻辑类似
ContentFragment中底部5个页签页签页面每个都是ViewPager页面
当时是在ContentAdapter的instantiateItem中,用mPagers.get(position),
然后container.addView(view)把它添加进来,即:
@Override
public Object instantiateItem(ViewGroup container, int position) {
BasePager pager = mPagers.get(position);
View view = pager.mRootView;// 获取当前页面的根布局
// pager.initData();// 初始化数据, 不要再此调用, 此处会默认上一页,当前页和下一页,浪费流量和性能
container.addView(view);
return view;
}
只不过当时为了提高性能,在页面对象选中后才去initData
即在ContentFragment的setOnPageChangeListener的onPageSelected中
@Override
public void onPageSelected(int position) {
mPagers.get(position).initData();// 只有当前页面被选中后才初始化数据
}
放在instantiateItem中也没问题,只不过稍微浪费点流量,性能,NewsMenuDetailPager中就不做这个优化了
getCount中返回的mTabList.size()和mPagers.size()一样,mTabList有多少个,mPagers就有多少个,return一个mPagers.size()也行,这两个都行,因为网络上(即mTabList)有多少个这样的数据,就有多少个这样的页面(即mPagers),所以拿的数量一样,在这就写成mPagers.size()
adapter写好后,就可以给ViewPager去设置adapter了,即在NewsMenuDetailPager的initData中,mViewPager.set一个Adapter,new一个NewsMenuAdapter,即:
@Override
public void initData() {
mPagers = new ArrayList<TabDetailPager>();
// 初始化12个页签页面对象
for (int i = 0; i < mTabList.size(); i++) {
TabDetailPager pager = new TabDetailPager(mActivity,mTabList.get(i));
mPagers.add(pager);
}
mViewPager.setAdapter(new NewsMenuAdapter());
}
这是给ViewPager设置数据的逻辑
运行程序,点新闻中心,新闻中心展示页签详情页了
因为NewsMenuDetailPager中直接在initView中加载了布局
布局中有mViewPager对象,当initData初始化页面数据时,马上给ViewPager初始化Adapter
Adapter塞的是每个TabDetailPager,TabDetailPager的initView实现布局实现的就是页签详情页的TextView
所以运行程序后,在新闻中心展现的是页签详情页
新闻中心内容部分ViewPager事件被父控件NoScrollViewPager拦截的处理
新闻中心内容部分ViewPager,新闻中心上边整体“标题栏+内容部分”也是ViewPager,NoScrollViewPager
现在返回的是super,super中做了复杂判断,有时false,有时true
希望不拦截事件就直接改成返回false
即重写NoScrollViewPager的onInterceptTouchEvent直接返回false
在NoScrollViewPager中重写onInterceptTouchEvent方法,返回false,表示不拦截子控件的事件,保证嵌套的子viewpager可以滑动,即:
//拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;//不拦截子控件的事件,保证嵌套的子viewpager可以滑动
}
运行程序,切到新闻中心滑动,可以流畅滑过来了
Day03 03.ViewPagerIndicator使用&样式修改 ##
在页签详情页TabDetailPager每个页面显示页面相关信息“北京,中国,国际,体育,生活等”
通过构造方法将数据从菜单详情页-新闻传递给页签详情页
网络数据是在菜单详情页-新闻NewsMenuDetailPager的构造方法中接收的mTabList
在initData中从mTabList集合取出当前位置信息以构造方法的方式传递给页签详情页TabDetailPager即可
在NewsMenuDetailPager的initData中把TabDetailPager修改为:
TabDetailPager pager = new TabDetailPager(mActivity,mTabList.get(i));
这时会报错误,鼠标放在TabDetailPager处,选择Change constructor'TabDetailPage(Activity)':Add parameter,跳到TabDetailPager中并自动给构造方法加了NewsTabData newsTabData参数,在成员变量处写一个页签网络数据对象mTabData来接收数据
private NewsTabData mTabData;// 页签网络数据
public TabDetailPager(Activity activity, NewsTabData newsTabData) {
super(activity);
mTabData = newsTabData;
}
初始化数据时初始化相关信息
在TabDetailPager中实现initData方法去更新initView方法中TextView的值
将TextView改成全局,在initData中去setText,即:
view.setText(mTabData.title);
这时可以将initView方法中的页签详情页删掉:
view.setText("页签详情页");
运行程序,点击新闻中心,内容部分就是北京,向右滑动,出现中国,国际,体育,生活等等
接下来去实现上边的指示器,可以指示页面的是北京,中国,国际,体育还是其他
用开源框架ViewPagerIndicator实现指示器效果
是ViewPager的指针项目,指针意思就是指某个页面,可以从GitHub下载
把GutHub玩熟悉以后别的资料都不需要,只需要一个GitHub就行,它上边涵盖了很多控件
ViewPagerIndicator使用流程:
引入ViewPagerIndicator包
在布局文件中声明这个自定义控件
仿照sample代码,初始化自定义控件,和viewpager绑定
重写PagerAdapter的getPageTitle方法,返回页面的标题
给MainActivity加主题样式
基于样式进行修改(背景样式+文字样式)
事件处理: 重写TabPagerIndicator的dispatchTouchEvent方法, 请求父控件及祖宗控件不拦截当前控件的事件
具体实现:
1.选中zhxa02,右键Properties,选择Android,Add,将ViewPagerIndicatorLibrary去Add进来
Add进来后报错内容'Fond 2 Versions of android-support-v4.jar in the dependency list'
解决ViewPagerIndicator-Library和SlidingMenuLibrary的V4冲突
在SlidingMenu-master的V4包,长度是385585,SHA-1类似MD5,文件的数据摘要是48c94ae...
MD5可以把字符串或文件算成一个数据摘要,SHA-1也一样
SHA-1是根据SHA-1算法对V4包文件,MD5是对密码,也可以对文件进行加密
在ViewPagerIndicator-master的V4包,SHA-1是53307dc
这两个V4包文件的SHA-1,长度都不一样,它两就冲突了
都是V4包,文件长度不一样,是因为V4包有好几个版本
上次v4冲突是当前项目zhxa02,和SlidingMenuLibrary冲突,把当前项目V4包删掉就可以了
现在是ViewPagerIndicator-Library的V4,不能删
因为ViewPagerIndicator-Library和其他库没有关系,不可能找SlidingMenuLibrary库
把SlidingMenuLibrary中的V4抄到ViewPagerIndicator-Library中的libs下,把它原来的V4包覆盖掉
这两个库中的V4包一样了就不会冲突了
覆盖掉后ViewPagerIndicator-Library中的v4包与sample中的V4冲突
解决ViewPagerIndicator-Library和sample的V4冲突
sample关联着ViewPagerIndicator,所以sample中的libs下的V4可以删掉,删掉后就不报错了
有两种方式,一种是删,不能删就把两个V4文件搞成一样的
2.在布局文件中声明这个自定义控件
在pager_news_menu_detail.xml的ViewPager正上方再搞一个自定义控件,这个自定义控件从例子程序ListSamples的simple_tabs.xml中可以抄过来,这就是我们的indicator
3.仿照sample代码,初始化自定义控件,和viewpager绑定
例子程序SampleTabsDefault中findViewById拿到indicator,给indicator去setViewPager,把ViewPager和indicator关联在一起,把这两行代码抄过来
在NewsMenuDetailPager的initView中初始化indicator,用Xutils注解方式在成员变量处用注解声明
@ViewInject(R.id.indicator)
private TabPageIndicator mIndicator;
在initData方法中给mIndicator去setViewPager
mIndicator.setViewPager(mViewPager);//将ViewPager和指针绑定在一起
只有把它两绑定在一起,才能在滑动ViewPager时让ViewPagerIndicator也滑动,反之亦然
绑定之前,必须保证ViewPager已经设置完数据,ViewPager去setAdapter设置数据
所以应该将mIndicator.setViewPager(mViewPager)放在initData的mViewPager.setAdapter(new NewsMenuAdapter)之后,不然会出现异常
4.重写PagerAdapter的getPageTitle方法,返回页面的标题
这个例子程序的SampleTabsDefault的GoogleMusicAdapter中有个方法getPageTitle
这里继承的是FragmentPagerAdapter,下一个项目可能会介绍到它
FragmentPagerAdapter意思是ViewPager中塞的是Fragment
getPageTitle是从成员变量处的CONTENT数组取东西,用position对长度取余,即:
return CONTENT[position % CONTENT.length].toUpperCase();
其实没有必要,因为position不会越界的,它取了一个余然后从CONTENT数组中去取的,然后转化成大写了
例子程序中Tabs除了ViewPager和它关联,有很明显特点是每个页面都有标题,这个标题是在getPageTitle去初始化,toUpperCase把它转成大写,原来是小写(Recent)
运行例子程序发现标签已都转成大写了(即RECENT),所以要重写PagerAdapter的getPageTitle方法,返回指针标题
返回的是北京,中国,国际,体育,这些标题,这个数据在mTabList中有
即在NewsMenuDetailPager的NewsMenuAdapter的getPageTitle方法中写:
return mTabList.get(position).title;
运行程序,点新闻中心,这时北京,中国,国际,体育,生活等标题都显示出来了,而且可以往右滑动
切换对应页面看不见效果,但是可以点击对应标题切换到对应页面
例子程序是给SampleTabsDefault的Activity设置了主题样式,打开例子程序清单文件,找下activity申明的地方,给SampleTabsDefault设置了theme主题,即:
android:theme="@style/Theme.PageIndicatorDefaults"
这是它自定义的叫PageIndicatorDefaults的Theme,照着它把主题也抄到zhxa02项目清单文件中
但NewsMenuDetailPager不是一个Activity,清单文件中怎么给设置主题
NewsMenuDetailPager属于MainActivity,直接给MainActivity设置主题
到zhxa02的AndroidManifest.xml中,在MainActivity处添加:
android:theme="@style/Theme.PageIndicatorDefaults"
这个主题能用是因为已把源码关联在一起了,可以随意用它的资源文件
这个主题点进去其实是ViewPagerIndicatorLibrary库(从eclipse的左上角看)的vpi_styles.xml
运行程序,直接点到新闻中心,北京,中国,国际,体育,生活,这些标签名出来了,变黑是因为那个主题样式本来就是黑的
到zhxa02的MainActivity,打开activity_main.xml布局文件,将布局文件背景色换成白色,即:
android:background="#fff"
运行程序,背景变白了,北京,中国,国际,体育,生活,这些标签名字本身是白色,所以看不见了
给MainActivity加主题样式
要改成黑色字,选中后是红色,下边标签是红色小短杠的样式
到zhxa02的清单文件查看样式写法
点击android:theme="@style/Theme.PageIndicatorDefaults"后到vpi_styles.xml中了
里边的PageIndicatorDefaults就是要的样式
<style name="Theme.PageIndicatorDefaults" parent="android:Theme">
<item name="vpiIconPageIndicatorStyle">@style/Widget.IconPageIndicator</item>
<item name="vpiTabPageIndicatorStyle">@style/Widget.TabPageIndicator</item>
</style>
里边有两种样式
vpiIconPageIndicatorStyle是图标的PageIndicator
vpiTabPageIndicator,要用的就是它,ctrl进去看下样式
它的样式还是在vpi_styles.xml中,为下:
<style name="Widget.TabPageIndicator" parent="Widget">
<item name="android:gravity">center</item>
<item name="android:background">@drawable/vpi__tab_indicator</item>
<item name="android:paddingLeft">22dip</item>
<item name="android:paddingRight">22dip</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
<item name="android:textAppearance">@style/TextAppearance.TabPageIndicator</item>
<item name="android:textSize">16sp</item>
<item name="android:maxLines">1</item>
</style>
要改它的背景图片就着重先看background怎么写的,vpi__tab_indicator点进去到了vpi_tab_indicator.xml中
发现它是状态很多的状态选择器,平时就两个状态,这里区分的细,焦点,获取不获取都有
要的效果很简单,只需要默认什么都没有,选中后下边是红色小短杠
把图片换成全透明,直接@一个android:color/transparent,默认如果没有被选中,是不是全透明的颜色
被选中之后是红色小短杠图片news_tab_item_bg_select.9.png,将它拷贝到库文件ViewPagerIndicatorLibrary的drawable-hdpi中
将ViewPagerIndicatorLibrary的vpi__tab_indicator.xml中图片vpi__tab_selected_holo换成news_tab_item_bg_select
unselected默认的全都换成全透明图片,即: @android:color/transparent
selected选中的全都换成小红短杠,即:@drawable/news_tab_item_bg_selector
运行程序,新闻中心的北京,中国,体育,国际等的背景都是透明,选中后就变成红色小短杠了
上边修改了背景图片的样式,接下来再改文字样式
文字大小搞成14sp吧,文字颜色之类的在textAppearance专门设置文字具体样式
点击TextAppearance.TabPageIndicator进来看下,textStyle样式为bold加粗的样式,textColor是颜色
默认是的话,它的颜色就说选中和不选中,好像都是这个白色吧,但是它把颜色用@color/vpi_dark_theme状态选择器,点进去又是状态选择器,只不过全都是颜色,换成自己的颜色,自己的颜色默认是黑色,选中后是红色,在vpi_dark_theme.xml中,enabled=false,这个表示不可用,不可用时应该是黑色,直接写黑色就可以了,即:
<item android:state_enabled="false" android:color="#000"/>
state_window_focused="false"表示没有获取焦点,也应该是黑色,即:
<item android:state_window_focused="false" android:color="#000"/>
state_pressed="true"表示被按下,应该是红色,即:
<item android:state_pressed="true" android:color="#f00"/>
state_selected="true"表示被选中,也是红色,即:
<item android:state_selected="true" android:color="#f00"/>
下边是默认颜色,应该是黑色,即:
<item android:color="#000"/> <!-- not selected -->
运行程序,点新闻中心可以了,这就是修改内容部分标签的背景样式和文字样式(状态选择器的样式修改)
Day03 04.页签滑动事件处理 ##
android有两个难点,自定义控件,事件处理
解决ViewPagerIndicator滑动时侧边栏出来的问题
ViewPagerIndicator的事件被SlidingMenu拦截了
又涉及父控件将子控件的事件拦掉的情况
上边处理‘标题栏加内容部分’父控件将内容部分ViewPager的滑动事件拦截问题时,是让它爹重写拦截事件的onIntercepter方法,直接false
这种方式弊端是如果父控件不只一个,只要有一个父控件拦截了事件,就得找出所有父控件分别重写onIntercepter方法去处理
让所有父控件都不要拦截事件的处理:
重写事件分发方法dispatchTouchEvent,然后调用getParent().requestDisallowInterceptTouchEvent(true);
getParent找所有父控件,传true,所有父控件就都不会拦截此事件了
ViewPagerIndicatorLibrary的TabPageIndicator中,加如下代码:
// 事件分发
// dispatchTouchEvent->onInterceptTouchEvent->OnTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);// 子控件希望父控件及祖宗控件不拦截当前控件的事件
return super.dispatchTouchEvent(ev);
}
运行程序,点击新闻中心,滑动北京,中国,国际,体育,侧边栏都不出来了
再说另外一个事件处理,往后滑到体育,再往回滑(在内容部分的ViewPager中滑)侧边栏又出来了
刚只是处理的内容部分的ViewPager上边的标签(ViewPagerIndicator),现在滑的是ViewPager
解决ViewPager滑动时侧边栏出来的问题
当切到北京时让侧边栏出来,当切到中国,国际,体育时让侧边栏不出来
可以通过控制侧边栏的可用不可用达到侧边栏出来不出来效果
照着刚才逻辑,让ViewPager重写dispatchTouchEvent方法解决也可以,但是要额外添加很多逻辑判断
之前在首页的设置时让侧边栏不出来,新闻中心,智慧服务,政务时出来,也用的侧边栏可用不可用
但现在在新闻中心的中国,国际时侧边栏也要禁用掉,所以要监听ViewPager的页面切换事件
NewsMenuDetailPager的initData中,当ViewPager和indicator同时使用时,点击事件必须设置给indicator
mIndicator.setOnPageChangeListener(this);
让当前类实现onPageChangeListener,实现方法onPageScrolled,onPageSelected,onPageScrollStateChanged
设置页面的滑动监听,一旦页面切换到onPageSelected方法,在这打印position,即:
@Override
public void onPageSelected(int position) {
System.out.println("当前位置:" + position);
}
运行程序,页签和ViewPager就同步切换了
接下来在onPageSelected方法中,判断如果当前位置等于0就打开SldingMenu,else禁用SldingMenu
打开和禁用SlidingMenu的方法在ContentFragment已写过一次,叫做setSlidingMenuEnable
拷贝到NewsMenuDetailPager中,在这个判断中调用并分别设置true和false,即:
@Override
public void onPageSelected(int position) {
System.out.println("当前位置:" + position);
if (position == 0) {
// 打开SlidingMenu
setSlidingMenuEnable(true);
} else {
// 禁用SlidingMenu
setSlidingMenuEnable(false);
}
}
运行程序,点击新闻中心,第一次点进去后肯定不会走onPageSelected方法,这个方法只有页面切了后才走
在第0个位置设置了true,其实不用去设置,它本来默认就可用,所以SlidingMenu能拉出来
所以第一次不需要初始化,因为SlidngMenu本身默认就是true
再到中国,国际,SlidingMenu出不来了,既然SlidingMenu用不了,就被内容部分的ViewPager给拦截了
Day03 05.点击按钮切换页签页面##
智慧北京完整项目条目(内容部分的页签)右侧有小箭头,点箭头也可以切页面
实现点击小箭头切换下一页功能
到pager_news_menu_detail.xml布局文件中,用id为btn_next_page的ImageButton实现小箭头
用ImageView也行,去掉背景,即:android:background="@null"
有时@null会出问题,一旦出现问题就把它改成全透明颜色即可,因为有些控件不支持@null
NewsMenuDetailPager的initView中初始化ImageButton并用XUtils注解方式设置点击事件
写一个方法nextPage,传View参数,给方法加注解OnClick,注解中将id传进去
这个还比原来复杂一些,原来可以在布局文件Pager_news_menu_detail的ImageButton控件上写个onClick属性
写完之后在NewsMenuDetailPager中写方法
这个xUtils不需要加属性,通过注解方式写了nextPage方法,知道这样用就行了,后边就不用它了
实现点击后跳到下一个页面,很简单,让ViewPager去setCurrentItem
设置第几个,用mViewPager去getCurrentItem后++,即:
mViewPager.setCurrentItem(++mViewPager.getCurrentItem());
报错Invalid argument to operation ++/--,不认识++,括起来还是不认识,好像还不能这样去写成一行,那就分开写
不支持拿api去++,只支持整数进行++,不智能
这就是跳到下一个页面的逻辑,即:
@OnClick(R.id.btn_next_page)
public void nextPage(View view) {
//切换到下一个页面
int currentItem = mViewPager.getCurrentItem();
mViewPager.setCurrentItem(++currentItem);
}
++currentItem到最后一个页面再++不会角标越界挂掉,ViewPager原生已做了处理
运行程序,到最后汽车时继续点也不会超出边界,这就是跳转到下一页的逻辑
Day03 06.页签网络数据获取及解析 ##
使用HttpUtils实现页签网络数据获取并用Gson解析数据
内容部分ViewPager的北京页签页面,分为两部分,上边部分是ViewPager,下边是listView
TabDetailPager的initView的TextView去掉,在layout文件夹下写一个布局文件pager_tab_detail.xml,此处略
在上边先是一个ViewPager,在pager_news_menu_detail.xml中有ViewPager布局抄下
ViewPager的id为vp_top_news,是头条新闻叫做topNews
listView的id为lv_news
在TabDetailPager的initView中初始化布局和控件
ViewPager中要填充4张图片,还有listView的数据,都要从网络获取
initData调用getDataFromServer();
然后写getDataFromServer()方法
new一个HttpUtils,叫做utils,用utils去send请求,参数分别为HttpMethod.GET,mUrl,第三个参数是new一个RequestCallBack,泛型是String,重写onSuccess,onFailure
// 请求网络数据
private void getDataFromNet() {
HttpUtils utils = new HttpUtils();
utils.send(HttpMethod.GET, mUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
CacheUtils.setCache(mActivity, mUrl, responseInfo.result);
processData(responseInfo.result, false);
lvList.onRefreshComplete(true);// 隐藏下拉刷新控件
}
@Override
public void onFailure(HttpException error, String msg) {
Log.e(TAG, "请求失败:" + msg);
lvList.onRefreshComplete(false);// 隐藏下拉刷新控件
Toast.makeText(mActivity, "请求失败", Toast.LENGTH_SHORT).show();
}
});
}
看接口中的json串,北京,中国,国际在url中都带有相关链接,这些链接都是服务器返回的,服务器返回什么链接就什么链接
北京是url:"/10007/list_1.json"链接,将这个链接在浏览器地址栏中输入localhost:8080/zhbj/10007/list_1.json,出来一大串json串
北京的json数据分为两部分:
topnews头条新闻,topnews中有蜗居生活,中秋赏月,天空翱翔,感官设计,是ViewPager数据
news新闻数据,网上大讲堂第多少期等,是listview的数据
构造方法TabDetailPager中声明下,并在成员变量处写个mUrl接收
private String ;// 页签网路数据的url
NewsMenuTab mNewsTab;// 页签数据
public TabDetailPager(Activity activity, NewsMenuTab newsTab) {
super(activity);
mNewsTab = newsTab;
mUrl = GlobalContants.SERVER_URL + mNewsTab.url;
}
然后在参数那写成mUrl,这是当前页签的网络接口
分别处理下onSuccess,onFailure
onFailure处理方式和新闻中心NewsCenterPager完全一样,把新闻中心的onFailure抄过来
onSuccess逻辑也一样,将新闻中心的onSuccess抄过来后,processData解析数据
解析数据得写方法processData,方法中new一个Gson,还得在domain包下写一个NewsBean对象去承载新闻详情的数据
NewsBean.java此处略
照着浏览器中localhost:8080/zhbj/10007/list_1.json接口中的json数据去写
topimage是内容部分上半部分ViewPager头条新闻中的图片
图片是从网络去拿的,先去请求接口拿到每一张图片的url链接topimage,因为图片不能直接塞在json中返回
json中只放图片url下载链接,有了url下载链接后再去请求下载地址http://10.0.2.2:8080/zhbj/10007/1452327318UU91.jpg
把10.0.2.2改成localhost在浏览器地址栏输入,即:http://localhost:8080/zhbj/10007/1452327318UU91.jpg
回车就是这张图片,请求下载后才去填充图片,这是头条新闻
还有新闻news,新闻news又是一个对象,所以再写一个普通新闻对象News.java
普通新闻和头条新闻类似,把TopNews中的抄过来,只不过普通新闻叫的是listimage不叫topimage,此处省略News.java
将数据在News中打印下,写个toString,打印下title就行
在NewsData中也打印下title,topnews,news
头条新闻也要打印下,在TopNews中写个toString,打印下title就行
bean对象写完后,在TabDetailPager的processData中用gson解析,返回的肯定是NewsBean对象叫它newsBean,即:
gson.fromjson(result,NewsBean.class);
把newsBean打印一下,即:
// System.out.println("页签页解析结果:" + newsBean);
运行程序,切到新闻中心,数据已打印出来,北京,中国打印了两次是因为ViewPager默认会把下一页加载过来,所以打北京时中国也会加载
Day03 07.头条新闻页面&滑动事件处理 ##
12个页签页面是最底层页面了,大概是6层布局,都是在MainActivity实现
TabDetailPager的initData调用了请求网络的数据getDataFromServer(),getDataFromServer方法中拿数据解析
用CacheUtils给页签页面TabDetailPager实现缓存功能
1)在initData中用CacheUtils去读缓存
把mUrl传进去,ctx传个mActivity,返回String对象叫做cache,判断缓存是否为空,不为空直接解析数据
@Override
public void initData() {
// view.setText(mTabData.title);
String cache = CacheUtils.getCache(mUrl, mActivity);
if (!TextUtils.isEmpty(cache)) {
processData(cache, false);
}
getDataFromServer();
}
2)在getDataFromServer的onSuccess拿到数据后去写缓存
mUrl传过来,json是result,ctx写个mActivity,这是写缓存
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
CacheUtils.setCache(mActivity, mUrl, responseInfo.result);
processData(responseInfo.result, false);
}
昨天专门在CacheUtils中介绍了缓存的读写,缓存现在是放在sp中,有时也可放在本地文件中
这时需要以url为文件名,以json为文件内容写进去,有同学试了这种思路,发现问题是要以url为文件名,但是url中有斜杠问号百分号字符,这种情况无法生成文件名,只需要对url进行MD5运算,以url的MD5值为文件名,MD5都是一些数字和字母
读缓存找有没有MD5这样的文件存在,MD5能避免创建不了文件的问题
接下来填充内容,新闻中心北京页签的ViewPager页面就是头条新闻
在TabDetailPager的getDataFromServer中调用processData解析完数据后,就可以拿到topnews
processData中拿一下,newsBean可以直接找data,下边的topnews集合就是头条新闻的数据,把它叫做mTopNewsList,即:
ArrayList<TopNews> mTopNewsList = newsBean.data.topnews;
把头条新闻的数据集合mTopNewsList搞成全局变量
有了数据后给ViewPager设置Adapter
在TabDetailPager中搞一个类TopNewsAdapter,继承的是PagerAdapter(listView继承的是BasePager,ViewPager继承的是PagerAdapter),并实现它的几个方法
isViewFromObject都是一样写法,直接return view == object;再实现instantiateItem方法
destroyItem方法 直接写:container.removeView((View) object);
getCount方法拿的是它的网络数据,(mTopNewsList)有多少个就是多少个 return mTopNewsList.size();
然后在instantiateItem方法去初始化布局,怎么去初始化数据?
头条新闻中就是一个一个ImageView对象,之前已写过了,在instantiateItem中new一个ImageView
即: ImageView view = new ImageView(mActivity);
给ImageView设置图片,之前本地图片都放在了资源目录下,直接通过资源文件id引入,现在这些图片全部是从网络上下载
使用BitMapUtils从网络下载图片
根据图片url去下载图片
第一步 下载图片
第二步 将图片设置给IamgeView
至少要经过这么两步运算
xutils提供了bitMapUtils专门去下载网络图片
在TopNewsAdapter构造方法中初始化BitMapUtils
通过它在instantiateItem中调用display(T container,String uri)
T类型的container,在这传的就是ImageView对象
String类型的url,指图片的下载链接从mTopNewsList集合可以拿到
第一步下载第二步设置,它一句话就搞定了,即在instantiateItem方法中:
mBitmapUtils.display(view, mTopNewsList.get(position).topimage);// 参1:要设置图片的ImageView对象;参2:图片下载链接
BitMapUtils的功能不局限于此,它还进行了缓存
图片一般都会做缓存,即第3步:图片缓存(以图片url为文件名,以图片为文件内容缓存起来)
因为第一次把图片下载完后,下次就没必要从网络上继续加载了,这时Xutils的BitMapUtils就已经对图片进行了缓存
它只需要加载一次,下次就无需加载直接从本地去读
仿开源框架BitmapUtils实现图片缓存功能
只需以图片url的md5为文件名(key),图片为文件内容(value)以文件形式保存起来,这就是做了一个缓存
图片只能以文件形式进行缓存
之前缓存浏览器中打开的json数据,就是字符串,可以放在数据库,文件,sp中
但图片是一个文件,必须以文件形式缓存起来
这是bitmapUtils的第三个功能,对图片进行了缓存
第4个功能,解决了内存溢出问题,从网络加载图片给ImageView设置时,图片非常多的情况下,快速滑动很容易造成内存溢出
BitmapUtils解决了内存溢出,mBitmapUtils.display(view, mTopNewsList.get(position).topimage);一句话全都搞定了
它底层怎么去做的,后边会仿照bitmaputils写个demo解释图片怎么去下载怎么去缓存
把它display后,container还得加一个addView,把imageView添加进来,即在instantiateItem中写如下代码:
container.addView(view);
同时也要把这个view返回回去,即: return view;
在processData中拿到数据后设置TopNewsAdapter,为了安全起见判断mTopNewsList是否为null
不等于空就给ViewPager设置TopNewsAdapter,参数去new一个TopNewsAdapter设置网页数据,即:
if (mTopNewsList != null) {
mViewPager.setAdapter(new TopNewsAdapter());
}
运行程序,点击新闻中心,老头蜗居图片加载过来了,而且页面可以滑,每个页面图片基本都一样,都是一个老头蜗居图
滑动老头图片,想看到另外3张图片,但一滑滑到中国页面了,它自己里边不能滑,只能去切页面了
解决头条新闻原生ViewPager事件被“内容部分的ViewPager+listView”拦截的问题
1)老头蜗居图片所在的地方是ViewPager
2)内容部分不包括页签ViewPagerIndicator的地方,即“内容部分的ViewPager+listview”的地方是一个ViewPager
3)最外边的“主页面标题栏+内容部分”,即主页面除了底部标签栏之外的地方,也是ViewPager
这是三层ViewPager嵌套,很少见,史无前例的复杂布局
因为父控件内容部分ViewPager+listview”的ViewPager把子控件头条新闻原生ViewPager的事件拦截掉了
照着今天上午思路,让这个孩子去请求所有父控件不要去拦截这个事件
当时需要在TabPageIndicator中重写dispatchTouchEvent方法去拦截
现在头条新闻ViewPager对象用的是系统原生ViewPager,原生的没办法重写它的方法
只能自定义ViewPager为HorizontalScrollViewPager继承ViewPager,重写它的构造方法
然后重写dispatchTouchEvent方法,去getParent().requestDisallowInterceptTouchEvent(true),传个true请求所有父控件不要拦截事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 请求所有父控件不要拦截触摸事件
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
到pager_tab_detail.xml中用全类名com.itcast.zhxa02.view.HorizontalScrollViewPager覆盖android.support.V4.view.ViewPager
TabDetailPager的ViewPager也要改成HorizontalScrollViewPager
运行程序,到新闻中心滑动北京页面的老头蜗居图片,可以把其他3张图片切换出来
解决因BitMapUitls底层给ImageView设置图片时,设置的是图片ImageViewResource而不是背景BackGroundResource导致的白边问题
发现头条新闻ViewPager上边没有贴着ViewPagerIndicator的边,上边留了一个白边
实际ViewPager是贴着ViewPagerIndicator的,留出白边是因为这个图片比较窄,ViewPager比较宽
有同学说直接设置背景就行,这种情况没法给它设置背景,因为mBitmapUtils.display(view, mTopNewsList.get(position).topimage)一句话让BitmapUtils去设置图片了,所以没法设置背景
这个原因是BitMapUitls底层给ImageView设置图片时,设置的是图片而不是背景,设置的是ImageViewResource
而不是BackGroundResource,除非改它的源码,没有必要改它的源码
之前已介绍过,给imageview设置ScaleType,FIT_XY表示宽高填充父窗体即可解决
即在TabDetailPager的instantiateItem方法中
view.setScaleType(ScaleType.FIT_XY);// 宽高填充父窗体
设置一个ScaleType就搞定了,运行程序,就没白边了
根据手势判断父控件在不同情况下是否需要拦截事件的实现
把情况归下类:
1)希望北京的头条新闻滑到最后一张图片(第4张图片)时,再滑能切到中国页面
2)中国的头条新闻滑到的第一个图片,再往左滑能滑到北京页面
3)头条新闻ViewPager下方还要添加ListView,上下滑动时应该让父控件处理
4)滑到北京(第一个页面)的头条新闻的第一个图片时,再滑把侧边栏拉出来
HorizontalScrollViewPager的dispatchTouchEvent中不能getParent().requestDisallowInterceptTouchEvent(true)这么写(注释掉即可),不能让父控件永远不拦截,需要做3个判断
要根据当时的手势来判断要拦截还是不拦截
那这个手势怎么去拿到?
dispatchTouchEvent的参数有个MotionEvent对象,从MotionEvent对象中可以拿到当前点击动作
switch判断ev.getAction()
第一个case,MotionEvent.ACTION_DOWN
第二个Case,MotionEvent.ACTION_MOVE
ACTION_DOWN按下时,获取当时X坐标和Y坐标,startX,startY,抽取成全局成员变量
int startX = (int) ev.getX();
int startY = (int) ev.getY();
ACTION_MOVE去移动时,获取当时移动后的坐标,endX,endY,这个没必要设成全局
int endX = (int) ev.getX();
int endY = (int) ev.getY();
在ACTION_MOVE中分别计算X和Y偏移量
int dx = endX - startX;
int dy = endY - startY;
计算偏移量是想判断是左右滑还是上下滑,哪个方向的偏移量大就向哪个方向滑
判断Math.abs(dx) > Math.abs(dy)说明是左右滑,else是上下滑
上下滑动需要父控件拦截,getParent().requestDisallowInterceptTouchEvent(false);传个false
左右滑动还得判断
判断dx > 0是向右滑动
向右滑中继续判断如果getCurrentItem() == 0当前是第一个页面,需要父控件拦截,getParent().requestDisallowInterceptTouchEvent(false); 传个false
else向左滑动
向左滑动中继续判断如果“getCurrentItem() == getAdapter().getCount() - 1”,当前是最后一个页面,需要父控件拦截,getParent().requestDisallowInterceptTouchEvent(false); 传个false
这3中情况需要拦截,剩余情况不需要拦截
一上来先让它去不要拦截,即在ACTION_DOWN刚点进来让它不要拦截,即:
getParent().requestDisallowInterceptTouchEvent(true);// 不要拦截
ACTION_DOWN中一上来让它不要拦截,getParent().requestDisallowInterceptTouchEvent(true);传个true
不调这句话,一上来就会拦截,后边ACTION_MOVE根本就不会走进去
所以在这个地方先不要让他爹去拦截,让孩子在MOVE时再判断是否拦截
运行程序,点新闻中心
第一个页面上下滑动时没作用
向右滑时,当前是第一个头条新闻页面需要父控件拦,这时侧边栏就可以出来
向左滑到最后一个页面时,再向左滑就滑动到中国页面的第一个页面了
市面上的客户端处理不会这么复杂,只能在里边去滑,滑到头就滑不动了,切页面要点页签去切页面
或者从下边去滑才能切页面
这个逻辑确实复杂,根据手势判断哪些条件下需要拦截不需要拦截来决定平滑切换,这就是手势滑动
Day03 08.头条新闻标题更新##
给头条新闻ViewPager添加动态切换的标题
下边给新闻加一个标题,头条新闻ViewPager左下方有中秋赏月,还有四个小圆点
用帧布局或相对布局把ViewPager和这个布局压在一起,上次已做过
TabDetailPager布局文件pager_tab_detail.xml中
在HorizontalScrollViewPager正上方增加FrameLayout,宽填充屏幕,高包裹内容
下边用相对布局
左边是id为tv_title的蜗居生活textView(标题栏),右边4个小圆点图片,宽是填充屏幕,高是包裹内容,设置背景background是半透明颜色#9000
TextView设置TextColor文字颜色白色#fff,textsize为14sp
给RelativeLayout设置layout_gravity为bottom,在HorizontalScrollViewPager下方
标题栏写好后,在TabDetailPager中给标题内容进行修改,先把TextView对象声明一下,即:
private TextView tvTitle; //头条新闻的标题
用ViewUtils去findViewById,即:
@ViewInject(R.id.tv_title)
private TextView tvTitle;// 头条新闻标题
页面切换时要去更新标题
给ViewPager设置页面切换事件监听,ViewPager在TabDetailPager的processData的setAdapter下边,给mViewPager设置页面切换监听,mViewPager.setOnPageChangeListener(this);
让TabDetailPager类实现OnPageChangeListener,上边去设置Listener时,传个this即可
页面被选中的onPageSelected方法中,拿到头条新闻位置对象mTopNewsList去get(position)拿到当前位置的TopNews对象,把它叫做topNews,再给tvTitle去setText, 文字是topNews.title,这是更新头条新闻的标题
@Override
public void onPageSelected(int position) {
// 更新头条新闻标题
TopNews topNews = mTopNewsList.get(position);
tvTitle.setText(topNews.title);
}
只有在页面滑动时才会做初始化,需要手动初始化第一次页面
在TabDetailPager的processData的if中手动初始化第一个页面,即:
tvTitle.setText(mTopNewsList.get(0).title);//初始化第一页标题
运行程序,第一页标题蜗居生活出来了,滑动出现中秋赏月,天空翱翔
Day03 09.头条新闻页面指示器(小圆点)##
给头条新闻ViewPager添加动态切换的圆点指示器
小圆点功能已写两遍,自定义控件写了一遍,zhbj项目新手引导页写了稍微高级的,再写就没意思了
用另外一种方式实现
ViewPagerIndicator本身具有此功能,Cicles下边,Default是点点点样子
此处需要的效果是,默认灰色,选中后红色,一跳一跳的
ViewPagerIndicator例子的Snap下照着去写,在ListSamples项目的SampleCirclesSnap.java,就几行代码
它的布局文件simple_circles.xml,ViewPager下边是CirclePageIndicator
在ListSamples例子程序中findViewById拿到它,给indicator设置ViewPager,和TabIndicator很像
只是加一句话setSnap(true)表示是快照方式为true,这样才会一跳一跳
把CirclePageIndicator抄到pager_tab_detail.xml布局文件中,在相对布局右侧
在TabDetailPager中,先去声明此控件,即:
@ViewInject(R.id.indicator)
private CirclePageIndicator mIndicator;
给mIndicator设置ViewPager,当ViewPager数据初始化完后设置,在processData的if判断中setAdapter下边,增加如下代码:
mIndicator.setViewPager(mViewPager);
这时去设置页面监听时,它两已绑定在一起,监听应该设置给mIndicator,这时应该注释掉重写写成 mIndicator.setOnPageChangeListener(this);
运行程序,效果出来了,不会自动滑动是因为没有给它设置快照setSnap(true)
在mIndicator.setViewPager(mViewPager)下边写:
mIndicator.setSnap(true);// 快照方式展示
样式不太一样,默认是灰色,选中后是红色,viewpagerIndicator开源框架支持修改样式
Cicles下边有个Styled(via layout),Styled它这有三个,via layout是通过布局修改样式
还有一个via methods,在代码中通过方法去修改布局,还有一个via theme,通过主题修改
布局方式修改简单一些,写个布局文件就可以,别的比较烦,看Styled(via layout)是怎么去写
ListSamples项目的SampleCirclesStyledLayout.java,这时原来的代码中完全一样,只需改它的布局文件
看下themed_circles.xml怎么写的,做了好多处理,app:自己定义属性,radius半径,fillColor,pageColor,strokeColor,strokeWidth
这些color都是什么color?
首先它有个背景,android:background="#FFCCCCCC",这是个灰色背景,实际上不需要背景
然后它的半径是10dp,fillColor是#FF888888(怎么判断它是什么颜色,最后6位每两位按顺序分别代表红绿蓝)
它是灰色被选中后的小圆点颜色,发现fillColor是被选中后的颜色,PageColor是#88FF0000是红色
strokeColor为#FF000000表示线条颜色,每个小圆点有个边框为黑色,strokeWidth为2dp表示线条宽度是2dp
把这些属性抄过来,给自己的布局文件pager_tab_detail.xml的CirclePageIndicator加属性
加过来后挨个改下,fillColor是选中后的颜色是红色,所以改为#f00,默认颜色pageColor是灰色#cccccc
radius半径改为3dp,strokeColor不需要有它的边框,可以删掉,stokeWidth最好别删,一删会有默认线条宽度,强制写成0dp,表示没有边框宽度
报错error:Error parsing XML:unbound prefix,是因为域名空间问题,没有绑定某个前缀,它不认识app:是啥
自定义属性必须在布局文件上边声明域名空间,从例子程序的themed_circles.xml中把域名空间抄过来,放在pager_tab_detail.xml上边LinearLayout处,即:
xmlns:app="http://schemas.android.com/apk/res-auto"
运行程序,点新闻中心,看到颜色修改过来了,而且还有个小功能,什么功能呢,可以点小圆点右侧左侧去切页面
它支持上下页翻页,其实没用,没人知道小圆点还能点
解决再次切入ViewPager后页面和小圆点不同步的问题
北京页面滑动到第3张图片(天空翱翔),突然切到体育,北京页面就被ViewPager自己销毁掉了
再切换到北京时,ViewPager要重新初始化北京页面,点北京页面切换看下,图片是第一张(蜗居生活)
但小红点还是位于第3个位置(天空翱翔),两个没有同步过来
ViewPagerIndicator自作聪明,正常情况下页面销毁后,所有变量乱七八糟的都销毁掉就行
但ViewPagerIndicator自作聪明销毁后把上次那个位置记录下来,下次再初始化时,就拿上次那个位置去搞
但每次进来后定义了第一个页面,这个点却是上次保存下来的点位置,所以会导致不同步
解决:
每次初始化数据后单独去调mIndicator
TabDetailPager的processData的if (mTopNewsList != null)判断中增加下边代码:
mIndicator.onPageSelected(0);//让圆点设置到第一个页面的位置
让第0个页面被选中必须调用这句话,因为indicator在页面销毁后默认保存上次位置状态
但我们不需要这个状态,每次都要从第一个圆点开始展示
运行程序,bug解决了
Day03 10.普通新闻列表展现##
实现了头条新闻功能后,接下来实现它下边普通新闻listview的数据
普通新闻ListView列表的实现
在TabDetailPager的processData方法中解析了数据后,之前拿的是头条新闻数据
现在要换数据拿了,newsBean中有个data,它下边有个news是普通新闻的集合,把它叫做mNewsList并搞成全局
在TabDetailPager的processData方法中加如下代码:
mNewsList = newsBean.data.news;
这是普通新闻的数据,在这判断mNewsList不等于null时给它去设置adapter,就写在TabDetailPager中,叫做NewsAdapter,并实现它的几个方法
getCount 应该return mNewsList.size();
getItem 应该return mNewsList.get(position); 返回值是一个News对象
getItemId 应该return position;
getView 写一个item布局list_item_news.xml
此处省略list_item_news.xml
相对布局中左边是id为iv_icon的ImageView,右上是id为tv_title的标题TextView,右下是id为tv_time的日期TextView
文字标题最多展示两行,设置android:maxLines="2",两行之后加省略号,设置android:ellipsize为end
图片宽高包裹内容,图片有多大就显示多大
如果服务器返回大小不一会导致布局凌乱,通常采用的是把宽高写死,保证不会过大或过小,宽度就写成120dp,高度80dp
但有时会有别的问题,比如现在把宽高设成120dp,80dp,就非得把高设成150dp,imageView本身就这么长,图片这么窄
不太好,希望imageView多大就多大,设置android:scaleType=“fitXY”,这个在代码中用过,这时就把它填充起来了
这样填充美女的脸就变形了,还有别的属性值centerCrop,Crop裁剪
写成android:scaleType=“centerCrop”,把宽高都设置成80dp,它会把左右两个美女都裁剪掉留下中间美女
这样就不会变形了,用fitXY三个美女都显示但不好看,centerCrop既能填充父窗体还能进行裁剪
这个属性确实非常常用,在不影响图片比例前提下能够填充屏幕,它采用裁剪方式实现
通常裁剪图片来适配宽高,这样图片才不会变形
在这介绍它,但是不用它,还是用fitXY
给图片ImageView加边框
完整项目每个图片都有小灰边框,要给ImageView加边框,android:background="#9000"灰色
同时设置背景和图片,即:android:src="@android/image_demo"
图片就把背景给盖住了,看不出任何效果
然后给ImageView设置内边距,即android:padding="1dp",这时边框就出来了
这是给图片加边框的方法,很常用
list_item_news.xml布局文件写完后,listView就开始加载了,TabDetailPager的普通新闻数据适配器NewsAdapter的getView方法中,判断convertView是否为空,为空去加载布局,即:
convertView = View.inflate(mActivity, R.layout.list_item_news,null);
需要一个ViewHolder,即:
static class ViewHolder {
public ImageView ivIcon;
public TextView tvTitle;
public TextView tvTime;
}
在getView中声明ViewHolder,即: ViewHolder holder = null;
在getView的if判断中new一个ViewHolder,即: holder = new ViewHolder();
然后
holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
holder.tvTime = (TextView) convertView.findViewById(R.id.tv_time);
最后将holder设置给convertView,即: convertView.setTag(holder);
else,convertView不为空,从convertView中取出holder中的布局
holder = (ViewHolder)convertView.getTag();
这时可以给控件设置图片之类的东西,通过getItem拿到当前新闻对象,即:
News news = getItem(position);
给图片设置,需要bitmapUtils,构造方法中new一个bitmapUtils,并抽取成成员变量,即:
public NewsAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);// 设置默认加载中的图片
//mBitmapUtils = new MyBitmapUtils();
}
在getView中可以用它了,即: mBitmapUtils.display(holder.ivIcon, news.listimage);
再给他设置标题和日期,如下:
holder.tvTitle.setText(news.title);
holder.tvTime.setText(news.pubdate);
最后把convertView返回回去
processData的if (mNewsList != null) { 中new这个adapter,并抽取成全局变量,即:
mNewsAdapter = new NewsAdapter();
同样在这个地方给lisView设置Adapter,即: lvList.setAdapter(mNewsAdapter);
运行程序,往下去滑,出现问题:
1.解决android系统设置scaleType值为centerCrop时图片边框下边线消失的问题
图片的边框下边这条线没了
是android系统的一个bug,android系统在去设置scaleType等于centerCrop去裁剪时,边框会出现小问题
改用fitXY可以解决这个问题,这是android系统的问题不用去管它,以后如果不设置边框尽量用centerCrop
3.解决listView滑动过程中出现黑色背景的问题
滑动时listView居然变成黑色的了,到根布局pager_tab_detail.xml中
上边是ViewPager,下边是listView,黑色是因为listView在底层绘制时,有一个缓冲颜色,默认是黑色,只需要把颜色换一下就行,在这个布局文件的listView添加属性: android:cacheColorHint="#fff"
默认的这样一个缓冲颜色改成白色#fff就好
android:cacheColorHint属性是为了防止listView滑动过程中出现黑色背景,需要设置此属性为白色或全透明
运行程序,滑动时不会出现黑色,而是正常显示了
有些同学可能会有疑惑,老师我之前用listView时没遇到黑色问题,为什么在这个地方会有问题?
这个要解释还稍微有点费劲,有可能是因为上边加了一个ViewPager,而这个listView现在只在下边这个小小区域内活动
有可能出现这样一个情况,以后不管他在什么情况下出现,只要发现listView一滑动是黑色,只需要给它在布局文件中加android:cacheColorHint="#fff"就解决了
不用纠结什么时候设置这个属性,出现bug时设置就行
4.将ViewPager作为ListView头布局实现整体上下滑动效果
希望在滑listView时ViewPager能作为listView的一份子参与到页面滑动过程中
pager_tab_detail.xml布局写控件时上边是ViewPager,下边是listView,两者没有任何关联
滑动listView时,ViewPager不会往上走,导致listView只能下边小范围滑动
Day03 11.将头条新闻以头布局形式添加给listview ##
完整项目效果ViewPager是listView的一个item条目,只不过样式非常特别,和普通样式完全不一样
通过给listView添加头布局,把ViewPager作为listView头布局展现
TabDetailPager的initView中通过lvList.addHeaderView()添加头布局
以头布局的形式将头条新闻ViewPager添加给listView,所以这个布局文件不能在pager_tab_detail.xml中去写
应该将头条新闻布局单独抽取出来,再写一个布局list_header.xml
头布局,把刚才的帧布局抄过来
此处省略list_header.xml
pager_tab_detail.xml界面中相当于只有一个listView,有了listView后先把头布局加载出来
在TabDetailPager的initView中用View去加载布局文件,即:
// 加载头条新闻作为头布局
View headerView = View.inflate(mActivity, R.layout.list_header, null);
这是头条新闻的头布局,塞给listView,即:
lvList.addHeaderView(headerView);// 将头条新闻布局以头布局的方式添加给listview,作为listview的一份子,当上下滑动时,头条新闻(ViewPager)也能跟随滑动
前边用注解去声明的ViewPager,即:
@ViewInject(R.id.vp_top_news)
private HorizontalScrollViewPager mViewPager;
View view = View.inflate(mActivity, R.layout.pager_tab_detail, null);
ViewUtils.inject(this, view);
但现在这个pager_tab_detail布局中没有ViewPager了,肯定会空指针异常
应该把当前头布局也注入一次,把ViewUtils.inject(this,view)方法抄一份放在如下位置:
// 加载头条新闻的头布局
View headerView = View.inflate(mActivity, R.layout.list_header, null);
ViewUtils.inject(this, headerView);// 必须将头布局也注入到xutils中,才能够初始化viewPager对象
原来只是把整体的listView注入,现在要把整个头布局(ViewPager)也要注入
注入后,头条新闻标题tvTitle,指示器mIndicator,mViewPager才都可以通过headerView去findViewById拿到
运行程序,切换到新闻中心,这时候整体上下滑动就可以实现了
Day03 12.BitmapUtils设置默认图片&listview去掉分割线##
去掉侧边栏ListView中间的分割线
fragment_left_menu.xml中的listView,属性divider 表示分割线
android:divider="" 可以指定分割线
要加线条图片,就@drawable/把线条图片加载进去
不要分割线 直接@null即可
即给ListView添加android:divider="@null"
除了这个属性,还有dividerHeight,可以设置分割线高度
运行程序,侧边栏中listView的分割线去掉了
不管是下边ListView图片还是上边ViewPager图片,会有加载中的默认图片,下载完后先给一张默认图片
玩应用时先给一张默认图,然后再去下载,下载完后默认图片才会变成真实图
作为android开发人员,要有一台android手机不断体验各种不同的软件,用它们时考虑分别怎么实现的
作为一个专业的android开发人员,都要成为软件的玩家,不断体验各种各样的软件,至少知道有这么个效果
不一定必须写出来,至少有思路
使用BitmapUtils设置默认图片功能
TabDetailPager的NewsAdapter的构造方法中,给mBitmapUtils设置configDefaultLoadingImage(int resId),即:
mBitmapUtils.configDefaultLoadingImage(R.drawable.news_pic_default);// 设置默认加载中的图片
运行程序,有默认的北京图片
头条新闻(ViewPager)也设置默认图片
到TabDetailPager的TopNewsAdapter的构造方法中,即:
mBitmapUtils.configDefaultLoadingImage(R.drawable.topnews_item_default);
运行程序,切换到新闻中心的一瞬间就会出现默认图片
图片缓存在DDMS/data/data/com.itcast.zhxa02/cache,清理缓存就是清理这个cache,里边的xBitmapCache就是xUtils的图片缓存,里边全是图片缓存文件,这个图片是用图片下载链接的MD5拼起来的,后边加了个后缀叫做.0
可以把它导出来加.png,打开就是老头蜗居图片
Day03 13.下拉刷新-隐藏头布局 ##
通过给ListView加头部局的形式实现下拉刷新效果
下拉刷新效果
下拉刷新,往下拉显示下拉刷新,再往下拉显示松开刷新,松开手是正在刷新
专门写个下拉刷新的listView,在view包new一个RefreshListView
RefreshListView是基于原生listView进行的功能扩展,继承的还是listView,重写下它的构造方法
此处省略RefreshListView.java
下拉刷新的listView,要给它加头布局,把布局文件pull_to_refresh_header.xml写出来
此处省略pull_to_refresh_header.xml
RefreshListView中写个方法initView加载布局
View mHeaderView = View.inflate(getContext,R.layout.pull_to_refresh_header,null);
声明为全局变量,以头布局形式塞给ListView,也就是调listview的addHeaderView方法,刚才是在外边调的ListView的这个方法,现在在ListView里边调,即:
this.addHeaderView(mHeaderView);//给listView添加头布局
这个initView方法在每个构造方法(有3个不同参数的构造方法)都调一下
pager_tab_detail.xml中将ListView控件名改为拷贝的RefreshListView的全路径com.itcast.zhxa02.view.RefreshListView
布局中的这些属性都能用,因为RefreshListView本身继承的是listView
回到TabDetailPager中,将:
@ViewInject(R.id.lv_news)
private ListView lvList;
改为下拉刷新的listView,即:
@ViewInject(R.id.lv_news)
private RefreshListView lvlist;
给listView加了下拉刷新的头布局了,RefreshListView只要在三个构造方法中初始化,马上就在RefreshListView中调用initView的this.addHeaderView(mHeaderView)去加载下拉刷新的头布局
运行程序,点新闻中心,下拉刷新效果就出来了
先加的下拉刷新头布局,再加的ViewPager头布局
是在TabDetailPager的initView中添加的头布局,这时lvList(listView)已经初始化好了,要是没有初始化好,lvList就应该是空指针异常,要初始化一定要走它的构造方法才能初始化,即在RefreshListView的三个构造方法中初始化
要初始化一个对象,底层肯定要走构造方法,不管走的是哪个构造方法,都马上在RefreshListView的initView中addView,这个View就是下拉刷新的头布局,所以下拉刷新的头布局是最先加上去的头布局
这个加完初始化完后,才给listView又回到TabDetailPager的initView中添加了头条新闻的头布局
顺序是谁先加(下拉刷新),谁在上边,谁后加(头条新闻)谁在下边
下拉刷新头布局加载出来后,一直都在这展示着,没有隐藏掉,要的效果是'下拉刷新的头布局'是隐藏掉的
只有向下拉,才会出来,一上来就应该把'下拉刷新的头布局'给隐藏掉,即在RefreshView的initView中隐藏'下拉刷新的头布局'
隐藏'下拉刷新的头布局'方法:
写一个控件时,有时会给控件设置paddingTop值,paddingTop如果是0,可能和父控件的最顶点相贴
paddingTop越大,这个控件越往下
可以给paddingTop设置负值,平时设置正值,正直越来越往下越来越往下,现在要给它设置负值往上走
以前写的都是正值,android可以写负值,写负值向相反方向走考虑,只不过不常用
设置负的上边距布局就会向上走,负的上边距要设置成负的多少才能完全隐藏掉,这个负paddingTop应该是刚好是‘下拉刷新的头布局’高度
在RefreshListView的initView中给mHeaderView(下拉刷新的头布局)去setpadding(int left,int top,int right,int bottom),上下左右的内边距都可以设置,left,right,bottom都不用管,直接设置成0,首先要拿到这个布局的高度
mHeaderView.getHeight()可以拿到控件高度,即: int mHeaderViewHeight = mHeaderView.getHeight();
这时把它设置成负的即可,即: mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
这时布局就会向上走了,可以在这打印它的高度,即: System.out.println("height:" + mHeaderViewHeight);
运行程序,点新闻中心,它还没隐藏,height打印的全是0
因为在初始化布局时,onCreate,从MainActivity来讲,整个页面还没有创建完成
创建流程是先去测量,再去layout,再去drawable,还没有测量完成,那你怎么知道宽高是多少,所以只能是0
非要知道它的宽高,可以手动去测量,先要mHeaderView直接去,既然不调measure,就非得在RefreshListView的initView方法帮你调下measure,即:mHeaderView.measure(widthMeasureSpec,heightMeasureSpec);
widthMeasureSpec,heightMeasureSpec这两个值不传,直接传个0表示手动测量,即:
mHeaderView.measure(0,0);//手动测量布局宽高
这样具体宽高就会由系统底层决定,我们不做任何决定,反正就调一下它告诉系统我就要测量
然后高度mHeaderViewHeight就应该等于mHeaderView去getMeasuredHeight,表示获取
刚才测量了,然后获取测量后的高度,把它传过来,即: mHeaderViewHeight = mHeaderView.getMeasuredHeight();
把刚才写的int mHeaderViewHeight = mHeaderView.getHeight();注释掉即可
运行程序,发现‘下拉刷新的头布局’就没了,它打印的高度height是58,已经获取出来了
但是initView方法可能走了很多次,所以打印了很多条,因为有12个页签页面,每个页签都要把每个页签都初始化好
现在‘下拉刷新的头布局’既然往上走隐藏掉了,往下拉应该能拉出来吧,看看它能不能拉出来,出不来对吧
Day03 14.总结 ##
----------------------------------------------------------------