智慧北京[上篇]
前言:最近学习了黑马的智慧北京项目后,收货良多,故在此记录分享一下项目开发的大体流程和开发过程中各业务功能的实现以及项目里所用到的一些开源框架。
项目介绍:
智慧北京作为一款集新闻,服务,政务…类的应用。提供各类新闻资讯,智慧服务,政务等服务的移动应用软件。项目整体分为首页,新闻中心,智慧服务,政务和设置五大模块。
其视乎每一个应用软件的都有 加载布局View,布局View的数据填充,view事件的监听这么一个过程。本项目遵循着MVC,面向对象的设计思想
项目截图:
一:项目UI架构
闪屏页:应用启动时可以看到一张旋转,缩放,渐变着的图片。这种效果是对Activity的相对布局(布局里有张宽高充满屏幕的ImageView)添加了旋转,缩放,渐变三个动画来实现的。onCreate()方法里调用initAnimation()
code:
private void initAnimation() {
//旋转动画
RotateAnimation rotateAnimation = new RotateAnimation(0,360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(1000);//旋转时间
rotateAnimation.setFillAfter(true);//旋转后停止在最后,保持动画结束状态
//缩放动画
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(1000);
scaleAnimation.setFillAfter(true);
//渐变动画
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(2000);
alphaAnimation.setFillAfter(true);
//动画集合来存储3个动画
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(alphaAnimation);
//开启动画
relativeLayout.startAnimation(animationSet);
//动画监听
animationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
//动画结束回调监听方法
@Override
public void onAnimationEnd(Animation animation) {
enterActivity();
}
});
}
新手引导页:新手引导页起到一个用户引导的作用,一般介绍软件里的功能。在闪屏页的动画结束后,若用户为第一次启动软件则进入的是新手引导页,否则直接进入主界面。故在此可以用SharedPreferences里存储的标记值来判断用户是否是第一次启动软件。在闪屏页动画结束的回调方法里来执行enterActivity();
code:
//判断用户是否是第一次启动应用(sp来存储用户的启动标记)。是则进入引导页,否进入主界面
private void enterActivity() {
Intent intent;
if(MySharePreUtis.getIsFristEnter()){
MySharePreUtis.setIsFristEnter(false);
intent = new Intent(SplashActivity.this, GuideActivity.class);
}else{
intent = new Intent(SplashActivity.this, MainActivity.class);
}
startActivity(intent);
finish();
}
- 新手引导页的页面滑动效果使用ViewPager来实现,底部的3灰色个小点和一个红色的滑动小点是采用一个RelativeLayout里包含一个LinearLayout和ImageView.圆形的小点同过在drawable里定义一个shape.xml来实现,LinearLayout里代码动态添加3个点,ImageView红色小点的滑动是在ViewPager的onPageSelected()回调方法里计算其滑动位置的。(小点移动距离=移动百分比*两点之间的距离)
引导页code:
public class GuideActivity extends Activity {
@Bind(R.id.guide_viewpager)
ViewPager viewPager;
@Bind(R.id.dian_linear)
LinearLayout linearLayout;
@Bind(R.id.but_start)
Button button;
@Bind(R.id.red_point)
ImageView redPoint;
private int pointDistance;//两点距离
private ArrayList<ImageView> imageViewArrayList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_guide);
ButterKnife.bind(this);
initData();
GuiderVpAdapter adapter = new GuiderVpAdapter(this,imageViewArrayList);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//页面滑动过程中回调方法
/**
* position:当前页位置
* positionOffset:移动百分比(要用到)
* positionOffsetPixels:滑动的具体像素
* 小点移动距离=移动百分比*两点之间的距离
*/
//通过设置小红点的marginLeft来使其移动点
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) redPoint.getLayoutParams();
lp.leftMargin = (int) (positionOffset * pointDistance) + position * pointDistance;
redPoint.setLayoutParams(lp);
}
@Override
public void onPageSelected(int position) {
//滑动到某个页面回调方法
if(position==imageViewArrayList.size()-1){
button.setVisibility(View.VISIBLE);
}else {
button.setVisibility(View.GONE);
}
}
@Override
public void onPageScrollStateChanged(int state) {
//页面状态改变回调方法
}
});
//获取两点之间的距离,是在UI界面绘制完成后才能获取到(onMeasure,onLayout,onDraw-->在activity的onResume方法后执行)
// 可以通过获取视图树添加观察监听器来监听onLayout()执行完
//获取视图树
redPoint.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//onLayout方法执行完成后回调
redPoint.getViewTreeObserver().removeGlobalOnLayoutListener(this);//移除监听,避免反复回调
// redPoint.getViewTreeObserver().removeOnGlobalLayoutListener(this);//API>=16
pointDistance = linearLayout.getChildAt(1).getLeft() - linearLayout.getChildAt(0).getLeft();
}
});
}
private void initData() {
imageViewArrayList = new ArrayList<>();
int[] imageRes = {R.mipmap.guide_1, R.mipmap.guide_2, R.mipmap.guide_3};
ImageView guideImage;
ImageView pointImage;
LinearLayout.LayoutParams lp;
for (int i = 0; i < imageRes.length; i++) {
//init 引导页
guideImage = new ImageView(this);
guideImage.setBackgroundResource(imageRes[i]);
imageViewArrayList.add(guideImage);
//init 点
pointImage = new ImageView(this);
pointImage.setBackgroundResource(R.drawable.point_gra);
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
if(i>0){
lp.leftMargin = DensityUtils.dip2pix(10,this);//转化为px,解决屏幕适配
}
pointImage.setLayoutParams(lp);
linearLayout.addView(pointImage);
}
}
@OnClick(R.id.but_start)
public void clickStart(View view){
startActivity(new Intent(GuideActivity.this, MainActivity.class));
finish();
}
}
应用主界面:(LeftMenuFragment + MainFragment)
- 主界面用一个Activity来承接,包含一个侧滑菜单和主页面。侧滑采用SliddingMenu来实现,侧滑和主页面布局都为一个FrameLayout
- 定义一个抽象父类BaseFragment,侧滑菜单LeftMenuFragment和主页面MainFragment继承BaseFragment。实现父类的抽象initView()方法实现各自的布局
- 最后通过将fragment管理器的事物将两个Fragment替换到FrameLayout里。
主界面code:
public class MainActivity extends SlidingFragmentActivity {
private static final String MAIN_FRAGMENT = "MAIN_FRAGMENT";
public static final String LEFT_FRAGMENT = "LEFT_FRAGMENT";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//无标题栏
setContentView(R.layout.activity_main);
initSlidMenu();
loadLeftAndMainLayout();
}
private void initSlidMenu() {
setBehindContentView(R.layout.left_menu);
SlidingMenu slidingMenu = getSlidingMenu();
slidingMenu.setMode(SlidingMenu.LEFT);//左/右侧滑出
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);//全屏触摸监听
//设置滑动时菜单的是否淡入淡出
slidingMenu.setFadeEnabled(true);
//设置淡入淡出的比例
slidingMenu.setFadeDegree(0.5f);
//设置滑动时拖拽效果:即slidingmenu的遮盖滑出效果
slidingMenu.setBehindScrollScale(0);
// slidingMenu.setBehindOffset(200);//屏幕预留宽度
//以200/480的屏幕预留宽度比例 * 不同屏幕的宽度:适应侧滑视图的屏幕【200:屏幕预留宽度 480:测试手机的屏幕宽度】
WindowManager wm = this.getWindowManager();
int width = wm.getDefaultDisplay().getWidth();
int ylWidth = width*200/480;
slidingMenu.setBehindOffset(ylWidth);
}
private void loadLeftAndMainLayout() {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ts = fm.beginTransaction();
ts.replace(R.id.main_FrameLayout, new MainFragment(),MAIN_FRAGMENT);
ts.replace(R.id.left_frameLayout, new LeftMenuFragment(),LEFT_FRAGMENT);
ts.commit();
}
//获取侧滑Fragment对象,供外部调用
public LeftMenuFragment getLeftMenuFragment(){
FragmentManager fm = getSupportFragmentManager();
LeftMenuFragment leftMenuFragment = (LeftMenuFragment) fm.findFragmentByTag(LEFT_FRAGMENT);
return leftMenuFragment;
}
//获取MainFragment对象,供外部调用
public MainFragment getMainFragment(){
FragmentManager fm = getSupportFragmentManager();
MainFragment mainFragment = (MainFragment) fm.findFragmentByTag(MAIN_FRAGMENT);
return mainFragment;
}
}
LeftMenuFragment布局
- ListView
MainFragment布局
- ViewPager + RadioGroup
Fragment + RadioGroup
此处采用 ViewPager + RadioGroup来实现
xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zt.zhbj.view.CustomerViewPager
android:id="@+id/main_viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<RadioGroup
android:id="@+id/radioGropu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@mipmap/bottom_tab_bg"
android:gravity="center"
android:orientation="horizontal"
>
<RadioButton
android:id="@+id/rb_home"
style="@style/bottom_tab_style"
android:text="首页"
android:drawableTop="@drawable/home_tab_selector"
android:checked="true"
/>
<RadioButton
android:id="@+id/rb_news"
style="@style/bottom_tab_style"
android:text="新闻中心"
android:drawableTop="@drawable/news_tab_selector"
/>
<RadioButton
android:id="@+id/rb_service"
style="@style/bottom_tab_style"
android:text="智慧服务"
android:drawableTop="@drawable/service_tab_selector"
/>
<RadioButton
android:id="@+id/rb_gov"
style="@style/bottom_tab_style"
android:text="政务"
android:drawableTop="@drawable/gov_tab_selector"
/>
<RadioButton
android:id="@+id/rb_setting"
style="@style/bottom_tab_style"
android:text="设置"
android:drawableTop="@drawable/setting_tab_selector"
/>
</RadioGroup>
</LinearLayout>
主界面(MainFragment)自定义ViewPager禁用其页面滑动
五个功能模块的切换无滑动,是重些ViewPager的onTouchEvent()方法将其返回值设置为true,消费此事件来实现vp的滑动禁用
@Override
public boolean onTouchEvent(MotionEvent ev) {
return true;//消费此事件。不做任何处理,从而实现对滑动事件的禁用
}
点击底部Tab切换的同时去除平滑滑动效果
viewPager.setCurrentItem(position,false);
二:各界面视图基类抽取
- 主界面MainFragment的五个Tab视图可以抽象一个父类,定义一个抽象的initView()方法和一个initData()方法,由其子类其继承父类完成各自的布局View的加载和数据的初始化,这样就实现了个功能模块布局的独立性和耦合性
侧滑菜单栏的四个功能模块也可以为各自的布局和数据的初始化方法抽象一个父类。点击四个Item新闻中心里的FrameLayout布局里不断的addView()即可实现四个item布局的切换
code:
//移除FrameLayout的view frameLayout.removeAllViews(); //将view布局添加到FrameLayout里 frameLayout.addView(pager.mRootView);
- 新闻中心新闻模块里各新闻tab为同一样的布局视图,故可以用一个类来绑定一个布局,在外部ViewPager的instantiateItem()里返回此类加载的布局,即可显示出来.
三:事件冲突
- Sliddingmenu和ViewPagerIndicator的滑动冲突解决:重写ViewPagerIndicator的dispatchTouchEvent()方法
code:
//事件分发
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);//请求他的所有父类/祖宗类(sdm虽然不是其父类)不要拦截ViewPagerIndicator的滑动事件
return super.dispatchTouchEvent(ev);
}
- 新闻中心新闻模块布局ViewPager和SlidingMenu侧滑菜单的滑动冲突:
解决此冲突可以在ViewPager的滑动回调方法里设置SlidingMenu的触摸模式SlidingMenu.TOUCHMODE_FULLSCREEN(全屏触摸则为开启sdm的滑动监听),设置为SlidingMenu.TOUCHMODE_NONE则为关闭
code:
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//第一页开启侧滑,其他也则关闭侧滑
if (position == 0) {
sdm.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
} else {
sdm.setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);
}
currentPosition = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
- 新闻中心新闻模块布局ViewPager和其item里ListView的头视图ViewPager的滑动冲突:重写头视图ViewPager里的dispatchTouchEvent()方法
code :
//事件分发
/**
* 1,上下滑动父类拦截(自己+listview列表的滑动)
* 2,向左滑动到最后一页 父类(vp)拦截
* 3,向右滑动到第一页 父类(vp)拦截
* 4,左右滑动,x坐标的滑动量(绝对值)大于y坐标的滑动量(绝对值),else 上下滑动
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);//请求父控件不拦截事件
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) ev.getX();
int moveY = (int) ev.getY();
int dx = moveX - downX;
int dy = moveY - downY;
if(Math.abs(dx)>Math.abs(dy)){ //左右滑动
if(dx>0){//向右滑
if(this.getCurrentItem()==0){
getParent().requestDisallowInterceptTouchEvent(false);//父控件拦截
}
}else { //左滑
if(this.getCurrentItem()==getAdapter().getCount()-1){
getParent().requestDisallowInterceptTouchEvent(false);//父控件拦截
}
}
}else {
//上下滑动
getParent().requestDisallowInterceptTouchEvent(false);//父类拦截
}
break;
}
return super.dispatchTouchEvent(ev);
}
由UI的事件传递机制:dispatchTouchEvent()–>onInterceptTouchEvent()–>onTouchEvent()可得若子类想要获取到事件,而不被父控件拦截
这可以重写父控件的onInterceptTouchEvent()方法将其返回值置false表示不消费用户的触摸事件,将事件传递给其子类处理
也可以重写子类的dispatchTouchEvent()方法,加入getParent().requestDisallowInterceptTouchEvent(true);true表示父控件不拦截事件,false拦截