GooglePlay Day01

谷歌电子市场笔记Day01 #

Day01 01.项目介绍 ##

关于这个项目不会像手机卫士和新闻客户端(智慧西安)那样详细讲解,个别的模块和代码用现成的代码,只详细讲解不会的地方。

我们现在开发的项目都是需要去调用服务器的,这个项目的服务器不在用tomcat服务器,而是用自己写的服务器,装在手机里边的,这样的好处是,运行时只要在手机里将服务器启动就可以了,不需要连接tomcat.

首页:
 上边是一个轮播条的效果,下来是listview展示的应用,最后还有下拉加载
 listview每个条目进去后是应用的详情页,里边的收藏和分享按钮的功能暂时没有实现
应用:
 应用页面只是简单的实现了下列表,不能点击进去,其实点击进去后和首页是一样的
游戏页面:
 游戏页面暂时没有实现,简单的写了下textview
专题:
 往下滑,是专题的一个一个的标签页,暂时不能点击进去
推荐:
 滑动时,是一种随机的,随机弹出一些关键字
分类:
 分为游戏和应用两种 用gridview实现 暂时不能点击进去
排行:
 自定义的控件实现
侧边栏:
 没有用slidingmenu,而是用google自带的一个控件
标题栏:
 不是以前那样自己写的标题栏,而是用google自带的actionbar标题栏
Day01 02.如何运行谷歌电子市场 ##

项目部署流程 ##

1.客户端:googlePlay(UTF-8编码)

2.服务端工程:WebServer

3.服务端工程运行到手机上面去,webInfo文件夹必须拷贝到手机内置sd卡路径下

4.工程需要通过actionBar去管理应用的头(Title),关联actionBar所在的库文件,android-support-v7-appcompat

5.关联Xutils的库文件

6.运行项目前, 必须先启动WebServer, 保证可以从服务器获取数据

Day01 03.自定义Application ##

框架搭建 ##

1.自定义Application

	/**
  • 自定义Application
     *

  • @author Kevin
     * 
     */
     public class BaseApplication extends Application {

      	private static Context context;
    
      	private static int mainThreadId;
    
      	private static Handler handler;
    
      
    
      	// 项目一开始运行,会先调用此方法
    
      	@Override
    
      	public void onCreate() {
    
      		super.onCreate();
    
      		context = getApplicationContext();
    
      		mainThreadId = android.os.Process.myTid();// 获取当前主线程id
    
      		handler = new Handler();
    
      	}
    
      
    
      	public static Context getContext() {
    
      		return context;
    
      	}
    
      
    
      	public static int getMainThreadId() {
    
      		return mainThreadId;
    
      	}
    
      
    
      	public static Handler getHandler() {
    
      		return handler;
    
      	}
    
      }
    

    要让这个项目使用BaseApplication,需要在清单文件中注册

    在application标签中添加如下:

    android:name=“com.itcast.googleplay02.global.BaseApplication”

    自定义它是为了做一些全局的处理,因为项目一运行,会走application的oncreate方法,在onCreate中便可以申明要做的全局处理

    比如:

    1.项目中很多地方都要用到context,以前用到时,到处去找哪里有context对象传过去,现在把context对象放在自定义的注册过的Baseapplication中,谁要用context找它就行,就不用在意当前页面是不是activity怎么传context这个问题

    2.将handler也放进来,以后想找handler就不用new了,直接Application找这个handler对象就行

    3.mainThreadId

    将这些声明完之后,要把它们暴露给第三方的工具使用的话,需要给他们分别写一个get方法,把这几个东西都get过去,这里不需要set,因为我们在oncreate这里已经设置好了

Day01 04.UIUtils实现 ##

2.UIUtils封装
 凡是涉及到界面显示的一些常用的东西,都找UIUtils去要
 比如:你想获取一个context对象,找UIUtils去拿就可以了,UIUtils去找BaseApplication中的context即可 这样就不用搭理BaseApplication了,以后要用context直接和UIUtils打交道就可以了

	/**
  • 工具类, 专门处理UI相关的逻辑
     *

  • @author Kevin
     * 
     */
     public class UIUtils {

      	public static Context getContext() {
    
      		return BaseApplication.getContext();
    
      	}
    
      
    
      	public static int getMainThreadId() {
    
      		return BaseApplication.getMainThreadId();
    
      	}
    
      
    
      	public static Handler getHandler() {
    
      		return BaseApplication.getHandler();
    
      	}
    
      
    
      	/**
    
  • 根据id获取字符串

  • 比如values下的strings文件中定义的字符串

  • name即是id
     */
     public static String getString(int id) {
     return getContext().getResources().getString(id);
     }

      	/**
    
  • 根据id获取图片
     */
     public static Drawable getDrawable(int id) {
     return getContext().getResources().getDrawable(id);
     }

      	/**
    
  • 根据id获取颜色值

  • 比如values下的colors文件中定义的颜色

  • name即是id
     */
     public static int getColor(int id) {
     return getContext().getResources().getColor(id);
     }

      	// 获取颜色的状态选择器 代码写布局时需要
    
      	public static ColorStateList getColorStateList(int id) {
    
      		return getContext().getResources().getColorStateList(id);
    
      	}
    
    
    
      	/**
    
  • 根据id获取尺寸

  • 比如values下的dimens文件中定义的尺寸

  • name即是id
     */
     public static int getDimen(int id) {
     return getContext().getResources().getDimensionPixelSize(id); //调用的此方法返回的是像素
     }

      	/**
    
  • 根据id获取字符串数组

  • 比如values下的strings文件中定义的字符串数组string-array

  • name即是id
     */
     public static String[] getStringArray(int id) {
     return getContext().getResources().getStringArray(id);
     }

      	/**
    
  • dp转px
     */
     public static int dip2px(float dp) {
     float density = getContext().getResources().getDisplayMetrics().density;
     return (int) (density * dp + 0.5f); //可能是一个float值,把它转为整数,四舍五入(+0.5f)
     }

      	/**
    
  • px转dp
     */
     public static float px2dip(float px) {
     float density = getContext().getResources().getDisplayMetrics().density;
     return px / density;
     }

      	/**
    
  • 加载布局文件
     */
     public static View inflate(int layoutId) {
     return View.inflate(getContext(), layoutId, null);
     }

      	/**
    
  • 判断当前是否运行在主线程
     *

  • @return
     */
     public static boolean isRunOnUiThread() {
     //获取当前线程id和主线程id比对 如果一致就是主线程 否则为子线程
     return getMainThreadId() == android.os.Process.myTid();
     }

      	/**
    
  • 保证当前的操作运行在UI主线程
     *

  • 如果想运行在主线程 需要给handler发一条消息,让handler去处理,

  • handler比较麻烦,在这直接写一个方法,方法中直接保证让运行在主线程,就

  • 不用handler去运行在主线程
     *

  • 首先需要运行什么对象 先给我传一个runnable对象

  • @param runnable
     */
     public static void runOnUiThread(Runnable runnable) {
     if (isRunOnUiThread()) {
     //如果当前是主线程 直接运行
     runnable.run();
     } else {
     //使用handler发runnable对象 让此对象运行在主线程
     getHandler().post(runnable);
     }
     }
     }

