GooglePlay Day02

谷歌电子市场笔记Day02 #

Day02 01.昨天内容总结 ##

Day02 02.对BaseAdapter的封装 ##

首页布局实现 ##

首页希望用一个ListView来替换掉 

使用ListView加载假数据

	HomeFragment.java

	

	//3ListView测试数据

	ArrayList<String> mList = new ArrayList<String>();	



	@Override

	public View onCreateSuccessView() {

		//1

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

		initData();

		//5

		view.setAdapter(new HomeAdapter());

		return view;

	}

	//2初始化数据

	private void initData() {

		for (int i = 0; i < 50; i++) {//小于50条数据

			//给ArrayList中添加50条测试数据

			mList.add("测试数据" + i);

		}

	}

	//4

	class HomeAdapter extends BaseAdapter {



		@Override

		public int getCount() {

			return mList.size();

		}



		@Override

		public String getItem(int position) {

			return mList.get(position);

		}



		@Override

		public long getItemId(int position) {

			return position;

		}



		@Override

		public View getView(int position, View convertView, ViewGroup parent) {

			//2

			ViewHolder holder = null;

			//1 

			if (convertView == null) {

				//  加载布局

				convertView = View.inflate(UIUtils.getContext(),

						R.layout.list_item_home, null);

				 

				holder = new ViewHolder();

				// 初始化控件

				holder.tvContent = (TextView) convertView

						.findViewById(R.id.tv_content);

				//设置tag

				convertView.setTag(holder);

			} else {

				holder = (ViewHolder) convertView.getTag();

			}

			//设置数据 

			holder.tvContent.setText(getItem(position));

			

			return convertView;

		}

	}



	static class ViewHolder {

		public TextView tvContent;

	}



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

listView item的布局



list_item_home.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:orientation="vertical" >



<TextView 

    android:id="@+id/tv_content"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:textSize="18sp"

    />

</LinearLayout>



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



现在希望把上边这个ListView进行一个封装 为什么要封装呢 是因为发现有好多代码写的很烦 比如说getCount getItem getItemId getView 这些 我们用一个父类将它封装起来 这样就不用频繁的每次去写了   所以写一个类继承BaseAdapter即可完成

对BaseAdapter的抽取封装

MyBaseAdapter.java



	/**
  • 数据适配器的基类
     *

  • @author Kevin
     * 
     */
     public class MyBaseAdapter extends BaseAdapter {
     //2
     private ArrayList list;
     //1
     public MyBaseAdapter(ArrayList list) {
     this.list = list;
     }

      	@Override
    
      	public int getCount() {
    
      		return list.size();
    
      	}
    
      	
    
      	//返回的是一个T
    
      	@Override
    
      	public T getItem(int position) {
    
      		return list.get(position);
    
      	}
    
      
    
      	@Override
    
      	public long getItemId(int position) {
    
      		return position;
    
      	}
    
      
    
      	@Override
    
      	public View getView(int position, View convertView, ViewGroup parent) {
    
      		return null;
    
      	}
    
      }
    

    注意: MyBaseAdapter根本就不知道子类的ArrayList具体有多少条数据

      所以以构造方法的方式传过来就可以了 参数为ArrayList<T> list
    

    这时候又有问题了 MyBaseAdapter怎么知道子类的ArrayList具体是什么类型呢

    所以用范型T来替代 报错是因为范型要在它的类上也声明一下

    1.在MyBaseAdapter中的全局变量上写一个private ArrayList list;

    这个list即为构造方法中的this.list

    把范型就理解为类去用 这个类就叫做T 只不过要用去申明一下 这个类你不确定到底是什么类型

    2.写完上边的MyBaseAdapter之后 HomeAdapter就不用继承BaseAdapter了 改为继承MyBaseAdapter 这里T是String 所以改为String

    这时候它报错是因为它没有实现父类的构造方法

    3实现过来之后 new HomeAdapter()报错

    这是因为父类MyBaseAdapter需要一个具体的list参数 所以把mList传过去 即为 new HomeAdapter(mList) 即通过构造方法的super(list)传给了父类MyBaseAdapter在全局变量上声明的list

    4 将HomeAdapter中不需要的getCount getItem getItemId删除掉


Day02 03.使用BaseHolder对getView进行封装 ##

上边对BaseAdapter抽取封装之后 减少了getCount getItem getItemId的重复去写 但是getView还是要写



分析: getView方法中每次都要写ViewHolder 每次都要写convertView去inflate一个布局 每次都去findViewById 每次都给setTag 每次都要去更新数据



所以可以尝试不用每次重复去写getView方法 让它的父类去做 但是这个很难封装 因为你看 每个ListView加载的布局都不一样  findVIewById的个数也不一样 设置数据更不一样 包括ViewHolder中控件多少都不一样  



解决办法:原来的ViewHolder只是不能findViewById 给ViewHolder再增加一些更高端的功能  写一个基本的BaseHolder 在BaseHolder中把能做的事情全都塞进去封装起来   

对BaseHolder的抽取封装

