智慧北京[上篇]
前言:最近學習了黑馬的智慧北京項目後,收貨良多,故在此記錄分享一下項目開發的大體流程和開發過程中各業務功能的實現以及項目裏所用到的一些開源框架。
項目介紹:
智慧北京作爲一款集新聞,服務,政務…類的應用。提供各類新聞資訊,智慧服務,政務等服務的移動應用軟件。項目整體分爲首頁,新聞中心,智慧服務,政務和設置五大模塊。
其視乎每一個應用軟件的都有 加載佈局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攔截