# 智慧北京黑马74期笔记 #
# 智慧北京黑马74期笔记 #
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
# 智慧北京黑马74期笔记 #
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
# 智慧北京黑马74期笔记 #
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
# 智慧北京黑马74期笔记 #
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
# 智慧北京黑马74期笔记 #
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
# 智慧北京黑马74期笔记 #
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
## 第三方分享平台: ShareSdk ##
## 图片缓存 ##
> 三级缓存
- 优先从内存中加载图片, 速度最快, 不浪费流量
- 其次从本地(sdcard)加载图片, 速度快, 不浪费流量
- 最后从网络下载图片, 速度慢, 浪费流量
> 内存溢出
不管android设备总内存是多大, 都只给每个app分配一定内存大小, 16M, 一旦超出16M就内存溢出了
> 引用
- 默认强引用, 垃圾回收器不会回收
- 软引用, 垃圾回收器会考虑回收 SoftReference
- 弱引用, 垃圾回收器更会考虑回收 WeakReference
- 虚引用, 垃圾回收器最优先回收 PhantomReference
## 屏幕适配 ##
> 养成良好的开发习惯: 多用dp,sp,不用px; 多用线性布局和相对布局, 不用绝对布局; 代码中如果必须设置像素的话, 将dp转为px进行设置
> 项目开发后期,对适配问题进行验证
- 图片适配
ldpi:240*320 0.75
mdpi: 320*240 1
hdpi: 480*800 1.5
xhdpi: 1280*720 2
xxhdpi: 1920*1080 3
设备密度:
常规做法: 做一套图 1280*720 切图, 放在hdpi或xhdpi下. 如果某个屏幕出了问题, 再针对该屏幕, 对相关出问题的图片进行替换.
- 布局适配(不太常用)
layout-800x480:专门针对480*800屏幕适配的布局文件, 一般只调整位置和大小, 不建议对控件类型和个数进行调整
- 尺寸适配(很常用)
//dp 和 px
//dp = px/设备密度
//values-1280x720/dimens.xml
- 权重适配
android:weightSum="3"
- 代码适配
## 项目角色 ##
- 产品经理(产品狗,产品汪)
决定开发什么东西, 需求文档(原型图)
- 程序员(码农, 程序猿, 攻城狮)
负责开发
- Android开发工程师
- IOS开发工程师
- 服务器开发工程师JavaEE/PHP/.Net (接口文档)
- 视觉设计师/UI设计师(美工)
效果图, 切图 1280*720分辨率(主流分辨率)
- 测试工程师
- 运营(销售, 推广, 打广告, 写软文)
## 开发流程 ##
- 需求分析
- 聊天模块
- 发语音
- 取消语音
- 上划取消
- 语音时间限制
- 发图片
- 发视频
- 朋友圈
- 摇一摇
- 漂流瓶
- 产品设计(需求文档)
- 需求评估(项目经理)
2-3个月 300行代码/天
确定上线时间
- 任务分配
燃尽图
- 开始开发
- 测试 (1-2周时间测试)
- 上线(将apk发布到应用市场)
- 版本迭代(项目周期拉短 20天-30天一个迭代)
## 闪屏页面开发 ##
- 旋转动画
- 缩放动画
- 渐变动画
## 库项目 Library ##
## 侧边栏 ##
> SlidingMenu
## 第三方分享平台: ShareSdk ##
## 图片缓存 ##
> 三级缓存
- 优先从内存中加载图片, 速度最快, 不浪费流量
- 其次从本地(sdcard)加载图片, 速度快, 不浪费流量
- 最后从网络下载图片, 速度慢, 浪费流量
> 内存溢出
不管android设备总内存是多大, 都只给每个app分配一定内存大小, 16M, 一旦超出16M就内存溢出了
> 引用
- 默认强引用, 垃圾回收器不会回收
- 软引用, 垃圾回收器会考虑回收 SoftReference
- 弱引用, 垃圾回收器更会考虑回收 WeakReference
- 虚引用, 垃圾回收器最优先回收 PhantomReference
## 屏幕适配 ##
> 养成良好的开发习惯: 多用dp,sp,不用px; 多用线性布局和相对布局, 不用绝对布局; 代码中如果必须设置像素的话, 将dp转为px进行设置
> 项目开发后期,对适配问题进行验证
- 图片适配
ldpi:240*320 0.75
mdpi: 320*240 1
hdpi: 480*800 1.5
xhdpi: 1280*720 2
xxhdpi: 1920*1080 3
设备密度:
常规做法: 做一套图 1280*720 切图, 放在hdpi或xhdpi下. 如果某个屏幕出了问题, 再针对该屏幕, 对相关出问题的图片进行替换.
- 布局适配(不太常用)
layout-800x480:专门针对480*800屏幕适配的布局文件, 一般只调整位置和大小, 不建议对控件类型和个数进行调整
- 尺寸适配(很常用)
//dp 和 px
//dp = px/设备密度
//values-1280x720/dimens.xml
- 权重适配
android:weightSum="3"
- 代码适配
## 消息推送 ##
> 客户端被动接收服务器数据, 都可以用上消息推送技术
> 应用场景: 广告, 即时通讯
> 极光推送
## 语音识别 ##
> 科大讯飞语音云
## 友盟统计 ##
# 智慧北京笔记 #
# Day01 #
## 项目介绍 ##
## 公司角色 ##
- 产品经理(产品狗,产品汪)
- 程序员(程序猿,码农)
- UI设计师(美工)
- 测试
- 运营
## 研发流程 ##
- 需求分析
- 朋友圈
- 发布内容
- 点赞评论
- 图片浏览
- 摇一摇
- 个人信息
- 聊天模块
- 产品设计
由产品经理编写产品文档, 设计产品原型
- 需求评估
根据产品原型, 分模块评估工作量, *工作日/1人力, 最终确定完成时间点, 产品经理会依据完成时间点, 再增加1-2周测试和修改bug的时间,最终确定上线时间点
- 需求分配
项目经理按照功能模块, 分配不同人员开发
- 项目开发
进入开发阶段, 项目经理要对交付时间负责. 项目经理需要时刻监督手下人员的完成情况, 随时进行督促和调整
燃尽图
- 项目测试
monkey压力测试
测试人员花1-2周进行多轮测试和回归测试, 最终使项目达到上线标准
Bugzilla, jira等在线bug提交平台
- 产品上线
开发人员使用正式签名进行打包, 发给运营人员提交到各大应用市场, 如豌豆荚, 91市场, 应用宝, GooglePlay等
- 迭代开发
第一版上线后, 需要继续开发第二版,第三版等, 成为项目迭代. 一般app迭代周期大概是20天至1个月
## SVN环境搭建 ##
## 欢迎页开发 ##
> 动画效果
- RotateAnimation
- ScaleAnimation
- AlphaAnimation
- AnimationSet
// 初始化欢迎页面的动画
private void initViews() {
RelativeLayout rlRoot = (RelativeLayout) findViewById(R.id.rl_root);
RotateAnimation rotate = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
rotate.setDuration(1000);// 动画执行时间
rotate.setFillAfter(true);// 动画结束后保持最终状态
ScaleAnimation scale = new ScaleAnimation(0, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
scale.setDuration(1000);
scale.setFillAfter(true);
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(2000);
alpha.setFillAfter(true);
AnimationSet set = new AnimationSet(false);
set.addAnimation(rotate);
set.addAnimation(scale);
set.addAnimation(alpha);
rlRoot.startAnimation(set);
}
> 替换logo
## 引导页开发 ##
> SharePreference工具类实现
记录是否要展示新手引导, 默认需要展现, 一旦用户点击开始体验按钮进入主页面时, 下次不再展现
> 布局页面
ViewPager + 开始体验Button + LinearLayout(位置指示器)
> "开始体验"按钮效果处理
1. Button背景selector
2. 文字颜色selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@android:color/black"/>
<item android:color="@android:color/white"/>
</selector>
android:textColor="@drawable/txt_guide_selector"
> 去除标题
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去除标题,必须在setContentView之前调用
> ViewPager填充数据
// 初始化ViewPager的数据
private void initData() {
int[] imageResIDs = { R.drawable.guide_1, R.drawable.guide_2,
R.drawable.guide_3 };
mImageList = new ArrayList<ImageView>();
for (int i = 0; i < imageResIDs.length; i++) {
ImageView image = new ImageView(this);
image.setBackgroundResource(imageResIDs[i]);// 注意设置背景, 才可以填充屏幕
mImageList.add(image);
}
}
> 增加位置指示器
- 使用shape绘制选中和不选中的圆形
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<solid android:color="@android:color/darker_gray"/>
</shape>
- 根据新手引导页数量动态添加圆点
View point = new View(this);
point.setBackgroundResource(R.drawable.shape_point_default);
LayoutParams params = new LayoutParams(10, 10);
if(i!=0) {
params.leftMargin = 10;
}
point.setLayoutParams(params);
llPointGroup.addView(point);
- 添加红色圆点
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp" >
<LinearLayout
android:id="@+id/ll_point_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
</LinearLayout>
<View
android:id="@+id/view_red_point"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/shape_point_selected" />
</RelativeLayout>
- 计算两个圆点之间的距离
1. 视图树的解释(可以参照hierarchyviewer这个sdk工具)
// measure -> layout -> draw
// 获得视图树观察者, 观察当整个布局的layout时的事件
viewRedPoint.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//此方法只需要执行一次就可以: 把当前的监听事件从视图树中移除掉, 以后就不会在回调此事件了.
viewRedPoint.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
// 点的间距 = 第1个点的左边 - 第0个点的左边;
int width = llPointGroup.getChildAt(1).getLeft()
- llPointGroup.getChildAt(0).getLeft();
System.out.println("间距: " + width);
}
});
- ViewPager监听滑动事件, 动态设置红点位置
/**
* 页面滑动监听
*
* @params position 当前选中的位置
* @params positionOffset 偏移百分比
* @params positionOffsetPixels 页面偏移长度
*/
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
int leftMargin = (int) (mPointWidth * (positionOffset + position));
// Log.d(TAG, "当前位置:" + position + ";偏移比例:" + positionOffset
// + ";点偏移:" + leftMargin);
RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) viewRedPoint
.getLayoutParams();
lp.leftMargin = leftMargin;
viewRedPoint.setLayoutParams(lp);
}
- 处理"开始体验"按钮的显示和隐藏, 以及点击跳转页面
## 主页面开发 ##
- SlidingMenu 开源项目
> 如何引入一个库文件, 代码演示
> 从Github上下载SlidingMenu源码, 运行例子程序, 进行演示
官网地址: https://github.com/jfeinstein10/SlidingMenu, 或者直接在github上搜索SlidingMenu
> SlidingMenu使用步骤(Demo演示)
1. 引入SlidingMenu的库
2. Activity继承SlidingFragmentActivity
3. onCreate改为public
4. 配置左右两侧菜单布局
setBehindContentView(R.layout.menu_left);// 设置左侧菜单
SlidingMenu slidingMenu = getSlidingMenu();// 获取SlidingMenu对象
slidingMenu.setSecondaryMenu(R.layout.menu_right);// 设置右侧菜单
slidingMenu.setMode(SlidingMenu.LEFT_RIGHT);// 设置菜单展现模式, 左右两侧都展示
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);// 设置触摸方式
slidingMenu.setBehindOffset(100);// 设置主页面剩余宽度
> 将SlidingMenu进入到项目中
- jar包冲突问题解决
SlidingMenu已经有了android-support-v4.jar, 智慧北京也有, 有时候两个jar会有api冲突, 为了解决这个问题, 可以直接删除智慧北京中的jar文件, 或者取消本地jar文件关联
- Fragment搭建
1. Fragment生命周期
2. 抽取基类BaseFragment
- 定义Activity常量,方便子类使用
- 定义抽象方法initView,初始化布局,必须实现
- 定义方法initData,初始化数据,可以不实现
3. 创建Fragment, 替换帧布局
private void initFragment() {
FragmentManager fm = getSupportFragmentManager();
// 开启事务
FragmentTransaction ft = fm.beginTransaction();
// 替换帧布局
ft.replace(R.id.fl_menu_left, new LeftMenuFragment(), FRAG_MENU_LEFT);
ft.replace(R.id.fl_main, new ContentFragment(), FRAG_CONTENT);
// 提交事务
ft.commit();
// fm.findFragmentByTag(arg0); 根据tag获取Fragment对象
}
- 主页面UI实现
- 框架结构: ViewPager+RadioGroup
RadioButton样式设置:
<style name="bottom_tab_style">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:button">@android:color/transparent</item>
<item name="android:drawableTop">@drawable/home_press</item>
<item name="android:gravity">center</item>
<item name="android:drawablePadding">5dip</item>
<item name="android:textColor">#000</item>
<item name="android:textSize">14sp</item>
</style>
- 标签选中状态处理
- 文字选择器
RadioButton必须指明id, 否则RadioGroup无法保证每次只选一个
- 图片选择器
android:state_checked="true"
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/govaffairs_press" android:state_checked="true"/>
<item android:drawable="@drawable/govaffairs"/>
</selector>
- xUtils开源框架
- ViewUtils //使用注解方式初始化View
使用方法参照github文档:
@ViewInject(R.id.rg_content_group)
private RadioGroup mRadioGroup;
//在Activity中注入:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ViewUtils.inject(this); //注入view和事件
...
textView.setText("some text...");
...
}
//在Fragment中注入:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bitmap_fragment, container, false); // 加载fragment布局
ViewUtils.inject(this, view); //注入view和事件
...
}
- 抽取页面共性,创建BasePager
> 共同方法
- 构造方法
public BasePager(Activity activity) {
mActivity = activity;
initView();
}
- initView //初始化View, 返回一个View对象
- initData //初始化数据
> 布局文件
>
整体为LinearLayout, 嵌套RelativeLayout(标题) + FrameLayout(正文)
标题布局单独写xml文件, include进来
<include layout="@layout/title_bar" />
title_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_red_bg" >
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="首页"
android:textColor="#fff"
android:textSize="23sp" />
<ImageButton
android:id="@+id/btn_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:background="@android:color/transparent"
android:src="@drawable/img_menu" />
</RelativeLayout>
- 分别实现5个子类
public class HomePager extends BasePager {
public HomePager(Activity activity) {
super(activity);
}
@Override
public void initData() {
tvTitle.setText("智慧北京");
btnMenu.setVisibility(View.GONE);
TextView tvContent = new TextView(mActivity);
tvContent.setText("首页");
tvContent.setTextColor(Color.RED);
tvContent.setTextSize(25);
tvContent.setGravity(Gravity.CENTER);
flContent.addView(tvContent);
}
}
- 将5个页面填充给ViewPager
## 主页面UI框架总结(思维导图) ##
# Day02 #
## 第一天总结 ##
## 细节处理 ##
- 自定义NoScrollViewPager
继承ViewPager, 实现onTouchEvent, return true;
/**
* 拦截ViewPager的触摸事件, 不做任何处理
*/
@Override
public boolean onTouchEvent(MotionEvent arg0) {
return true;
}
- RadioGroup处理
监听RadioGroup选择事件, 对ViewPager的当前页面进行切换
mRadioGroup.check(R.id.rb_home);// 设置默认选项为首页
mRadioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb_home:
mViewPager.setCurrentItem(0);// 让ViewPager切换到第一个页面
setSlidingMenuEnable(false);
break;
}
}
});
- 性能优化
为了节省流量, 只有用户切换到该页面时,才加载当前页面的数据. ViewPager会默认预加载下一页数据.
// 监听ViewPager的选中事件
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mPagerList.get(position).initData();// 当该页面选中时,才开始初始化当前页面的数据
}
);
- 设置"首页"和"设置"页面SlidingMenu不可用
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);
}
}
## 新闻中心页面UI框架搭建 ##
- 新闻中心页面布局分析
- 服务器环境搭建
将服务器相关代码和文件(zhbj)拷贝到"apache-tomcat-7.0.57\webapps\ROOT"目录下
- 访问链接http://10.0.2.2:8080/zhbj/categories.json, 获取json数据
- 使用工具格式化json, 方便查看.(HiJson,JsonView,在线网站等)
- 使用xUtils访问网络数据
HttpUtils utils = new HttpUtils();
// RequestCallBack的泛型表示返回的数据类型, 在此我们只需要json的字符串文本, 所以传递String就可以
utils.send(HttpMethod.GET, GlobalContants.NEWS_URL,
new RequestCallBack<String>() {
// 请求成功
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
Log.d(TAG, "result-->" + result);
processData(result);
}
// 请求失败
@Override
public void onFailure(HttpException error, String msg) {
Log.e(TAG, "请求失败:" + msg);
}
});
- 使用gson解析
Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。
Gson gson = new Gson();
NewsBean news = gson.fromJson(result, NewsBean.class);
- 新闻中心对象的创建
/**
* 封装新闻中心的对象
*
* 注意: 参数起名一定要和json中的命名一致, 否则gson无法解析
*
* @author Kevin
*
*/
public class NewsBean {
public ArrayList<NewsMenuBean> data;
public ArrayList<String> extend;
public int retcode;
public class NewsMenuBean {
public ArrayList<NewsMenuTab> children;
public String id;
public String title;
public int type;
public String url;
public String url1;
@Override
public String toString() {
return "NewsMenuBean [children=" + children + ", title=" + title
+ "]";
}
}
public class NewsMenuTab {
public String id;
public String title;
public int type;
public String url;
@Override
public String toString() {
return "NewsMenuTab [id=" + id + ", title=" + title + "]";
}
}
@Override
public String toString() {
return "NewsBean [data=" + data + ", retcode=" + retcode + "]";
}
}
- 侧边栏数据填充
- 初始化ListView
mListView = new ListView(mActivity);
mListView.setBackgroundColor(Color.BLACK);//设置背景色
mListView.setDividerHeight(0);//分割线高度设置为0(去掉分割线)
mListView.setSelector(android.R.color.transparent);//将点击效果设置为透明(去掉点击效果)
mListView.setPadding(0, 40, 0, 0);//设置边距
- ListView的item布局:
- left_menu_item.xml
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="新闻"
android:textColor="@drawable/txt_left_menu_selector"
android:padding="20dp"
android:drawablePadding="10dp"
android:textSize="23sp"
android:enabled="false"
android:drawableLeft="@drawable/btn_left_menu_selector"
>
</TextView>
- txt_left_menu_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true" android:color="#ff0000"/>
<item android:color="#ffffff" android:state_enabled="false"/>
</selector>
- btn_left_menu_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/menu_arr_select" android:state_enabled="true"/>
<item android:drawable="@drawable/menu_arr_normal"/>
</selector>
- 侧边栏菜单点击事件处理
- 点击item, 切换颜色
记录选中item的位置, 点击后刷新listview,更新item状态
- 点击item, 隐藏侧边栏
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
slidingMenu.toggle();// 如果侧边栏打开,则关闭;如果关闭,则打开
- 点击标题栏的菜单按钮,显示侧边栏
- 侧边栏对应的4个详情页面 *
- 分析UI框架结构, 抽取菜单详情页基类
基类: BaseMenuDetailPager
public abstract class BaseMenuDetailPager {
public Activity mActivity;
public View mRootView;
public BaseMenuDetailPager(Activity activity) {
this.mActivity = activity;
mRootView = initView();
}
/**
* 初始化界面
* @return
*/
public abstract View initView();
/**
* 初始化数据
*/
public void initData() {
};
}
NewsMenuDetailPager//新闻
TopicMenuDetailPager//专题
PhotosMenuDetailPager//组图
InteractMenuDetailPager//互动
- NewsCenterPager中, 维护菜单详情页面的集合
- 点击侧边栏item, 切换不同页面, 标题也要发生相应变化
- 缓存处理
将json数据保存在SharedPreference中, 每次读取网络数据前,先从SP从获取数据, 然后继续访问网络,加载最新数据
// 先从缓存中读取数据并展示
String cache = CacheUtils.getCache(mActivity, GlobalContants.NEWS_URL);
if (!TextUtils.isEmpty(cache)) {
processData(cache);
}
// 请求成功
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
Log.d(TAG, "网络Json数据: " + result);
CacheUtils.setCache(mActivity, GlobalContants.NEWS_URL,
result);
processData(result);
}
## 新闻详情页开发 ##
- 定义TabDetailPager
继承BaseMenuDetailPager, 表示每一个页签的对象, 填充ViewPager数据
- 滑动事件处理
/**
* 表示不对事件进行拦截, 从而可以使嵌套在ViewPager内部的ViewPager可以响应滑动动作
*
* @param arg0
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
return false;
}
/**
* 拦截ViewPager的触摸事件, 不做任何处理
*/
@Override
public boolean onTouchEvent(MotionEvent arg0) {
return false;
}
- ViewPagerIndicator
ViewPager指针项目,在使用ViewPager的时候能够指示ViewPager所在的位置,就像Google Play中切换的效果一样,还能使用在应用初始化的介绍页面
下载开源项目, 演示Demo
下载地址: https://github.com/JakeWharton/Android-ViewPagerIndicator
- NewsMenuDetailPager
菜单新闻详情页
TabPageIndicator mIndicator;
@Override
public void initData() {
mTabDetailPagers = new ArrayList<TabDetailPager>();
// 初始化页签数据
for (NewsMenuTab tab : mNewsMenuTabs) {
TabDetailPager pager = new TabDetailPager(mActivity, tab);
mTabDetailPagers.add(pager);
}
mTabAdapter = new TabDetailPagerAdapter();
mViewPager.setAdapter(mTabAdapter);
mIndicator.setViewPager(mViewPager);
}
## ViewPagerIndicator使用流程 ##
- 引入library库
- 布局文件
在布局文件中声明TabPageIndicator控件, 必须和ViewPager搭配使用
<com.viewpagerindicator.TabPageIndicator
android:id="@+id/tpi_news_menu"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<android.support.v4.view.ViewPager
android:id="@+id/vp_news_menu"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" />
- 初始化
1. 将指针和ViewPager关联起来, ViewPager页面发生变化时, 指针也会跟随变化
mIndicator.setViewPager(mViewPager);
mAdapter = new NewsMenuAdapter();
mViewPager.setAdapter(mAdapter);
2. 在ViewPager的Adapter中,重写该方法:
/**
* 返回页面标题, 用于在指针中显示
*/
@Override
public CharSequence getPageTitle(int position) {
return mNewsTabDataList.get(position).title;
}
- 样式修改
参照Demo, 给Activity设置主题样式
<activity
android:name=".MainActivity"
android:theme="@style/Theme.PageIndicatorDefaults" />
修改样式中的图片, 文字颜色等
<style name="Theme.PageIndicatorDefaults" parent="android:Theme">
<item name="vpiIconPageIndicatorStyle">@style/Widget.IconPageIndicator</item>
<item name="vpiTabPageIndicatorStyle">@style/Widget.TabPageIndicator</item>
</style>
//页签样式修改
<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:textColor">@drawable/vpi__tab_text_color</item>//修改页签字体颜色
<item name="android:maxLines">1</item>
</style>
vpi__tab_indicator.xml
将默认图片改为透明: @android:color/transparent
将选中图片改为自定义图片
vpi__tab_textcolor.xml
将vpi__tab_indicator.xml代码拷贝一份,重命名为vpi__tab_textcolor.xml,
其中的drawable改为color, 默认颜色为黑色,选中颜色为红色
## 第二天课程总结 ##
# Day03 #
## 前两天课程总结 ##
结合UI框架结构来分析
## 细节处理 ##
- 页签右侧增加箭头
使用水平LinearLayout
//点击事件处理
@OnClick(R.id.iv_news_tab_next)
public void nextTab(View view) {
mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
}
- 页签滑动的事件处理
向右滑动页签条, 会把侧边栏拉出来, 为了避免这个问题, 可以
重写TabPageIndicator的方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);//禁止父控件拦截触摸事件
return super.dispatchTouchEvent(ev);
}
- 页签对应页面右划时,控制侧边栏显示和隐藏
mIndicator.setOnPageChangeListener(this);//设置ViewPager的滑动监听
@Override
public void onPageSelected(int arg0) {
Log.d(TAG, "onPageSelected=" + arg0);
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
if (arg0 == 0) {
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
} else {
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);
}
}
注意: 如果ViewPagerIndicator和ViewPager搭配使用时, 设置ViewPager滑动监听的方法必须在ViewPagerIndicator中执行!!!!
## 页签详情页开发 ##
- 布局文件
ViewPager + ListView
- 获取网络数据
- 对象封装
/**
* 页签数据封装
*
* @author Kevin
*
*/
public class TabDetailBean {
public int retcode;
public TabDatailData data;
public class TabDatailData {
public String countcommenturl;
public String more;
public ArrayList<News> news;
public String title;
public ArrayList<Topic> topic;
public ArrayList<TopNews> topnews;
@Override
public String toString() {
return "TabDatailData [news=" + news + ", title=" + title
+ ", topnews=" + topnews + "]";
}
}
/**
* 新闻列表
*
* @author Kevin
*
*/
public class News {
public String comment;
public String commentlist;
public String commenturl;
public String id;
public String listimage;
public String pubdate;
public String title;
public String type;
public String url;
@Override
public String toString() {
return "News [title=" + title + "]";
}
}
public class Topic {
public String description;
public String id;
public String listimage;
public String sort;
public String title;
public String url;
}
/**
* 顶部新闻条
*
* @author Kevin
*
*/
public class TopNews {
public String comment;
public String commentlist;
public String commenturl;
public String id;
public String topimage;
public String pubdate;
public String title;
public String type;
public String url;
@Override
public String toString() {
return "TopNews [topimage=" + topimage + ", title=" + title + "]";
}
}
@Override
public String toString() {
return "TabDetailBean [data=" + data + "]";
}
}
- 缓存处理
- 数据展示
- 顶部新闻数据展示(轮播条)
- ViewPager适配器
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView image = new ImageView(mActivity);
image.setScaleType(ScaleType.FIT_XY);//设置图片展现样式为: 宽高填充ImageView(图片可能被拉伸或者缩放)
image.setImageResource(R.drawable.topnews_item_default);
container.addView(image);
utils.display(image, mTopNews.get(position).topimage);//参1表示ImageView对象, 参2表示图片url
return image;
}
- ScaleType简单介绍
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@drawable/image_demo"
/>
- 处理轮播条滑动事件(自定义HorizontalScrollViewPager)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 请求父控件不要拦截触摸事件
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
- 使用BitmapUtils加载网络图片
BitmapUtils utils = new BitmapUtils(mActivity);
utils.configDefaultLoadingImage(R.drawable.topnews_item_default);// 设置加载中的图片
utils.display(image, mTopNews.get(position).topimage);//参1表示ImageView对象, 参2表示图片url
- 添加轮播条位置指示器
查看Demo中的SampleCirclesSnap.java, 仿照该方法添加代码
CirclePageIndicator mIndicator;
mIndicator.setViewPager(mViewPager);
mIndicator.setSnap(true);// 设置圆点的切换方式, 快照方式
- 位置指示器样式修改
<com.viewpagerindicator.CirclePageIndicator
android:id="@+id/cpi_tab_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@android:color/transparent"//背景色
android:padding="5dp"
app:fillColor="#ff0000"//选中后的颜色
app:pageColor="#aaaaaa"//默认颜色
app:radius="3dp"//半径
app:strokeWidth="0dp" //圆环线条的粗细
/>
- 头条新闻标题TextView赋值
- 头条新闻滑动事件细节优化
/**
* 上下滑动时, 父控件拦截事件
*
* 向右滑动, 当前页是第一页时, 父控件拦截事件; 向左滑动, 当前是最后一页时, 父控件拦截事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 请求父控件不要拦截触摸事件
getParent().requestDisallowInterceptTouchEvent(true);
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float endX = ev.getX();
float endY = ev.getY();
float dx = endX - startX;
float dy = endY - startY;
// 判断是会否左右滑动
if (Math.abs(dx) > Math.abs(dy)) {
// 向右滑动
if (dx > 0) {
if (getCurrentItem() == 0) {
getParent().requestDisallowInterceptTouchEvent(false);
}
} else {
if (getCurrentItem() == getAdapter().getCount() - 1) {
getParent().requestDisallowInterceptTouchEvent(false);
}
}
} else {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
- 注意
多切换几个页签后, ViewPager会重新初始化页面,显示第一张图片, 但是CirclePageIndicator默认会保存上次滑动位置, 导致小圆点显示的位置不准确.可以在初始化Indicator时, 增加下面代码:
mIndicator.onPageSelected(0);//设置当前选中页面为第一个, 保证圆点标记位置正确
- 列表新闻展示
- ListView元素布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp" >
<ImageView
android:id="@+id/iv_news_icon"
android:layout_width="100dp"
android:layout_height="60dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:background="#999999"
android:padding="1dp"
android:scaleType="fitXY"
android:src="@drawable/image_demo" />
<TextView
android:id="@+id/tv_news_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginBottom="5dp"
android:layout_toRightOf="@+id/iv_news_icon"
android:ellipsize="end"
android:lines="2"
android:text="饭卡上剪短发了卡死的减肥路口见阿斯顿浪费就爱谁离开发顺丰卡死的卷发山东龙口锋娟啊的"
android:textColor="#000000"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_news_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tv_news_title"
android:layout_below="@id/tv_news_title"
android:text="2015-01-09 16:32"
android:textColor="#999999"
android:textSize="15sp" />
</RelativeLayout>
- ListView滑动出现黑色背景的问题
android:cacheColorHint="#fff"//设置改属性,颜色值为白色
- ListView增加HeaderView, 使顶部新闻和列表新闻成为一个整体
View headerView = View.inflate(mActivity,
R.layout.top_news_header_view, null);
lvList.addHeaderView(headerView);// 增加顶部新闻为HeaderView
- 使用ViewHolder对ListView进行优化
- ListView网络数据填充
- 自定义下拉刷新
- 自定义ListView
- ListView头布局
refresh_listview_header.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="wrap_content"
android:orientation="horizontal" >
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:layout_gravity="center" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/common_listview_headview_red_arrow" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/custom_progressbar"
android:visibility="invisible" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新"
android:textSize="16sp"
android:textColor="#ff0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="最后刷新时间: 1990-09-09 09:09:09" />
</LinearLayout>
</LinearLayout>
- 自定义ProgressBar
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" >
<shape
android:innerRadius="15dp"
android:shape="ring"
android:thickness="3dp"
android:useLevel="false" >
<gradient
android:centerColor="#33ff0000"
android:endColor="#ffffff"
android:startColor="#ff0000"
android:type="sweep" />
</shape>
</rotate>
- 初始化头布局
/**
* 初始化头布局
*/
private void initHeaderView() {
mHeaderView = View.inflate(getContext(),
R.layout.refresh_listview_header, null);
ivArrow = (ImageView) mHeaderView
.findViewById(R.id.iv_pull_list_header);
pbProgress = (ProgressBar) mHeaderView
.findViewById(R.id.pb_pull_list_header);
tvTitle = (TextView) mHeaderView
.findViewById(R.id.tv_pull_list_header_title);
tvTime = (TextView) mHeaderView
.findViewById(R.id.tv_pull_list_header_time);
this.addHeaderView(mHeaderView);
mHeaderView.measure(0, 0);// 测量View
mHeaderHeight = mHeaderView.getMeasuredHeight();// 获取View的高度
LogUtils.d("header height=" + mHeaderHeight);
mHeaderView.setPadding(0, -mHeaderHeight, 0, 0);// 隐藏头布局
initAnimation();
}
- 滑动事件处理
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (startY == -1) {
startY = (int) ev.getY();
}
// 如果当前正在刷新, 不做任何处理
if (mCurrentState == REFRESHING) {
break;
}
int endY = (int) ev.getY();
int dY = endY - startY;// 移动偏移量
int firstVisiblePosition = getFirstVisiblePosition();// 查看第一个显示的item属于第几个
LogUtils.d("firstVisiblePosition=" + firstVisiblePosition);
if (dY > 0 && firstVisiblePosition == 0) {// 向下移动
int paddingTop = dY - mHeaderHeight;
if (paddingTop > 0 && mCurrentState != RELEASE_REFRESH) {// 进入松开刷新的状态
mCurrentState = RELEASE_REFRESH;
refreshHeaderViewState();
} else if (paddingTop < 0 && mCurrentState != PULL_DOWN_REFRESH) {// 进入下拉刷新状态
mCurrentState = PULL_DOWN_REFRESH;
refreshHeaderViewState();
}
mHeaderView.setPadding(0, paddingTop, 0, 0);// 设置头布局padding
return true;
}
break;
case MotionEvent.ACTION_UP:
startY = -1;
if (mCurrentState == RELEASE_REFRESH) {
// 将当前状态更新为正在刷新
mCurrentState = REFRESHING;
mHeaderView.setPadding(0, 0, 0, 0);
refreshHeaderViewState();
} else if (mCurrentState == PULL_DOWN_REFRESH) {
mHeaderView.setPadding(0, -mHeaderHeight, 0, 0);// 隐藏头布局
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
- 下拉刷新的几种状态
public static final int PULL_DOWN_REFRESH = 1;// 下拉刷新
public static final int RELEASE_REFRESH = 2;// 松开刷新
public static final int REFRESHING = 3;// 正在刷新
/**
* 根据当前状态, 更新下拉刷新界面
*/
private void refreshHeaderViewState() {
switch (mCurrentState) {
case PULL_DOWN_REFRESH:
tvTitle.setText("下拉刷新");
ivArrow.setVisibility(View.VISIBLE);
pbProgress.setVisibility(View.INVISIBLE);
ivArrow.startAnimation(animDown);
break;
case RELEASE_REFRESH:
tvTitle.setText("松开刷新");
ivArrow.setVisibility(View.VISIBLE);
pbProgress.setVisibility(View.INVISIBLE);
ivArrow.startAnimation(animUp);
break;
case REFRESHING:
ivArrow.clearAnimation();// 必须清除动画, 否则View.INVISIBLE不起作用
tvTitle.setText("正在刷新...");
ivArrow.setVisibility(View.INVISIBLE);
pbProgress.setVisibility(View.VISIBLE);
if (mListener != null) {
mListener.onRefresh();// 下拉刷新回调
}
break;
default:
break;
}
}
- 下拉刷新箭头旋转动画
/**
* 初始化箭头的旋转动画
*/
private void initAnimation() {
animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animUp.setDuration(200);
animUp.setFillAfter(true);
animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animDown.setDuration(200);
animDown.setFillAfter(true);
}
- 下拉刷新监听
/**
* 下拉刷新的回调接口
* @author Kevin
*
*/
public interface RefreshListener {
public void onRefresh();
}
/**
* 设置下拉刷新监听
*
* @param listener
*/
public void setOnRefreshListener(RefreshListener listener) {
mListener = listener;
}
- 增加下拉刷新时间
/**
* 获取格式化后的当前时间
*/
public String getCurrentTime() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(new Date());
}
- 收起下拉刷新控件
/**
* 当刷新完成后,隐藏下拉刷新控件, 初始化各项数据
*/
public void onRefreshComplete(boolean needUpdateTime) {
mHeaderView.setPadding(0, -mHeaderHeight, 0, 0);
tvTitle.setText("下拉刷新");
ivArrow.setVisibility(View.VISIBLE);
pbProgress.setVisibility(View.INVISIBLE);
if (needUpdateTime) {
tvTime.setText(getCurrentTime());
}
mCurrentState = PULL_DOWN_REFRESH;
}
- 第一次初始化数据时, 显示下拉刷新控件
/**
* 第一次初始化数据时, 显示下拉刷新控件
*/
public void setRefreshing() {
tvTitle.setText("正在刷新...");
ivArrow.setVisibility(View.INVISIBLE);
pbProgress.setVisibility(View.VISIBLE);
mHeaderView.setPadding(0, 0, 0, 0);
}
## 第三天总结 ##
# Day04 #
## 加载更多 ##
- 布局文件
- 初始化脚布局
/**
* 初始化脚布局
*/
private void initFooterView() {
mFooterView = View.inflate(getContext(),
R.layout.refresh_listview_footer, null);
this.addFooterView(mFooterView);
mFooterView.measure(0, 0);// 测量View
mFooterHeight = mFooterView.getMeasuredHeight();
mFooterView.setPadding(0, -mFooterHeight, 0, 0);// 隐藏头布局
setOnScrollListener(this);
}
- 监听是否滑动到底部
/**
* 滑动状态发生变化
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 快速滑动或者静止时
if (scrollState == SCROLL_STATE_IDLE
|| scrollState == SCROLL_STATE_FLING) {
if (getLastVisiblePosition() == getCount() - 1 && !isLoadMore) {
LogUtils.d("到底部了");
isLoadMore = true;
mFooterView.setPadding(0, 0, 0, 0);
setSelection(getCount());// 设置ListView显示位置
if (mListener != null) {
mListener.onLoadMore();
}
}
}
}
- 加载更多回调方法
/**
* 下拉刷新的回调接口
*
* @author Kevin
*
*/
public interface RefreshListener {
/**
* 下拉刷新的回调方法
*/
public void onRefresh();
/**
* 加载更多的回调方法
*/
public void onLoadMore();
}
- 加载完成
/**
* 当刷新完成后,隐藏下拉刷新控件, 初始化各项数据
*/
public void onRefreshComplete(boolean needUpdateTime) {
if (isLoadMore) {
isLoadMore = false;
mFooterView.setPadding(0, -mFooterHeight, 0, 0);// 隐藏脚布局
}
}
- 加载更多数据
/**
* 加载更多数据
*/
private void getMoreDataFromNet() {
if (mMoreUrl != null) {
HttpUtils utils = new HttpUtils();
utils.send(HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
processData(responseInfo.result, true);
lvList.onRefreshComplete(false);// 隐藏下拉刷新控件
}
@Override
public void onFailure(HttpException error, String msg) {
Log.e(TAG, "请求失败:" + msg);
lvList.onRefreshComplete(false);// 隐藏下拉刷新控件
Toast.makeText(mActivity, "加载更多失败", Toast.LENGTH_SHORT)
.show();
}
});
} else {
lvList.onRefreshComplete(false);
Toast.makeText(mActivity, "没有更多数据了", Toast.LENGTH_SHORT).show();
}
}
- 解析更多数据
protected void processData(String result, boolean more) {
Gson gson = new Gson();
TabDetailBean tabInfo = gson.fromJson(result, TabDetailBean.class);
if (!TextUtils.isEmpty(tabInfo.data.more)) {
mMoreUrl = GlobalContants.SERVER_URL + tabInfo.data.more;
} else {
mMoreUrl = null;
}
if (!more) {// 不加载更多
.....
} else {// 加载更多
ArrayList<News> news = tabInfo.data.news;
if (mNewsList != null) {
mNewsList.addAll(news);
mNewsAdapter.notifyDataSetChanged();
}
}
}
## Item点击 ##
- 获取Item点击事件(注意position变化)
/**
* 设置Item点击监听
*/
lvList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Log.d(TAG, "Item点击:" + position);
}
});
----------------------------
OnItemClickListener mItemClickListener;
/**
* 响应Item点击
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(parent, view, position
- getHeaderViewsCount(), id);// 将原始position减去HeaderView的数量,才是准确的position
}
}
/**
* 处理Item点击事件
*/
@Override
public void setOnItemClickListener(
android.widget.AdapterView.OnItemClickListener listener) {
mItemListener = listener;
super.setOnItemClickListener(this);
}
- 标记已读未读
String ids = SharePreferenceUtils.getString(mActivity,
PREF_NEWS_READ, "");
if (!ids.contains(news.id)) {// 只有在不包含该id时才添加
ids = ids + news.id + ",";
SharePreferenceUtils.putString(mActivity, PREF_NEWS_READ, ids);// 更新已读id列表
}
// mNewsAdapter.notifyDataSetChanged();// 刷新ListView
mNewsAdapter.changeTextColor(view);// 局部刷新ListView
-------------------
if (ids.contains(getItem(position).id)) {// 如果再已读列表中
holder.tvTitle.setTextColor(Color.GRAY);
} else {
holder.tvTitle.setTextColor(Color.BLACK);
}
-------------------
/**
* 局部刷新TextView
*
* @param view 被点击的Item对象
*/
public void changeTextColor(View view) {
TextView tvTitle = (TextView) view.findViewById(R.id.tv_news_title);
tvTitle.setTextColor(Color.GRAY);
}
## 新闻详情页 ##
- 页面跳转,传参
- 布局
- 标题(include)
- WebView简介
- 启用js
- 启用缩放
if (!TextUtils.isEmpty(mUrl)) {
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);// 打开js功能
settings.setBuiltInZoomControls(true);// 显示放大缩小的按钮
settings.setUseWideViewPort(true);// 双击缩放
// mWebView.loadUrl("http://www.itcast.cn");
mWebView.setWebViewClient(new WebViewClient() {
// 监听网页加载结束的事件
@Override
public void onPageFinished(WebView view, String url) {
mProgress.setVisibility(View.GONE);
}
});
mWebView.loadUrl(mUrl);
}
- 字体缩放
- 字体选择对话框
/**
* 展示修改字体的对话框
*/
private void showChangeSizeDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
String[] items = new String[] { "超大号字体", "大号字体", "正常字体", "小号字体",
"超小号字体" };
// 设置单选对话框
builder.setSingleChoiceItems(items, mSelectedSizeIndex,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "选中:" + which);
mCurrentSizeIndex = which;
}
});
builder.setTitle("字体设置");// 设置标题
builder.setPositiveButton("确定", new AlertDialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (mCurrentSizeIndex) {
case 0:
mWebView.getSettings().setTextSize(TextSize.LARGEST);// 设置WebView中字体的大小
break;
case 1:
mWebView.getSettings().setTextSize(TextSize.LARGER);
break;
case 2:
mWebView.getSettings().setTextSize(TextSize.NORMAL);
break;
case 3:
mWebView.getSettings().setTextSize(TextSize.SMALLER);
break;
case 4:
mWebView.getSettings().setTextSize(TextSize.SMALLEST);
break;
default:
break;
}
mSelectedSizeIndex = mCurrentSizeIndex;
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
- 分享
- ShareSDK
官网: http://www.mob.com/
ShareSDK是中国最大的APP内分享服务提供商,ShareSDK社会化分享
全面支持微信,微博,QQ空间,来往,易信,Facebook等国内外40个平台
步骤:
1. 下载ShareSDK
2. 运行SDK工具,生成项目包,进行覆盖
3. 查看Android文档
4. 创建应用, 生成appkey,替换项目中的appkey
5. 拷贝相关权限和代码
6. 注意sdcard根目录下需要有test.jpg这张图片,否则会分享失败
7. 运行程序,进行测试
8. 可以修改主题样式
OnekeyShare oks.setTheme(OnekeyShareTheme.SKYBLUE);//设置主题样式
注意:
- 微博测试账号: [email protected], 12345678
- QQ测试账号: 1810189557, itheima
- zhbj48 appkey:5701c42963ec
## 轮播条 ##
- Handler机制
- 轮播条实现
if (mHandler == null) {
// 创建轮播条的Handler
mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
int item = mViewPager.getCurrentItem();
if (item < mTopNews.size() - 1) {
item++;
} else {// 判断是否到达最后一个
item = 0;
}
// Log.d(TAG, "轮播条:" + item);
mViewPager.setCurrentItem(item);
mHandler.sendMessageDelayed(Message.obtain(), 4000);
};
};
mHandler.sendMessageDelayed(Message.obtain(), 4000);// 延时4s发送消息
}
- 按住轮播条,停止轮播
class TopNewsTouchListener implements OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "手指按下");
mHandler.removeCallbacksAndMessages(null);// 移除消息队列中的所有元素
break;
case MotionEvent.ACTION_CANCEL:// 事件取消(比如按下后开始移动,
// 那么就不会响应ACTION_UP动作了)
Log.d(TAG, "事件取消");
mHandler.sendMessageDelayed(Message.obtain(),
TOP_NEWS_CHANGE_TIME);
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "手指抬起");
mHandler.sendMessageDelayed(Message.obtain(),
TOP_NEWS_CHANGE_TIME);
break;
default:
break;
}
return true;
}
}
- 轮播条点击事件处理(OnTouch事件被拦截后,就无法响应点击事件了.)
image.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(mActivity,
NewsDetailActivity.class);
intent.putExtra("news_url", mTopNews.get(position).url);
mActivity.startActivity(intent);
}
});
## 组图 ##
- 布局
ListView + GridView + FrameLayout
- 网络数据获取并解析
- ListView数据填充
- item布局实现
- 数据填充
- 标题栏切换按钮
NewsCenterPager.java
/**
* 设置当前详情页面
*
* @param position
*/
public void setCurrentDetailPager(int position) {
Log.d(TAG, "详情页面:" + position);
BaseMenuDetailPager detailPager = mMenuDetailPagers.get(position);
if (detailPager instanceof PhotosMenuDetailPager) {// 如果是组图页面,就展示组图切换按钮
btnPhotoSwitch.setVisibility(View.VISIBLE);
} else {
btnPhotoSwitch.setVisibility(View.GONE);
}
flContent.removeAllViews();// 填充界面前,先把以前的界面清空
flContent.addView(detailPager.mRootView);// 添加当前详情页的布局
detailPager.initData();// 初始化数据
tvTitle.setText(mLeftMenuList.get(position).title);
}
------------------------------------
PhotosMenuDetailPager.java
/**
* 修改图片展现方式(ListView或者GridView)
*/
private void switchPhotosDisplay() {
if (isListShow) {
isListShow = false;
lvList.setVisibility(View.GONE);
gvList.setVisibility(View.VISIBLE);
gvList.setAdapter(mAdapter);
btnPhotoSwitch.setImageResource(R.drawable.icon_pic_list_type);
} else {
isListShow = true;
lvList.setVisibility(View.VISIBLE);
gvList.setVisibility(View.GONE);
lvList.setAdapter(mAdapter);
btnPhotoSwitch.setImageResource(R.drawable.icon_pic_grid_type);
}
}
# Day05 #
## 三级缓存 ##
- 三级缓存流程分析
1. 内存缓存 速度快, 优先读取
2. 本地缓存 速度其次, 内存没有,读本地
3. 网络缓存 速度最慢, 本地也没有,才访问网络
- 三级缓存工具类
> MyBitmapUtils
/**
* 自定义图片加载工具类
*
* @author Kevin
*
*/
public class MyBitmapUtils {
private NetCacheUtils mNetCacheUtils;
private LocalCacheUtils mLocalCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;
public MyBitmapUtils() {
mMemoryCacheUtils = new MemoryCacheUtils();
mLocalCacheUtils = new LocalCacheUtils();
mNetCacheUtils = new NetCacheUtils(mLocalCacheUtils, mMemoryCacheUtils);
}
/**
* 加载图片的核心api
*
* @param ivPic
* ImageView对象
* @param url
* 图片链接
*/
public void display(ImageView ivPic, String url) {
ivPic.setImageResource(R.drawable.pic_item_list_default);
// 从内存缓存读去图片数据
Bitmap bitmap = mMemoryCacheUtils.getBitmapFromMemory(url);
if (bitmap != null) {// 如果内存存在,就直接获取并返回给listview
ivPic.setImageBitmap(bitmap);
System.out.println("从内存读取图片");
return;
}
// 从本地SD卡读取缓存的图片数据
bitmap = mLocalCacheUtils.getBitmapFromLocal(url);
if (bitmap != null) {// 如果本地文件存在,就直接设置并返回
ivPic.setImageBitmap(bitmap);
System.out.println("从本地读取图片");
mMemoryCacheUtils.putBitmapToMemory(url, bitmap);// 设置内存图片
return;
}
// 从网络 获取下载图片数据
mNetCacheUtils.getBitmapFromNet(ivPic, url);
}
}
- 网络缓存
> NetCacheUtils
/**
* 网络缓存工具类
*
* @author Kevin
*
*/
public class NetCacheUtils {
LocalCacheUtils mLocalCacheUtils;
MemoryCacheUtils mMemoryCacheUtils;
public NetCacheUtils(LocalCacheUtils localCacheUtils,
MemoryCacheUtils memoryCacheUtils) {
mLocalCacheUtils = localCacheUtils;
mMemoryCacheUtils = memoryCacheUtils;
}
public void getBitmapFromNet(ImageView ivPic, String url) {
BitmapTask task = new BitmapTask();
task.execute(new Object[] { ivPic, url });
}
class BitmapTask extends AsyncTask<Object, Void, Bitmap> {
private ImageView imageView;
private String url;
/**
* 返回的对象会自动回传到onPostExecute里面
*/
@Override
protected Bitmap doInBackground(Object... params) {
imageView = (ImageView) params[0];
url = (String) params[1];
imageView.setTag(url);
Bitmap bitmap = downloadBitmap(url);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
// 这里的result就是doInBackground返回回来的对象
if (result != null) {
String ivUrl = (String) imageView.getTag();
if (url.equals(ivUrl)) {// 确保imageview设置的是正确的图片(因为有时候listview有重用机制,多个item会公用一个imageview对象,从而导致图片错乱)
imageView.setImageBitmap(result);
System.out.println("从网络缓存读取图片");
// 向本地保存图片文件
mLocalCacheUtils.putBitmapToLocal(url, result);
// 向内存保存图片对象
mMemoryCacheUtils.putBitmapToMemory(url, result);
}
}
}
}
/**
* 下载图片
*
* @param url
* @return
*/
private Bitmap downloadBitmap(String url) {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestMethod("GET");
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
InputStream inputStream = conn.getInputStream();
//图片压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//表示压缩比例,2表示宽高都压缩为原来的二分之一, 面积为四分之一
options.inPreferredConfig = Config.RGB_565;//设置bitmap的格式,565可以降低内存占用
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.disconnect();
}
return null;
}
}
- 向本地SD卡里缓存存储图片数据
> LocalCacheUtils
/**
* 本地缓存工具类
*
* @author Kevin
*
*/
public class LocalCacheUtils {
private static final String LOCAL_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath() + "/zhbj_cache";
/**
* 从本地读取图片
*
* @param url
* @return
*/
public Bitmap getBitmapFromLocal(String url) {
try {
String fileName = MD5Encoder.encode(url);
File file = new File(LOCAL_PATH, fileName);
if (file.exists()) {
// 图片压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;// 表示压缩比例,2表示宽高都压缩为原来的二分之一, 面积为四分之一
options.inPreferredConfig = Config.RGB_565;// 设置bitmap的格式,565可以降低内存占用
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
file), null, options);
return bitmap;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 向本地存图片
*
* @param url
* @param bitmap
*/
public void putBitmapToLocal(String url, Bitmap bitmap) {
try {
String fileName = MD5Encoder.encode(url);
File file = new File(LOCAL_PATH, fileName);
File parent = file.getParentFile();
// 创建父文件夹
if (!parent.exists()) {
parent.mkdirs();
}
bitmap.compress(CompressFormat.JPEG, 100,
new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 内存缓存
- 引用级别
- 强引用 默认引用, 即使内存溢出,也不会回收
- 软引用 SoftReference, 内存不够时, 会考虑回收
- 弱引用 WeakReference 内存不够时, 更会考虑回收
- 虚引用 PhantomReference 内存不够时, 最优先考虑回收!
//用法举例
Bitmap bitmap = new Bitmap();
SoftReference<Bitmap> sBitmap = new SoftReference<Bitmap>(bitmap);
Bitmap bitmap2 = sBitmap.get();
- 这些避免内存溢出的引用方式在Android 2.3+的版本上已经不再起太大作用, 因为垃圾回收器会频繁回收非强引用的对象, Android官方建议使用LRUCache. 相关链接: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
- LRUCache
可以自动控制内存大小, 及时回收不常用的对象, 用法和HashMap类似
> MemoryCacheUtils
/**
* 内存缓存工具类
*
* @author Kevin
*
*/
public class MemoryCacheUtils {
// HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<String,
// SoftReference<Bitmap>>();
LruCache<String, Bitmap> mMemoryCache;
public MemoryCacheUtils() {
int maxMemory = (int) Runtime.getRuntime().maxMemory();// 当前手机分配给app进程的最大内存,虚拟机默认16M
System.out.println("maxMemory:" + maxMemory);
mMemoryCache = new LruCache<String, Bitmap>(maxMemory / 8) {
@Override
protected int sizeOf(String key, Bitmap value) {
int size = value.getRowBytes() * value.getHeight();// 返回bitmap占用的内存大小
System.out.println("sizeof:" + size);
return size;
}
};
}
/**
* 从内存读取图片
*
* @param url
* @return
*/
public Bitmap getBitmapFromMemory(String url) {
// SoftReference<Bitmap> softBitmap = mMemoryCache.get(url);
// System.out.println("读取内存图片。。。" + softBitmap);
// if (softBitmap != null) {
// Bitmap bitmap = softBitmap.get();
// System.out.println("读取内存图片成功。。。" + bitmap);
// return bitmap;
// }
Bitmap bitmap = mMemoryCache.get(url);
return bitmap;
}
/**
* 向内存存图片
*
* @param url
* @param bitmap
*/
public void putBitmapToMemory(String url, Bitmap bitmap) {
// System.out.println("设置内存图片。。。");
// SoftReference<Bitmap> softBitmap = new
// SoftReference<Bitmap>(bitmap);// 通过软引用对对象包装
// mMemoryCache.put(url, softBitmap);
mMemoryCache.put(url, bitmap);
}
}
## 屏幕适配 ##
- 图片适配
- 开启4种分辨率的模拟器
- 在drawable的多个目录下放置内容不同但命名相同的图片
- 运行程序,查看在不同模拟器上的显示效果
- 常规做法: 美工只做一套1280*720的图片,放置在drawable-xhdpi的目录下, ImageView宽高指定为确定的值, 不包裹屏幕
- 布局适配
- 针对特定分辨率,创建layout文件夹: layout-800x480, layout-land(表示横屏)
- 800x480 和其他分辨率模拟器对比
- 常规做法: 该方式不到万不得已,一般不用
- 尺寸(dimens)适配
- 设备密度:
float density = getResources().getDisplayMetrics().density;
- dp = px / 设备密度
- 常规设备密度: 320x240(0.75), 480x320(1), 800x480(1.5), 1280x720(2)
- 设置dp值, 在不同屏幕上查看显示的比例
- 创建文件夹values-1280x720, 在dimens.xml中制定尺寸, 适配屏幕
- 常规做法: 此方法比布局适配更常用. 美工提供像素px值, 我们使用前需要用px除以设备密度,转换成dp后,写在布局文件中
- 案例分析: 智慧北京新手引导页小圆点处理
- 权重适配
android:weightSum="3" //表示总权重数
常规做法: 当布局有严格比例分配时, 可以使用权重来处理
- 代码适配
int width = getWindowManager().getDefaultDisplay().getWidth();
int height = getWindowManager().getDefaultDisplay().getHeight();
tv1.setLayoutParams(new LayoutParams((int)(width*0.5), (int)(height*0.2)));
常规做法: 如果是自定义的控件, 没有使用xml布局文件时, 可以在代码中动态设置宽高
案例分析: 智慧北京侧边栏宽度处理
## 消息推送 ##
- 极光推送
官方地址: https://www.jpush.cn/
- 创建应用,获取appkey
- 按照文档流程, 写推送例子程序
1. 下载sdk
2. 拷贝sdk中的AndroidManifest.xml文件, 修改相应内容
3. 自定义MyReceiver
4. 后台发送消息, 演示效果
5. 查看高级功能, 在MyReceiver中打印获取的信息及附加信息
6. 注意: 极光推送后台最多只能创建3个应用, 所以演示前需要确保已存在应用少于3个
- 下载Demo并演示
- 推送原理介绍
- XMPP协议
一种基于TCP/IP的应用层协议, 专门用于消息推送, 数据格式为xml
- 长连接
使用socket请求时, 服务端和客户端都不主动关闭输入输出流, 从而实现长连接
- 心跳包
客户端每隔一段时间(比如1分钟)向服务器发送一段极短的数据,称为心跳包. 服务器收到数据, 就证明客户端还活着, 就会保持连接,向客户端推送消息. 否则断开连接,节省服务器性能. 客户端重连后,服务器将未发送成功的消息重新发出.
## 语音识别 ##
科大讯飞语音云
http://open.voicecloud.cn/
下载SDK, 阅读SDK文档(MSC开发手册), 按照文档,实现如下三个功能
- 语音识别
- 语音识别弹窗
- 语音朗诵
# Day06 #
## 聊天机器人 ##
- 项目演示
- 美女
- 你好
- 天王盖地虎
- 你是谁
- 布局搭建
- ListView + LinearLayout
- 语音识别
- 参照科大讯飞sdk相关文档
/**
* 开始语音识别
*
* @param view
*/
public void startVoice(View view) {
RecognizerDialog iatDialog = new RecognizerDialog(this, mInitListener);
iatDialog.setParameter(SpeechConstant.DOMAIN, "iat");
iatDialog.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
iatDialog.setParameter(SpeechConstant.ACCENT, "mandarin ");
iatDialog.setListener(recognizerDialogListener);
iatDialog.show();
}
- 数据解析
/**
* 解析语音json数据
*/
private String parseJson(String json) {
Gson gson = new Gson();
VoiceBean bean = gson.fromJson(json, VoiceBean.class);
ArrayList<WsBean> ws = bean.ws;
StringBuffer sb = new StringBuffer();
for (WsBean wsBean : ws) {
ArrayList<CwBean> cw = wsBean.cw;
for (CwBean cwBean : cw) {
String w = cwBean.w;
sb.append(w);
}
}
System.out.println("解析数据为:" + sb.toString());
return sb.toString();
}
- ListView数据展示
- item布局处理(提问+回答)
- 语音朗诵
/**
* 机器人讲话
*/
private void speak(String answer) {
// 1.创建SpeechSynthesizer对象, 第二个参数:本地合成时传InitListener
SpeechSynthesizer mTts = SpeechSynthesizer
.createSynthesizer(this, null);
// 2.合成参数设置,详见《科大讯飞MSC API手册(Android)》SpeechSynthesizer 类
mTts.setParameter(SpeechConstant.VOICE_NAME, "vixying");// 设置发音人
mTts.setParameter(SpeechConstant.SPEED, "50");// 设置语速
mTts.setParameter(SpeechConstant.VOLUME, "80");// 设置音量,范围0~100
// 设置合成音频保存位置(可自定义保存位置),保存在“./sdcard/iflytek.pcm”
// 保存在SD卡需要在AndroidManifest.xml添加写SD卡权限
// 如果不需要保存合成音频,注释该行代码
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, "./sdcard/iflytek.pcm");
// 3.开始合成
mTts.startSpeaking(answer, null);
}