BaseHolder.java

	

	/**
  • ViewHolder的基类
     *

  • 此类实现了以下功能

    1. 初始化item布局
    1. findViewById方法(由子类在初始化布局时实现)
    1. 给view设置tag
    1. 刷新界面
       *
  • 此类相当于是对getView方法的封装

  • @author Kevin
     */
     public abstract class BaseHolder {

      	private View mRootView;// item的布局对象(根布局)
    
      	//6 item对应的数据 放在全局变量处
    
      	private T data; //item的数据
    
      	
    
      	//1构造方法
    
      	public BaseHolder() {
    
      		// 3初始化布局
    
      		mRootView = initView();
    
      		//4给view设置tag this指就是这个BaseHolder
    
      		mRootView.setTag(this);
    
      	}
    
      	// 2初始化布局的方法 必须由子类实现 对应于getItem中的加载布局&&初始化控件
    
      	public abstract View initView();
    
      
    
      	// 9返回布局对象
    
      	public View getRootView() {
    
      		return mRootView;
    
      	};
    
      
    
      	// 5设置数据
    
      	public void setData(T data) {
    
      		this.data = data;
    
      		//8一有数据 马上刷新
    
      		refreshView(data);
    
      	}
    
      
    
      	// 7获取数据
    
      	public T getData() {
    
      		return data;
    
      	}
    
      
    
      	// 8刷新界面,更新数据,子类必须实现
    
      	public abstract void refreshView(T data);
    
      }
    

    BaseHolder步骤:

      1.首先写一个构造方法BaseHolder
    
      2.然后必须初始化布局 对应getView中的 加载布局和初始化控件 即 inflate和findViewById 
    
      基类BaseHolder根本不知道加载什么布局 所以添加abstract修饰 让子类去实现 
    
      并返回给基类BaseHolder 即返回类型为View对象
    
      
    
      布局在什么时候初始化呢? 在构造方法BaseHolder中一上来写一个initView方法并写mRootView接收一下 即item的根布局
    
      
    
      上边做的是对应getView中的 加载布局 而getView中的初始化控件 findViewById 步骤可以放在 initView中来 即先inflate一下布局 在方法里边让子类去findViewById就可以初始化 
    
      
    
      所以initView有两个功能 加载布局和初始化控件
    
      3.设置Tag 是要将holder塞给convertView 其实就是我们的View对象吧 怎样在BaseHolder中设置Tag呢  首先 BaseHolder本身是对ViewHolder的封装 就是那个holder 现在要将这个holder塞给我们的convertView convertView其实就是listView中的每个item重用的根布局 即mRootView就是convertView
    
       所以可以写成 mRootView.setTag(this); 
    
    
    
      4.设置数据也希望封装到BaseHolder中 
    
      但BaseHolder中必须要有数据才能设置数据
    
      所以  写方法setData 给View把数据设置过来 
    
      设置什么数据呢 getView中setText(getItem(position));中设置的是getItem里边的String对象 但实际getItem中是统一让他的父类用T来表示 这个T就是我们要的数据 只是不确定什么类型 所以用T来表示 所以为setData(T data) 并在BaseHolder类名上申明T 否则报错 
    
      
    
      需要将数据放在全局的变量里边 所以在写一个 private T data;
    
      接着就可以在setData中写 this.data = data;
    
    
    
      写setData后再写一个getData 当然你可以将private T data; 改为public T data; 到时候直接.data也可以拿到
    
    
    
      setData和getData 只是希望BaseHolder能拿到这个数据  但是我们现在还根本就没有对这个数据 进行更新 即根据这个数据要把页面刷新一下 
    
    
    
      5.写refreshView方法去刷新数据 这个方法才是真正的设置数据 父类决定不了 所以abstract之后 让子类实现 
    
      
    
      refreshView在哪调呢 我有了数据就可以马上掉这个方法进行刷新 所以 在setData中就可以调一下来刷新数据 即一有数据就马上刷新 方便起见 我们可以将data直接传过去 这样让子类去实现方法时 就直接拿到这个data了
    
    
    
      -------------------------------------------
    

    写一个HomeHolder实现BaseHolder

      HomeHolder.java
    
      /**
    
  • 首页holder
     *

  • @author Kevin
     * 
     */
     public class HomeHolder extends BaseHolder {

      	private TextView tvContent;
    
      	
    
      	//加载布局&&初始化控件
    
      	@Override
    
      	public View initView() {
    
      		View view = View.inflate(UIUtils.getContext(), R.layout.list_item_home,
    
      				null);
    
      		tvContent = (TextView) view.findViewById(R.id.tv_content);
    
      		return view;
    
      	}
    
      
    
      	@Override
    
      	public void refreshView(String data) {
    
      		tvContent.setText(data);
    
      	}
    
      }
    

    BaseHolder封装好了 并且子类HomeHolder也实现好了

    接下来就可以在父类MyBaseAdapter的getView方法中去用刚才封装的BaseHolder了

    步骤:1和平时一样 先判断 2这个时候应该new一个holder 我们new的是一个BaseHolder

      3接下来需要写一个(不是继承来的)getHolder方法初始化这个holder获取一个holder对象 因为BaseHolder是一个父类 必须有一个子类去实现它  
    
      在判断里边我们到底该用哪个子类来实现呢 BaseHolder是一个父类 不知道返回的是HomeHolder还是什么 所以要子类去决定到底返回什么holder   
    
      所以 getHolder方法应该abstract 让子类去实现 返回的是一个BaseHolder
    
    
    
      4在判断中写holder = getHolder();而getHolder时 HomeFragment的HomeAdapter是MyBaseAdapter的子类 所以需要实现getHolder方法 而且因为这个是HomeFragment页面 所以应该返回一个new HomeHolder(); 
    
      	
    
      再看new HomeHolder的时候 做了什么事情呢 
    
      那现在HomeHolder中没有实现自己的构造方法 那肯定用的是父类BaseHolder的构造方法 父类BaseHolder的构造方法先去调了一下 initView 即: mRootView = initView(); 在initView中加载了布局和初始化控件 同时它又设置了一个Tag 即:mRootView.setTag(this);
    
      
    
      这些事情在什么时候发生的呢 就是在MyBaseAdapter中new BaseHolder这个对象的时候发生的 那也就是说步骤4 getHolder 其实就是调的new了一个HomeHolder 即new HomeHolder的时候发生了这些事情 
    
    
    
      以上就是在判断中getHolder时 我们做了的一些事情
    
    
    
      5接着在写一个else else中的用法都是一样 因为我们已经设置了Tag 即convertView != null 那这时候再getTag拿下次的holder的时候 就可以从convertView里面去拿  即:
    
      holder = (BaseHolder<T>) convertView.getTag();
    
    
    
      6接下来要刷新数据 我们BaseHolder中已经写了一个方法叫做 setData 其实就是把数据塞进去 然后在刷新一下界面(BaseHolder中在setData方法中调了refresh方法) 设置的这个数据可以通过getItem拿到(即getItem中返回的list.get(position))
    
    
    
      7最后需要返回一个View对象 这个View对象 在BaseHolder中一上来 把这个对象初始化之后 放在了一个全局变量mRootView中了,所以直接返回holder.mRootView
    

    MyBaseAdapter.java

      @Override
    
      public View getView(int position, View convertView, ViewGroup parent) {
    
      	//2 new一个holder
    
      	BaseHolder<T> holder = null;
    
      	//1判断 
    
      	if (convertView == null) {
    
      		//4创建holder对象时 加载了布局 也初始化了控件,设置了tag 
    
      		holder = getHolder();
    
      	} else {
    
      		//5
    
      		holder = (BaseHolder<T>) convertView.getTag();
    
      	}
    
      	//6刷新界面,更新数据
    
      	holder.setData(getItem(position)); //将数据设置给holder,同时刷新界面
    
    
    
      	return holder.getRootView();
    
      }
    
    
    
      // 3 初始化holder  返回BaseHolder的子类,必须实现
    
      public abstract BaseHolder getHolder();
    

    HomeFragement中HomeAdapter原来写的getView方法和ViewHolder就可以删掉了

    此时HomeFragement.java中的HomeAdapter如下:

      class HomeAdapter extends MyBaseAdapter<String> {
    
    
    
      	public HomeAdapter(ArrayList<String> list) {
    
      		super(list);
    
      	}
    
      
    
      	@Override
    
      	public BaseHolder<String> getHolder() {
    
      		return new HomeHolder();
    
      	}
    
      }
    

    调试程序 效果和没有封装时一样 如果有很多个Fragment时 可以节省很多代码量

    而且MyBaseAdapter这个基类写一遍 以后就不用再写了

    以后主要写一个继承了BaseHolder的具体的holder(HomeHolder)

    再在具体的子fragment页面(比如HomeFragment)的继承了MyBaseAdapter的具体Adapter(比如HomeAdapter)中实现getHolder方法返回一个具体的holder(比如return new HomeHolder();)即可

Day02 04.listview加载多种布局类型&添加加载更多布局 ##

注意:如何去封装一个东西?



 在基类中 如果不知道子类是什么类型 那就用T 不知道子类中的方法到底该怎么去做 那就 用抽象方法abstract 让子类去实现 而在基类中所有东西 都必须是基类 



比如在MyBaseAdapter中 用Holder的时候 你肯定不知道人家Holder怎么去实现 所以用BaseHolder 这就是在基本类中里边你要全部用基本类的一些东西 在子类中实现 



如果非得把某个东西抽取出来 那这时候就根据它们的共性  一些方法只能子类去实现的那就写成抽象方法 让子类去实现 

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

上拉“加载更多” 的封装抽取 ##

基于目前ListView的getView的方法 再给它加一个上拉加载更多的功能 智慧北京中已经做过了 



首页 应用 专题 都要有下拉加载  游戏没有实现 推荐 分类 排行不需要上拉加载  



大部分页面都要有上拉加载更多 那就把上拉加载更多封装起来 以后要用到就用一下上拉加载更多的功能即可  



问题:上拉加载更多的逻辑到底该怎么实现?



思路1 在将智慧北京项目时 因为它有一个头布局还有一个脚布局 通过下拉刷新给它加个头布局 下拉加载的时候给它加个脚布局 



思路2 能否就不用脚布局 就用一个普通的加载中的item布局 比如 就在页面的最底端再给它搞个布局 即“圆形进度条”表示加载中的布局  

有人觉得 这个加载中的布局和 人家Listview布局长得不一样 



但我们的listview 是完全可以实现在一个listview中 展现各种各样的 不同布局 比如在分类这个Fragment中 就是一个listview 即分类名称游戏 就是listview的一个item 休闲 棋牌 益智 这一行有人觉得是gridView但其实是listview的一个item  



让ListView展现不同的布局的话 首先 在MyBaseAdapter中去做 MyBaseAdapter一做之后 所有的页面就都可以了 



首先getCount表示数据有多大就返回多少个 但是因为我们加了一个 加载中的布局(不是脚布局 而是一个普通的item布局 只不过加载方式和别的不一样 ) 所以应该+1



然后再去getItem 这个我们不用去做 这个是从数据中去取的 getItemId也不用去理它 getView也先不用去理它 

现在就是数量+1了 但是getView中还是普通布局 即加载的是我们HomeHolder中的list_item_home这个布局  但是有时候我们可能有各种各样的布局 有时候是两种 三种 四种 都有可能 这时候我们怎么告诉我们的 MyBaseAdapter 到底有多少 目前该加载 哪个布局呢 



所以我们现在就需要实现它的两个方法 

getViewTypeCount 这个返回的是布局类型的个数 现在我们是两种类型 普通和加载更多 所以返回2即可

getItemViewType 这个根据布局位置返回当前布局类型



在成员变量处声明下 我们的两种布局类型 ITEM_LORD_MORE ITEM_LIST_VIEW



这时候就可以在getItemViewType中通过根据布局位置判断  返回当前布局类型

 最后一个位置的地方应该是 加载更多 的布局 即getCount()是它的总数量 -1即是最后一个item 



getInnerType 这个方法不写成abstact 是因为子类可以选择不实现这个方法 不实现时默认显示普通布局类型中的LIST_ITEM_NORMAL



