文章目录
1.组图布局 & 请求网格数据
之前我们完成了侧边栏中“新闻”的布局,现在需要侧边栏中实现“组图”的布局并请求网格数据。由于这个功能同时拥有ListView和GridView,所以可以考虑底层布局用FrameLayout来实现
- 新增paper_photos_menu_detail.xml布局文件,作为“组图”的根布局,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv_list2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null"/>
<GridView
android:id="@+id/gv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:numColumns="2"/>
</FrameLayout>
- 修改PhotosMenuDetailPaper,应用刚刚创建好的布局,并获取到实例对象,代码如下:
package com.example.zhbj.base.impl.menudetail;
import android.app.Activity;
import android.view.View;
import android.widget.GridView;
import android.widget.ListView;
import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.view.annotation.ViewInject;
/**
* 菜单详情页 - 组图
*/
public class PhotosMenuDetailPaper extends BaseMenuDetailPaper {
/**
* ListView实例对象
*/
@ViewInject(R.id.lv_list2)
private ListView lvList;
/**
* GridView实例对象
*/
@ViewInject(R.id.gv_list)
private GridView gvList;
public PhotosMenuDetailPaper(Activity activity) {
super(activity);
}
@Override
public View initViews() {
// 给空的帧布局动态添加布局对象
/*
TextView view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("菜单详情页-组图");
*/
View view = View.inflate(mActivity, R.layout.paper_photos_menu_detail,null);
ViewUtils.inject(this,view);
return view;
}
}
- 修改GlobalConstans,添加一个新的URL常量,表示组图的接口地址,代码如下:
package com.example.zhbj.global;
public class GlobalConstans {
/**
* 服务器根域名,地址
*/
public static final String SERVER_URL = "http://10.0.2.2:8080/zhbj";
/**
* 分类接口地址
*/
public static final String CATEGORY_URL = SERVER_URL + "/categories.json";
/**
* 组图接口地址
*/
public static final String PHOTOS_URL = SERVER_URL + "/photos/photos_1.json";
}
- 在domain包下新建PhotosBean类,用于将获取到的JSON数据解析出来,代码如下:
package com.example.zhbj.domain;
import java.util.ArrayList;
/**
* 组图网络数据
*/
public class PhotosBean {
public PhotosData data;
public class PhotosData{
public ArrayList<PhotoNews> news;
}
public class PhotoNews{
public String title;
public String listimage;
}
}
- 修改PhotosMenuDetailPaper,添加initData()方法,用于将数据进行初始化,再添加getDataFromServer()方法,用于从服务器上获取数据,再添加processData()方法,用于加载数据。除此之外,再通过缓存工具类来读/写缓存,代码如下:
package com.example.zhbj.base.impl.menudetail;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.widget.GridView;
import android.widget.ListView;
import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.PhotosBean;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.google.gson.Gson;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import java.util.ArrayList;
/**
* 菜单详情页 - 组图
*/
public class PhotosMenuDetailPaper extends BaseMenuDetailPaper {
/**
* ListView实例对象
*/
@ViewInject(R.id.lv_list2)
private ListView lvList;
/**
* GridView实例对象
*/
@ViewInject(R.id.gv_list)
private GridView gvList;
/**
* 存储照片新闻的集合
*/
private ArrayList<PhotosBean.PhotoNews> mPhotoList;
public PhotosMenuDetailPaper(Activity activity) {
super(activity);
}
@Override
public View initViews() {
// 给空的帧布局动态添加布局对象
/*
TextView view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("菜单详情页-组图");
*/
View view = View.inflate(mActivity, R.layout.paper_photos_menu_detail,null);
ViewUtils.inject(this,view);
return view;
}
/**
* 初始化数据
*/
@Override
public void initData() {
String cache = CacheUtil.getCache(mActivity, GlobalConstans.PHOTOS_URL);
if (!TextUtils.isEmpty(cache)){
processData(cache);
}
getDataFromServer();
}
/**
* 从服务器获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, GlobalConstans.PHOTOS_URL, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,GlobalConstans.PHOTOS_URL,result);
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
/**
* 加载数据
* @param result
*/
private void processData(String result){
Gson gson = new Gson();
PhotosBean photosBean = gson.fromJson(result, PhotosBean.class);
mPhotoList = photosBean.data.news;
}
}
2.填充组图页面数据
将布局设置好后,接下来就是向布局中填充页面数据
- 修改PhotosMenuDetailPaper,添加内部类适配器,为ListView填充数据,代码如下:
package com.example.zhbj.base.impl.menudetail;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ListView;
import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.PhotosBean;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.google.gson.Gson;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import java.util.ArrayList;
/**
* 菜单详情页 - 组图
*/
public class PhotosMenuDetailPaper extends BaseMenuDetailPaper {
/**
* ListView实例对象
*/
@ViewInject(R.id.lv_list2)
private ListView lvList;
/**
* GridView实例对象
*/
@ViewInject(R.id.gv_list)
private GridView gvList;
/**
* 存储照片新闻的集合
*/
private ArrayList<PhotosBean.PhotoNews> mPhotoList;
public PhotosMenuDetailPaper(Activity activity) {
super(activity);
}
@Override
public View initViews() {
// 给空的帧布局动态添加布局对象
/*
TextView view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("菜单详情页-组图");
*/
View view = View.inflate(mActivity, R.layout.paper_photos_menu_detail,null);
ViewUtils.inject(this,view);
return view;
}
/**
* 初始化数据
*/
@Override
public void initData() {
String cache = CacheUtil.getCache(mActivity, GlobalConstans.PHOTOS_URL);
if (!TextUtils.isEmpty(cache)){
processData(cache);
}
getDataFromServer();
}
/**
* 从服务器获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, GlobalConstans.PHOTOS_URL, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,GlobalConstans.PHOTOS_URL,result);
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
/**
* 加载数据
* @param result
*/
private void processData(String result){
Gson gson = new Gson();
PhotosBean photosBean = gson.fromJson(result, PhotosBean.class);
mPhotoList = photosBean.data.news;
}
class PhotosAdapter extends BaseAdapter{
@Override
public int getCount() {
return mPhotoList.size();
}
@Override
public PhotosBean.PhotoNews getItem(int position) {
return mPhotoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
}
- 增加list_item_photo.xml布局文件,作为ListView的卡片式布局,代码如下:
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="@drawable/pic_item_list_default"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_pic"
android:layout_width="match_parent"
android:layout_height="180dp"
android:scaleType="centerCrop"
android:src="@drawable/pic_item_list_default"/>
<TextView
android:id="@+id/tv_title4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:textColor="#000"
android:textSize="18sp"
android:text="标题"
android:singleLine="true"/>
</LinearLayout>
</LinearLayout>
- 修改PhotosMenuDetailPaper,重写getView()方法,并且再编写内部类ViewHolder,代码如下:
package com.example.zhbj.base.impl.menudetail;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.PhotosBean;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import java.util.ArrayList;
/**
* 菜单详情页 - 组图
*/
public class PhotosMenuDetailPaper extends BaseMenuDetailPaper {
/**
* ListView实例对象
*/
@ViewInject(R.id.lv_list2)
private ListView lvList;
/**
* GridView实例对象
*/
@ViewInject(R.id.gv_list)
private GridView gvList;
/**
* 存储照片新闻的集合
*/
private ArrayList<PhotosBean.PhotoNews> mPhotoList;
public PhotosMenuDetailPaper(Activity activity) {
super(activity);
}
@Override
public View initViews() {
// 给空的帧布局动态添加布局对象
/*
TextView view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("菜单详情页-组图");
*/
View view = View.inflate(mActivity, R.layout.paper_photos_menu_detail,null);
ViewUtils.inject(this,view);
return view;
}
/**
* 初始化数据
*/
@Override
public void initData() {
String cache = CacheUtil.getCache(mActivity, GlobalConstans.PHOTOS_URL);
if (!TextUtils.isEmpty(cache)){
processData(cache);
}
getDataFromServer();
}
/**
* 从服务器获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, GlobalConstans.PHOTOS_URL, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,GlobalConstans.PHOTOS_URL,result);
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
/**
* 加载数据
* @param result
*/
private void processData(String result){
Gson gson = new Gson();
PhotosBean photosBean = gson.fromJson(result, PhotosBean.class);
mPhotoList = photosBean.data.news;
// 给ListView设置数据
lvList.setAdapter(new PhotosAdapter());
}
class PhotosAdapter extends BaseAdapter{
/**
* BitmapUtils的对象实例
*/
private BitmapUtils mBitmapUtils;
public PhotosAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}
@Override
public int getCount() {
return mPhotoList.size();
}
@Override
public PhotosBean.PhotoNews getItem(int position) {
return mPhotoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_photo,null);
holder = new ViewHolder();
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title4);
holder.ivPic = (ImageView) convertView.findViewById(R.id.iv_pic);
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
PhotosBean.PhotoNews item = getItem(position);
holder.tvTitle.setText(item.title);
mBitmapUtils.display(holder.ivPic,item.listimage);
return convertView;
}
}
static class ViewHolder{
public TextView tvTitle;
public ImageView ivPic;
}
}
- 修改PhotosMenuDetailPaper,修改processData()方法,给GridView填充数据,代码如下:
/**
* 加载数据
* @param result
*/
private void processData(String result){
Gson gson = new Gson();
PhotosBean photosBean = gson.fromJson(result, PhotosBean.class);
mPhotoList = photosBean.data.news;
// 给ListView设置数据
lvList.setAdapter(new PhotosAdapter());
// 给GridView设置数据
gvList.setAdapter(new PhotosAdapter());
}
3.组图切换展示方式
我们已经把数据填充到ListView和GridView中了,接下来就需要编写逻辑来在这两个视图之间切换
- 修改title_bar.xml,在标题上添加一个图形按钮,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_red_bg">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="25sp"
android:text=""/>
<ImageButton
android:id="@+id/btn_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@null"
android:layout_marginLeft="10dp"
android:src="@drawable/img_menu"/>
<ImageButton
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@null"
android:layout_marginLeft="10dp"
android:visibility="gone"
android:src="@drawable/back"/>
<LinearLayout
android:id="@+id/ll_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:visibility="gone"
android:orientation="horizontal">
<ImageButton
android:id="@+id/btn_textsize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:layout_marginRight="10dp"
android:src="@drawable/icon_textsize"/>
<ImageButton
android:id="@+id/btn_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/icon_share"/>
</LinearLayout>
<ImageButton
android:id="@+id/btn_display"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="@null"
android:src="@drawable/icon_pic_grid_type"
android:visibility="gone"/>
</RelativeLayout>
- 修改BasePaper,获取标题栏上刚刚创建好的实例,代码如下:
package com.example.zhbj.base;
import android.app.Activity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import com.example.zhbj.MainActivity;
import com.example.zhbj.R;
import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;
/**
* 5个标签页的基类
*
* 共性:子类都有标题栏,所以可以直接在父类中加载布局页面
*/
public class BasePaper {
/**
* Activity对象
*/
public Activity mActivity;
/**
* 标题的对象
*/
public TextView tvTitle;
/**
* 标题按钮的对象
*/
public ImageButton btnMenu;
/**
* 当前页面的根布局
*/
public View mRootView;
/**
* 切换布局的对象
*/
public ImageButton btnDisplay;
/**
* 内容的对象
*/
public FrameLayout flContainer;
public BasePaper(Activity activity) {
mActivity = activity;
// 在页面对象创建时,就初始化了布局
mRootView = initView();
}
/**
* 初始化布局
*/
public View initView(){
View view = View.inflate(mActivity, R.layout.base_paper, null);
tvTitle = (TextView) view.findViewById(R.id.tv_title);
btnMenu = (ImageButton) view.findViewById(R.id.btn_menu);
flContainer = (FrameLayout) view.findViewById(R.id.fl_container);
btnDisplay = (ImageButton) view.findViewById(R.id.btn_display);
// 点击菜单按钮,控制侧边栏开关
btnMenu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggle();
}
});
return view;
}
/**
* 控制侧边栏的开关
*/
private void toggle() {
MainActivity mainUI = (MainActivity) mActivity;
SlidingMenu slidingMenu = mainUI.getSlidingMenu();
slidingMenu.toggle(); // 如果当前为开,则关;反之亦然
}
/**
* 初始化数据
*/
public void initData(){
}
}
- 修改NewsCenterPaper,修改processData()方法,修改创建对象时的传参值,代码如下:
/**
* 解析从服务器获取的JSON数据
*/
private void processData(String json) {
Gson gson = new Gson();
// 通过json和对象类,来生成一个对象
newsMenu = gson.fromJson(json, NewsMenu.class);
// 找到侧边栏对象
MainActivity mainUI = (MainActivity) mActivity;
LeftMenuFragment fragment = mainUI.getLeftMenuFragment();
fragment.setMenuData(newsMenu.data);
// 网络请求成功之后,初始化四个菜单详情页
mPapers = new ArrayList<>();
mPapers.add(new NewsMenuDetailPaper(mActivity,newsMenu.data.get(0).children)); // 通过构造方法传递数据
mPapers.add(new TopicMenuDetailPaper(mActivity));
mPapers.add(new PhotosMenuDetailPaper(mActivity,btnDisplay));
mPapers.add(new InteractMenuDetailPaper(mActivity));
// 设置新闻菜单详情页为默认页面
setMenuDetailPager(0);
}
- 修改PhotosMenuDetailPaper,修改构造方法,并且实现接口OnClickListener,代码如下:
package com.example.zhbj.base.impl.menudetail;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.PhotosBean;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import java.util.ArrayList;
/**
* 菜单详情页 - 组图
*/
public class PhotosMenuDetailPaper extends BaseMenuDetailPaper implements View.OnClickListener {
/**
* ListView实例对象
*/
@ViewInject(R.id.lv_list2)
private ListView lvList;
/**
* GridView实例对象
*/
@ViewInject(R.id.gv_list)
private GridView gvList;
/**
* 存储照片新闻的集合
*/
private ArrayList<PhotosBean.PhotoNews> mPhotoList;
public PhotosMenuDetailPaper(Activity mActivity, ImageButton btnDisplay) {
super(mActivity);
btnDisplay.setOnClickListener(this); // 设置切换按钮的监听
}
@Override
public View initViews() {
// 给空的帧布局动态添加布局对象
/*
TextView view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("菜单详情页-组图");
*/
View view = View.inflate(mActivity, R.layout.paper_photos_menu_detail,null);
ViewUtils.inject(this,view);
return view;
}
/**
* 初始化数据
*/
@Override
public void initData() {
String cache = CacheUtil.getCache(mActivity, GlobalConstans.PHOTOS_URL);
if (!TextUtils.isEmpty(cache)){
processData(cache);
}
getDataFromServer();
}
/**
* 从服务器获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, GlobalConstans.PHOTOS_URL, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,GlobalConstans.PHOTOS_URL,result);
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
/**
* 加载数据
* @param result
*/
private void processData(String result){
Gson gson = new Gson();
PhotosBean photosBean = gson.fromJson(result, PhotosBean.class);
mPhotoList = photosBean.data.news;
// 给ListView设置数据
lvList.setAdapter(new PhotosAdapter());
// 给GridView设置数据
gvList.setAdapter(new PhotosAdapter());
}
@Override
public void onClick(View v) {
}
class PhotosAdapter extends BaseAdapter{
/**
* BitmapUtils的对象实例
*/
private BitmapUtils mBitmapUtils;
public PhotosAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}
@Override
public int getCount() {
return mPhotoList.size();
}
@Override
public PhotosBean.PhotoNews getItem(int position) {
return mPhotoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_photo,null);
holder = new ViewHolder();
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title4);
holder.ivPic = (ImageView) convertView.findViewById(R.id.iv_pic);
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
PhotosBean.PhotoNews item = getItem(position);
holder.tvTitle.setText(item.title);
mBitmapUtils.display(holder.ivPic,item.listimage);
return convertView;
}
}
static class ViewHolder{
public TextView tvTitle;
public ImageView ivPic;
}
}
- 修改PhotosMenuDetailPaper,重写onClick()方法,实现按钮的方法逻辑,代码如下:
package com.example.zhbj.base.impl.menudetail;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.example.zhbj.R;
import com.example.zhbj.base.BaseMenuDetailPaper;
import com.example.zhbj.domain.PhotosBean;
import com.example.zhbj.global.GlobalConstans;
import com.example.zhbj.util.CacheUtil;
import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import com.lidroid.xutils.view.annotation.ViewInject;
import java.util.ArrayList;
/**
* 菜单详情页 - 组图
*/
public class PhotosMenuDetailPaper extends BaseMenuDetailPaper implements View.OnClickListener {
/**
* ListView实例对象
*/
@ViewInject(R.id.lv_list2)
private ListView lvList;
/**
* GridView实例对象
*/
@ViewInject(R.id.gv_list)
private GridView gvList;
/**
* 存储照片新闻的集合
*/
private ArrayList<PhotosBean.PhotoNews> mPhotoList;
/**
* 判断是否是ListView
*/
private boolean isListView = true;
private ImageButton btnDisplay;
public PhotosMenuDetailPaper(Activity mActivity, ImageButton btnDisplay) {
super(mActivity);
this.btnDisplay = btnDisplay;
btnDisplay.setOnClickListener(this); // 设置切换按钮的监听
}
@Override
public View initViews() {
// 给空的帧布局动态添加布局对象
/*
TextView view = new TextView(mActivity);
view.setTextSize(22);
view.setTextColor(Color.RED);
view.setGravity(Gravity.CENTER); // 居中显示
view.setText("菜单详情页-组图");
*/
View view = View.inflate(mActivity, R.layout.paper_photos_menu_detail,null);
ViewUtils.inject(this,view);
return view;
}
/**
* 初始化数据
*/
@Override
public void initData() {
String cache = CacheUtil.getCache(mActivity, GlobalConstans.PHOTOS_URL);
if (!TextUtils.isEmpty(cache)){
processData(cache);
}
getDataFromServer();
}
/**
* 从服务器获取数据
*/
private void getDataFromServer() {
HttpUtils utils = new HttpUtils();
utils.send(HttpRequest.HttpMethod.GET, GlobalConstans.PHOTOS_URL, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
String result = responseInfo.result;
processData(result);
CacheUtil.setCache(mActivity,GlobalConstans.PHOTOS_URL,result);
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
/**
* 加载数据
* @param result
*/
private void processData(String result){
Gson gson = new Gson();
PhotosBean photosBean = gson.fromJson(result, PhotosBean.class);
mPhotoList = photosBean.data.news;
// 给ListView设置数据
lvList.setAdapter(new PhotosAdapter());
// 给GridView设置数据
gvList.setAdapter(new PhotosAdapter());
}
@Override
public void onClick(View v) {
if (isListView){
// 显示GridView
lvList.setVisibility(View.GONE);
gvList.setVisibility(View.VISIBLE);
btnDisplay.setImageResource(R.drawable.icon_pic_list_type);
isListView = false;
}else {
// 显示ListView
lvList.setVisibility(View.VISIBLE);
gvList.setVisibility(View.GONE);
btnDisplay.setImageResource(R.drawable.icon_pic_grid_type);
isListView = true;
}
}
class PhotosAdapter extends BaseAdapter{
/**
* BitmapUtils的对象实例
*/
private BitmapUtils mBitmapUtils;
public PhotosAdapter() {
mBitmapUtils = new BitmapUtils(mActivity);
mBitmapUtils.configDefaultLoadingImage(R.drawable.pic_item_list_default);
}
@Override
public int getCount() {
return mPhotoList.size();
}
@Override
public PhotosBean.PhotoNews getItem(int position) {
return mPhotoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = View.inflate(mActivity,R.layout.list_item_photo,null);
holder = new ViewHolder();
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title4);
holder.ivPic = (ImageView) convertView.findViewById(R.id.iv_pic);
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
PhotosBean.PhotoNews item = getItem(position);
holder.tvTitle.setText(item.title);
mBitmapUtils.display(holder.ivPic,item.listimage);
return convertView;
}
}
static class ViewHolder{
public TextView tvTitle;
public ImageView ivPic;
}
}
- 修改NewsCenterPaper,修改setMenuDetailPager()方法,代码如下:
/**
* 设置新闻中心的详情页
* @param position
*/
public void setMenuDetailPager(int position){
BaseMenuDetailPaper paper = mPapers.get(position);
// 判断是否是组图,如果是,显示切换按钮,否则隐藏
if (paper instanceof PhotosMenuDetailPaper){
btnDisplay.setVisibility(View.VISIBLE);
}else {
btnDisplay.setVisibility(View.GONE);
}
// 清除之前帧布局显示的内容
flContainer.removeAllViews();
// 修改当前帧布局显示的内容
flContainer.addView(paper.mRootView);
// 初始化当前页面的数据
paper.initData();
// 修改标题栏
tvTitle.setText(newsMenu.data.get(position).title);
}
4.三级缓存原理
到这里,其实整个项目已经开发完毕了。但是为了更进一步,我们需要学习更多关于应用优化的内容,这就是这篇要学习的内容:三级缓存
按照先后顺序,三级缓存分别指的是:
-
内存缓存
速度很快,不浪费流量,优先
-
本地缓存(SDCard)
速度快,不浪费流量,其次
-
网络缓存
速度慢,浪费流量,最后
5.网络缓存开发
前面我们提到了三级缓存的原理,现在就来动手实现一下
- 由于xUtils中的BitmapUtils已经实现了网络缓存,我们这里也来写一个类似的工具类。在util包下新建一个bitmap包,表示图片工具类,再新建一个MyBitmapUtils,表示实现了网络缓存的图片工具类,代码如下:
package com.example.zhbj.util.bitmap;
import android.widget.ImageView;
/**
* 自定义三级缓存工具类
*/
public class MyBitmapUtils {
/**
* 加载图片进行展示
* @param imageView ImageView组件
* @param url
*/
public void display(ImageView imageView,String url){
// 一级缓存:内存缓存
// 二级缓存:本地缓存
// 三级缓存:网络缓存
}
}
- 在bitmap包下新建NetCacheUtil,表示网络缓存的工具类,实现相应逻辑,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 网络缓存工具类
*/
public class NetCacheUtils {
/**
* ImaegView对象
*/
private ImageView mImageView;
/**
* 图片url
*/
private String url;
/**
* 异步下载图片
* @param imageView ImageView组件
* @param url uri地址
*/
public void getBitmapFromNet(ImageView imageView, String url) {
// 调用AsyncTask进行异步下载
new BitmapTask().execute(imageView,url);
}
class BitmapTask extends AsyncTask<Object,Void,Bitmap>{
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(Object... params) {
mImageView = (ImageView) params[0];
url = (String) params[1];
// 给当前ImageView打上标签
mImageView.setTag(url);
// 使用url下载图片
Bitmap bitmap = download(url);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
// 将下载好的图片设置给ImaegView
/*
注意:由于ListView的重用机制,导致某个item有可能展示它所重用的那个item的图片,导致图片错乱(即数据不同步)
解决方案:确保当前设置的图片和当前显示的ImageView完全匹配
*/
String tag = (String) mImageView.getTag(); // 获取和当前ImageView绑定的url
if (url.equals(tag)){ // 判断当前下载的图片url是否和ImageView中的url一致,如果一致,说明图片正确
mImageView.setImageBitmap(bitmap);
}
}
}
/**
* 使用url下载图片
* @param url url地址
* @return
*/
public Bitmap download(String url) {
// 创建连接对象
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(6000);
conn.setReadTimeout(6000);
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200){
InputStream in = conn.getInputStream();
// 使用输入流生成一个Bitmap对象
Bitmap bitmap = BitmapFactory.decodeStream(in);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null){
conn.disconnect();
}
}
return null;
}
}
- 修改MyBitmapUtils,调用NetCacheUtil的getBitmapFromNet()方法,代码如下:
package com.example.zhbj.util.bitmap;
import android.widget.ImageView;
/**
* 自定义三级缓存工具类
*/
public class MyBitmapUtils {
/**
* 网络缓存工具类对象
*/
private NetCacheUtils mNetCacheUtils;
public MyBitmapUtils() {
mNetCacheUtils = new NetCacheUtils();
}
/**
* 加载图片进行展示
* @param imageView ImageView组件
* @param url uri地址
*/
public void display(ImageView imageView,String url){
// 一级缓存:内存缓存
// 二级缓存:本地缓存
// 三级缓存:网络缓存
mNetCacheUtils.getBitmapFromNet(imageView,url);
}
}
6.本地缓存开发
之前开发完网络缓存后,会发现每当要从网上获取数据时都要下载数据,这将十分不便,所以现在来开发本地缓存
- 这里要使用MD5的一个工具类,MD5Encoder,将它放在util包下,代码如下:
package com.example.zhbj.util;
import java.security.MessageDigest;
public class MD5Encoder {
public static String encode(String string) throws Exception {
byte[] hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) {
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
}
- 在util包下新建LocalCacheUtils,表示本地缓存的工具类,并且完善相应逻辑,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import com.example.zhbj.util.MD5Encoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 本地缓存工具类
*/
public class LocalCacheUtils {
/**
* 缓存文件夹的路径
*/
private String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/zhbj_cache/";;
/**
* 写缓存
* @param url url地址
* @param bitmap 图片
*/
public void setLocalCache(String url,Bitmap bitmap){
// 将图片保存在本地文件
File dir = new File(PATH);
if (!dir.exists() || !dir.isDirectory()){
dir.mkdirs(); // 创建文件夹
}
try {
File cacheFile = new File(dir, MD5Encoder.encode(url)); // 创建本地的文件,以url的md5命名
// 将图片压缩保存在本地;参数1:图片格式,参数2:压缩比(0-100,100表示不压缩),参数3:输出流
bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(cacheFile));
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 读缓存
* @param url url地址
*/
public Bitmap getLocalCache(String url){
try {
File cacheFile = new File(url,MD5Encoder.encode(url)); // 创建本地的文件,以url的md5命名
if (cacheFile.exists()){
// 缓存存在
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(cacheFile));
return bitmap;
}
}catch (Exception e){
}
return null;
}
}
- 修改MyBitmapUtils,调用LocalCacheUtils的getLocalCache()方法,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.widget.ImageView;
/**
* 自定义三级缓存工具类
*/
public class MyBitmapUtils {
/**
* 本地缓存工具类对象
*/
private LocalCacheUtils mLocalCacheUtils;
/**
* 网络缓存工具类对象
*/
private NetCacheUtils mNetCacheUtils;
public MyBitmapUtils() {
mLocalCacheUtils = new LocalCacheUtils();
mNetCacheUtils = new NetCacheUtils(mLocalCacheUtils);
}
/**
* 加载图片进行展示
* @param imageView ImageView组件
* @param url uri地址
*/
public void display(ImageView imageView,String url){
// 一级缓存:内存缓存
// 二级缓存:本地缓存
Bitmap bitmap = mLocalCacheUtils.getLocalCache(url);
if (bitmap != null){
imageView.setImageBitmap(bitmap);
return;
}
// 三级缓存:网络缓存
mNetCacheUtils.getBitmapFromNet(imageView,url);
}
}
- 修改NetCacheUtils,在从网上获取数据时将缓存写入文件中,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 网络缓存工具类
*/
public class NetCacheUtils {
/**
* ImaegView对象
*/
private ImageView mImageView;
/**
* 图片url
*/
private String url;
/**
* 本地缓存的工具类对象
*/
private LocalCacheUtils mLocalCacheUtils;
public NetCacheUtils(LocalCacheUtils localCacheUtils) {
mLocalCacheUtils = localCacheUtils;
}
/**
* 异步下载图片
* @param imageView ImageView组件
* @param url uri地址
*/
public void getBitmapFromNet(ImageView imageView, String url) {
// 调用AsyncTask进行异步下载
new BitmapTask().execute(imageView,url);
}
class BitmapTask extends AsyncTask<Object,Void,Bitmap>{
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(Object... params) {
mImageView = (ImageView) params[0];
url = (String) params[1];
// 给当前ImageView打上标签
mImageView.setTag(url);
// 使用url下载图片
Bitmap bitmap = download(url);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
// 将下载好的图片设置给ImaegView
/*
注意:由于ListView的重用机制,导致某个item有可能展示它所重用的那个item的图片,导致图片错乱(即数据不同步)
解决方案:确保当前设置的图片和当前显示的ImageView完全匹配
*/
if (bitmap != null){
String tag = (String) mImageView.getTag(); // 获取和当前ImageView绑定的url
if (url.equals(tag)){ // 判断当前下载的图片url是否和ImageView中的url一致,如果一致,说明图片正确
mImageView.setImageBitmap(bitmap);
// 写本地缓存
mLocalCacheUtils.setLocalCache(url,bitmap);
}
}
}
}
/**
* 使用url下载图片
* @param url url地址
* @return
*/
public Bitmap download(String url) {
// 创建连接对象
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(6000);
conn.setReadTimeout(6000);
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200){
InputStream in = conn.getInputStream();
// 使用输入流生成一个Bitmap对象
Bitmap bitmap = BitmapFactory.decodeStream(in);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null){
conn.disconnect();
}
}
return null;
}
}
7.内存缓存开发
之前开发完本地缓存后,现在再来开发内存缓存
- 在bitmap包下新建MemoryCacheUtils,表示内存缓存的工具类,并且完善相应逻辑,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import java.util.HashMap;
/**
* 内存缓存工具类
*/
public class MemoryCacheUtils {
/**
* 存储内存空间的map对象
*/
private HashMap<String, Bitmap> mHashMap = new HashMap<>();
/**
* 写缓存
* @param url url地址
* @param bitmap bitmap对象
*/
public void setMemoryCache(String url,Bitmap bitmap){
mHashMap.put(url,bitmap);
}
/**
* 读缓存
* @param url url地址
* @return
*/
public Bitmap getMemoryCache(String url){
return mHashMap.get(url);
}
}
- 修改MyBitmapUtils,调用MemoryCacheUtils的getMemoryCache()方法,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.widget.ImageView;
/**
* 自定义三级缓存工具类
*/
public class MyBitmapUtils {
/**
* 内存缓存工具类对象
*/
private MemoryCacheUtils mMemoryCacheUtils;
/**
* 本地缓存工具类对象
*/
private LocalCacheUtils mLocalCacheUtils;
/**
* 网络缓存工具类对象
*/
private NetCacheUtils mNetCacheUtils;
public MyBitmapUtils() {
mMemoryCacheUtils = new MemoryCacheUtils();
mLocalCacheUtils = new LocalCacheUtils();
mNetCacheUtils = new NetCacheUtils(mLocalCacheUtils,mMemoryCacheUtils);
}
/**
* 加载图片进行展示
* @param imageView ImageView组件
* @param url uri地址
*/
public void display(ImageView imageView,String url){
// 一级缓存:内存缓存
Bitmap bitmap = mMemoryCacheUtils.getMemoryCache(url);
if (bitmap != null){
imageView.setImageBitmap(bitmap);
return;
}
// 二级缓存:本地缓存
bitmap = mLocalCacheUtils.getLocalCache(url);
if (bitmap != null){
imageView.setImageBitmap(bitmap);
// 写内存缓存
mMemoryCacheUtils.setMemoryCache(url,bitmap);
return;
}
// 三级缓存:网络缓存
mNetCacheUtils.getBitmapFromNet(imageView,url);
}
}
- 修改NetCacheUtils,在从网上获取数据时将缓存写入内存中,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 网络缓存工具类
*/
public class NetCacheUtils {
/**
* ImaegView对象
*/
private ImageView mImageView;
/**
* 图片url
*/
private String url;
/**
* 内存缓存的工具类对象
*/
private MemoryCacheUtils mMemoryCacheUtils;
/**
* 本地缓存的工具类对象
*/
private LocalCacheUtils mLocalCacheUtils;
public NetCacheUtils(LocalCacheUtils localCacheUtils, MemoryCacheUtils memoryCacheUtils) {
mLocalCacheUtils = localCacheUtils;
mMemoryCacheUtils = memoryCacheUtils;
}
/**
* 异步下载图片
* @param imageView ImageView组件
* @param url uri地址
*/
public void getBitmapFromNet(ImageView imageView, String url) {
// 调用AsyncTask进行异步下载
new BitmapTask().execute(imageView,url);
}
class BitmapTask extends AsyncTask<Object,Void,Bitmap>{
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(Object... params) {
mImageView = (ImageView) params[0];
url = (String) params[1];
// 给当前ImageView打上标签
mImageView.setTag(url);
// 使用url下载图片
Bitmap bitmap = download(url);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
// 将下载好的图片设置给ImaegView
/*
注意:由于ListView的重用机制,导致某个item有可能展示它所重用的那个item的图片,导致图片错乱(即数据不同步)
解决方案:确保当前设置的图片和当前显示的ImageView完全匹配
*/
if (bitmap != null){
String tag = (String) mImageView.getTag(); // 获取和当前ImageView绑定的url
if (url.equals(tag)){ // 判断当前下载的图片url是否和ImageView中的url一致,如果一致,说明图片正确
mImageView.setImageBitmap(bitmap);
// 写本地缓存
mLocalCacheUtils.setLocalCache(url,bitmap);
// 写内存缓存
mMemoryCacheUtils.setMemoryCache(url,bitmap);
}
}
}
}
/**
* 使用url下载图片
* @param url url地址
* @return
*/
public Bitmap download(String url) {
// 创建连接对象
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(6000);
conn.setReadTimeout(6000);
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200){
InputStream in = conn.getInputStream();
// 使用输入流生成一个Bitmap对象
Bitmap bitmap = BitmapFactory.decodeStream(in);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null){
conn.disconnect();
}
}
return null;
}
}
8.内存溢出原理
现在完成了三级缓存的开发,接下来就要将目光着重放在一级内存:内存缓存中,因为内存缓存可能会发生内存溢出的问题
先来简单介绍两个概念:栈和堆
假设现在有一行语句:Person p = new Person()
执行后在内存中存放如下:
- 栈:存放p(引用)
- 堆:存放Person(对象)
在Davik虚拟机中,默认给每个app分配16M
内存空间,和手机配置无关
而内存溢出指的是:堆里的对象过多,超过了16M
的内存空间,导致了内存溢出;根本原因是虚拟机挂掉了
Java中具有GC(垃圾回收器)机制,会自动回收没有引用的对象。但是这里编写的内存缓存工具类中引用的是HashMap,存放的都是有引用的对象,因此不会被这个机制影响
9.软应用介绍
介绍完内存溢出后,现在按照引用强度顺序,简单介绍一下Java中的引用体系:
- 强引用(默认):当内存不够时,垃圾回收器也不会考虑回收
- 软引用:当内存不够时,垃圾回收器会考虑回收,对象类为SoftReference
- 弱引用:当内存不够时,垃圾回收器更会考虑回收
- 虚引用:当内存不够时,垃圾回收器最会考虑回收,对象类为PhantomReference
接下来,围绕软引用对MemoryCacheUtils进行改造,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import java.lang.ref.SoftReference;
import java.util.HashMap;
/**
* 内存缓存工具类
*/
public class MemoryCacheUtils {
/**
* 存储内存空间的map对象
*/
private HashMap<String, SoftReference<Bitmap>> mHashMap = new HashMap<>();
/**
* 写缓存
* @param url url地址
* @param bitmap bitmap对象
*/
public void setMemoryCache(String url,Bitmap bitmap){
SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap); // 用软引用包装btmap
mHashMap.put(url,soft);
}
/**
* 读缓存
* @param url url地址
* @return
*/
public Bitmap getMemoryCache(String url){
SoftReference<Bitmap> soft = mHashMap.get(url);
if (soft != null){
Bitmap bitmap = soft.get(); // 从软引用取出当前对象
return bitmap;
}
return null;
}
}
10.LruCache的使用
之前我们编写了运用软应用的内存缓存工具类,但是并没有派上作用。
翻阅文档之后,是因为:
因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。
所以,我们需要找到SoftReference的替代品,也就是LruCache,对MemoryCacheUtils进行改造,代码如下:
package com.example.zhbj.util.bitmap;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.v4.util.LruCache;
import java.lang.ref.SoftReference;
import java.util.HashMap;
/**
* 内存缓存工具类
*/
public class MemoryCacheUtils {
/**
* 存储内存空间的map对象
*/
private HashMap<String, SoftReference<Bitmap>> mHashMap = new HashMap<>();
/**
* LruCache对象实例
*/
private LruCache<String,Bitmap> mLrucache;
public MemoryCacheUtils() {
long maxMemory = Runtime.getRuntime().maxMemory(); // 获取虚拟机分配的最大内存,默认为16mb
// 需要传入一个内存缓存上限
mLrucache = new LruCache<String,Bitmap>((int) (maxMemory / 8)){
/**
* 返回单个对象占用内存的大小
* @param key
* @param value
* @return
*/
@Override
protected int sizeOf(@NonNull String key, @NonNull Bitmap value) {
// 计算图片占用的内存大小
int byteCount = value.getByteCount();
return byteCount;
}
};
}
/**
* 写缓存
* @param url url地址
* @param bitmap bitmap对象
*/
public void setMemoryCache(String url,Bitmap bitmap){
/*
SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap); // 用软引用包装btmap
mHashMap.put(url,soft);
*/
mLrucache.put(url,bitmap);
}
/**
* 读缓存
* @param url url地址
* @return
*/
public Bitmap getMemoryCache(String url){
/*
SoftReference<Bitmap> soft = mHashMap.get(url);
if (soft != null){
Bitmap bitmap = soft.get(); // 从软引用取出当前对象
return bitmap;
}
return null;
*/
return mLrucache.get(url);
}
}
11.LruCache原理分析
LRU:Least Recentlly Used,最近最少使用算法,LruCache就是对HashMap的封装
详细的原理,可以参考网上,这里不再详述
12.三级缓存总结
80%的内存溢出问题,都来源于图片加载,而解决内存溢出时,可以采用三级缓存策略
-
网络缓存:AsyncTask
-
本地缓存:File + MD5
-
内存缓存:LruCache(替换掉SoftReference)
另外,内存溢出和内存泄漏是有区别的,这点请注意
13.屏幕适配
在处理好三级缓存后,接下来就要进行项目的第二项优化:屏幕适配
屏幕适配有五大适配方式:
- 图片适配
- 布局适配
- 尺寸适配
- 权重适配
- 代码适配
接下来分别介绍
13.1 图片适配
为了演示图片适配的效果,可以新建一个项目作为演示,随意在drawable中添加一张名为test.jpg的图片,然后启动分辨率不同的虚拟机,可以看到图片显示的差距
观察res目录下的drawable目录,可以看到有5个名字相似的目录,如图所示:
当然高版本的话会变成mipmap,如图所示:
在这几个文件夹中,都存放了不同分辨率的同一张图片ic_launcher.png和这张图片的圆角形式ic_launcher_round.png,如图所示:
其中,这五个等级的图片在启动应用后会根据相应的分辨率来播放图片,具体如下:
-
ipdi:240 x 320
-
mpdi:320 x 480
-
hpdi:480 x 800
-
xhdpi:1280 x 720
-
xxhdpi:1920 x 1080
系统会根据当前屏幕分辨率优先加载对应dpi目录的图片吗,如果匹配到该目录的图片文件不存在,会去查找相近目录的同名图片文件
在实际开发中,美工做一套图就可以了,一般基于主流分辨率(1280 * 720)即可。
13.2 布局适配
上面介绍了图片适配,现在来介绍布局适配
跟图片适配相似的,布局也可以写多个同名布局来适配不同分辨率的手机
使用方法就是写一个指定格式的layout文件夹,如图所示:
480 x 800,指的是专门适配800x480的屏幕
13.3 尺寸适配(重点)
在布局文件中,若是指定了控件的具体形状属性,在不同分辨率的手机上显示的效果也会相应地进行适配,造成这一点的主要原因是长度单位dp
每个设备都有一个设备密度,可以通过getResources().getDisplayMetrics().density
这个api进行获取,各个不同分辨率的设备密度如下:
-
240 * 320:0.75,ldpi
-
320 * 480:1.0,mdpi
-
480 * 800:1.5,hdpi
-
1280 * 720:2,xhdpi
而长度单位dp和px(像素)的关系如下:
d
p
=
p
x
/
设
备
密
度
dp = px / 设备密度
dp=px/设备密度
针对尺寸适配,在res/values下有个专门的文件dimens.xml来处理尺寸适配的问题
跟布局适配同样的,也可以创建一个指定格式的values文件夹,如图所示:
在该目录下新建一个dimens.xml,作用与values/dimens.xml相同
一般来说代码都是需要填入px的,而这里可以将dp切换成px,写一个工具类即可,代码如下:
package com.example.zhbj.util;
import android.content.Context;
/**
* 长度单位转换的工具类
*/
public class DensityUtil {
/**
* 将dp转换成px
* @param dp
* @param context
*/
public static int dpTopx(float dp, Context context){
float density = context.getResources().getDisplayMetrics().density;
int px = (int) (dp * density + 0.5f);
return px;
}
/**
* 将px转换成dp
* @param px
* @param context
*/
public static float pxTodp(int px, Context context){
float density = context.getResources().getDisplayMetrics().density;
float dp = px / density;
return dp;
}
}
13.4 权重适配
上面介绍了图片适配,现在来介绍权重适配
在布局中,可以调用布局(组件)的这个属性:
android:weightSum=""
来指定组件在这个布局中的占用大小和位置
能用权重适配则尽量使用权重适配,但缺点是必须用线性布局
13.5 代码适配
上面介绍了权重适配,现在来介绍代码适配
跟权重适配类似,即在代码中获取到屏幕的宽高,即:
WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE);
int width = wm.getDefaultDisplay().getWidth(); // 获取宽度
int height = wm.getDefaultDisplay().getHeight(); // 获取高度
通过WindowManager获取屏幕的宽高,然后给组件动态设置属性:
TextView textView = (TextView)findViewById(R.id.textview); // 获取控件实例
LinearLayout.LayoutParams params = (LayoutParams)textView.getLayoutParams();// 获取控件的参数值
params.width = width / 3; // 设置控件的宽度
params.height = height / 5; // 设置控件的高度
textView.setLayoutParams(params); // 重置控件的属性
13.6 总结
基于主流屏幕(1280 * 720)开发,开发后期,才会开始考虑屏幕适配
养成良好的开发习惯:
- 多用线性布局和相对布局
- 少用绝对布局
- 多用dp,不用px,如果一定需要使用px,可以将dp转化成px使用
14.源码地址
到这里,这个项目整体就算结束了。这里放出源码地址,方便读者进行更深一步地学习
新闻资讯类App 智慧西安