Day01 01.项目介绍 ##
智慧北京:互联网新闻客户端,类似于市面上的网易新闻
重点实现了新闻中心功能
-闪屏页(欢迎页):
animation 项目刚进来时旋转的页面
-新手引导页:
viewPager ViewPagerIndicator(小红点) activity跳转
闪屏页->新手引导页,用于介绍应用都有哪些功能,可以滑动,最后点击“开始体验”->进入主页面
-主页面:
pagerTab ViewPager
五个标签,只能点击(首页 新闻中心 智慧服务 政务 设置)
-首页:
新闻中心: 切换到新闻中心会加载相应新闻,上边导航条对新闻分类:北京 中国 国际 体育 生活 旅游 (导航条可以滑动切换,网易新闻就有这样一个导航条)
北京:切换到北京,上边有轮播条,轮播头条新闻
下边是新闻列表,点击某个item进入新闻详情页面
新闻列表:有下拉刷新,“加载更多”
新闻详情页面:底部点击可展开全文
右上角点击可设置字体大小
右上角还有分享,可把对应新闻分享出去
分享页面:有多个渠道,新浪微博,QQ空间,微信好友,微信朋友圈,QQ,邮件,信息,有道云笔记,印象笔记
智慧服务:没有实现 >政务:没有实现 >设置:没有实现
-侧边栏:
点击左上角按钮或往屏幕右侧滑动会拉出侧边栏,里面有: 新闻 专题 组图 互动
新闻:点击新闻进入详情页:新闻 即新闻中心
专题:
点击专题进入详情页:专题
组图:
点击组图进入详情页:组图,组图可直接查看大图片,点击右上角可切换展示方式:listview gridView
互动:
点击互动进入详情页:互动
主要实现了: 整体框架搭建 + 新闻中心,剩余模块都没实现,新闻中心最复杂,剩余模块可拷贝新闻中心
Day01 02.公司角色介绍 ##
智慧北京非常接近实际开发项目,实际开发中,会有很多复杂逻辑,比如调网络接口,这些数据都是从服务器返回给我们解析的
手机卫士几乎和服务器没有任何交互,更像一个工具,只是在本地玩,不太像主流软件,市面上主流软件90%以上都要和服务器交互
公司角色 ##
人事,财务,行政助理,老总,产品经理,技术支持,开发人员,测试,售前,销售经理,销售
开发人员–程序员–工程师
Android开发工程师 >服务器开发人员 java(javaWeb方向) .net php 将接口(链接)给我们,调接口返回json串解析 接口文档:它会有几十个接口,详细记录每个接口 比如登陆,把用户名,密码传给服务器,对接口文档有疑问,随时和服务器人员沟通 >ios开发工程师
产品经理:(产品狗,产品汪)
负责提需求 一般选择女生比较好
项目经理:
开发人员主管,是比较资深的开发人员,下边管一堆小开发人员
UI设计师、视觉设计师(美工): 学平面设计的
效果图:在手机上开发之前的效果图
标注图:把文字大小,背景,文字,图片,颜色,全都标记出来,用于严格要求一款产品,基于主流屏幕1280*720(设备密度是2)
将px转化成dp
实际开发中,用的是MarginTop,所以应该把像素转化为dp
dp=px/设备密度 每个屏幕设备密度都不一样
比如:主流屏幕1280*720,距离顶部是72px,那72/2=36dp 写marginTop时就写36dp 这样屏幕适配才会好
UI:Android手机那么多,到底应该基于哪个屏幕设计?
主流屏幕1280*720
切图:drawable下边的图片,从效果图上一刀一刀割下来的图片
美工先把效果图画出来,给产品经理和老板过目,然后开始切图给开发者去用
测试
开发完成后,将apk发给测试人员找bug,写测试报告
市面上很多还是拿着不太高的工资,在北京拿着4,5千工资,让他人肉去点
目前高级测试人员还不是很多,自动化测试,搞一个工具去跑,这个不多
高级测试人员,会点代码,拿的工资有时不低于开发人员
运营
产品完成后需要推广,打广告,写软文,把apk发布到应用市场
就像销售要把它卖出去让很多人用
软文:朋友圈文章,通过标题把你吸引进去,前边是动人的故事,最后关联到产品上,经常由运营人员去写
Day01 03.开发流程介绍##
android开发组长需要了解的研发流程
研发流程 ##
1.需求分析会议
大产品经理召集小产品经理,根据以往经验和客户心理讨论做哪些模块,将大模块分为很多子模块,形成需求文档,设计产品原型图
需求文档,有时会用产品原型图替代
产品原型图:类似于需求文档,功能丰富,界面粗糙,可点击交互,这个网页是用工具去生成
项目经理根据产品原型图,把项目分解成不同功能模块,分模块评估工作量,制定出计划排期,每人每天完成哪些功能,确定完成时间点
然后与开发人员技术选型,用燃尽图分配任务,开发人员按照开发规范开发,让美工按产品原型图按1280*720屏幕画效果图并切图
2.需求评估:
产品经理拿产品原型图给项目经理,评估做完项目需要多少人,多少时间
一般android项目都是3个月左右,快点的是1-2个月,没有超过半年的项目
android开发人员不会很多,即使是在大公司,一般都是3个,很少有超过5个
所以要你评估时间就说2,3个月,人数3个
产品经理确定上线时间,如果需求评估是8月到10月开发完,还要考虑测试时间,测试一般需要1-2周,所以上线时间一般是10月中旬
3.需求分配:项目经理按功能模块,分配不同人员开发
4.项目开发:
开发阶段,项目经理对交付时间负责,随时督促和调整手下人员完成进度
分配多少人去开发,项目经理用燃尽图把控进度,比如张三全部开发量是一个柱状,每天按照完成量把柱子往下调一些,直到彻底变成了空空的柱状
5.项目测试:
monkey压力测试,花1-2周多轮测试,回归测试,使项目达到上线标准
Bugzilla, jira等在线bug提交平台
6.产品上线:
开发人员使用正式签名打包,让运营人员提交到各大应用市场:豌豆荚, 91市场, 应用宝, GooglePlay
7.迭代开发: 第一版上线后,添加新模块功能,继续开发第二版,第三版,形成项目迭代
项目迭代也需要测试,时间比较短,比如3,4天或一周
一般app迭代周期20天-1个月,用2周开发,2周测试
外包公司项目做完,收完钱,就会做下一个新项目而不是迭代开发
快速迭代:有些公司需要快速开发,2周一迭代,时间把控上尽量缩短开发测试时间,测试可能就测2天
快速迭代为了在用户面前刷存在感,期待感,但微信迭代比较长,一般2-3个月才发一个版本
这些流程,实际开发中有些是会省略掉
(程序员其实有一个好处就是想走就走,来一场说走就走的旅行,这个行业搞技术的人永远不用愁没有饭吃,在北京,上海,广州,都可以上班,在家都可以上班,不怕得罪领导,大不了辞职了工资更高,一般IT行业的跳糟率是一年一跳糟,干一年,发个年终奖,第二年就跳糟加薪,8k,10k,13k,15k,18k,不跳槽,指着老板加工资,得自己主动向老板提,否则不会主动给你涨,主动提1年也就涨1千,一般都是2,3年合同,但是可以随便离职,不会有任何违约金,在试用期内,我说了走之后你必须3天之内放我走,转正之后我说了走1个月之内必须放我走,不放我走是违法的,这1个月是用来交接,不像公务员,领导看你不顺眼,天天给你穿小鞋,你不想干想辞职都不敢,因为你好不容易才考个公务员,甚至是搞关系才能进去,家里人都不会同意辞职的)
SVN环境搭建 ##
Day01 04闪屏页开发(欢迎页) ##
补间动画效果: 动画分为:帧动画,补间动画,属性动画
补间动画:只需要定义开始和结束的“关键帧”,其他过渡帧由系统自动计算并补齐,Android中提供了4种补间动画:
RotateAnimation 旋转动画
ScaleAnimation 缩放动画
AlphaAnimation 渐变动画
TranslateAnimation 平移动画
AnimationSet 动画集合
闪屏页面是一个补间动画集合(旋转,缩放(从小到大),渐变(越来越明显))
闪屏页是红色背景上边是一个马:
activity_splash.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/splash_bg_newyear" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/splash_horse_newyear" />
</RelativeLayout>
闪屏页面需要一个整体布局的动画,所以给根布局RelativeLayout加id为rl_root
运行程序,发现闪屏页有个状态栏和标题栏,实际上闪屏页是全屏:
在清单文件中给闪屏页设置主题
给application也就是整个activity设置过主题,也可以给单个activity设置主题
给SplashActivity设置主题:
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
0.SplashActivity的onCreate写一个initViews并创建,在initViews()中初始化控件:
private void initViews() {
RelativeLayout rlRoot = (RelativeLayout) findViewById(R.id.rl_root);
}
1.初始化旋转动画RotateAnimation
RotateAnimation animRotate = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
参数:
fromDegrees: 0 toDegrees:360 从0度到360度大旋转
pivotXType和pivotYType: 类型是Animation.RELATIVE_TO_SELF 从中心往外旋转
pivotXValue和pivotYValue: 值是0.5f,居中,从中间开始旋转(0代表左上角,1代表右下角,0.5就代表对角线上的中间位置)
给渐变动画设置时间setDuration: 1000毫秒 animRotate.setDuration(1000);
2.初始化缩放动画ScaleAnimation
ScaleAnimation animScale = new ScaleAnimation(0, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
参数:fromX,fromY:0 toX,toY:1 从无到有
pivotXType和pivotYType:类型是Animation.RELATIVE_TO_SELF
即:从中心往外缩放
pivotXValue和pivotYValue:值是0.5f,居中,从中间开始缩放
给缩放动画设置时间setDuration:1000毫秒 animScale.setDuration(1000);
3.初始化渐变动画AlphaAnimation对象
AlphaAnimation animAlpha = new AlphaAnimation(0, 1);
参数为: fromAlpha: 0 toAlpha: 1 从哪到哪,从0到1 都是相对值
给渐变动画设置时间setDuration:2000毫秒
animScale.setDuration(2000); 基于在跑起来后,还要多额外展示一秒钟
4.三个动画要同时运行,初始化动画集合AnimationSet:
AnimationSet set = new AnimationSet(true);
参数:
参数shareInterpolator:是否要共用插补器,默认一般为true
关于shareInterpolator属性,设置为true或者false好像效果都一样,动画渲染器不细致的观察确实很难看出效果上的不同
addAnimation将三个动画分别添加进来:
set.addAnimation(animRotate);
set.addAnimation(animScale);
set.addAnimation(animAlpha);
最后用rlRoot布局开启set动画
rlRoot.startAnimation(set);
5.在onCreate中调用initViews
6.闪屏页面结束后跳页面,怎么判断动画结束
隔2秒再跳页面,可以监听动画set结束事件,setAnimationlistener: 监听动画状态
这是一个动画回调监听,当动画结束后,会走到onAnimationEnd中跳页面
set.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
});
7.要跳什么页面?
第一次进来跳新手引导页面
怎么知道是第一次进来?
可通过sp,从它里面取出状态
(sp写起来很烦,抽成工具类PrefUtils)
在onAnimationEnd中用PrefUtils看有没有跳过去:
@Override
public void onAnimationEnd(Animation animation) {
boolean isGuideShow = PrefUtils.getBoolean(
getApplicationContext(), "is_guide_show", false);
}
});
通过PrefUtils去getBoolean拿到一个布尔值:
PrefUtils.getBoolean(ctx,key,defValue);
ctx:这里为getApplicationContext()
key:is_guide_show 即:新手引导页有没有展现过
defValue:默认新手引导页是展现还是没展现过,那肯定是没有,即false
返回的是一个boolean值,叫做isGuideShow
8.选择跳什么页面
if (!isGuideShow) {
// 第一次进入,要跳新手引导页面
startActivity(new Intent(getApplicationContext(),
GuideActivity.class));
} else {
// 跳主页面
startActivity(new Intent(getApplicationContext(),
MainActivity.class));
}
finish();
没有展现要跳新手引导页,如果isGuideShow等于false,跳到新手引导页面,否则跳主页面,最后将当前闪屏页面结束掉:finish();
SplashActivity.java
/**
-
闪屏页面
*/
public class SplashActivity extends Activity {@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); initViews(); } // 初始化闪屏页面的动画 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); // 监听动画状态 set.setAnimationListener(new AnimationListener() { // 动画开始 @Override public void onAnimationStart(Animation animation) { // 开始执行动画的时候,回调这个方法 } // 动画重复 @Override public void onAnimationRepeat(Animation animation) { } // 动画结束的回调 @Override public void onAnimationEnd(Animation animation) { boolean isGuideShow = PrefUtils.getBoolean( getApplicationContext(), "is_guide_show", false); if (!isGuideShow) { // 第一次进入,要跳新手引导页面 startActivity(new Intent(getApplicationContext(), GuideActivity.class)); } else { // 跳主页面 startActivity(new Intent(getApplicationContext(), MainActivity.class)); } //最后,要将当前的闪屏页面结束掉 finish(); } }); // 启动动画 rlRoot.startAnimation(set); } }
SharePreference工具类实现
PrefUtils工具类的写法:
1.
public static void putBoolean(Context ctx, String key, boolean value) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
sp.edit().putBoolean(key, value).commit();
}
1)写一个方法putBoolean: 意思是设置一个布尔值变量
2)在方法中首先通过context拿一个SharedPreferences
所以首先在方法参数中传一个context对象ctx
getSharedPreferences参数:
name:就叫做config : "config"
mode:Context.MODE_PRIVATE :私有的,只有自己可以访问
它会返回一个SharedPreferences,把它叫做sp
3)sp.edit().putBoolean(key, value) 传一个key和value
所以首先要在putBoolean参数中传一个String key, boolean value
putBoolean(key, value)后,需要commit:
sp.edit().putBoolean(key, value).commit();
设置一个布尔值变量就搞定了,以后只需要调PrefUtils.putBoolean,key和value传进来,它自己就去写好了,不用再去声明SharedPreferences变量了,这是put
2.
public static boolean getBoolean(Context ctx, String key, boolean defValue) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
return sp.getBoolean(key, defValue);
}
1)有put还应该有个get,供使用者调用,再写一个getBoolean,意思是获取一个布尔值变量
2)在方法中首先拿一个SharedPreferences
通过context去getSharedPreferences
所以首先要在方法参数中传一个context对象ctx
参数:
name:就叫做config : "config"
mode:Context.MODE_PRIVATE :私有的,只有自己可以访问
它会返回一个sp,把它叫做sp
3)通过sp去getBoolean
sp.getBoolean(key, defValue); 即还需要将key传过来,它还有一个默认值defValue,默认值也让开发者自己去指定,所以在参数中也需要传过来 :boolean defValue
getBoolean完了之后将它return回去,返回值是boolean类型 :
return sp.getBoolean(key, defValue);
Boolean值搞定了,除了Boolean值,还有个String也可以搞
3.
public static void putString(Context ctx, String key, String value) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
sp.edit().putString(key, value).commit();
}
复制putBoolean方法
将方法名改为putString
参数boolean value改为String value
putBoolean(key, value)改为putString(key, value)
4.
public static String getString(Context ctx, String key, String defValue) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
return sp.getString(key, defValue);
}
复制getBoolean方法
将方法名改为getString
参数boolean defValue改为String defValue
sp.getBoolean(key, defValue)改为sp.getString(key, defValue);
返回值类型boolean改为String
5.
public static void putInt(Context ctx, String key, int value) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
sp.edit().putInt(key, value).commit();
}
public static int getInt(Context ctx, String key, int defValue) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
return sp.getInt(key, defValue);
}
有时候也会遇到整数int情况,同理如上复制修改即可
------------------------------------------------------
替换app图标:
创建的项目运行后,android自带app图标太难看,换成“智慧西安”图标
方法1:
将“智慧西安”图标名称改为ic_launcher.png 并替换原ic_launcher.png即可
方法2:
将“智慧西安”图标拷贝到/zhxa/res/drawable-hdpi下
打开清单文件并将原application下的android:icon="@drawable/ic_launcher"
改为 android:icon="@drawable/icon_xian"
修改app名称:
application下的android:label="@string/app_name"是app图标下边的名称,可以在此修改
注意:修改图片之后,有时显示的还是android自带的app图标,此时可以clean项目或删除gen或bin,将模拟器上的项目删除,重新运行项目即可
------------------------------------------------------
创建页面并分别注册:
现在应该有这样一个新手引导页面GuideActivity和主页面MainActivity,分别创建这两个activity,并在清单文件中分别注册
<activity android:name=".MainActivity" />
<activity android:name=".GuideActivity" />
并且需要两个布局文件activity_guide.xml和activiy_main.xml
Day01 05.新手引导UI结构搭建 ##
智慧北京数据先清掉(将sp中的数据清掉),才能看到新手引导页面,因为第一次进来才会跳新手引导页面,否则不会展现新手引导页面
1.去除标题
新手引导页,主页面,都没有标题栏,做的时候把标题栏去掉
方法1:清单文件中
给application也就是整个activity设置过主题,也可以给单个activity设置主题:
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
方法2:代码中
需要在onCreate的setContentView上边添加代码:
requestWindowFeature(Window.FEATURE_NO_TITLE);
选择在代码中去掉标题栏:
在GuideActivity,MainActivity所在位置增加:
requestWindowFeature(Window.FEATURE_NO_TITLE);
2.新手引导页布局页面:
ViewPager + 开始体验Button + LinearLayout(位置指示器)
新手引导页用ViewPager实现,以前做的很窄,现在是全屏
这个ViewPager有个小特点
1)上边压了小圆点,小圆点也画过,只不过稍微有点区别,因为这个小控件,滑动时会根据手指头动
其实是一个水平方向的LinearLayout,并且需要动态填充三个小圆点
2)ViewPager最后一页还有一个开始体验Button
就照着最复杂的最后一页把布局先写出来
在Android Private Libraries下的android-support-v4.jar下的
android.support.v4.view下拷贝ViewPager.class的全路径
android.support.v4.view.ViewPager放在activity_guide.xml下
1.上边activity_guide.xml布局写完后,就可以到GuideActivity中去findViewById了
返回一个ViewPager叫做mViewPager
2.ViewPager需要一个Adapter,写一个Adapter叫做GuideAdapter
mViewPager = (ViewPager) findViewById(R.id.vp_guide);
mViewPager.setAdapter(new GuideAdapter());
给ViewPager设置Adapter
一:GuideAdapter中getCount是图片有多少个就返回多少个,新手引导页是三张图片
1.将三张图片放在drawable-hdpi文件下
2.GuideActivity成员变量处写一个数组mImageIds把3张图片维护起来
//写一个数组,把这三个图片放进去
private int[] mImageIds = new int[] { R.drawable.guide_1,
R.drawable.guide_2, R.drawable.guide_3 };
3.GuideAdapter的getCount中返回数组长度:
return mImageIds.length;
二:GuideAdapter中isViewFromObject中一般都是写
return arg0 == arg1;
三:GuideAdapter中destroyItem中一般都是写
container.removeView((View) object);
四:GuideAdapter中instantiateItem初始化ViewPager的item,即三个大ImageView,也就是引导页三个页面
1.写一个ImageView,叫做view,参数getApplicationContext
ImageView view = new ImageView(getApplicationContext());
2.给ImageView去setImageResouce不一定能填充父窗体,但Background肯定可以填充(setBackgroundResource),图片是从mImageIds数组取,第几个用position表示:
view.setBackgroundResource(mImageIds[position]);
3.最后把这个view塞给container
container.addView(view);
4.最后将view返回回去
return view;
五.adapter写完后给mViewPager设置adapter
mViewPager.setAdapter(new GuideAdapter());
运行程序,可以滑动,但开始体验按钮在每个页面都出现,这样把页面主体结构就搭建出来了
Day01 06.页面指示器(小圆点)开发 ##
1.使用shape绘制选中和不选中的圆形
/zhxa02/res/drawable
shape_point_red.xml 小红点布局
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:height="10dp"
android:width="10dp" />
<solid android:color="#f00" />
</shape>
shape_point_gray.xml 小灰点布局
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<size
android:height="10dp"
android:width="10dp" />
<solid android:color="#cccccc" />
</shape>
shape 形状,oval 圆角,椭圆
size 大小 solid 颜色
红色 #f00,黑色 #cccccc
2.根据新手引导页数量动态添加3个灰色圆点
guideActivty中初始化三个小灰点的控件LinearLayout并声明成成员变量
private LinearLayout llContainer;
llContainer = (LinearLayout) findViewById(R.id.ll_container);
在GuideA ctivity的initData的for循环中new一个ImageView,取名point
给point通过setImageResource设置灰点
加左边距需要布局参数LayoutParams,它是线性布局参数,因为它爹就是线性布局
if判断外边,将params设置给ImageView
将ImageView小圆点添加给容器
for (int i = 0; i < mImageIds.length; i++) {
ImageView point = new ImageView(this);//写一个ImageView对象
point.setImageResource(R.drawable.shape_point_gray);
// 从第二个圆点开始设置左边距
if (i > 0) {
// params.leftMargin = 10;将dp转成像素,不转也可以
params.leftMargin = DensityUtils.dp2px(10, this);
}
//布局参数:是一个线性布局的参数,宽高包裹内容,即图片形状有多大就显示多大
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
point.setLayoutParams(params);
llContainer.addView(point);
}
不能将LayoutParams放在for循环外作为全局变量
for循环中相当于每次都new了一个LinearLayout.LayoutParams
每个LayoutParams就是每个ImageView(point),三个ImageView必须有三个ImageView对象
不能共用一个,每个都应该有独立的LayoutParams塞给ImageView(point)
point.setLayoutParams(params);
3.添加红色圆点
在activity_guide.xml中,3个小灰点布局是id为ll_container的LinearLayout
用RelativeLayout把小灰点LinearLayout包起来保证能压在一起,RelativeLayout中再写一个id为iv_red_point的ImageView就是小红点布局
三个灰点ImageView是动态new出来的,小红点ImageView是在布局文件中写好的
<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_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
</LinearLayout>
<ImageView
android:id="@+id/iv_red_point"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/shape_guide_point_selected" />
</RelativeLayout>
初始化id为iv_red_point的小红点,并声明成成员变量
private ImageView ivRedPoint;// 小红点
ivRedPoint = (ImageView) findViewById(R.id.iv_red_point);
运行项目,小灰点已在代码中动态绘制出来,小红点刚好压在第一个小灰点上
4.计算两个圆点之间的距离
两个圆点距离指第0个小圆点左侧到第1个小圆点左侧,不是两个小圆点中间间距
用第1个小圆点左边距 - 第0个小圆点左边距 ,实际上第0个小圆点left值为0
//两个圆点之间距离
private int mPointWidth;
// 计算两个圆点距离
mPointWidth = llContainer.getChildAt(1).getLeft()- llContainer.getChildAt(0).getLeft();
System.out.println("圆点距离:" + mPointWidth);
抽取成成员变量,运行项目,查看日志,发现圆点距离是0,两个圆点left值全都是0
布局流程:
mearsure --> layout --> draw
不能在onCreate中直接调用获取位置layout,measure都没调,调了measure后才能获取位置Layout,有了位置Layout才能获取getWidth,getHeight,还有这两个left值,draw在OnCreate后才开始绘制
手机卫士初始化归属地位置其实不能直接调用修改布局参数的方式实现
应该监听layout执行结束事件后再去getChildAt(1)获取位置信息,计算圆点间距
用小红点ivRedPoint获取视图树监听getViewTreeObserver并添加全局监听addOnGlobalLayoutListener
设置layout全局监听,layout结束后会回调onGlobalLayout,相当于布局位置确定后再计算圆点距离
ivRedPoint.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
// layout方法执行结束之后,回调此方法
@Override
public void onGlobalLayout() {
// 取消视图树监听
ivRedPoint.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// 计算两个圆点的距离
mPointWidth = llContainer.getChildAt(1).getLeft()- llContainer.getChildAt(0).getLeft();
System.out.println("圆点距离:" + mPointWidth);
}
});
运行项目,日志中打印出圆点距离20,打印了3次
onGlobalLayout有时会被系统回调好多次,性能会浪费几毫秒,还是让它调一次比较好
可以在onGlobalLayout中调用removeGlobalOnLayoutListener移除此监听,让它保证只调一次
因为已经进来onGlobalLayout中没有必要再去监听了,所以删除掉视图树自己即可
removeGlobalOnLayoutListener()和removeOnGlobalLayoutListener()区别
都可remove掉OnGlobalLayoutListener对象
1)removeOnGlobalLayoutListener();和对象名字一样,比较顺口
可能会报错,版本兼容问题,原因是要求的Api是16,现在最低兼容的是8
2)removeGlobalOnLayoutListener();被废弃了,开发者最初写兼容时不小心写成了GlobalOn,API16后加横杠废弃了,但是OnGlobal要版本兼容,为了兼容更多版本还是用过时的GlobalOn
实际开发中明确知道距离比如是10像素,那你可以写死,通过计算去获取距离可以实现屏幕适配,这样计算更精准
因为安卓机型多,shape_point_red.xml和shape_point_gray.xml中size的height和width是10dp,但不一定是10像素
320480屏幕设备密度1,把dp转成px,乘以1就能拿到10像素,但1280720上,10dp相当于20px,所以在不同屏幕上说不准到底是多少像素,这样计算就更精准一些
视图树:
比如说你要画一个东西, 根布局是相对布局,相对布局中还有几个线性布局或其他布局,线性布局中又有几个ImageView,TextView,一层一层越来大,有点像倒着的树结构,这就是视图树
查看视图树的工具:
android中sdk的tools工具中有一个hierarchyviewer.bat工具 hierarchy就是层级
双击打开,选要看哪个页面视图,选中GuideActivity,点击Load View Hierarchy(加载视图树结构)
想知道一个项目布局结构可以用hierarchyviewer.bat工具查看,通过它不用看代码,就能知道层级结构
5.ViewPager监听滑动事件, 动态设置红点位置
上边计算出两个原点距离mPointWidth后,给mViewPager去setOnPageChangeListener监听页面滑动事件
距离mPointWidth * 偏移量positionOffset = 圆点左边距leftMargin
小红点默认左边距是0,滑动时,leftMargin越来越大,实际上是在改它的leftMargin,把它强转成整数
给小红点ivRedPoint通过调用布局参数getLayoutParams的方式设置左边距leftMargin,都是通过布局参数去设置左边距
小红点在activity_guide.xm中写的,它本身有布局参数,它布局参数的爹是RelativeLayout
这个地方没必要去new,因为在activity_guide.xml布局文件中小红点本身有它的布局参数,基于原来布局参数去修改,在代码中不用new,直接getLayoutParams获取即可
//页面滑动事件
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
//某个页面被选中后才回调
@Override
public void onPageSelected(int position) {
}
/**
-
页面滑动监听
-
@params position 当前选中的位置
-
@params positionOffset 偏移百分比
-
@params positionOffsetPixels 页面偏移长度
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {int leftMargin = (int) (mPointWidth * (positionOffset + position));//小红点随手指移动时不断获取左边距 // System.out.println("当前位置:" + position + ";偏移量:" + positionOffset); RelativeLayout.LayoutParams lp = (LayoutParams)viewRedPoint.getLayoutParams();//重新修改布局参数 lp.leftMargin = leftMargin;//动态修改小红点的左边距 viewRedPoint.setLayoutParams(lp);//将params动态设置给小红点ivRedPoint } //页面滑动状态改变之后回调 @Override public void onPageScrollStateChanged(int state) { } }); 运行程序,查看效果 防止小红点滑动完后归零 这个不是动画而是在不断改布局参数,滑动松开手后小红点又回去是因为偏移量变成了0了 偏移量0 * mPointWidth = leftMargin还是0 应该将上一次移动的位置宽度加上 mPointWidth * position 一定要把当前有的位置宽度加上,即: int leftMargin = (int) (mPointWidth positionOffset + mPointWidth position);
Day01 07.开始体验按钮 ##
回顾上午知识:
1.首先实现闪屏页是三个动画集合,有旋转,缩放,渐变动画同时运行
2.运行结束后监听动画结束事件setAnimationListener,在onAnimationEnd中拿到判断新手引导页是否展现,默认false没有展现过就展示,否则跳转到主页面
跳转到的新手引导页是用ViewPager实现的三个页面,在给ViewPager设置的GuideAdapater中初始化3张ImageView图片
滑动页面时希望小红点跟着手指移动,相对布局中,用线性布局(小灰点的容器)动态添加3个小灰点,再加一个小红点ImageView或view,,实现了叠加在一起的效果
mViewPager监听滑动事件setOnPageChangeListener,在onPageScrolled中可以拿到当前位置position,滑动偏移百分比positionOffset
偏移百分比 * 圆点距离marginLeft拿到小红点当前应该移动到哪里,圆点距离marginLeft = 第二个点的left值 - 第一个点left值
不能在onCreate中直接拿,layout方法必须在onCreate方法执行结束后才开始绘制,所以必须通过视图树监听小红点的addOnGlobalLayoutListener的onGlobalLayout,layout结束位置就确定了,在这个方法中再计算两个圆点距离
在setOnPageChangeListener中的onPageScrolled方法中,通过距离mPointWidth * 偏移量positionOffset,再加上小红点当前位置的一个信息,就是小红点左边距leftMargin,将小红点左边距通过布局参数重新设置给小红点,运行程序小红点左边距会实时根据手指发生变化
新手引导页最核心最复杂的逻辑就是小红点逻辑
-------------------------------------------------------------------
“开始体验”按钮的实现
只有在第三个页面才显示,监听页面选中状态,如果是第三个页面才能把Button去VISIBLE,否则GONE
在activity_guide.xml中给id为btn_start的button默认设置gone
android:visibility="gone"
在GuideActivity的onCreate中初始化id为btn_start的button并声明成成员变量
private Button btnStart;// 开始体验
btnStart = (Button) findViewById(R.id.btn_start);
gone和invisible区别
invisible隐藏掉,gone消失掉
invisible相当于隐身,还占原来位置还可以点击,消失掉就没了不占位置
此处用哪个都没有区别,如下情况有区别
一个线性布局中有两个按钮,按钮权重都设置为1
两个按钮会等分布局,第一个按钮设置为invisible时
第二个按钮还在原来位置,因为第一个按钮还占位置
第一个按钮设置为gone时,由于第二个按钮权重是1,第二个按钮会占满整个线性布局
在setOnPageChangeListener的onPageSelected中判断显示和隐藏
// 页面选中
@Override
public void onPageSelected(int position) {
// Log.d(TAG, "onPageSelected:" + position);
if (position == mImageList.size() - 1) {
btnStart.setVisibility(View.VISIBLE);
} else {
btnStart.setVisibility(View.GONE);
}
}
“开始体验”按钮为红色,点击后为黄色
在drawable下增加按钮状态选择器btn_guide_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_red_pressed" android:state_pressed="true"></item>
<item android:drawable="@drawable/button_red_normal"/>
</selector>
在activity_guide.xml给按钮设置状态选择器
android:background="@drawable/btn_guide_selector"
“开始体验”按钮文字为白色,点击后文字为黑色
在color下增加颜色状态选择器txt_guide_selector.xml
(放在drawable下也可以,但是为了区分它是颜色的状态选择器还是放在color文件夹下)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#000"/>
<item android:color="#fff"/>
</selector>
在activity_guide.xml给按钮设置颜色状态选择器
android:textColor="@color/txt_guide_selector"
@什么,要看它的文件夹 @color,@drawable,@layout,没有@values但是有个@string,所以都要看它前缀是什么
“开始体验”按钮点击事件,跳主页面
用户点“开始体验”按钮,新手引导页已被看,下次就不能再进新手指导页
给sp去putBoolean设置“is_guide_show”,在闪屏页设置过
参数ctx:getApplicationContext()
参数key:“is_guide_show” 表示它的新手引导页有没有展示过
参数value:true, 表示新手引导页面已经展示过了
不能写this,写this指的是OnClickListener,跳完主页面MainActivity之后,要finish()销毁GuideActivity
btnStart.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 新手引导页已经展示,下次启动不再展示
PrefUtils.putBoolean(getApplicationContext(), "is_guide_show",true);// 记录已经展现过了新手引导页
startActivity(new Intent(getApplicationContext(),
MainActivity.class));
finish();
}
});
运行程序,点击“开始体验”按钮跳主页面MainActivity,退出程序,再进入直接就进入主页面
原因是第一次进入时,在新手引导页点击“开始体验”时onClick中给PrefUtils设置了"is_guide_show"为true
再次进入程序时,闪屏页SplashActivity在动画结束方法onAnimationEnd中通过PrefUtils获取"is_guide_show"
此时已是true,通过判断就跳到主页面了
Day01 08.Library库项目介绍 ##
代码演示如何创建并引入一个库文件
自定义控件最后一节课提到可以将项目以库形式引入,相当于一个jar包,可以用它所有api
1.项目是库文件还是普通项目
项目配置文件project.properties的android.library=true 就说明是库项目,否则为普通项目
因为加了android.library=true,右键librarydemo项目,选择Android,标记is Library 是勾选上的,普通项目没勾选
2.创建库文件
1)new Android Application
Application Name命名为libraryDemo
Package Name命名为com.itcast.librarydemo
2)点next 将 Mark this project as a library勾上
3)最后finish即可创建library项目
4)libraryDemo库项目中写一个Utils,并在类中写一个doSomething方法并打印
public static void doSomething(){
System.out.println("doSomething");
}
5)zhxa项目右键Properties,Android,Add,选中LibraryDemo点击ok便可引进来,这两个项目便关联上了
6)zhxa项目MainActivity的onCreate中调LibraryDemo中Utils的doSomething方法
Utils.doSomething();
点进Utils方法,它是LibraryDemo中的Utils方法
3.lirary库文件比jar包有优势
jar包放在libs下也可实现上边效果
但lirary库文件不仅可共享java类文件,还可共享资源drawable图片,layout,values的所有文件
4.主项目关联库文件后打包上线不变
zhxa项目和库文件关联后,将来发布上线打包时,以前怎么打包,现在还怎么打包就成
zhxa项目右键,选中Android Tools下的Export Signed Application Package...
next,系统会自动把库文件关联上,主项目打包库文件会自动关联进来
5.取消库文件关联
选中项目zhxa,右键Properties,Android,选中需要取消的库,点击Remove即可
一个项目刚一起来就报错,不一定是项目原因,可能是关联的库没找到,重新关联即可
Day01 09.SlidingMenu介绍 ##
主页面侧边栏效果,用开源自定义控件SlidingMenu实现(可以滑动的菜单)
GitHub上搜SlidingMenu找到项目路径,下载下来将SlidingMenu-master放到zhxa项目路径下
同时导多个项目直接选中根目录确定,选example和library,不勾选example_update点finish
将SlidingMenuLibrary和ExampleListActivity引进来,ExampleListActivity报错很多错误,资源文件values也报错
一般上边代码报错是因为资源文件报错R文件没法生成,.java文件都要用到R文件,就会全报错
选中项目ExampleListActivity,点击Properties,选中Android,SlidingMenuLibrary关联上了,但下边有个红叉ABS,ABS指actionBar
每次新建项目一上来上边就有个黑框标题栏actionbar都要去掉,手机卫士,智慧北京一上来就把它去掉了
actionbar是在android4.0后才有的,ABS包是为了让4.0以上和2.几版本都能用上4.0风格
ABS包也是一个开源项目,github上能搜到,在例子程序ExampleListActivity中根本没导入进来,所以导致程序没法运行
单独搞个example_update,是example的升级版,把标题栏效果全去掉了,现在就不需要ABS包了
把example_update导入进来就不报错了
Day01 10.侧边栏demo ##
新建项目SlidingMenuDemo,包名com.itcast.slidingmenudemo
选中SlidingMenuDemo右键Properies,Android,将SlidingMenuLibrary库Add进来
MainActivity将onCreateOptionsMenu方法删掉,让MainActivity继承SlidingActivity,这个SldingActivity是SlidingMenu的activity,因为已经关联起来,SldingActivity就具备侧边栏效果了
发现onCreate报错,点ctrl+1提示Change method visibility to ‘public’
SlidingActivity的onCreate是public,子类不能局限,把MainActivity的onCreate的protected改为public就不报错了
SlidingMenu用法
第一步:关联SlidingMenu库文件
第二步:当前activity继承SlidingActivity
第三步:onCreate方法改为public
如果要在activity中添加多个Fragment,就不能继承SlidingActivity了
必须继承FragmentActivity,它对应的就是SlidingFragmentActivity,这样不单有侧边栏效果,还可以添加Fragment
后边要用到它继承的是SlidingFragmentActivity
原来是setContentView,再加一个setBehindContentView设置后边侧边栏的ContentView
setBehindContentView肯定是SlidingFragmentActivity的,它是侧边栏布局
setContentView(R.layout.activity_main)//设置当前页面的主要布局
setBehindContentView(R.layout.left_menu);//设置左侧侧边栏布局
layout新建left_menu.xml,放一个TextView叫做侧边栏,根布局LinearLayout换成红色
android:background="#f00"
运行SlidingMenuDemo项目,侧边栏效果出来了
想修改可以获取侧边栏对象getSlidingMenu():
SlidingMenu slidingMenu = getSlidingMenu();//获取侧边栏对象
给slidingMenu设置secondaryMenu,设置第2个侧边栏,第一个是左侧,第二个肯定是右侧,再写一个布局文件right_menu.xml
把background修改为绿色“#0f0”与左侧侧边栏区别
slidingMenu.setSecondaryMenu(R.layout.right_menu);//设置右侧侧边栏
运行程序,左边侧边栏绿色,右边侧边栏出不来是默认只显示一个侧边栏
让左右同时显示侧边栏需要调setMode()
slidingMenu.setMode(SlidingMenu.LEFT_RIGHT);
运行程序,左右侧边栏都有,且左侧侧边栏为红色,右侧侧边栏为绿色,但是在屏幕边缘滑动才会出来
在屏幕中间滑动时出来可设置TouchModeAbove():
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);//全屏触摸,默认是边界触摸
运行程序,全屏触摸侧边栏都能出来,但感觉侧边栏太宽了,setBehindOffset()设置屏幕预留宽度,预留宽度指打开侧边栏后主页面还要留多宽,比如预留100个像素
slidingMenu.setBehindOffset(100);//设置屏幕预留的宽度100px
运行程序,主页面预留了100px
这是侧边栏基本api,如果要使用额外的功能要深入去看demo怎么写的,智慧北京中这些就足够开发了
Day01 11.使用Fragment搭建UI框架 ##
给智慧北京的MainActiviy加侧边栏
1.选中zhbj项目右键Properties,Android,把SlidingMenuLibrary添加进来
2.让MainActivity继承SlidingFragmentActivity
3.把MainActivity的onCreate的protected改为public
4.给它设置侧边栏 setBehindContentView(R.layout.left_menu);
5.将SlidingMenuDemo项目的left_menu.xml拷贝到zhbj项目的layout文件夹下,侧边栏就出来了
6.还可给它设置屏幕预留大小大概是200像素
SlidingMenu slidingMenu = getSlidingMenu();//获取侧边栏对象
7.给对象setBehindOffset为200像素
slidingMenu.setBehindOffset(200);//预留200px宽度
8.设置全屏触摸
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);// 全屏触摸
这样把侧边栏就写完了,运行程序报错为:
Unable to execute dex:Multiple dex files define Landroid/support/v4/
无法执行dex文件,多个dex文件被定义,supportV4下边什么什么的api
因为同时有好几个supportV4包打架了,zhbj项目的libs下边有个supportV4,和它关联的SlidingMenuLibrary中的libs下也有个supportV4,这两个supportV4冲突了,把zhbj的supportV4删除掉即可
删不掉是因为在占用,选中libs文件夹,右键Explore in File System,来到它所在的文件夹中,选中supportV4,右键选择文件粉碎中的删除文件即可
回到文件中刷新项目即可
刷新项目后整个项目变成感叹号了,遇到这种问题很纠结,没有任何地方报错
往往因为它的一个包引入的有问题,选中项目右键Properties,Android,引入的包打个绿色对勾,说明没问题,选择java Build Path,它右边的Order and Export中的android-support-v4.jar打个红色叉叉说明有问题,Libraries中也是
原因是刚才强制将supportV4删掉了,这时报个红叉叉就会变成感叹号错误,选中Libraries中的android-support-v4.jar,点击Remove即可,这时候就不报错了
这种错不要着急,去看BuildPath有没有问题,有叉叉的一定要先删掉,删掉后想办法看能不能重新关联一下,一般删掉就可以
重新运行程序,侧边栏效果实现了
1.完整项目侧边栏中新闻,专题,组图,互动是一个listView
2.主页面是另外一个样式,从界面上是完全分离的
为了让业务逻辑实现解耦,可以让侧边栏和主页面分别用两个Fragment表示
侧边栏叫LeftMenuFragment,主页面叫ContentFragment
涉及主页面逻辑全部在ContentFragment实现,涉及到侧边栏逻辑全部在LeftMenuFragment实现
MainActivity最核心框架由两个Fragment搭建的
在zhbj项目中再建一个包com.itcast.zhbj.fragment
先写最基本的BaseFragment,让两个fragment继承基类BaseFramgent
BaseFramgent肯定继承的Fragment对象
android.app只能兼容android4.0以上
android.support.v4是android2.几都可兼容
所以继承support-v4的Fragment对象
Fragment到底是什么?它和activity又有什么区别呢?Fragment是从几点0开始有的呢?
从3.0以后才开始有Fragment,3.0是平板电脑,平板电脑屏幕太大,左侧可能有些页面,右边可能又有一些别的东西,不像手机屏幕只能展现一部分,这时就会将这个屏幕分成好多块,如果用一个activity做就很烦
因为左边这块可能是一块逻辑,右边这块又是另外一个逻辑,全放一个activity去维护很费劲
谷歌为了让平板电脑开发更容易,将activity页面细分成一个一个Fragment
Fragment中文是碎片,它是一个大布局分成一个个小碎片,让平板电脑开发起来更加容易
发现4.0后用普通手机也可以用Fragment做,所以4.0后将3.0平板电脑特有的Fragment引入到了4.0系统
让在小屏幕上也可以用一个一个Fragment实现这样的效果,Fragment在手机卫士缓存清理已用过一次
Fragment和Activity关系
两个人都是在展现页面,但是Fragment要依附于activity,activity才是展示界面的组件
为了让界面更加实现解耦,把它划分为一个个Fragment,这个Fragment必须基于activity展现
Framgent生命周期比Activity复杂很多
onAttach(),粘贴,像便签一样粘贴到activity
onCreate()
onCreateView() 创建布局
onActivityCreated() 当activity创建结束后会调这个方法
onStart()
onResume()
onPause()
onStop()
onDestroy()
凡是activity有的生命周期,Fragment都有,onCreate(),onStart(),onResume(),onPause(),onStop(),onDestroy()都有
onAttach()对应的是onDetach() 另外一个onCreateView(),创建这个布局,Fragment应该有个布局,activity创建一个布局很容易,setContentView()实现
Fragment得在onCreateView()中实现,对应的是onDestoryView()
最核心的就是onCreateView()
onActivityCreated()这块通常会用到它,因为初始化布局得重写这个方法
BaseFragment中写一下Fragment生命周期
1)onCreate时Fragment创建,通过getActivity拿到Fragment所依赖的activity对象
public Activity mActivity;//这个对象就是MainActivity
/**
-
Fragment创建
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = getActivity();// 获取Fragment所依赖的Activity对象
}Fragment没有继承任何东西,只实现了两个接口 Fragment implements android.content.ComponentCallbacks, android.view.View$OnCreateContextMenuListener 没有继承Context,那Fragment本身就不是context,但在Android开发中经常用到Context,没有context可以找它爹activity,activity就是context对象,可以拿这个mActivity当做context用 2)onCreateView()初始化Fragment布局 每一个Fragment布局都不一样,父类BaseFragment不知道子类要实现什么布局,应该交由子类去实现 写一个initView方法由子类实现,用abstract修饰成抽象,BaseFragment类也需要用abstract修饰 让子类去实现后返回一个View对象,将intView方法的返回值void修改为View onCreateView中直接调initView()返回view对象,这个view就是子类实现的view,直接返回就行 // Fragment填充布局 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = initView(); return view; } /**
-
初始化布局
-
@return
*/
public abstract View initView();3)onActivityCreated方法,是当activity创建结束后要做什么事情 一般都是要初始化数据再写一个initData()方法 // Fragment所依赖的Activity创建完成 @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initData(); } /**
-
初始化数据的方法, 子类可以不实现
*/
public void initData() {
}这个方法子类可以不实现,因为数据不一定,有些类可能只是展示一个页面,不需要初始化数据,子类可以不实现 没必要把initData方法abstract抽象,在onActivityCreated中调用,所在的activity创建结束后初始化数据 到onActivityCreated()时activity才创建结束,在onCreate()中提前getActivity()会出现activity为空否 不会,调onAttach()时要将fragment添加到activity上,这时肯定有activity,只不过还没创建完成,可能已创建好了,但activity布局还没绘制好,onCreate可能没执行完,但activity已经有了 到onActivityCreated时说明onCreate已经完全执行结束 所以在onCreate方法中getActivity不会为空,如果不放心在onActivityCreated方法中获取也可以 这是Fragment的基类BaseFragment com.itcast.zhbj.fragment public abstract class BaseFragment extends Fragment { public Activity mActivity;//这个对象就是MainActivity /**
-
Fragment创建
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = getActivity();// 获取Fragment所依赖的Activity对象
}/**
-
Fragment初始化布局
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = initView();
return view;
}/**
-
Fragment所依赖的Activity创建结束
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 初始化数据
initData();
}// 初始化布局的方法, 子类必须实现 public abstract View initView(); /**
-
初始化数据的方法, 子类可以不实现
*/
public void initData() {} } BaseFragment写完后,就可以分别实现两个子类了 先实现侧边栏LeftMenuFragment,继承的是BaseFragment /**
-
侧边栏Fragment
* -
@author Kevin
-
@date 2015-8-29
*/
public class LeftMenuFragment extends BaseFragment {@Override public View initView() { View view = View.inflate(mActivity, R.layout.fragment_left_menu, null); return view; } } 1.写两个布局文件fragment_left_menu.xml和fragment_content.xml 2.在侧边栏LeftMenuFragment中的initView中通过View.inflate(context,resource,root)实现布局 View view = View.inflate(mActivity, R.layout.fragment_left_menu, null); 3.最后将view对象返回给BaseFragment 主页面contentFragment实现布局的逻辑和侧边栏的完全一样,复制修改为fragment_content即可 项目本身就有布局, activity_main.xml,left_menu.xml 这两个是在MainActivity中先setContentView(R.layout.activity_main); 再去setBehindContentView(R.layout.left_menu)设置侧边栏为left_menu 原来是写死的布局,现在要将侧边栏部分和主页面部分分别用LeftMenuFragment和contentFragment替换掉 侧边栏部分和主页面部分的布局已没有任何意义 activity_main.xml中刚开始写的textview控件没意义,把它删掉,把布局文件写成id为fl_main的帧布局FragmentLayout 掏空成frameLayout目的就是为了将来用Fragment把帧布局完全替换掉 如果遇到没有确定要写成什么,比如主页面动态用Fragment填充,或某个布局动态去给它塞,这时它里边通常会用FrameLayout动态添加 线性布局得一个一个排,就有些额外逻辑在里边,但FrameLayout就天然一个一个往上压,这个反倒不会有额外干扰 侧边栏也用FrameLaout替代,这时侧边栏是LeftMenu.xml,同样把textview控件删掉,把LinearLayout用id为fl_left_menu的Framelayout替换掉 MainActivity中写个方法initFragment来初始化Fragment对象 通过getSupportFragmentManager(),返回一个Fragment管理器FragmentManager,把它叫做fm 有些同学直接getFragmentManager()也不报错,这个是4.0以后才有的,要用SupportV4下边的就调getSupportFragmentManager 通过Fragment调beginTransaction()开启事务: FragmentTransaction transaction = fm.beginTransaction();// 开始事务 通过transaction调用replace替换Fragment,第一个参数是int值就是要替换的帧布局的id,第二个参数去new一个LeftMenuFragment,用Fragment替换帧布局, 这样侧边栏把刚才的帧布局替换掉了 transaction.replace(R.id.fl_left_menu, new LeftMenuFragment()); 还要替换一个主页面,再写一个transaction.replace(R.id.fl_main, new ContentFragment()); 用这两个把帧布局替换掉后要把这个事务提交了:transaction.commit(); 在MainActivity的onCreate中调用下initFragment()即可 为了后期使用方便,transaction替换时会有一个带三个参数的方法,即:replace(int arg0,Fragment arg1,String arg2) 这里这个String值参数是一个标记,可以给这个Fragment打一个标记,相当于给它去写一个名字,这个名字就叫做TAG_LEFT_MENU,把它声明为全局变量 private static final String TAG_LEFT_MENU = "TAG_LEFT_MENU"; 再来一个TAG_CONTENT,同样声明为全局变量 private static final String TAG_CONENT = "TAG_CONTENT"; 后边可以通过这种方式拿到这两个Fragment对象,比如FragmentManager可以去findFragmentByTag拿到对象 在这设置标记相当于给以后留一手,现在是在这直接new了一个LeftMenuFragment和ContentFragment 想找到这个对象很费劲,可以通过findFragmentByTag拿到,就像找一个view通过findViewById去拿 运行程序,他俩已将原来的FrameLayout替换掉了,现在这两个地方都是Fragment了 总结: 1.写基类BaseFragment 2.实现子类:LeftMenuFragment和ContentFragment 3.将MainActivity中的侧边栏布局和主页面布局掏空,用FrameLayout替换 4.用Fragment替换FrameLayout 为什么要替换,能一次性写上去否 不能一次性写上去,其实替换反倒做起来比较简单 比如原来没有用Fragment,侧边栏是一个写死的布局,主页面是一个写死的布局,突然想改造成Fragment 要让侧边栏这块用Fragment表示,主页面这块要掏出来用Fragment表示,这时只需把原来布局直接改成FrameLayout 然后用一个Fragment把FrameLayout替换掉就可以了,这只是一方面,再比如希望把主页面划分为三个Fragment表示 比如标题栏是一个Fragment,中间部分是一个Fragment,下边又要一个Fragment,只需要把标题栏用一个FrameLayout占位,中间地方用FrameLayout占位,下边地方用FrameLayout占位,分别用三个Fragment把上边替换,中间替换,下边替换 在这个FrameLayout中再去塞Fragment,这样就不用考虑Fragment到哪里展现,因为FrameLayout已把位置确定好了 只需要去替换就行,反倒让布局写起来简单一些
Day01 12.底栏标签页处理 ##
用思维导图软件MindjetMindManager简单梳理一下
最中心有一个MainActivity,把MainActivity划分为了侧边栏LeftMenuFragment,主页面ContentFragment
接下来实现主页面ContentFragment
可以将它分为两部分内容,一部分是上边内容页(包括标题到标签页以上所有),另外一部分是下边标签页,分别去实现一下
标签页每次只能选一个,之前接触的ridioButton控件每次只能选一个,这个地方就可以用RadioButton来实现
RadoButton和RadioGroup加在一起使用,RadioButton实现底栏页签效果,上边内容页由五个页面不断切标签变化
讲自定义控件时,讲的那个自定义ViewPager,上边RadioButton一点,页面在下边切换
上边用一个ViewPager实现,把左右滑动禁用掉就可以了
ContentFragment的fragment_content.xml中分为ViewPager再加上RadioGroup
找到SlidingMenuLibrary中的Android Dependencies下的android-support.v4下的View下的ViewPager
选中ViewPager,右键Copy Qualified Name复制ViewPager全类名到fragment_content.xml中的ViewPager控件名处
给它添加id为vp_content,权重等于1就把剩余高度填充起来了,高度给它设置成0dp,下边再来一个RadioGroup
RadioGroup中是一个一个RadioButton,第一个RadioButton叫‘首页’,给第一个RadioButton设置text为首页
属性android:button="@null"表示把原来那个圆圈去掉,同时首页标签上边有个小房子图片,白的叫home.png,红的叫home_press.png
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="@android:color/transparent"
android:button="@null"
android:drawableTop="@drawable/home"
android:gravity="center"
android:padding="5dp"
android:text="首页"
android:textColor="#fff"/>
照着它去写(写完了之后把复制过来的这个RadioButton删除即可)
在style范围内drawableTop每个页签的图片都不一样,抽取style涉及drawableTop这种私有,应该让实现它的每个子类去具体写,而不在style中去写
为了简单起见,抽取的底部标签样式styles.xml
styles.xml
<style name="TabBottomStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_vertical</item>
<item name="android:layout_weight">1</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:button">@null</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/txt_tab_selector</item>
<item name="android:drawablePadding">5dp</item>
<item name="android:padding">5dp</item>
<item name="android:textSize">14sp</item>
</style>
这时可以在fragment_content.xml中把style抽取了的属性删除掉,搞一个style="@"
应该是android:style吗,这个地方比较特殊,直接是style,因为android找不到,就写style="@style/TabBottomStyle"
那这时就可以复制RadioButton(此时复制的虽然还是5份,但相当于少了很多)
'首页'标签的状态选择器btn_home_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/home_press" android:state_checked="true"></item>
<item android:drawable="@drawable/home"/>
</selector>
fragment_content.xml中的‘首页’的RadioButton中,把drawableTop属性值改为
android:drawableTop="@drawable/btn_home_selector"
这是图片被选中之后的状态选择器,文字也是一样的,文字默认是白色,选中之后是红色
在color文件夹下把txt_guide_selector.xml复制一份改为txt_tab_selector.xml
这个地方是指的是被选中之后的背景颜色,不是摁下的颜色,不能用android:state_pressed属性,应该用android:state_checked表示被勾选上,这是被选中后标签的文字状态选择器
解决文字换行将图片顶高的问题
试着把padding删掉,即删除 <item name="android:padding">5dp</item>
运行项目,发现将padding删除好点了,最起码文字没有再换行导致图片被顶起来
所以可能是padding的原因,可能是padding被撑的有点大,就先这样
选中状态的问题
点'首页'所在的标签它会由白色变为红色,点'新闻中心'所在的标签它也会由白色变为红色,但是“新闻中心”标签选中变为红色后,"首页"还是处于选中状态为红色,为什么都选中了呢?每个加个id就可以单独去选了
给'首页'所在的RadioButton加id为:
android:id="@+id/rb_home"
给'新闻中心'所在的RadioButton加id为:
android:id="@+id/rb_news"
给'智慧服务'所在的RadioButton加id为:
android:id="@+id/rb_smart"
给'政务'所在的RadioButton加id为:
android:id="@+id/rb_gov"
给'设置'所在的RadioButton加id为:
android:id="@+id/rb_setting"
还希望默认第一个‘首页’标签被选中
给‘首页’所在的RadioButton添加属性,android:checked="true",表示第一个标签页‘首页’被选中
运行程序,‘首页’标签是被选中的,而且切换标签时不会再出现都被选中的错误
Day01 13.总结 ##