Day01 05.PagerTab的使用(页签自定义控件) ##

指针自定义控件 ##

1.使用自定义的PagerTab(第三方自定义控件,实现页签效果,类似viewPagerIndicator中的TabPageIndicator)

将PagerTab.java拷贝过来即可,不需要关联整个项目

将PagerTab.java拷贝过来后报错,其中一项是需要BaseActivity,所以需要创建一个BaseActivity



2.定义BaseActivity时,继承ActionbarActivity是为了使标题栏actionbar效果在     2.x版本上也适用

所以需要关联android-support-v7-appcompat

关联时,v7包中的v4包和项目中的v4包冲突

解决v4包冲突问题(删除当前项目中的v4包,使用android-support-v7-appcompat中的v4包)



3.将PagerTab.java拷贝过来后报错,其中一项是需要在UIUtils中新增方法getColorStateList



4.将PagerTab.java拷贝过来后报错,其中一项是需要增加资源文件

/drawable/bg_tab_text.xml(描述标签背景),

/color/tab_text_color.xml(描述标签文字颜色)



		tab_text_color.xml



		<?xml version="1.0" encoding="utf-8"?>

		<selector xmlns:android="http://schemas.android.com/apk/res/android">

		

		    <item android:state_pressed="true" android:color="@color/tab_text_color_selected"/>

		    <item android:state_selected="true" android:color="@color/tab_text_color_selected"/>

		    <item android:color="@color/tab_text_color_normal"/>

		

		</selector>



		----------------------------------------------

		bg_tab_text.xml



		<?xml version="1.0" encoding="utf-8"?>

		<selector xmlns:android="http://schemas.android.com/apk/res/android">

		

		    <item android:drawable="@color/bg_tab_pressed" android:state_pressed="true"></item>

		    <item android:drawable="@android:color/transparent"/>

		

		</selector>

主页面展示 ##

项目整体结构为 上边是一个PagerTab下边是一个viewPager

主页面布局文件

	activity_main.xml



	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

	    xmlns:tools="http://schemas.android.com/tools"

	    android:layout_width="match_parent"

	    android:layout_height="match_parent"

	    android:orientation="vertical" >

	

	    <com.itcast.googleplayteach.ui.PagerTab

	        android:id="@+id/pager_tab"

	        android:layout_width="match_parent"

	        android:layout_height="42dp"

	        android:background="@drawable/bg_tab" />

	

	    <android.support.v4.view.ViewPager

	        android:id="@+id/vp_pager"

	        android:layout_width="match_parent"

	        android:layout_height="match_parent" />

	

	</LinearLayout>



	注意: 必须给PagerTab设置背景图片, 否则PagerTab的onDraw方法不会调用,导致无法绘制Tab分割线和指示器.(原因: PagerTab继承ViewGroup, ViewGroup是一个容器,系统为了节省性能,默认不调用onDraw方法,只有设置背景之后,才会调用)

主页面加载

	public class MainActivity extends BaseActivity {



	private PagerTab mPagerTab;

	private ViewPager mViewPager;



	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_main);



		mPagerTab = (PagerTab) findViewById(R.id.pager_tab);

		mViewPager = (ViewPager) findViewById(R.id.vp_pager);



		mViewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));



		mPagerTab.setViewPager(mViewPager);//将ViewPager和页签控件绑定在一起

	}



	class MyPagerAdapter extends FragmentPagerAdapter {



		private String[] mTabNames;// 页签名称集合



		public MyPagerAdapter(FragmentManager fm) {

			super(fm);

			mTabNames = UIUtils.getStringArray(R.array.tab_names);

		}



	   /**

		 *加载每个页签标题

		 *

		 *页签标题要想显示出来,就要重写PagerAdapter的getTitle这个方法,将标题返回回去

		 *

		 */

		@Override

		public CharSequence getPageTitle(int position) {

			return mTabNames[position];

		}



		// 根据页面位置,加载对应的Fragment对象,

			将onCreateView方法的返回view填充给ViewPager 此方法类似PagerAdapter的instantiateItem方法

		@Override

		public Fragment getItem(int position) {

			System.out.println("getitem-->" + position);

            // 从工厂类中生产Fragment并返回	

			return FragmentFactory.createFragment(position);

		}



		// 加载fragment个数

		@Override

		public int getCount() {

			return mTabNames.length;

		}

	}

	}



