该项目主要是对一个食谱的json网络解析,运用ListView将解析的图片、文本展示给用户。实现了ListView的分页加载,利用convertView的复用,并且解决了图片的错位问题,也将图片进行了缓存,实现了良好的用户体验和内存优化
Android的优化
1.布局优化:在xml中布局页面减少嵌套
2.java代码中用面向对象的思想,把相同的代码封装,相同的功能都有封装类
3.栈内存的优化:尽量不使用递归,使用算法
4.堆内存的优化:少创建对象,能重复使用的对象就继续使用
一、ListView的优化
1.属性的优化
宽高和高度都设置math—parent getView()只会执行一次
wrap—content getView()会执行多次
2.convertView的复用
前提:convertView的类型和新建的View的类型是相同,所以可以重复使用
优点:减少了创建的对象的数量,节省资源,降低内存的消耗,提高了性能
系统会把第一屏的数据先进行加载(屏幕当前显示的数量+1),当滑系统会把移动屏幕的页面缓存起来,重复利用该页面(convertView)
3.ViewHolder的使用
将控件封装,达到重用,减少findViewById的次数
View中标签的tag属性:
3.1和View中的Id相似,都能标识控件的唯一性
3.2view.setTag(object)设置控件的标签
3.3view.getTag()获取控件的标签
三、布局代码
1.主页面ListView控件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<ListView
android:id="@+id/listview"
android:layout_width="match_parent "
android:layout_height="match_parent "
android:divider="#00f"
android:dividerHeight="1dp"/>
<!--属性优化,将ListView控件的宽高设置为 match_parent -->
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="addMore"
android:layout_alignParentBottom="true"
android:visibility="gone"
android:text="加载更多"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/bt_top"
android:text="回到顶部"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:visibility="gone"
android:layout_margin="10dp"/>
</RelativeLayout>
2.ListView的Item的布局
<?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="match_parent" >
<ImageView
android:id="@+id/pic"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="45dp"
android:layout_toRightOf="@id/pic"
android:text="名称"
android:textSize="20sp" />
</RelativeLayout>
四、适配器代码
package com.example.listviewdemo.adapter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.listviewdemo.R;
import com.example.listviewdemo.back.ImageCallBack;
import com.example.listviewdemo.task.DownLoadImageTask;
import com.example.listviewdemo.task.mode.CookBook;
public class MyAdapter extends BaseAdapter{
private Context context;
private List<CookBook> data;
//保存当前下载的地址和图片的图片内容
private Map<String,Bitmap> map=new HashMap<String,Bitmap>();
public MyAdapter(Context context, List<CookBook> data) {
super();
this.context = context;
this.data = data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView==null)
{
LayoutInflater inflater=LayoutInflater.from(context);
convertView = inflater.inflate(R.layout.item_lv,parent,false);
holder=new ViewHolder();
holder.iv_pic=(ImageView) convertView.findViewById(R.id.pic);
holder.tv_title=(TextView) convertView.findViewById(R.id.tv_title);
convertView.setTag(holder);
}else
{
holder=(ViewHolder) convertView.getTag();
}
//为控件设置内容
holder.iv_pic.setImageResource(R.drawable.ic_launcher);
holder.tv_title.setText(data.get(position).getTitle());
//开启异步任务加载网络图片,通过接口回调,将图片返回--------
String imgUrl=data.get(position).getPicUrl();//当前下载的图片的地址
//把控件和地址绑定
holder.iv_pic.setTag(imgUrl);
//判断内存中(map)中,是否有该图片,如果有则从内存中取出图片,并显示出来
//如果没有开启异步任务下载图片,将下载后的图片,存入内存中(map),方便下次使用
if (!map.containsKey(imgUrl)) {
new DownLoadImageTask(new ImageCallBack() {
/**
* String imgpath当前异步任务加载的图片地址
* Bitmap bitmap当前异步任务 根据地址(imgpath)加载到的位图
*/
@Override
public void sendBitmap(String imgpath,Bitmap bitmap) {
if (bitmap!=null) {
//判断当前加载完成的图片地址,是否和控件上绑定的图片地址一致,如果一致则显示,如果不一致,则不显示
if (imgpath.equals(holder.iv_pic.getTag())) {
holder.iv_pic.setImageBitmap(bitmap);
}
map.put(imgpath, bitmap);
}
}
}).execute(imgUrl);
}else
{
holder.iv_pic.setImageBitmap(map.get(imgUrl));
}
return convertView;
}
class ViewHolder
{
private TextView tv_title;
private ImageView iv_pic;
}
}
五、实体类bean
package com.example.listviewdemo.task.mode;
/**
* 食谱实体类
* @author WangJ
*
*/
public class CookBook {
private String title;
private String picUrl;
public CookBook(String title, String picUrl) {
super();
this.title = title;
this.picUrl = picUrl;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPicUrl() {
return picUrl;
}
public void setPicUrl(String picUrl) {
this.picUrl = picUrl;
}
}
六、接口回调
1.文本和图片地址下载的接口回调
package com.example.listviewdemo.back;
import java.util.List;
import com.example.day09_listview03.task.mode.CookBook;
public interface DataCallBack {
public void sendResult(List<CookBook> data);
}
2.图片下载的接口回调
package com.example.listviewdemo.back;
import android.graphics.Bitmap;
/**
* 图片的接口回调
* @author WangJ
*
*/
public interface ImageCallBack {
public void sendBitmap(String imgpath,Bitmap bitmap);
}
七、解析数据和请求网络
1.解析数据
package com.example.listviewdemo.task;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import com.example.listviewdemo.task.mode.CookBook;
public class ParseJson {
public static List<CookBook> parseJson(String jsonStr) {
List<CookBook> data = null;
try {
if (jsonStr != null) {
data = new ArrayList<CookBook>();
JSONObject jsonObject = new JSONObject(jsonStr);
JSONArray jsonArray = jsonObject.getJSONArray("data");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject2 = jsonArray.getJSONObject(i);
String title = jsonObject2.getString("title");
String picUrl = jsonObject2.getString("pic");
data.add(new CookBook(title, picUrl));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
2.请求网络工具类
package com.example.listview03demo.tools;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* Http 加载网络数据
* @author Administrator
*
*/
public class HttpUtils {
/**
* 加载网络数据
* @param path 路径
* @return String
*/
public static String getStringResult(String path) {
HttpURLConnection conn=null;
InputStream is = null;
StringBuilder sBuilder = null;
try {
//1, 得到URL 对象
URL url = new URL(path);
//2, 打开连接
conn = (HttpURLConnection) url.openConnection();
//3, 设置请求方式
conn.setRequestMethod("GET");
//4, 连接
conn.connect();
//5, 判断返回的结果码 (200),得到响应数据
if(conn.getResponseCode() == 200)
{
is = conn.getInputStream();
sBuilder = new StringBuilder();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer))!=-1) {
sBuilder.append(new String(buffer,0,len));
}
}
} catch (Exception e) {
e.printStackTrace();
}finally
{
try {
if (conn!=null) {
conn.disconnect();
}
if (is!=null) {
is.close();
}
} catch (Exception e2) {
}
}
return sBuilder.toString();
}
/**
* 加载网络图片
* @param path
* @return byte[]
*/
public static byte[] getByteResult(String path)
{
HttpURLConnection conn=null;
InputStream is=null;
ByteArrayOutputStream baos=null;
try {
//1, 得到URL 对象
URL url = new URL(path);
//2, 打开连接
conn = (HttpURLConnection) url.openConnection();
//3, 设置请求方式
conn.setRequestMethod("GET");
//4, 连接
conn.connect();
//5, 判断返回的结果码 (200),得到响应数据
if(conn.getResponseCode() == 200)
{
is = conn.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len=is.read(buffer))!=-1) {
baos.write(buffer, 0, len);
baos.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if (conn!=null) {
conn.disconnect();
}
if (is!=null) {
is.close();
}
if (baos!=null) {
baos.close();
}
} catch (Exception e2) {
}
}
return baos.toByteArray();
}
}
八、开启异步
package com.example.listviewdemo;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.example.listviewdemo.adapter.MyAdapter;
import com.example.listviewdemo.back.DataCallBack;
import com.example.listviewdemo.task.DownLoadDataTask;
import com.example.listviewdemo.task.mode.CookBook;
public class MainActivity extends Activity {
private ListView listview;
//地址
private String path="http://www.qubaobei.com/ios/cf/dish_list.php?stage_id=1&limit=20&page=";
private int num=1;//当前页码
private MyAdapter adapter;
private boolean isLast=false;
private Button button;//加载更多按钮
private List<CookBook> totalList=new ArrayList<CookBook>();//要显示的总数据
private Button bt_top;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView) findViewById(R.id.listview);
button = (Button) findViewById(R.id.button);
bt_top = (Button) findViewById(R.id.bt_top);
//开启异步任务,加载数据,加载第一页
new DownLoadDataTask(this,new DataCallBack() {
@Override
public void sendResult(List<CookBook> data) {
//返回当前加载的数据
totalList.addAll(data);
adapter=new MyAdapter(MainActivity.this, totalList);
listview.setAdapter(adapter);
}
}).execute(path+num);
listview.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (isLast&&scrollState==SCROLL_STATE_IDLE) {
//显示加载更多
button.setVisibility(View.VISIBLE);
}else
{
//隐藏
button.setVisibility(View.GONE);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//判断是否到底部
isLast=(firstVisibleItem+visibleItemCount==totalItemCount);
//判断是否显示回到底部
if (firstVisibleItem>5) {
bt_top.setVisibility(View.VISIBLE);
}else {
bt_top.setVisibility(View.GONE);
}
}
});
//回到列表的顶部
bt_top.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
listview.setSelection(0);
}
});
}
public void addMore(View v)
{
//当前页面+1
num++;
//2.开启异步任务加载数据
new DownLoadDataTask(MainActivity.this, new DataCallBack() {
@Override
public void sendResult(List<CookBook> data) {
if (data==null||data.size()<=0) {
Toast.makeText(MainActivity.this,
"已结加载到最后一页", 1).show();
}else
{
//加载的数据放入总数据中
totalList.addAll(data);
//刷新适配器
adapter.notifyDataSetChanged();
}
}
}).execute(path+num);
//3.隐藏“加载数据”
button.setVisibility(View.GONE);
}
}