这时候在MyBaseAdapter中 getView时会出现一个问题 因为 在getView中getHolder时返回的是HomeHolder HomeHolder中加载的永远是普通布局类型中的list_item_home.xml 即没有写加载更多的布局类型 那要写的话这时候要在if (convertView == null) {下边这去判断 判断是普通类型还是加载更多的布局类型 通过这个返回不同的ViewHolder 如果加载更多你就不能返回为空 



判断可以通过getItemViewType根据布局位置拿到当前布局类型 来判断  



刷新界面更新数据时 要根据当前的数据把界面更新一下 但是作为加载更多的布局 它根本就不用更新数据  它只需要展示或隐藏就可以 所以需要在这加 

if (getItemViewType(position) != LIST_ITEM_MORE) 来判断

加载更多的布局作为listview最后一个item来展现

MyBaseAdapter

	private static final int LIST_ITEM_MORE = 0;// 加载更多 的布局类型

	private static final int LIST_ITEM_NORMAL = 1;// 普通布局类型



	@Override

	public int getCount() {

		return list.size() + 1;// 加载更多布局也占一个位置,所以数量加1

	}

	

	//获取布局类型的个数 子类可以重写此方法 来修改布局类型个数 所以这里个数可以写死

	@Override

	public int getViewTypeCount() { 

		return 2;// 两种布局类型,一种普通布局,一种加载更多的布局

	}

	

	//根据布局位置返回当前布局类型

	@Override

	public int getItemViewType(int position) {

		if (position == getCount() - 1) { //最后一个item

			//加载更多 的布局

			return LIST_ITEM_MORE;

		} else {

			// 返回普通布局类型

			return getInnerType();

		}

	}



	// 普通布局也有可能返回多种类型, 子类可以重写该方法,来修改返回值 这里可以默认显示普通布局类型 //这里不加参数 int position 

	public int getInnerType() {

		return LIST_ITEM_NORMAL; //返回普通布局类型

	}



	@Override

	public View getView(int position, View convertView, ViewGroup parent) {

		//还可以写成 BaseHolder holder = null;

		BaseHolder holder;

		if (convertView == null) {



			//根据当前item的类型来初始化不同的Holder对象

			if (getItemViewType(position) == LIST_ITEM_MORE) {



				// 返回加载更多的Holder对象

				// 因为所有界面加载更多的UI显示效果都是一致的,所以加载更多的业务逻辑可以做详细处理

				holder = new MoreHolder();

			} else {



				// 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag

				holder = getHolder();

			}

		} else {

			holder = (BaseHolder) convertView.getTag();

		}



		if (getItemViewType(position) != LIST_ITEM_MORE) {

			// 刷新界面,更新数据

			holder.setData(getItem(position));

		} 



		return holder.getRootView();

	}

MoreHolder

	/**
  • MoreHolder的数据类型为整数,用来表示加载的几种状态
     */
     public class MoreHolder extends BaseHolder {

      @Override
    
      public View initView() {
    
      	View view = View.inflate(UIUtils.getContext(),
    
      			R.layout.list_item_more, null);
    
      	return view;
    
      }
    
    
    
      @Override
    
      public void refreshView(Integer data) {
    
      }
    

    注意:MoreHolder中需要有一个范型 需要指定一个数据类型 而作为一个 加载更多 没有什么数据 一般item的普通布局可能是一个个对象数据 而加载更多的布局 可能有几种状态 用整形来表示


    加载更多的布局

      list_item_more.xml
    
    
    
      <LinearLayout
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content"
    
          android:gravity="center"
    
          android:orientation="horizontal" >
    
    
    
          <ProgressBar
    
              android:layout_width="wrap_content"
    
              android:layout_height="wrap_content"
    
              android:indeterminateDrawable="@drawable/custom_progress" />
    
    
    
          <TextView
    
              android:id="@+id/textView1"
    
              android:layout_width="wrap_content"
    
              android:layout_height="wrap_content"
    
              android:layout_marginLeft="5dp"
    
              android:text="正在加载..."
    
              android:textColor="#000"
    
              android:textSize="16sp" />
    
       </LinearLayout>
    

运行项目, 发现主页面HomeFragment增加了加载更多的item布局

总结: 我们写了一个listview 而加载中的布局不是用脚布局去写的 它只是listview中的一个item布局 

 一会用普通的布局展示数据 一会又展示加载中的布局 那这个加载中的布局要怎么展示呢

listview展示多种布局类型流程 

0.在MyBaseAdapter中的getCount方法返回的个数+1

1.在MyBaseAdapter中重写getViewTypeCount 返回布局类型个数2

2.在MyBaseAdapter中重写getItemViewType 返回布局类型 

我们现在定义的是LIST_ITEM_MORE = 0; LIST_ITEM_NORMAL = 1;

有些人可能会定义这个类型比较随意 他可能写一个 2 ,3 或者 1,2

但是注意:此处类型必须从0开始



这个问题写错了 你又看不懂 折腾半天 因为你不可能想到这个地方要从0开始 一般人想不到

为什么要从0开始呢?  人家底层就是这样做的 而且getItemViewType这个方法底层可能也要用到 它用到之后 它底层可能是一个数组 或集合 那个东西 都是从0开始的 所以这个地方从1开始不行 

3.在MyBaseAdapter中的getView方法中判断当前布局类型 根据不同类型 加载不同布局

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

Day02 05.加载更多状态分析 ##

加载更多布局状态

	某些页面不需要加载更多数据,所以不能展现加载更多的布局; 

	而某些页面加载布局时有可能失败,这时候应该展示"加载失败,点击重试"的布局

	所以加载更多的布局分如下三种定义状态进行展示:



MoreHolder.java



	public static final int STATE_HAS_MORE = 0; //可以加载更多

	public static final int STATE_NO_MORE = 1; //没有加载更多

	public static final int STATE_ERROR_MORE = 2; //加载更多失败

		

	private LinearLayout llLoadMore; //可以加载更多

	private TextView tvLoadError; //加载更多失败



	public MoreHolder(boolean hasMore){

		// 将加载更多的状态以数据的方式设置进去

		setData(hasMore ? STATE_HAS_MORE : STATE_NO_MORE);

		//setData(STATE_ERROR_MORE); //测试加载更多失败的布局时放开

	}



	@Override

	public View initView() {

	

	View view = UIUtils.inflate(R.layout.list_item_more);

	

	llLoadMore = (LinearLayout) view.findViewById(R.id.ll_load_more);

	tvLoadError = (TextView) view.findViewById(R.id.tv_load_error);

	

	return view;



	}

	

	public void refreshView(Integer data){



		System.out.println("refreshView:" + data);



		//根据不同状态 显示不同布局

		switch(data){

	 	

		case STATE_HAS_MORE:

		  //显示加载更多布局,隐藏加载失败布局

		   llLoadMore.setVisibility(View.VISIBLE);

		   tvLoadError.setVisibility(View.GONE);

			

			break;

		case STATE_NO_MORE:

		   //都不显示

		   llLoadMore.setVisibility(View.GONE);

		   tvLoadError.setVisibility(View.GONE);

			

			break;

	

		case STATE_ERROR_MORE:

			//隐藏加载更多布局,显示加载失败布局

		   llLoadMore.setVisibility(View.GONE);

		   tvLoadError.setVisibility(View.VISIBLE);



			break;



		}

	}



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



定义完加载更多的布局的这三种状态后 这时候我们怎么去更新它的状态呢?



	之所以在MoreHolder中写一个refreshView方法 之所以在MoreHolder中定义范型为Integer 就是为了用Integer来表示它的这三种状态 不同的状态界面是不一样的 所以在refreshView方法中要根据不同的状态 显示不同的布局  



	状态在什么时候传过去的呢 这个refreshView其实在BaseHolder的setData中去调了一下 把它当成状态传递过去 

	所以可以这样做 在MyBaseAdapter中 刷新界面,更新数据时, 做了判断 即当前item不是加载中的item布局时 才刷新界面,更新数据 而现在我们要判断 在是加载中的item的布局时 到底应该返回什么状态 

	

	所以在那个地方加个else{去判断即可 这个时候也可以去调一下MoreHolder的setData 把我们的状态当成数据给传递过去 这个时候数据我们也根本不知道到底是什么数据 所以可以在MoreHoldler中给它设置一下  即给它设置当前的状态 即:

	private int mCurrentState = STATE_HAS_MORE;

	然后再给它写个getCurrentState方法 把mCurrentState返回给它 这时候就可以继续在MyBaseAdapter加个else{处 因为此处的holder是BaseHolder 它本身没有getCurrentState这样的一个方法 那我们就必须把此处的BaseHolder强转成MoreHolder 即 MoreHolder moreHolder = (MoreHolder) holder;

	然后给它设置数据(此处把状态当成数据)即 moreHolder.setData();

Day02 06.加载更多布局状态实现 ##

	上午我们在MoreHoldler中定义了一个变量mCurrentState来记录它当前的状态 当时有的犯晕 这块不用去定义变量 把它和getCurrentState删掉 

	因为:我们的MoreHolder继承的是BaseHolder,BaseHolder中本来就有这个数据 即:private T data; 这个数据完全可以表示它当前的一个状态 而这个数据的类型刚好是一个整形 因为我们MoreHolder本身的范围就是一个整形 通过data就可以表示了 不用再申明额外的变量了 



	给MoreHolder再加一个构造方法 因为现在项目运行时 每个Fragment都有一个加载更多的布局“加载中...” 但是游戏 推荐 分类 排行都不需要上拉加载 所以在构造方法中直接传一个参数表示hasMore 表示它到底有没有上拉加载 通过有没有上拉加载我们决定上拉加载的状态是什么  这个时候因为BaseHolder本身就有一个setData方法表示它当时的一个数据  通过这个方法把数据给它传进去  所以应该这样传 在构造方法中 setData看hasMore是true或false 如果是true 上拉加载的状态应该是可以加载更多 即STATE_HAS_MORE  否则的状态应该是没有更多数据 即STATE_NO_MORE 这样我们在构造方法中 一开始new它的时候(初始化的时候) 就已经决定它到底应该是由更多数据还是没有更多数据 

	加载更多失败状态 我们只有在加载之后才能知道 



	这个时候 MyBaseAdapter的new MoreHolder();报错了 因为需要传数据看是有上拉加载还是没有 传一个true表示有 但有些页面没有 所以不能写死 因为每个Fragment中的listView的父类都是MyBaseAdapter 所以父类应该考虑到每个孩子的情况 所以需要写一个方法hasMore 返回一个true 并在new MoreHolder();中传入这个方法 即new MoreHolder(hasMore()); 

	什么意思呢 它表示当前是否有上拉加载 子类可以重写这个方法 表示没有上拉加载或者有  但是你在hasMore中还是返回true表示有上拉加载啊  这里是写死的 但是这个方法子类可以重写 那在HomeFragment中就可以重写这个方法 返回false  HomeFragment中的hasMore方法返回false 父类MyBaseAdapter的hasMore方法返回的是true 那到底是true还是false呢 肯定是false 因为是以子类为准的    这样写其实和上午写的getInnerType是一样的



	那它是既然是true的话 我们在new MoreHolder中的构造方法的时候 状态肯定是可以加载更多STATE_HAS_MORE 即有上拉加载的布局 

	那我们MoreHolder中的构造方法的setData的时候 就把hasMore的状态STATE_HAS_MORE同时在BaseHolder中的setData方法中以参数的形式赋值给了BaseHolder的data 同时还调了一下刷新页面的逻辑refreshView(data)  那一刷新页面 肯定调的是MoreHolder的refreshView方法 所以在这个地方就可以 MoreHolder中的refreshView方法中传的data参数数据就是 STATE_HAS_MORE 在这个方法中打印下日志 system.out.println("refreshView:" + data);  data显示0 就是代表STATE_HAS_MORE

	所以接下来用switch判断 此时就用一个list_item_more.xml来表示可以加载更多和加载更多失败 即如下:



	加载更多的布局文件更新(新增加载失败的布局):

	list_item_more.xml



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

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

	    android:layout_width="match_parent"

	    android:layout_height="wrap_content"

	    android:orientation="vertical" >

	

	    <LinearLayout

	        android:id="@+id/ll_load_more"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:gravity="center"

	        android:orientation="horizontal" >

	

	        <ProgressBar

	            android:layout_width="wrap_content"

	            android:layout_height="wrap_content"

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

	

	        <TextView

	            android:id="@+id/tv_item_more"

	            android:layout_width="wrap_content"

	            android:layout_height="wrap_content"

	            android:layout_marginLeft="5dp"

	            android:text="正在加载..."

	            android:textColor="#000"

	            android:textSize="16sp" />

	    </LinearLayout>

	

	    <TextView

	        android:id="@+id/tv_load_error"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:gravity="center"

			android:padding="10dp"

	        android:text="加载失败,点击重试"

	        android:textColor="#000"

	        android:textSize="16sp" />

	

	</LinearLayout>



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

MyBaseAdapter添加hasMore方法

MyBaseAdapter.java



	@Override

	public View getView(int position, View convertView, ViewGroup parent) {

		//还可以写成BaseHolder holder = null;

		BaseHolder holder;

		if (convertView == null) {

			// 根据当前item的类型来初始化不同的Holder对象

			if (getItemViewType(position) == LIST_ITEM_MORE) {

				// 初始化更多的Holder对象

				// 因为所有界面加载更多的UI显示效果都是一致的,所以加载更多的业务逻辑可以做详细处理

				holder = new MoreHolder(hasMore());

			} else {

				// 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag 

				holder = getHolder();

			}

		} else {

			holder = (BaseHolder) convertView.getTag();

		}



		if (getItemViewType(position) != LIST_ITEM_MORE) {

			// 刷新界面,更新数据

			holder.setData(getItem(position));

		} else {

			// 加载更多 如果当前展示的是加载更多的布局,需要开始加载下一页数据

			system.out.println("加载更多的数据显示出来了......");



			MoreHolder moreHolder = (MoreHolder) holder;

			if (moreHolder.getData() == MoreHolder.STATE_HAS_MORE) {// 加载之前要判断是否有更多数据

				loadMoreData(moreHolder); //加载MoreHolder的数据 

			}

		}



		return holder.getRootView();

	}





	// 返回是否需要加载更多数据, 子类可以重写该方法

	public boolean hasMore() {

		return true;

	}

修改hasMore的返回值,true或false,分别进行演示效果(展示或不展示加载更多布局)
 加载失败的布局也可以演示 可以在MoreHolder的构造方法中写死 即setData(STATE_ERROR_MORE);
 -------------------------------------------------------------------

Day02 07.加载更多逻辑实现 ##

注意:显示加载更多的布局的时候 是希望可以真的把更多的布局加载出来 所以在上边的MyBaseAdapter中写一个loadMoreData方法让它去加载更多的数据

MyBaseAdapter 添加 加载更多数据的方法

MyBaseAdapter.java

	

	//10标记当前是否正在加载更多

	private boolean isLoadMore = false; 



	/**
  • 1 加载更多数据
     */
     public void loadMoreData(final MoreHolder holder) {

      	if (!isLoadMore) { //11 标记当前是正在加载更多
    
      		isLoadMore = true; //12 设置标记为ture 表示加载开始 这样下次进来看到为true就不会再加载一遍了 
    
      		new Thread() {
    
      			@Override
    
      			public void run() {
    
      				//3 获取更多数据
    
      				final ArrayList<T> moreData = onLoadMore();
    
      
    
      				UIUtils.runOnUiThread(new Runnable() { //9 更新主界面要在UI主线程中去做
    
      
    
      					@Override
    
      					public void run() {
    
      						if (moreData == null) {// 4 如果没有拿到数据,说明加载失败
    
      							holder.setData(MoreHolder.STATE_ERROR_MORE);
    
      						} else {
    
      							// 5 每页返回20条数据,如果发现获取数据小于20条,说明已经没有更多数据了
    
      							if (moreData.size() < 20) {
    
      								holder.setData(MoreHolder.STATE_NO_MORE);
    
      							} else {
    
      								//6 每一页获取数据等于20天,说明还有更多数据
    
      								holder.setData(MoreHolder.STATE_HAS_MORE);
    
      							}
    
      
    
      							// 7 将下一页的数据追加到当前集合当中
    
      							list.addAll(moreData);
    
      							// 8 根据网络返回的数据结果 刷新当前listview
    
      							notifyDataSetChanged();
    
      							//或是写成如下 如果不是在子线程中 可以写成this.notifyDataSetChanged();
    
      							//MyBaseAdapter.this.notifyDataSetChanged();
    
      						}
    
    
    
      						isLoadMore = false; //13 数据加载完了之后 重置标记为false 表示加载结束 
    
      					}
    
      				});
    
      			}
    
      		}.start();
    
      	}
    
      }
    
    
    
      // 2 加载更多数据,必须由子类实现
    
      public abstract ArrayList<T> onLoadMore();
    

    注意:

      加载数据一般放在子线程中 
    
      加载更多数据具体怎么实现呢?  loadMoreData这个类它不知道到底应该调哪个接口 只能让子类具体的页面去实现 所以再写一个加载更多数据的抽象方法onLoadMore 让子类去实现 我们加载更多数据 肯定是期望能够把那个更多的数据返回给我 就像智慧北京一样 第一页数据 刷的一下是一个ArrayList  然后第二页数据我们再去请求接口 又是一个ArrayList 我就把这个ArrayList追加到上一个ArrayList的基础上 就是这样去做ListView加载更多的分页的 所以onLoadMore也要返回一个ArrayList
    
    
    
      谷歌电子市场这个项目 每页接口返回的是20条数据  这个要根据接口去定义 到底每页要返回多少条数据
    
    
    
      网络返回的数据结果状态 对应于我们定义的加载更多的三种状态
    
    
    
      MoreHolder 是MyBaseAdapter的“else{//加载更多” 时new出来的对象 在“if (getItemViewType(position) == LIST_ITEM_MORE) {”时初始化的MoreHolder
    
      所以直接在调loadMoreData这个方法的时候 把MoreHolder给它传过来  这时候就可以调一下holder(名字叫holder其实是MoreHolder)的setData方法 设置状态
    
      
    
      在setData之后(点进入跟踪),会掉Baseholder中setData方法中的refreshView方法而refreshView方法又是在MoreHolder中 根据不同状态显示不同布局,去更新一下主界面 更新主界面不能在子线程中去做 所以调UIUtils.runOnUiThread	在主线程中去操作 
    
    
    
       写一个else if等于20 那如果万一服务器返回了30条 就显示不了了 所以我们就写一个else把大于等于20都包括进去 这样就算服务器返回40条 我们也可以通过if else解决 
    
    
    
      为了避免LoadMoreData被频繁的调用 我们加	一个全局的标记 标记它当时是否正在加载更多 这个逻辑我们经常去做  	
    
    
    
      loadMoreData方法写完之后 到底在什么时候调用呢  一般是调监听 当listView滑动到底部的时候这样一个事件 一旦滑动到底部 就应该调一下这个方法 就和智慧北京一样 给listView去监听它的onscroolListener 根据它的状态去判断是否到最底部  
    
      但在这个地方 我们没有必要这样判断 我们有一个非常简单的办法  只要加载中这个item显示出来 就认为它到了最底部 而加载中在显示的时候 它会调getView方法 即我们一个Item要显示出来 每次都会掉一下getView方法才会显示  所以可以在getView方法中 去加载更多的数据 
    
    
    
      --------------------------------------------------------
    

    HomeFragment需要onLoadMore方法 我们可以模拟下测试数据

      HomeFragment.java
    
    
    
      class HomeAdapter extends MyBaseAdapter<String> {
    
    
    
      	public HomeAdapter(ArrayList<String> list) {
    
      		super(list);
    
      	}
    
    
    
      	@Override
    
      	public BaseHolder<String> getHolder() {
    
      		return new HomeHolder();
    
      	}
    
    
    
      	@Override
    
      	public ArrayList<String> onLoadMore() {
    
      		// 模拟更多数据
    
      		// i = 20即为初始化20条下一页的测试数据
    
      		ArrayList<String> moreData = new ArrayList<String>();
    
      		for (int i = 20; i < 40; i++) {
    
      			//添加测试数据到集合
    
      			moreData.add("测试数据" + i);
    
      		}
    
    
    
      		SystemClock.sleep(2000);// 模拟耗时操作 可以在这个地方直接睡 因为onLoadMore这个方法运行在子线程 
    
    
    
      		/*//此方法也可以睡2秒钟 作用于线程 需try catch
    
      		try {
    
      			Thread.sleep(2000);
    
      		} catch (InterruptedException e) {
    
      			e.printStackTrace();
    
      		}*/
    
    
    
      		//返回添加了测试数据的集合
    
      		return moreData;
    
      		//return null; //测试加载失败时放开 
    
      	}
    
      }
    

    注意: onLoadMore的返回值类型为ArrayList 说明它在等着接收返回一个ArrayList

    那我们就模拟一个ArrayList

    此时测试:

      睡两秒会加载下一页 在睡两秒加载下一页 只要你需要 就可以一直下一页 
    
      但是 自己运行了 只能出现加载一次下一页的效果 有需要的话可以找找原因
    

Day02 08.导入网络框架&几个工具类介绍 ##

请求网络的框架 ##

接下来就要正式的请求网络了 不可能一上来都是些假数据 没意思



请求网络有许多方式 比如会用一些xUtils工具 或者自己用httpclient或httpUrlconnection 一般情况下网络都是可以封装一下的 



现在就用之前写好的 一个网络的框架 即http包下的三个类 HttpClientFactory.java 

HttpHleper.java 

HttpRetry.java 

来表示我们的网络的这样一个情况   现在将这三个类先拷贝到项目中 这三个类专门是 处理我们的网络请求 我们就没有用xUtils 
1核心类是一个 HttpHleper 请求网络就是用这个类

分析:

在这个类中有一个URL  URL就是我们的一个链接地址 “http://127.0.0.1:8090” 这个127.0.0.1其实就是localhost 

在这个地方为什么可以用localhost呢  我们之前是不是有一个10.0.2.2,肯定是模拟器请求服务器本地 那现在我的服务器就在手机里边 所以这时候就是127.0.0.1 端口号8090是我们服务器开发定义的一个端口号 即webservice

这就是我们一个总的根的地址 实际在工作中这个地址 肯定是线上的www什么什么的 



它有这么一些方法 比如说get	即get请求的话 你把url传过去 它给你请求一个get 然后把结果封装成一个 HttpResult 返回给你  

post也一样 它搞了一个post也去请求 最后把结果HttpResult传过去 

还有下载文件 download 



那具体execute是干什么的呢 它是用到了一个httpClient httpClient再去做处理 

下边是一个HttpResult 即网络结果的一个封装

就这么些 代码也不多 



2 HttpRetry 这个类专门去负责重试 

我们现在这个机制里(在HttpHelper中)边是有一个重试机制 在执行网络访问的方法HtttpResult中用一个while循环去请求 我们如果发现万一这个的的话结果为空或者异常了的话  这时候我们会继续在while循环中 去重试一下  

而这个HttpRetry就决定 它到底 什么时机可以重试 什么时机不需要重试 

我们简单的去看下它的代码: 

它定义了一个RETRY_SLEEP_TIME_MILLIS这样的常量 去表示重试休息的时间为1秒 即我们第一次失败了 隔一秒再去重试 而不是马上重试  

然后下边是两个HashSet集合 exceptionWhitelist 和exceptionBlacklist 这个就和 ArrayList比较像 然后里边是一个 白名单和黑名单 表示我们在哪些情况下需要继续 哪些情况下不需要继续 不如说 把不需要重试的放在白名单中 异常能重试的放在了黑名单中 



然后下边有一个方法 retryRequest 来判断它到底要不要重试 retry =ture默认是需要重试 

如果在黑名单中就把它改为false 白名单中就改为 true

而且还定义了默认重试的次数是多少次 如果超过了最大次数就不用重试 而且重试的时候我们一般都让它睡一会 



而HttpRetry这个类就在我们的HttpHleper类中用到了 点击HttpRetry的构造方法 右键  选择call Hierarchy 看看谁去初始化它了  点过去后 是在HttpClientFactory类中 的设置重试次数时用到了 在这将刚才的那个类HttpRetry(  HttpRetry实现自HttpRequestRetryHandler所以HttpRetry本质是HttpRequestRetryHandler)  的对象传给setHttpRequestRetryHandler 顺便把重试的次数传过来 传过来 我们现在的重试次数是5次   点击进去看完重试次数后然后返回来 返回来之后呢 这是我们创建一个HttpClient了  此时看到createHttpClient方法报黄色警告线 说明没有地方调用它 返回去再点击HttpRetry的构造方法 右键  选择call Hierarchy看看谁去初始化它了 只有这个地方初始化它了 但这个地方追踪下去之后 看到报黄色警告没有地方调用它 那讲了半天没用 



那我们目前可能是有一个默认的 Android是有一个默认的重试的机制 

它是直接在HttpHelper的execute方法中 搞了一个httpClient 然后create 点击跟踪进去到了HttpClientFactory的create方法  但是在这里它没有给它设置retryHanlder 没有设置就算了 那可能它用的是默认的 

在HttpHelper中的execute方法中 它就直接拿了一个默认的 retryHanlder(在注释 获取重试机制那) 它直接通过这个默认的决定这个retry到底是true还是false  通过retryHanlder.retryRequest放回的是一个true或false决定



这个简单了解下就行了 不必要较真 其实这个类你只要会用就行 



那现在这个HttpHelper.java对象有几处报错 这个地方它用到了一个工具 logUtils 那logUtils呢其实就是log log知道吧 log.d啊 log.e啊

它是对这些的一个封装 



那我们把logUtils也拷贝过来放在Utils的包下  

  

1.logUtils重要的一个功能是它的第一个状态 LEVEL_NONE = 0; 它就是用来关闭log的 比如说把mDebuggable =“LEVEL_VERBOSE” 改为LEVEL_NONE 那所有的方法日志都不会打印出来 那相当于你不要改任何代码 代码中你写的代码该打还打 但是 根本就不输出 这解决了我们开发的一个痛点 就说打日志是为了开发者看起来方便  但是在发布上线之前 已经全部都没有问题都ok了 干嘛还打那么多日志 而且有可能被一些有心者把这个日志一分析就知道 一些核心的业务逻辑是怎么处理的 所以我们如果全部用logUtils打印日志的话 就没有这个问题 即:在上线之前 把mDebuggable改为LEVEL_NONE 然后再去打包 这样所有的log日志全都不打了 



2.StringUtils 它就一个方法 isEmpty 判断是不是为空 有些同学会说不是有一个TextUtils.isEmpty不挺方便的吗 它既能判空还能判null 点进去看下isEmpty的源码是怎么写的 它里边的判断是 如果等于null或者长度等于0 return true 

而我们StringUtils中的代码是怎么写的 如果它不等于null而且不等于空串(而且空串的前提是trim一下 去掉空格 即有可能它是好几个空格)而且还要判断它 不是null这个字符串(如果你是一个字符串 字符串是个null 它是不是也认为是个空的 而且这个还trim了一下去掉空格) 只有在这种情况下才不是空的 剩下的其他情况才认为是空的 



即StringUtils比系统原生的 要严格了一些 所以用StringUtils 当然看你的需求 这个无所谓 你用TextUtils.isEmpty挺方便的 个人觉得谷歌电子市场这个项目封装的太狠了 当然他可能也是为了更加稳定 代码更少 bug更少的考虑 但是这样就复杂度太高 可读性太差    



谷歌电子市场就是一个老师搞得一个项目 谷歌电子市场是之前现成的一个已经上线的电子市场  就不说是哪个软件了 那个公司的项目经理来这边做兼职 他就把这个项目带过来了 稍微改一下 精简了一下 现在给大家讲的这个框架结构 包括所谓的apr 都是人家那个项目中现有的代码 



3.IOUtils 它也就一个方法 close 关闭流 因为我们平时关个流还需要trycatch 他就闲trycatch太烦了 抽取这个工具类后就不用每次都trycatch 省了三行代码 这个工具类中穿了个Closeable的对象 有些同学可能对Closeable不了解 Closeable是所有的inputStream outputStream 的祖宗 

选中Closeable 按ctrl+t 看它所有的一个继承关系即可知  你想Closeable的命名--可被关闭 可知在android中所有可被关闭的东西 都放在这个地方了 

拷贝网络框架代码和相关工具类

将http包下的几个网络请求相关的类拷贝到本项目中

  • HttpClientFactory.java
  • HttpHleper.java
  • HttpRetry.java

将utils包下几个工具类拷贝到本项目中

IOUtils
StringUtils
LogUtils

		分别介绍这三个工具类的作用和使用方式

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

Day02 09.网络加载BaseProtocol封装 ##

上边将该拷贝的类拷贝过来后,接下来就正式搞网络的这样一个封装 

我们需要有一个对象 使用它来调用HttpHelper的一些内容



这时候我们又得继续去封装 我们写一个网络访问的最基本的类BaseProtocol 通过这个基本的类把所有的共性抽取到这个类里边来 这时候我们在new 一个包http.protocol 

抽取BaseProtocol.java

	/**
  • 访问网络的基类

  • @author Kevin
     */
     public abstract class BaseProtocol {

      	/**
    
  • 1获取网络数据

  • @param index

  • 分页请求数据的起始位置
     */
     public T getData(int index) {
     //2 判断缓存是否存在 如果存在 直接返回 否则调用网络
     String result = getDataFromNet(index);
     return data;
     }

      	/**
    
  • 3从网络获取数据
     *

  • @param index 分页时 表示从第几个位置开始返回数据

  •        分页请求数据的起始位置
    
  • @return
     */
     private String getDataFromNet(int index) {

      		//4 http://127.0.0.1:8090/home?index=20&name=ddd&age=xxx
    
      		HttpResult result = HttpHelper.get(HttpHelper.URL + getKey()
    

“?index=” + index + getParams());

			if (httpResult !=null) { //5 判断httpResult不为空时才做操作



				//5.1 获取网络返回的json数据

				String result = httpResult.getString(); 

	

			   	  return result; //返回json数据				

			}else{

				return null;

			}

		}

	

		// 4.1返回网络接口的关键词 获取网络接口的具体地址,每个页面都不一样,必须由子类实现

		public abstract String getKey();

	

		//4.2返回网络接口的参数 获取网络接口的具体参数,每个页面都不一样,必须由子类实现

		public abstract String getParams();

	

		/**
  • 6 解析json数据 ,每个页面要求的解析对象都不一样,必须由子类实现
     *

  • @param result
     */
     public abstract T parseJson(String result);

      }
    

    干脆我们写一个方法来获取 网络的一个对象 即getData 方法 数据肯定是一个对象 现在不知道对象是什么类型 所以返回类型为T 即一般情况为ArrayList 但是ArrayList 中 这个到底是什么对象我们不知道

    4 HttpHelper.URL + 后边的东西 怎么去跟呢

    我们去看下 服务器的实现 即去看下WebSerser的ServlertConfig.java 那这里边 定义的就是每个接口的数据 比如HomeServlet 返回我们的首页数据 那它就写成 “/home” ,CategoryServlet 返回我们的分类数据 那它就写成"/category"

    而BaseProtocol后边的东西跟什么 它不知道你哪个页面要取的是什么 即在这不能把这个链接写死 还是要让子类去实现

    所以还得写个抽象方法getKey 去作为链接的这样一个关键词

    HttpHelper.URL + getKey() 之后还要跟东西

    在HttpHelper中 URL = “http://127.0.0.1:8090/” 即域名已经出来了 关键词比如说是Home 也出来了 但后边跟什么参数 比如我想分页 分页的话我要告诉服务器去请求 哪一页 比如 index=10 表示返回10条以后的一个数据 有时候我们也会跟一些 比如 name=xxx &&age=xxx 即我们的网络链接有时候要跟参数 当然有些地方可能不需要参数 那我们就要把参数的位置给预留出来

    所以还得写个抽象方法getParams 去作为参数的这样一个关键词

    我们每个Fragment页面都有一个分页效果 智慧北京中分页效果是 先把第一页数据拿到手 第一页数据中有个字段叫做 more 我们从这个more里边就把下一页数据拿到手了吧 也就是每一页数据它都是不同的链接

    但是在谷歌电子市场里边 不是这样做的 谷歌电子市场是通过什么去控制分页呢 它没有去通过链接去分页 它每一页的数据 全都是那一个链接 那唯一的区分 就是参数不一样 那通过一个什么参数呢 它是通过一个index 的参数来决定它下一页的一个节度

    讲到这的话 这的逻辑就稍微像我们的手机卫士 的分页逻辑了 当时手机卫士中是黑名单 黑名单的话用一个数据库去保存嘛 那数据中是不是有一个limit0-20 limit几到几 limit几到几 但是我们需要在调这个数据的时候 需要把那个从第几个那个位置传进去 它才能把第几页的数据返回回来啊 这个地方是一样的 你那个地方的几 就是我们这个地方的index 那如果index=0 就表示我从第0条数据开始 你给我返回20条 因为服务器已经约定好了每页就是20条数据嘛 那如果你index写个20的话 就表示从第20个那个数据给我返回 那通过index这样一个参数是不是也完全可以控制 我们的这样一个结果啊 不需要额外的链接了吧 而且这个更灵活 你想多少条数据就多少条数据 不像那个智慧北京 你哗一下搞一页数据还得再搞个链接 哪有那么多链接给你分配啊

    那现在我们每个页面都有可能要分页 所以 我就干脆把这个index的值就写死在这个URL里边 那这时候我们就直接加一个"?index="+"" +getParams()", ““如果是0表示第一页 如果是20表示从第二页开始再加载 那index这个数据我们不知道 所以可以在getDataFromNet的时候 我把index传进来 所以”” 就可以换成index

    这时候在getData中的getDataFromNet报错是因为也要把index传进来 那getData也不知道 也要传进来

    HttpHelper.get返回的是一个HttpResult 因为有时候网络如果有异常 它是不是就会为空 所以要判断一下

    有人觉得getData和getDataFromNet都是从网络获取数据 那为什么还要写成两个方法 这是因为在getData中准备添加缓存的操作

Day02 10.写缓存 ##
Day02 11.读缓存 ##

怎么去写缓存呢 那缓存的话 其实就两个方法 一个是设置缓存setCache 一个是获取缓存getCache



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

读写缓存

BaseProtocol.java



	/**
  • 读缓存 从本地缓存中读取数据
     */
     private String getCache(int index) {
     // 1 获取系统缓存目录
     File cacheDir = UIUtils.getContext().getCacheDir();
     // 2 以网络链接作为文件名称,保证特定接口对应特定数据
     File cacheFile = new File(cacheDir, getKey() + “?index=” + index
    getParams());

      	if (cacheFile.exists()) {// 缓存文件存在
    
      		BufferedReader reader = null;
    
      		try {
    
      			//3 new一个BufferedReader 用来读缓存
    
      			reader = new BufferedReader(new FileReader(cacheFile));
    
      			// 5 读取第一行内容,即缓存截止时间
    
      			String line = reader.readLine();
    
      			//6将String类型的截止时间 转化成Long类型 等价于Long.parseLong(line)
    
      			long deadline = Long.valueOf(line);
    
      			//7 当前时间小于缓存截止时间,说明缓存还在有效期范围内
    
      			if (System.currentTimeMillis() < deadline) {
    
      				String str = null;
    
      				//9 new 一个StringBuffer对象 保存读出来的每一行 
    
      				StringBuffer sb = new StringBuffer();
    
      				while ((str = reader.readLine()) != null) { //8 不为null的话 从第二行开始一行一行的去读(第一行在上边5的时候已经读了)
    
      					sb.append(str); //10 读出来的每一行 追加到我们的StringBuffer中 
    
      				}
    
      				return sb.toString(); //11 将StringBuffer中读出来的数据转化成字符串传回去
    
      			}
    
      		} catch (Exception e) {
    
      			e.printStackTrace();
    
      		} finally {
    
      			//4 关闭流 
    
      			IOUtils.close(reader);
    
      		}
    
      	}
    
      	return null; //12 缓存过期 返回null
    
      }
    
    
    
      /**
    
  • 写缓存 向本地缓存写数据
     */
     private void setCache(String json, int index) {
     // 1获取系统缓存目录
     File cachePath = UIUtils.getContext().getCacheDir();
     // 2以网络链接作为文件名称,保证特定接口对应特定数据
     //文件名称为 getKey() + “?index=” + index + getParams()
     File cacheFile = new File(cachePath, getKey() + “?index=” + index
    getParams());

      	FileWriter writer = null;
    
      	try {
    
      		//3 给缓存中写东西 
    
      		writer = new FileWriter(cacheFile);
    
      		
    
      		// 6 设置缓存有效期, 截止时间设定为半小时之后
    
      		long deadline = System.currentTimeMillis() + 30  60  1000;
    
      		// 将缓存截止时间写入文件第一行 写成writer.write(String.valueOf(deadline) +"\n"); 更专业
    
      		writer.write(deadline + "\n");
    
      		
    
      		//4 写缓存 即把传过来的json放进缓存文件中去
    
      		writer.write(json);  
    
      		
    
      		writer.flush(); //7 刷新一下
    
      	} catch (IOException e) {
    
      		e.printStackTrace();
    
      	} finally {
    
      		//5 关闭流 
    
      		IOUtils.close(writer);
    
      	}
    
      }
    

    回顾智慧北京 里边写缓存和读缓存 当时我们是把json数据 保存在了sharePreference中

    非常简单 几行代码就搞定了 但可能不是太专业 因为sp保存的都是写配置文件 你把json数据保存进去

    感觉乱七八糟的 现在我们就把它正式的写在我们的本地文件里边

    我们写在哪个文件目录下呢

    每个应用都有一个缓存目录即CacheDir 手机卫士清理缓存的时候还用到了这个缓存目录CacheDir

    在这个目录的基础上再去写我们的缓存文件 文件名一般是以它的URL 这个是接口的数据

    那就把接口的链接地址作为它的一个文件名啊

    比如http://127.0.0.1:8090/home?index=20&name=ddd&age=xxx

    但是这个接口的链接地址乱七八糟的 斜杠冒号问号

    这个就根本创建不了文件 我们有两种处理方式

    一种是我们计算它的MD5值 其实在这个地方我们不用去计算

    刚才乱七八糟的符号主要是前边的http://127.0.0.1:8090/这些导致的

    而前边的这些是不是永远都一样啊 只是后边的home?index=20&name=ddd&age=xxx这些

    就已经能表示我们的接口是什么接口了 所以用后边这些就可以

    而后边这些是什么呢 不就是getKey() + “?index=” + index + getParams()这些

    设置缓存有效期 之前在将智慧北京的时候没有考虑过缓存的有效期 那一年前的那个新闻访问了下

    它设置了下缓存 今年又打开应用 你看到的是一年前的新闻 这样是不行的

    这个时候你不管怎么着都要去获取下最新的数据 当时讲智慧北京的时候有一个细节

    即先获取缓存 获取完缓存之后 还继续调网络 这个还好点 至少这次的这个数据还会再刷新进来

    但这里就不那样了 有缓存就显示缓存 没缓存再显示网络 那现在你有一年前的缓存你不可能让它显示在这吧

    所以要对缓存设置一个有效期限

    怎么设置有效期呢 我们可以定义一个long类型的时间 deadLine是指我们的最后截止时间

    用当时的系统时间即currentTimeMillis()

    然后将时间deadLine写在缓存文件的第一行

    怎样保证写在文件第一行呢 必须把writer.write(json);这行代码放在设置有效期的下面

    即你第一个先写进来 那是不是就是第一行

    读缓存时用BufferedReader来读 是因为可以用它的readLine方法 一行一行的读

    这时候应该new一个Read对象 缓存是FileReader 所以new FileReader() 把cacheFile放进去


    读缓存和写缓存完成之后 就可以在getData方法中判断缓存是否存在

    BaseProtocol.java

      /**
    
  • 获取数据

  • 先读缓存 缓存中没有的话再去访问网络获取数据

  • @param index

  •        分页请求数据的起始位置
    

*/
 public T getData(int index) {
 // 先从本地缓存中读取数据,如果有,就直接返回,如果没有,才从网络加载
 String result = getCache(index);
 if (StringUtils.isEmpty(result)) { //如果缓存为空
 result = getDataFromNet(index); //从网络上去获取
 }
 //如果当时没有缓存也没有网 那这时候这个result肯定为null 所以再判断一下这个result是否为null 
 if(result != null){ //不为null时才会去解析数据
 return parseJson(result); 
 }else{
 return null;
 }
 }

	/**
  • 访问网络获取数据
     *

  • @param index

  •        分页请求数据的起始位置
    
  • @return
     */
     private String getDataFromNet(int index) {
     HttpResult httpResult = HttpHelper.get(HttpHelper.URL + getKey()
    “?index=” + index + getParams());
     if (httpResult != null) {
     String result = httpResult.getString(); //获取网络返回的json数据
     if (!StringUtils.isEmpty(result)) {
     // 将缓存写到本地文件中
     setCache(index, result);
     return result;
     }
     }

      	return null;
    
      }
    

    注意: 真正的调用读缓存和写缓存

    是在getData的String result = getCache(index); 处调用读缓存

    在 getDataFromNet的setCache(index, result); 处调用写缓存

    读写缓存我们都在放在了BaseProtocol.java 所以以后子类都不需要考虑到底是读缓存还是写缓存

    子类只需要考虑自己去怎么请求一些链接 怎么去解析数据就够了


Day02 12.首页网络数据解析和展示 ##

	接下来就应该去实现它的子类 现在那我只想拿一下首页的数据 所以写一个HomeProtocol.java



	/**
  • 首页网络数据加载实现
     *

  • @author Kevin
     * 
     */
     public class HomeProtocol extends BaseProtocol<ArrayList> {

      	private ArrayList<AppInfo> mAppList;// 首页应用列表集合
    
      	private ArrayList<String> mPictureList;// 首页轮播条图片url集合
    
      
    
      	@Override
    
      	public String getKey() { //在服务器的ServlertConfig.java代码中查看到首页是叫"/home"链接
    
      		return "home";
    
      	}
    
      
    
      	@Override
    
      	public String getParams() { //首页没有参数
    
      		return "";
    
      	}
    
      
    
      	@Override
    
      	public ArrayList<AppInfo> parseJson(String result) {
    
      		try {
    
      			JSONObject jo = new JSONObject(result);
    
      
    
      			// 应用列表集合解析
    
      			JSONArray ja = jo.getJSONArray("list"); //解析list数组 list数组放的是一个一个对象(大括号)
    
      			mAppList = new ArrayList<AppInfo>();
    
      			for (int i = 0; i < ja.length(); i++) { //遍历这个对象数组 
    
      				AppInfo info = new AppInfo(); //new一个AppInfo的对象
    
      
    
      				JSONObject jo1 = ja.getJSONObject(i); //返回这样一个jsonObject(对象)
    
      				
    
      				info.des = jo1.getString("des");
    
      				info.downloadUrl = jo1.getString("downloadUrl");
    
      				info.iconUrl = jo1.getString("iconUrl");
    
      				info.id = jo1.getString("id");
    
      				info.name = jo1.getString("name");
    
      				info.packageName = jo1.getString("packageName");
    
      				info.size = jo1.getString("size");
    
      				info.stars = jo1.getDouble("stars");
    
      
    
      				mAppList.add(info);
    
      			}
    
      
    
      			// 轮播条图片解析 
    
      			mPictureList = new ArrayList<String>();
    
      			JSONArray ja1 = jo.getJSONArray("picture"); //解析Picture数组 Picture数组放的是一个一个字符串
    
      			for (int i = 0; i < ja1.length(); i++) { //遍历这个字符串数组
    
      				 String str = ja1.getString(i); //返回这样一个字符串
    
      				mPictureList.add(str);
    
      			}
    
      
    
      			return mAppList;
    
      
    
      		} catch (Exception e) {
    
      			e.printStackTrace();
    
      		}
    
      		return null;
    
      	}
    
      
    
      }
    

    注意:

    getKey的话就要看首页到底是什么链接

    我们在服务器的ServlertConfig.java代码中看到 我们的首页是叫"/home"

    所以我们直接在getKey方法中return 一个home就好了

    getParams的话 我们首页没有参数 有同学说没有参数那就直接返回一个null就行了

    没有参数但是还不能返回null 因为我们在BaseProtocol的getDataFromNet中HttpHelper.get时

    getParams这个地方如果返回来一个null的话 它这个位置就是一个字符串的null

    所以应该return一个空串

    BaseProtocol 中的范型T指的是我们解析这个json数据的时候

    应该返回一个什么样的对象 我们先看下网络返回的这个首页的json到底是个什么样子

    看下我们服务器的代码 服务器的代码在WebInfos下的homelist0 homelist1 homelist2 homelist3

    我们用记事本打开看下homelist0 它里面的数据其实就是首页数据的这样一个封装

    我们把这些数据复制到HiJson工具打开 可以清晰的看到它的结构 它里边有list和picture两个数组

    list中是首页一个一个的应用的数据 picture中是首页轮播条的图片

    所以要把list和picture这两份数据都要解析下来 要解析json 很容易联想到用Gson去解析json

    我们就不用Gson 当然用Gson这个很容易马上搞出来 但我们就用最基本的jsonObject去解析

    用jsonObject去解析的话 就比较费劲了

    首先看到格式化的json数据homelist0的第一行是个大括号

    遇到大括号 就是jSONObject对象

    我们先解析简单的picture picture是一个数组

    遇到数组该怎么解析呢 数组它有另外一个对象 叫 JSONArray

    即JSONObject叫对象

    JSONArray叫数组

    解析应用列表集合时 返回的是这样一个一个的jsonObject(对象)

    然后在通过这个jsonObject(d对象) 把每个字段都解析出来 既然是每个字段

    那这时候我们要把它只能封装成一个对象了

    所以我们要用一个对象 把这些字段都封装起来

    我们再建一个包 叫domain 再写一个类 叫做 AppInfo

    AppInfo中都是什么字段呢 把格式化的list中的一个字段复制到AppInfo中 方便写

    最后parseJson需要一个返回值 现在我们首页应用和轮播条图片都解析了

    有两个集合 一个是首页应用的集合 一个是轮播条图片的集合

    那么到底应该返回哪个呢 我们这地方返回首页应用的集合 图片我们回头再说

    现在主要还是展示的首页应用的列表


HomeProtocol实现&解析json

首页应用列表集合的解析

domain包中

	

AppInfo.java



	/**
  • 首页应用信息封装
     *

  • @author Kevin
     *
     */
     public class AppInfo {

      	public String des;
    
      	public String downloadUrl;
    
      	public String iconUrl;
    
      	public String id;
    
      	public String name;
    
      	public String packageName;
    
      	public String size;
    
      	public double stars;
    
      	
    
      }
    

    上边Json解析完了之后 我们就可以在HomeFragment中调用了

    之前在onCreateSuccessView方法中直接调的是initData方法

    1.在initData中初始化假数据 其实initData这个方法现在用不到了 我们把它注释掉

    那在哪里初始化呢 在我们加载网络数据的时候有个回调方法 叫onLoad 之前测试用

    一直返回的是LOAD_SUCCESS 在这个地方我们要调网络 那我们就在这调网络

    注意: 在onLoad方法调用网络时 首先初始化HomeProtocol

    然后通过HomeProtocol去调用getData 双击getData方法跟踪

    发现在父类BaseProtocol 中有个getData 就是在getData方法中请求网络的

    而且在getData方法中也顺便把缓存也判断了 然后把类型直接返回回来了

    index写0 表示刚进来加载第一页数据 返回的是一个ArrayList

    把它声明成成员变量private ArrayList data;

    2 之前添加假数据的 ArrayList mList = new ArrayList();

    用不到了 所以把它注释掉

    3 注释掉之后 原来new HomeAdapter时 的参数为假数据的mlist 换成我们刚写的data

    4但是HomeAdapter还会报错 因为我们以前的测试数据 是一个字符串数组

    所以把它所有报错的的String范型改为AppInfo即可

    5但是它的super(list)还会报MyBaseAdapter的ArrayList< AppInfo >未定义 所以把MyBaseAdapter的范型String 也改为AppInfo

    6 这时下边加载更多数据的方法onLoadMore的返回值类型Arraylist也报错

    同样把String改为AppInfo

    7 这时onLoadMore中的返回值报错 这是我们以前添加的假数据 所以把onLoadMore中以前添加的假数据都注释掉 先返回一个null

测试首页网络数据的加载

	HomeFragment.java



	private ArrayList<AppInfo> data;

	

	@Override

	public ResultState onLoad() {

		//从网络加载数据

		HomeProtocol protocol = new HomeProtocol();

		data = protocol.getData(0);//加载第一页数据

		return ResultState.STATE_SUCCESS;

	}



	@Override

	public ArrayList<AppInfo> onLoadMore() {

		return null;

	}



这里第一次写代码时 将上述7个步骤改完后getHolder的返回值类型BaseHolder<String>报错 将这个String不能改AppInfo

还有一个错误不改也不报错

是getHolder添加了没用的参数int position 所以把这改过来 

还要去基类中把getHolder的参数也删掉



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



上边HomeFragment中的onLoad方法中调网络之后 我们要把网络的数据填充到我们的listView布局中 具体在HomeHolder中的refreshView中填充的 



	HomeHolder.java



	@Override

	public void refreshView(AppInfo data) {

		tvContent.setText(data.name);

	}



这时候HomeHolder的范型String 改为 AppInfo 

则refreshView的参数类型String也要改为AppInfo





这时候TextView也不能再简单的setText了 而是应该调用应用的名称显示出来

 

访问网络需要添加网络权限 





运行程序 查看结果 

并测试缓存是否写正确 即按home键退出程序 然后关闭服务器 在进入程序 

和开启服务器时一样有数据展示 则说明缓存起作用了 



然后看缓存又没有写进去 打开DDMS 

然后 data/com.itcast.googleplay02/cache/home?index=0 把这个文件导出到桌面(eclipse是linux环境 认识home?index=0这个文件名 但window环境不认识

所以导出时随便改个window认识的即可) 打开看 里边就是首页的json数据







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

Day02 13.网络数据校验 ##

HomeFragment中的onLoad方法中 我们不管网络是否请求成功 都统一返回了成功

我们至少应该对数据进行判断 看是否我们需要的东西 



判断data如果不等于null else的话就是等于null 则返回ERROR 表示加载失败

先判断不等于空 其次因为我们拿的data 一般都是ArrayList集合列表  

突然唰给我返回一个集合对象 它不是一个列表 那我们这个data肯定也是有问题的 

所以还应该判断data是不是一个集合 如果是集合 就可以把这个data强转成一个集合了  



判断数据正确不正确 我们是放在基类BaseFragment 中专门去写个check方法判断

基类中肯定不知道data是一个集合 所以用Object表示任意类型 然后再强转成集合就可以了

校验网络数据合法性

	BaseFragment.java



	/**
  • 校验数据的合法性,返回相应的状态

  • @param data

  • @return
     */
     public ResultState check(Object data) {
     if (data != null) {
     if (data instanceof List) {//判断当前对象是否是一个集合
     List list = (List) data;//如果是集合 就把它强转成一个集合
     if (!list.isEmpty()) {//数据不为空,访问成功
     return ResultState.STATE_SUCCESS;
     } else {//空集合
     return ResultState.STATE_EMPTY;
     }
     }
     }

      	return ResultState.STATE_ERROR;
    
      }
    
    
    
      --------------------------------------------------
    

    上边校验完网络数据合法性后 就可以直接在HomeFragment中去用了

      HomeFragment.java
    
    
    
      @Override
    
      public ResultState onLoad() {
    
      	// 从网络加载数据
    
      	HomeProtocol protocol = new HomeProtocol();
    
      	data = protocol.getData(0);// 加载第一页数据
    
    
    
      	return check(data);
    
      }
    

    这样就可以把网络异常的情况 也可以让它反应过来

    模拟网络异常情况 看我们写的逻辑 是否正确

    将服务器关闭 缓存也清除之后 再打开程序 就会显示’加载失败,请重试’

    按home键退出程序 把服务器再打开 然后进入程序 点击’加载失败 请重试’按钮 即会再次开始加载数据

Day02 14.总结 ##

今天我们主要是围绕首页这个页面 我们讲了主要是这么几大模块

对ListView的封装 封装的话来到我们的HomeFragment中来 

先继承一个MyBaseAdapter实现它几个方法 

着重实现下它的getView 

getView的话 必须由一个BaseHolder来承载它 BaseHolder帮他做了好多事情 比如说加载布局 设置tag 刷新界面 都让BaseHolder去做 



这是一个listview的adapter的一个封装 封装好了以后 我们的首页就看起来非常的精简了 最后就一个构造方法载一个getHolder就搞定了 



然后在HomeFragment中加了一个onLoadMore 更多的这样一个界面



onLoadMore也是在我们的MyBaseAdapter中去处理的 加载更多的话 首先它是另外一种类型 所以 我们重写了两个方法

一个是getViewTypeCount返回类型的个数 我们是两个

一个是getItemViewType根据它布局的位置去返回类型 



然后在MyBaseAdapter的getView中 去new一个Moreholder 

MoreHolder中一旦显示出来 我们就加载更多数据即loadMoreData



 加载更多数据的话 我们也是在子线程中 去运行的 

 这个加载更多也是一个大模块 

 首先是Adapter的封装 然后是BaseHolder的封装 再就是加载更多的封装

 然后对网络的封装



 对网络封装的先把protocol下的三个类拷贝过来 

 然后写个BaseProtocol去封装它的网络数据 并加上缓存 

 getCache和setCache都是去操作文件 而不再是操作sp 



网络封装完了后再去 实现它的子类HomeProtocol 在它里边解析json



这时候涉及一个面试问题 

我们在解析json的时候 我们知道可以用Gson去解析 用jsonObject也可以去解析

那就写熟悉网络获取以及解析 这样都可以说 







总结day1-2



首先我们写了一个自定义的BaseApplication 并且注意在清单文件中把这个Application声明出来 



接着专门写了个工具类 UIUtils 也是一个全局的工具类 



然后就写我们的Mainactivity 在它里边用到了一个指针的效果PagerTab 它有点类似于viewPagerIndicater  那我们当然可以用ViewPagerIndicater把这个实现出来

但我们在这采用的是导入了一个现成的自定义控件PagerTab.java代码



PagerTab刚导入进来会报一些错 



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



它又需要一些资源文件 比如 bg_tab_text.xml tab_text_color.xml 我们都导入了进来



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

设置它的高度42dp 背景bg_tab

然后在写了一个ViewPager



这时候要给我们的ViewPager去填充Fragment 如果要给
  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值