注意:以前MyPagerAdapter继承PagerAdapter时,是因为viewPager中是广告条之类的东西,都是一个一个的图片或页面,现在准备在ViewPager中塞一个又一个fragment 所以应该继承FragmentPagerAdapter

Day01 06.使用Fragment填充viewpager ##

页签标题以字符串数组的形式写在values下的Strings中,如下:

<string-array name="tab_names">

    <item>首页</item>

    <item>应用</item>

    <item>游戏</item>

    <item>专题</item>

    <item>推荐</item>

    <item>分类</item>

    <item>排行</item>

</string-array>

Fragment抽取

注意:每一个页面都是一个fragment,fragment根据它的位置(position)返回的都不一样,但是他们都是fragment,所以可以抽取一个基类BaseFragment



	public class BaseFragment extends Fragment {



		//初始化fragment布局

		@Override

		public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

			//先随便添加一个布局 否则调试时pagerTab滑动不正常

			TextView view = new TextView(UIUtils.getContext());

			view.setText(this.getClass().getSimpleName()); //当前类名是什么就显示什么文字

			return view;

		}

	}



	分别实现子类HomeFragment(首页),AppFragment(应用),GameFragment(游戏),SubjectFragment(专题),RecommendFragment(推荐),CategoryFragment(分类),HotFragment(排行)

Fragment工厂

在创建Fragent时,到底怎么决定创建哪个Frament呢?可以写一个Frament工厂类,来帮助我们生产Fragment

	/**
  • Fragment工厂类

  • 生产Fragment
     */
     public class FragmentFactory {

      	4.// 保存Fragment集合,方便复用 
    
      	不用每次加载ViewPager的MyPagerAdapter中的getItem方法时 都重新创建一个新的Fragment 下次再用时直接从这个HashMap集合中去取 <键,值>
    
      	private static HashMap<Integer, BaseFragment> mFragmentMap = new HashMap<Integer, BaseFragment>();
    
      
    
      	// 1.根据指针位置,生产相应的Fragment
    
      	public static BaseFragment createFragment(int position) {
    
      		
    
      		//3.最后要把这个Fragent返回回去,所以声明一个BaseFragment
    
      			//此时先从HashMap集合(缓存)中看是否之前创建过Fragment
    
      			根据键查找值,每个键就是position
    
      		BaseFragment fragment = sFragmentMap.get(position);
    
      		//5.判断HashMap中对应的Fragment是否为空
    
      		if (fragment == null) {  //没有创建过 才开始创建
    
      		   //2.根据位置,判断它是第几个Frament 就生产第几个Fragment
    
      			switch (position) {
    
      			case 0: //首页的Fragment
    
      				fragment = new HomeFragment();
    
      				break;
    
      			case 1: //应用的Fragment
    
      				fragment = new AppFragment();
    
      				break;
    
      			case 2: //游戏的Fragment
    
      				fragment = new GameFragment();
    
      				break;
    
      			case 3:	//专题的Fragment
    
      				fragment = new SubjectFragment();
    
      				break;
    
      			case 4: //推荐的Fragment
    
      				fragment = new RecommendFragment();
    
      				break;
    
      			case 5: //分类的Fragment
    
      				fragment = new CategoryFragment();
    
      				break;
    
      			case 6: //排行的Fragment
    
      				fragment = new HotFragment();
    
      				break;
    
      
    
      			default:
    
      				break;
    
      			}
    
      			//不为空(创建过)时,将Fragment添加到HashMap集合(缓存)
    
      			sFragmentMap.put(position, fragment);
    
      		}
    
      
    
      		return fragment;
    
      	}
    
      }
    

运行程序

第一次运行出现崩溃, 要求添加主题AppCompat. 这是因为引入了AppCompat包来实现Actionbar效果, 该包要求必须添加AppCompat相关的主题

		 <activity

            android:name="com.itcast.googleplayteach.MainActivity"

            android:label="@string/app_name"

            android:theme="@style/Theme.AppCompat.Light" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />



                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>	

Day01 07.LoadingPage加载状态分析&自定义进度条 ##

加载页面(LoadingPage) ##

使用Fragment填充完ViewPager之后,接下来考虑实现每个fragment页面

每个fragment页面不太一样,但是我们可以抽取一些共性,让子类实现起来比较容易

总结加载页面过程中可能出现的几种状态

未加载 --什么也没有干时

正在加载 --有网络或服务器启动后,进度条表示正在加载中 > 加载失败 --没有网络时或服务器关闭时 > 数据为空 --服务器异常时,返回空数据

访问成功 --加载成功时,显示数据即可

将上边抽取的5条共性写一个自己定义的类来分别处理这些状态,这5种状态可以用一个空的帧布局来表示,这个空的自定类布局为LoadingPage.java



每一个Fragment都要用到LoadingPage这个布局,所以让BaseFragment来统一处理就行

BaseFragment依靠LoadingPage来加载布局

	public class BaseFragment extends Fragment {

	

		@Override

		public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

			//		TextView textView = new TextView(UIUtils.getContext());

           //		textView.setText(this.getClass().getSimpleName());

			LoadingPage view = new LoadingPage(UIUtils.getContext());

			return view;

		}

	}

