#Day02 #
#首页布局实现 ##
使用ListView加载假数据
HomeFragment.java
@Override
public View onCreateSuccessView() {
ListView view = new ListView(UIUtils.getContext());
initData();
view.setAdapter(new HomeAdapter());
return view;
}
private void initData() {
for (int i = 0; i < 50; i++) {
mList.add("测试数据" + i);
}
}
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) {
ViewHolder holder = null;
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);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tvContent.setText(getItem(position));
return convertView;
}
}
static class ViewHolder {
public TextView tvContent;
}
BaseAdapter抽取
/**
-
数据适配器的基类
* -
@author Kevin
*
*/
public class MyBaseAdapter extends BaseAdapter {private ArrayList<T> list; public MyBaseAdapter(ArrayList<T> list) { this.list = list; } @Override public int getCount() { return list.size(); } @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; } }
BaseHolder抽取
/**
-
ViewHolder的基类
* -
此类实现了以下功能
-
- 初始化item布局
-
- findViewById方法(由子类在初始化布局时实现)
-
- 给view设置tag
-
- 刷新界面
*
- 刷新界面
-
此类相当于是对getView方法的封装
* -
@author Kevin
*
*/
public abstract class BaseHolder {private View mRootView;// item的布局对象 private T data;// item对应的数据 public BaseHolder() { mRootView = initView();// 初始化布局 mRootView.setTag(this);// 给view设置tag } // 初始化布局的方法必须由子类实现 public abstract View initView(); // 返回布局对象 public View getRootView() { return mRootView; }; // 设置数据 public void setData(T data) { this.data = data; refreshView(data); } // 获取数据 public T getData() { return data; } // 刷新界面,更新数据,子类必须实现 public abstract void refreshView(T data); } ------------------------------------------- MyBaseAdapter.java @Override public View getView(int position, View convertView, ViewGroup parent) { BaseHolder<T> holder = null; if (convertView == null) { // 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag holder = getHolder(position); } else { holder = (BaseHolder<T>) convertView.getTag(); } // 刷新界面,更新数据 holder.setData(getItem(position)); return holder.getRootView(); } // 返回BaseHolder的子类,必须实现 public abstract BaseHolder<T> getHolder(int position); ---------------------------------------------- /**
-
首页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); } } --------------------------------------------- class HomeAdapter extends MyBaseAdapter<String> { public HomeAdapter(ArrayList<String> list) { super(list); } @Override public BaseHolder<String> getHolder(int position) { return new HomeHolder(); } }
加载更多抽取 ##
加载更多的布局作为listview最后一个item来展现
MyBaseAdapter
private static final int ITEM_LORD_MORE = 0;// 更多布局类型
private static final int ITEM_LIST_VIEW = 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) {
// 最后一个条目
return ITEM_LORD_MORE;
} else {
// 返回普通布局类型
return getInnerType(position);
}
}
// 普通布局也有可能返回多种类型, 重写改方法,可以返回更多普通布局的类型
public int getInnerType(int position) {
return ITEM_LIST_VIEW;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseHolder holder = null;
if (convertView == null) {
//根据当前item的类型来初始化不同的Holder对象
if (getItemViewType(position) == ITEM_LORD_MORE) {
// 返回加载更多Holder对象
// 因为所有界面加载更多的UI显示效果都是一致的,所以加载更多的业务逻辑可以做详细处理
holder = new MoreHolder();
} else {
// 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag
holder = getHolder(position);
}
} else {
holder = (BaseHolder) convertView.getTag();
}
if (getItemViewType(position) != ITEM_LORD_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.layout_loading_more, null); return view; } @Override public void refreshView(Integer data) { } ------------------------------------------- layout_loading_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布局
加载更多布局状态
某些页面不需要加载更多数据,所以不能展现加载更多的布局; 而某些页面加载布局时有可能失败,这时候应该展示"加载失败,点击重试"的布局,所以加载更多的布局分如下三种状态进行展示:
public static final int TYPE_HAS_MORE = 0;// 可以加载更多
public static final int TYPE_NO_MORE = 1;// 不能加载更多
public static final int TYPE_LOAD_ERROR = 2;// 加载更多失败
------------------------------------------------------
在初始化MoreHolder时,就应该确定好是否支持加载更多的布局展示:
public MoreHolder(boolean hasMore) {
// 将加载类型以数据的方式设置进去
setData(hasMore ? TYPE_HAS_MORE : TYPE_NO_MORE);
}
------------------------------------------------------
加载更多的布局文件更新(新增加载失败的布局):
<?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_loading_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/textView1"
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>
---------------------------------------------------------
刷新界面时,要根据当前的状态来更新布局展示
@Override
public void refreshView(Integer data) {
// 根据当前状态,来更新界面展示
switch (data) {
case TYPE_HAS_MORE:
llLoadingMore.setVisibility(View.VISIBLE);
tvLoadError.setVisibility(View.GONE);
break;
case TYPE_NO_MORE:
llLoadingMore.setVisibility(View.GONE);
tvLoadError.setVisibility(View.GONE);
break;
case TYPE_LOAD_ERROR:
llLoadingMore.setVisibility(View.GONE);
tvLoadError.setVisibility(View.VISIBLE);
break;
}
}
MyBaseAdapter添加hasMore方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseHolder holder = null;
if (convertView == null) {
// 根据当前item的类型来初始化不同的Holder对象
if (getItemViewType(position) == ITEM_LORD_MORE) {
// 返回加载更多Holder对象
// 因为所有界面加载更多的UI显示效果都是一致的,所以加载更多的业务逻辑可以做详细处理
holder = new MoreHolder(hasMore());
} else {
// 在初始化holder的同时,已经对布局进行了加载,也给view设置了tag
holder = getHolder(position);
}
} else {
holder = (BaseHolder) convertView.getTag();
}
if (getItemViewType(position) != ITEM_LORD_MORE) {
// 刷新界面,更新数据
holder.setData(getItem(position));
} else {
// 如果当前展示的是加载更多的布局,需要开始加载下一页数据
MoreHolder moreHolder = (MoreHolder) holder;
if (moreHolder.getData() == MoreHolder.TYPE_HAS_MORE) {// 加载之前要判断是否有更多数据
loadMore(moreHolder);
}
}
return holder.getRootView();
}
// 返回是否需要加载更多数据, 子类可以重写该方法
public boolean hasMore() {
return true;
}
修改hasMore的返回值,true或false,分别进行演示效果(展示或不展示加载更多布局)
MyBaseAdapter加载更多数据
private boolean isLoadMore = false;//标记当前是否正在加载更多
/**
-
加载更多数据
*/
public void loadMore(final MoreHolder holder) {
if (!isLoadMore) {
isLoadMore = true;
new Thread() {
@Override
public void run() {
//获取更多数据
final ArrayList moreList = onLoadMore();UIUtils.runOnUiThread(new Runnable() { @Override public void run() { if (moreList == null) {// 如果没有拿到数据,说明加载失败 holder.setData(MoreHolder.TYPE_LOAD_ERROR); } else { // 每页返回20条数据,如果发现获取数据小于20条,说明已经没有更多数据了 if (moreList.size() < 20) { holder.setData(MoreHolder.TYPE_NO_MORE); } else { holder.setData(MoreHolder.TYPE_HAS_MORE); } // 将下一页的数据追加到当前集合当中 list.addAll(moreList); // 刷新当前listview notifyDataSetChanged(); } isLoadMore = false; } }); } }.start(); } } // 加载更多数据,必须由子类实现 public abstract ArrayList<T> onLoadMore(); -------------------------------------------------------- HomeFragment.java class HomeAdapter extends MyBaseAdapter<String> { public HomeAdapter(ArrayList<String> list) { super(list); } @Override public BaseHolder<String> getHolder(int position) { return new HomeHolder(); } @Override public ArrayList<String> onLoadMore() { // 模拟更多数据 // 初始化20条下一页的测试数据 ArrayList<String> moreList = new ArrayList<String>(); for (int i = 20; i < 40; i++) { moreList.add("测试数据" + i); } SystemClock.sleep(2000);// 模拟耗时操作 return moreList; } }
网络框架 ##
拷贝网络框架代码和相关工具类
将http包下的几个网络请求相关的类拷贝到本项目中
将utils包下几个工具类拷贝到本项目中
IOUtils
StringUtils
LogUtils
分别介绍这三个工具类的作用和使用方式
BaseProtocol抽取
/**
-
访问网络的基类
* -
@author Kevin
*
*/
public abstract class BaseProtocol {/**
-
获取数据
* -
@param index
-
分页请求数据的起始位置
*/
public T getData(int index) {
String result = getDataFromNet(index);
return parseJson(result);
}
/**
-
访问网络获取数据
* -
@param index
-
分页请求数据的起始位置
-
@return
*/
private String getDataFromNet(int index) {
HttpResult result = HttpHelper.get(HttpHelper.URL + getKey()
“?index=” + index + getParams());
return result.getString();
}// 获取网络接口的具体地址,每个页面都不一样,必须由子类实现 public abstract String getKey(); // 获取网络接口的具体参数,每个页面都不一样,必须由子类实现 public abstract String getParams(); /**
-
解析json数据 ,每个页面要求的解析对象都不一样,必须由子类实现
* -
@param result
*/
public abstract T parseJson(String result);}
读写缓存
/**
-
从本地缓存中读取数据
*/
private String getCache(int index) {
// 获取系统缓存目录
File cacheDir = UIUtils.getContext().getCacheDir();
// 以网络链接作为文件名称,保证特定接口对应特定数据
File cacheFile = new File(cacheDir, getKey() + “?index=” + index
getParams());if (cacheFile.exists()) {// 缓存文件存在 BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(cacheFile)); String validTime = reader.readLine();// 读取第一行内容,缓存截止时间 if (System.currentTimeMillis() < Long.parseLong(validTime)) {// 当前时间小于缓存截止时间,说明缓存还在有效期范围内 String line = null; StringBuffer sb = new StringBuffer(); while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.close(reader); } } return null; } /**
-
向本地缓存写数据
*/
private void setCache(String result, int index) {
// 获取系统缓存目录
File cacheDir = UIUtils.getContext().getCacheDir();
// 以网络链接作为文件名称,保证特定接口对应特定数据
File cacheFile = new File(cacheDir, getKey() + “?index=” + index
getParams());FileWriter writer = null; try { writer = new FileWriter(cacheFile); // 缓存有效期限, 截止时间设定为半小时之后 long validTime = System.currentTimeMillis() + 30 60 1000; writer.write(validTime + "\n");// 将缓存截止时间写入文件第一行 writer.write(result); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(writer); } } --------------------------------------------- /**
-
获取数据
* -
@param index
-
分页请求数据的起始位置
*/
public T getData(int index) {
// 先从本地缓存中读取数据,如果有,就直接返回,如果没有,才从网络加载
String result = getCache(index);
if (result == null) {
result = getDataFromNet(index);
}
if (result != null) {
return parseJson(result);
}
return null;
}
/**
-
访问网络获取数据
* -
@param index
-
分页请求数据的起始位置
-
@return
*/
private String getDataFromNet(int index) {
HttpResult result = HttpHelper.get(HttpHelper.URL + getKey()
“?index=” + index + getParams());
if (result != null) {
String strResult = result.getString();
if (!StringUtils.isEmpty(strResult)) {
// 将缓存写到本地文件中
setCache(strResult, index);
return strResult;
}
}return null; }
HomeProtocol实现&解析json
/**
-
首页应用信息封装
* -
@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; } --------------------------------------------- /**
-
首页访问网络
* -
@author Kevin
*
*/
public class HomeProtocol extends BaseProtocol<ArrayList> {private ArrayList<AppInfo> mAppList;// 应用列表集合 private ArrayList<String> mPicList;// 广告图片url集合 @Override public String getKey() { 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"); mAppList = new ArrayList<AppInfo>(); for (int i = 0; i < ja.length(); i++) { AppInfo info = new AppInfo(); JSONObject jo1 = (JSONObject) ja.get(i); 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); } // 解析头条广告图片信息 mPicList = new ArrayList<String>(); JSONArray ja1 = jo.getJSONArray("picture"); for (int i = 0; i < ja1.length(); i++) { mPicList.add(ja1.getString(i)); } return mAppList; } catch (Exception e) { e.printStackTrace(); } return null; } }
测试首页网络数据的加载
HomeFragment.java
@Override
public ResultState onLoad() {
//从网络加载数据
HomeProtocol protocol = new HomeProtocol();
mList = protocol.getData(0);//加载第一页数据
return ResultState.STATE_SUCCESS;
}
@Override
public ArrayList<AppInfo> onLoadMore() {
return null;
}
------------------------------------
HomeHolder.java
@Override
public void refreshView(AppInfo data) {
tvContent.setText(data.name);
}
校验网络数据合法性
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.java @Override public ResultState onLoad() { // 从网络加载数据 HomeProtocol protocol = new HomeProtocol(); mList = protocol.getData(0);// 加载第一页数据 return check(mList); }