正在加载

	public class LoadingPage extends FrameLayout {

	

		private View mLoadingView;

	

		//有三个构造方法 不用布局文件而是使用代码去new时 使用这个构造方法即可

		public LoadingPage(Context context) {

			super(context);

			initView();

		}



		//初始化布局

		private void initView() {

			// 正在加载  添加到帧布局中

			if (mLoadingView == null) { //防止View添加好几次

				mLoadingView = onCreateLoadingView();

				addView(mLoadingView); //将当前的状态添加到LoadingPage

			}

		}

	

		/**
  • 正在加载 的布局
     *
  • 进度条
     */
     private View onCreateLoadingView() {
     return UIUtils.inflate(R.layout.layout_loading);
     }
     }

自定义Progressbar

这个效果其实是两个进度条重合在一块,分别正向和逆向旋转形成的

layer-list在做进度条时一般会用到,表示一层一层的往上压 item分别代表每层

	----------------------------------------------

	进度条的布局

	layout_loading.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="match_parent"

android:gravity="center"

android:orientation="vertical" >



<ProgressBar

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:indeterminateDrawable="@drawable/custom_progress" />

</LinearLayout>



注意:加载进度条的状态选择器使用的是indeterminateDrawable

	----------------------------------------------

	进度条的状态选择器  

	custom_progress.xml



	<?xml version="1.0" encoding="utf-8"?>

	<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

	

	    <!-- 正向旋转的圆,速度快 -->

	    <item>

		            <!--表示从0度旋转到720度 旋转的中心点为居中-->

	        <rotate

	            android:drawable="@drawable/spinner_big_inner"

	            android:fromDegrees="0"

	            android:pivotX="50%"

	            android:pivotY="50%"

	            android:toDegrees="720" />

	    </item>

	

	    <!-- 逆向旋转的圆,速度慢 -->

	    <item>

	        <rotate

	            android:drawable="@drawable/spinner_big_outer"

	            android:fromDegrees="360" 

	            android:pivotX="50%"

	            android:pivotY="50%"

	            android:toDegrees="0" />

	    </item>

	  

	</layer-list>

Day01 08.LoadingPage不同状态布局实现 ##

加载失败&数据为空
 同样在LoadingPage中实现

	/**
  • 初始化布局
     */
     private void initView() {
     // 加载失败 添加到帧布局中
     if (mErrorView == null) { //防止View添加好几次
     mErrorView = onCreateErrorView();
     addView(mErrorView);
     }

      	// 数据为空 添加到帧布局中
    
      	if (mEmptyView == null) { //防止View添加好几次
    
      		mEmptyView = onCreateEmptyView();
    
      		addView(mEmptyView);
    
      	}
    
      }
    
      ----------------------------------------
    
    
    
      /**
    
  • 加载失败 的布局
     */
     private View onCreateErrorView() {
     return UIUtils.inflate(R.layout.layout_error);
     }

      /**
    
  • 数据为空 的布局
     */
     private View onCreateEmptyView() {
     return UIUtils.inflate(R.layout.layout_empty);
     }

      ------------------------------------------
    
      加载失败的布局文件 layout_error.xml
    
    
    
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    
          android:layout_width="match_parent"
    
          android:layout_height="match_parent"
    
          android:gravity="center"
    
          android:orientation="vertical" >
    
      
    
          <ImageView
    
              android:id="@+id/iv_error"
    
              android:layout_width="wrap_content"
    
              android:layout_height="wrap_content"
    
              android:src="@drawable/ic_error_page" />
    
      
    
          <Button
    
              android:id="@+id/btn_retry"
    
              android:layout_width="wrap_content"
    
              android:layout_height="wrap_content"
    
              android:background="@drawable/btn_selector"
    
              android:paddingLeft="10dp"
    
              android:paddingRight="10dp"
    
              android:textColor="#000"
    
              android:text="加载失败,请重试" />
    
      
    
      </LinearLayout>
    
    
    
      按钮的状态选择布局文件 btn_selector.xml
    
    
    
      <?xml version="1.0" encoding="utf-8"?>
    
      <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
    
    
      <item android:drawable="@drawable/btn_pressed" android:state_pressed="true"></item>
    
      <item android:drawable="@drawable/btn_normal"/>
    
    
    
      </selector>
    
    ---------------------------------------------
    
      数据为空的布局文件 layout_empty.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="match_parent"
    
      android:gravity="center"
    
      android:orientation="vertical" >
    

    <ImageView

      android:id="@+id/iv_empty"
    
      android:layout_width="wrap_content"
    
      android:layout_height="wrap_content"
    
      android:src="@drawable/ic_empty_page"
    
      />    
    

    运行程序,会发现 正在加载 加载失败 数据为空 的布局文件层叠在一起 而我们需要根据状态决定哪个显示哪个隐藏。

根据当前状态,展示相应页面

同样在LoadingPage中实现



	//定义这5种状态以及当前状态

	private static final int STATE_UNLOAD = 0;// 未加载

	private static final int STATE_LOADING = 1;// 正在加载

	private static final int STATE_LOAD_EMPTY = 2;// 数据为空

	private static final int STATE_LOAD_ERROR = 3;// 加载失败

	private static final int STATE_LOAD_SUCCESS = 4;// 访问成功



	private int mCurrentState = STATE_UNLOAD;// 当前状态 默认为未加载



	private void initView() {

		

		showRightPage(); //将正在加载 数据为空 加载失败添加到布局后 再调用此方法 该显示的显示 该隐藏的隐藏



	}



	/**
  • 根据当前状态,展示正确页面
     */
     private void showRightPage() {

      	/*if(mCurrentState == STATE_LOADING || mCurrentState == STATE_UNLOAD){
    
      		mLoadingView.setVisibility(View.VISIBLE);
    
      		} else{
    
      			mLoadingView.setVisibility(View.GONE);
    
      		}
    
      		*/
    
      	//这条语句等价于下边if括号中的这条三元表达式
    
    
    
      	if (mLoadingView != null) {
    
      		//1.如果当前状态为正在加载或未加载,显示正在加载状态的进度条否则隐藏掉
    
      		mLoadingView
    
      				.setVisibility((mCurrentState == STATE_LOADING || mCurrentState == STATE_UNLOAD) ? View.VISIBLE
    
      						: View.GONE);
    
      	}
    
      	
    
      	if (mEmptyView != null) { 
    
      		//2.如果当前状态为数据为空,显示数据为空布局否则隐藏掉
    
      		mEmptyView
    
      				.setVisibility(mCurrentState == STATE_LOAD_EMPTY ? View.VISIBLE
    
      						: View.GONE);
    
      	}
    
      	  
    
      	if (mErrorView != null) { 
    
               // 3.如果当前状态为加载失败,显示加载失败布局否则隐藏掉
    
      		mErrorView
    
      				.setVisibility(mCurrentState == STATE_LOAD_ERROR ? View.VISIBLE
    
      						: View.GONE);
    
      	}
    
    
    
         // 5 如果 访问成功的布局为null 或者当前状态为 访问成功 
    
      		//上边三种状态的添加和
    
      	if (mSuccessView == null && mCurrentState == STATE_LOAD_SUCCESS) {
    
      		//才去初始化 访问 成功布局
    
      		mSuccessView = onCreateSuccessView();
    
      		if (mSuccessView != null) {// 防止子类返回null 空指针异常 
    
      			addView(mSuccessView);
    
      		}
    
      	}
    
    
    
      	if (mSuccessView != null) { // 防止子类返回null 空指针异常
    
      		//6如果当前状态为 访问成功,显示 加载成功布局 否则隐藏掉
    
      		mSuccessView
    
      				.setVisibility(mCurrentState == STATE_LOAD_SUCCESS ? View.VISIBLE
    
      						: View.GONE);
    
      	}
    
      }
    

访问成功
 用public修饰 因为这个方法 LoadingPage是决定不了的 每个Fragment布局不一样 应该由不同的fragment去实现 所以写成一个abstract抽象方法 让子类去实现 (此时LoadingPage也变为abstract修饰)

	/**
  • 4访问成功布局, 子类必须实现
     */
     public abstract View onCreateSuccessView();

    注意:有人有疑问 觉得上边5的mCurrentState已经判断了,会走进去

      	走进去后说明状态为 访问成功 第一次肯定要添加 下边6判断之后肯定要显示 访问成功 状态 
    
      	但有时候状态不是 访问成功 但它可能已经在5添加完成 即已经把这个走了一遍
    
      	后来又掉了一下showRightPage方法 但是掉的时候可能不一定是 访问成功 状态 它不会走进5添加访问成功 而下边6判断之后也就将访问成功状态隐藏掉了 
    

    注意: 防止子类返回null 空指针异常 发生在7个子Fragment实现了onCreateSuccessView但是什么都没来的及做时 简单的return null情况下 运行调试程序时发生的空指针异常

      ---------------------------------------------------
    
    
    
      public abstract class BaseFragment extends Fragment {
    
    
    
      	private LoadingPage mLoadingPage;
    
      
    
      	@Override
    
      	public View onCreateView(LayoutInflater inflater, ViewGroup container,
    
      			Bundle savedInstanceState) {
    
    
    
      	//当LoadingPage中有抽象方法onCreateSuccessView时,BaseFragment就不能new了 必须把它的这个方法实现下来 来初始化 访问成功的布局 但是BaseFragment也不能实现 而是由baseFragment的子类即各种子Fragment来具体实现
    
    
    
      		mLoadingPage = new LoadingPage(UIUtils.getContext()) { 
    
      			@Override //此处实现的是loadingpge的加载成功布局
    
      			public View onCreateSuccessView() {
    
      				//没法实现 所以在此处返回BaseFragment中抽象的onCreateSuccessView方法
    
      				return BaseFragment.this.onCreateSuccessView();
    
      			}
    
      
    
      		};
    
      		return mLoadingPage;
    
      	}
    
    
    
      	//BaseFragment中抽象的onCreateSuccessView方法
    
      	// 由子类实现 访问成功布局 的方法
    
      	public abstract View onCreateSuccessView();
    
      }
    

    注意:

      LoadingPage觉得它没法决定onCreateSuccessView,必须让孩子去决定 当然她现在没有孩子,而只是有一个使用者BaseFragment 即BaseFragment调用她初始化她了 
    
      
    
      所以决策权又给了BaseFragment 即谁用人家LoadingPage就必须实现人家的onCreateSuccessView方法 这里实现的方式是 new的时候去实现
    
      但是BaseFragement也是不能决定这个 访问成功的布局如何展现 所以他还是要委托他的子类去展现 
    
      
    
      既然要让子类去展现 那就要在BaseFragment中再创建一个onCreateSuccessView方法 让他的孩子来实现 实现完之后 BaseFragment直接掉他孩子的这个方法就可以了  所以new实现后再 return BaseFragment.this.onCreateSuccessView();
    
      
    
      而BaseFragment子类必须实现BaseFragment定义的onCreateSuccessView()
    

Day01 09.LoadingPage加载数据逻辑&枚举 ##

加载网络数据
 上边只是实现了下刚开始进来的状态效果 实际上希望加载数据显示出来 不加载数据的话肯定显示不出来 一直停留在进度条状态

	LoadingPage.java



	/**
  • 加载数据
     */
     public void loadData() {
     // 1.当前状态清零(重置当前状态)
     if (mCurrentState == STATE_LOAD_EMPTY
     || mCurrentState == STATE_LOAD_ERROR
     || mCurrentState == STATE_LOAD_SUCCESS) {
     mCurrentState = STATE_UNLOAD; 
     }
     //2进行判断 当状态为未加载时
     if (mCurrentState == STATE_UNLOAD) {
     // 3开始加载数据 异步加载网络数据
     new Thread() {
     @Override
     public void run() {

      				// 4在这里进行加载数据的操作 加载网络数据 返回一个状态 就可以根据状态决定展示什么页面 
    
    
    
      				final ResultState mState = onLoad();
    
    
    
      				// 必须在主线程更新界面 运行在主线程 即5,6相当于是在更新主界面
    
      				UIUtils.runOnUiThread(new Runnable() {
    
    
    
      					@Override
    
      					public void run() {
    
      						if (mState != null) { //state为空时会报空指针异常 
    
      							// 5根据网络加载返回结果,更新当前状态,刷新界面
    
      							mCurrentState = mState.getState();
    
      							// 6更新当前页面 根据state来决定到底应该显示哪个页面
    
      							showRightPage();
    
      						}
    
      					}
    
      				});
    
      			}
    
      		}.start();
    
      	}
    
      }
    
    
    
      /**
    
  • 加载网络数据,必须子类实现
     *

  • @return 返回加载状态
     */
     public abstract ResultState onLoad();

      /**
    
  • 使用枚举表示访问网络的几种状态
     */
     public enum ResultState {
     //1枚举
     STATE_SUCCESS(STATE_LOAD_SUCCESS), // 访问成功
     STATE_EMPTY(STATE_LOAD_EMPTY), // 数据为空
     STATE_ERROR(STATE_LOAD_ERROR);// 访问失败

      	//3声明state
    
      	private int state;
    
      	
    
      	//2ResultState类(枚举)的构造方法 修饰符private 它不允许额外的new出一些东西 只允许自己在内部new 内部去new是通过三个内部类的方式
    
      	private ResultState(int state) {
    
      		this.state = state;
    
      	}
    
      	
    
      	//4公开一个方法去获取State
    
      	public int getState() {
    
      		//将state return回去
    
      		return state;
    
      	}
    
      }
    

    注意:1.当前状态清零 即将 数据为空 加载失败 访问成功 的时候 都是可以准备要加载(未加载) 即加载之前把当前的状态置为STATE_UNLOAD

      不把正在加载STATE_LOADING置为未加载 是因为已经开始加载了,没有必要再去加载了
    

    注意:一般加载数据 都是加载网络数据 加载网络数据一般都需要去开启一个子线程去请求网络数据

    注意:加载网络数据的逻辑 也是属于子类去实现的 因为LoadingPage页面根本不知道你要调什么接口 应该怎么加载 所以需要写一个加载网络数据的抽象方法onLoad 让子类实现

    加载数据完了之后 应该在界面进行展现 但是这时候加载数据可能出现 失败 成功 数据为空的状态 所以需要根据状态来刷新界面

    所以需要知道在网络加载完之后 你需要给我传回来到底是什么状态 所以在onLoad方法中添加返回值ResultState 用枚举来封装它的返回值

    枚举可以定义它的不同的状态 用分号表示枚举结束了 枚举本质意义是 ResultState是一个类

    而STATE_SUCCESS STATE_EMPTY STATE_ERROR 这三个什么类型都不是 分别是一个一个匿名内部类的名称 枚举用这种简单的方式把他们声明了出来 它们三个并不是整数也不是一个而是一个对ResultState实现的内部类

    是一个类的话 可以给类写构造方法 所以给ResultState类写一个构造方法 给它传一个当前这三个枚举所对应的状态 上边已经定义了五种状态 到底是哪种需要传过来

    一般情况下我们要new一个类 它有一个默认的无参构造方法 我们不需要传任何参数直接可以new 一旦我给一个类加了带参的构造方法之后 这时候你在new这个类的时候 必须把这个构造方法的参数传进去 三个枚举报错是因为 它们本质是对ResultState类的匿名的实现 实现完了之后必须把state这个构造方法参数给它传进去 它们才不会报错

    举例说明枚举:

    新建一个类Person.java如下

      /*
    
      	 //类似枚举原理
    
      	public class Person {
    
      		public Person P1 = new Person(1);
    
      		public Person P2 = new Person(2);
    
      		public Person P3 = new Person(3);
    
      		
    
      		//构造方法
    
      		public Person(int age) {
    
      		}
    
      	}*/
    
      有时候我们希望在Person类中声明几个对象 比如p1 p2 p3 这个就和刚才枚举中写的STATE_SUCCESS STATE_EMPTY STATE_ERROR 有点类似 即STATE_SUCCESS就是p1, STATE_EMPTY就是p2,STATE_ERROR就是p3
    
      只不过枚举的方式 它可以把public Person = new Person都可以省略掉
    
    
    
      这时候如果给Person加一个构造带参的方法 上边声明的三个对象p1 p2 p3 就会报错 即必须传 它的年龄 
    
      
    
      枚举不一定一直得写 了解即可 有时候表示一个状态 不用用枚举也行 几个整数就表示了 
    
      用枚举是因为感觉更专业一些 android源码中偶尔会用到枚举表示某几种状态
    
      用整数的话 那个值可能别人瞎改 比如说定义个0 1 2 3 但是传个 7 8 9 10 过来也能接收 项目也不报错 但用枚举的话 你必须从我这个地方引入值
    
      ------------------------------------------------------------
    
    
    
      BaseFragment.java
    
    
    
      @Override
    
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
    
      		Bundle savedInstanceState) {
    
      	mLoadingPage = new LoadingPage(UIUtils.getContext()) {
    
      		
    
      		//加载成功 的布局
    
      		@Override
    
      		public View onCreateSuccessView() {
    
      			return BaseFragment.this.onCreateSuccessView();
    
      		}
    
      		
    
      		//加载数据
    
      		@Override
    
      		public ResultState onLoad() {
    
      			return BaseFragment.this.onNetLoad();
    
      		}
    
    
    
      	};
    
      	return mLoadingPage;
    
      }
    
    
    
      // 由子类实现创建布局的方法
    
      public abstract View onCreateSuccessView();
    
    
    
      // 由子类实现加载网络数据的逻辑, 该方法运行在子线程
    
      public abstract ResultState onNetLoad();
    
    
    
      // 开始加载网络数据
    
      public void loadNetData() {
    
      	if (mLoadingPage != null) {
    
      		mLoadingPage.loadData();
    
      	}
    
      }
    

    注意: 在LoadingPage中写了加载数据的loadData方法 在这个方法中才开始调用加载网络数据的抽象方法onLoad 所以loadData方法必须有人去调用 否则onLoad方法永远没有人去调用 现在没有人去调用loadData方法,但是BaseFragment用到了LoadingPage,所以可以让BaseFragment去调用loadData 所以要在BaseFragment中定义一个方法也叫做loadData(当然可以随便起名),在它里面调用一下mLoadingPage的loadData方法 就可以加载数据了 当然最后为了保险起见,可以判断下mLoadingPage是否为空 在实际开发中我们一般也这样 即用到外边的变量 你可能觉得不会为空 但最好判断下 省的到时候万一报空指针错误

    在BaseFragment中定义完加载页面数据的loadData方法之后 谁去调用呢

      那就看谁用到了baseFragment,MainActivity用到了BaseFragment MainActivity中塞的是一个又一个BaseFragment 即 在ViewPager的MyPagerAdapter的getItem中
    

    页面要初始化

    而加载数据的时机是希望它先到当前页面再加载当前页面的数据

    但是实际上ViewPager是默认会把下一页也加载进来 为了不让ViewPager加载下一页

    可以监听ViewPager的页面切换事件,只要切换到那个页面 我们再去调loadData的方法

    因为ViewPager和PagerTab绑定在一起了 所以给PagerTab设置就行

      ----------------------------------------------
    

Day01 10.加载网络数据测试 ##

	MainActivity.java



	mPagerTab.setOnPageChangeListener(new OnPageChangeListener() {



		@Override

		public void onPageSelected(int position) {

			//3.打印一下日志 测试数据是否加载上了

			System.out.println("加载数据:" + position);

			//1.根据position拿到它对应的fragment 对比MyPagerAdapter的getItem可知 是Fragment工厂中去取的

			BaseFragment fragment = FragmentFactory

					.createFragment(position);

			//2.加载页面数据

			fragment.loadNetData();

		}



		@Override

		public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

		}



		@Override

		public void onPageScrollStateChanged(int state) {

		}

	});



注意:在调用v4包的setOnPageChangeListener时因为没有关联源码 所以参数会出现arg0,arg1,arg2等表示不明确的参数名 所以需要关联源码 但绑源码还不太好绑,因为还有v7包 所以可以和智慧北京一样,给它一个配置文件 android-support-v4.jar.properties放在/android-support-v7-appcompat/libs/下 重启eclipse即可



其实配置文件android-support-v4.jar.properties中是sdk中v4源码的路径 所以应该换成自己sdk中v4的路径 才能使用 



注意:3.打印一下日志 测试数据是否加载上了时 退出应用 第一次进入后 从logcat可以看出,首页默认会走一遍加载数据0, 这个和之前项目不一样 因为之前首页页面没有切 就不会把首页页面数据自动加载  以前需要手动强制加载一下 

这个项目自动加载是因为mPagerTab已经做了一些这方面的封装  这里又是给mPagerTab设置OnPageChangelistener 所以让我们无需手动的对第一页初始化 它也会在刚开始的时候手动的调用一下onPageSelected 让我们一上来就可以加载数据0 这样我们就不用自己手动的去加载数据0



注意:我们的数据到底该怎么加载呢?

我们在MainActivity的PagerTab的setOnPageChangeListener中的onPageSelected中调的是BaseFragment中的loadNetData 而BaseFragment中的loadNetData掉的是LoadingPage中的loadData  LoadingPage中的loadData调的是onLoad这个方法来加载数据  而onLoad这个方法是BaseFragment来实现的 而BaseFragment的onLoad又是由BaseFragment定义的onNetLoad来实现的 而BaseFragment定义的onNetLoad又是由每一个子Fragment来实现的 



所以当应用的页面一切换 它一调用onPageSelected中的loadNetData 那就会马上调到当前页面的onNetLoad方法来加载数据 

每个子Fragment中的onNetLoad就称为加载网络数据的回调方法 在它里面就可以做一些耗时操作来访问网络 这里访问网络就没必要new一个子线程去访问网络了 因为这个onNetLoad就是LoadingPage的loadData方法中的子线程中去调用的 本身就运行在子线程中 

----------------------------------------------------------

Day01 11.加载失败后重试 ##

加载失败,点击重试

LoadingPage.java



	// 加载失败

	if (mErrorView == null) {

		mErrorView = onCreateErrorView();

		// 点击重试

		mErrorView.findViewById(R.id.btn_retry).setOnClickListener(

				new OnClickListener() {



					@Override

					public void onClick(View v) {

						loadData();

					}

				});

		addView(mErrorView);

	}

测试不同状态的界面展示效果

	/**
  • 首页
     *

  • @author Kevin
     * 
     */
     public class HomeFragment extends BaseFragment {

      	//当onNetLoad如果返回的结果是成功,此方法就会回调 当返回的结果是加载失败或数据为空时 此方法就不用回调 而是由LoadingPage的showRightPage方法中已经处理好显示哪种状态的页面
    
      	
    
      	@Override
    
      	public View onCreateSuccessView() {
    
      		先随便添加一个布局 否则调试时 加载成功 布局 异常
    
      		TextView view = new TextView(UIUtils.getContext());
    
      		view.setText(this.getClass().getSimpleName()); //当前类名是什么就显示什么文字
    
      		return view; //返回的这个view将添加给空的帧布局
    
      	}
    
      	
    
      	//加载网络数据的回调方法 此方法本身就运行在子线程,无需new新的线程
    
      	@Override
    
      	public ResultState onNetLoad() {
    
      		return ResultState.STATE_SUCCESS; //在这里先写死为加载成功
    
      	}
    
      
    
      }
    
    
    
      ------------------------------------
    
    
    
      /**
    
  • 应用

  • @author Kevin
     *
     */
     public class AppFragment extends BaseFragment {

      	@Override
    
      	public View onCreateSuccessView() {
    
      		return null;
    
      	}
    
      
    
      	@Override
    
      	public ResultState onNetLoad() {
    
      		return ResultState.STATE_EMPTY;  //在这里先写死为数据为空
    
      	}
    
      
    
      }
    
    
    
      --------------------------------------
    
    
    
      /**
    
  • 游戏

  • @author Kevin
     *
     */
     public class GameFragment extends BaseFragment {

      	@Override
    
      	public View onCreateSuccessView() {
    
      		// TODO Auto-generated method stub
    
      		return null;
    
      	}
    
      
    
      	@Override
    
      	public ResultState onNetLoad() {
    
      		return ResultState.STATE_ERROR;  //在这里先写死为加载失败
    
      	}
    
      
    
      }
    

Day01 12.总结 ##

总结:



谷歌电子市场的框架比较复杂 但我觉得还是没有智慧北京复杂 智慧北京至少页面太多 而谷歌电子市场就一个页面 然后用几个Fragment就搞定了



首先写了 BaseApplication 



然后写了一个UIUtils 



然后加了一个新的布局叫PagerTab 

PagerTab刚导入进来会报一些错 



比如它没有BaseActivity 那我们就创建一个BaseActivity 让它继承ActionBarActivity 

	

要继承ActionBarActivity 必须先把supportv7包导进来 才能继承

( 2.x版本是没有actionbar这样一个东西的 继承它的作用是为了Actionbar版本的兼容 目前来讲的话 其实actionbar就是我们应用的标题栏) 



这时候要运行的话需要给ActionBar加一个主题 它才不会奔溃 即给清单文件中的Application增加如下:

android:theme="@style/Theme.AppCompat.light"



它又需要一些资源文件 

比如drawable包下的 bg_tab_text.xml 

    color包下的tab_text_color.xml 我们都导入了进来



然后在activity_main.xml的布局文件中把PagerTab配置一下 

设置它的高度42dp 背景图片bg_tab

然后在写了一个ViewPager布局



然后在主页面MainActivity中把mPagerTab和mViewPager绑定在一起

给mPagerTab去设置页面监听



这时候我们的ViewPager的数据怎么填充呢 我们是重写了一个getItem

即重写getItem根据它当前的位置给它返回一个Fragment对象



Fragment对象又是通过一个工厂类去创建不同的Fragment把它返回回去

同时给它加了一个缓存(HashMap)



今天讲的比较复杂的地方就是刚才说的 要写Fragment 先写一个BaseFragment 



而BaseFragment有了之后 考虑到有好几种状态 所以用一个帧布局也就是LoadingPage 把它封装起来 



那BaseFrament中只需要去

new 一个LoadingPage 这个LoadingPage它就会自己根据它当时的状态去显示它到底应该是什么界面 



一个Fragment要去 写它的布局 必须重写它的onCreateView 而我们现在只需要让它的爹BaseFragment把onCreateView做处理 我们的子类Fragment就不用去做任何的处理 只需要重写onCreateSuccessView和onLoad方法就可以 



再回到LoadingPage中 首先去掉了一下initView  那initView中把加载中 数据为空 加载失败都初始化好 分别用addView添加到布局中 然后在initView中调用showRightPage方法 根据它当时的状态 去显示不同的布局  



而成功的布局必须由子类去实现 BaseFrament是它的调用者 BaseFragment是爹 也不知道怎么实现 所以由baseFragment自己写一个这样的方法 让它的子类去实现 而加载网络数据onLoad也是一样 



最后我们再梳理一下 我们的流程

MainActivity中先切换Fragment页面 

切换页面后调Fragment的loadNetData  

Fragment的loadNetData掉的是LoadingPage的loadData 

LoadingPage的loadData new了一个子线程调用onLoad



onLoad的话 有谁去实现的呢 由我们的BaseFragment去实现 BaseFragment实现不了 它又自己写了一个onLoad 由BaseFragment的子类HomeFragment去实现  然后返回一个LOAD_SUCCESS这样一个结果 



而这个返回结果 会返回到LoadingPage的子线调用的onLoad处的state

然后根据这个state结果 去getState决定我们的界面该怎么展现

即mCurrentState等于LOAD_SUCCESS时showRightPage时 会走到success中来 而我们又掉了下onCreateSuccessView 而它又由BaseFragment来实现的 它又由它的子类去实现 它的子类HomeFragment一旦访问成功 都会走到onCreateSuccessView这个方法中来 



就说我们绕了好几圈 所以理解代码的话 就像上边的流程一样去理解 



一点一点跟踪下去 看她调了什么 掉完了之后再看它上边是怎么实现的 

这个结构你可能自己写不出来 但是至少把这个代码看懂
  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值