转载请注明出处:http://blog.csdn.net/einarzhang/article/details/44806975
本系列文章主要介绍如何利用Android开发一个简单的健康食谱软件。用到的相关技术如下所示:
- 提供GridView和ListView的基本使用
- 利用universal-image-loader异步加载网络图片
- 通过HttpClient获取网络http请求数据
- 滑动分页加载数据
软件所用的所有数据均来源于http://doc.yi18.net/cookwendang提供的食谱接口,感谢他们!
软件文件结构如下所示:
MainActivity:主界面Acitivity
MListActivity:子分类列表Acitivity
CListActivity:食谱列表Activity
DetailActivity:食谱详情Activity
MainGridAdapter:主界面食谱分类适配器
CListAdapter:食谱列表适配器
Cook:用于保存食谱信息的pojo
HttpUtils:提供Http请求相关功能
MUtils:提供食谱相关的处理逻辑功能
搜索食谱
我们在主界面提供了搜索食谱功能,当用户输入食谱名称时,软件将给用户展现出想要的食谱列表。搜索食谱我们主要用到http://api.yi18.net/cook/search接口。
我们在MUtils方法增加搜索食谱方法:
public static ArrayList<Cook> search(final String keyword, final int page) { String url = "http://api.yi18.net/cook/search"; String result = HttpUtils.httpGet(url, new HashMap<String, Object>(){{ put("keyword", URLEncoder.encode(keyword)); put("page", page); put("limit", 20); }}); ArrayList<Cook> dataMap = new ArrayList<Cook>(); if(result != null) { try { JSONObject root = new JSONObject(result); if(root.getBoolean("success")) { JSONArray datas = root.getJSONArray("yi18"); for(int i = 0, len = datas.length(); i < len; i++) { JSONObject obj = datas.getJSONObject(i); Cook c = new Cook(); c.id = obj.optInt("id"); c.name = obj.optString("name"); if(c.name != null) { c.name = c.name.replace("<font color=\"red\">", "").replace("</font>", ""); } c.food = obj.optString("description"); c.img = obj.optString("img"); c.tag = obj.optString("keywords"); dataMap.add(c); } } } catch (Exception e) { } } return dataMap; }
该搜索方法提供了食谱名称和分页页码参数,便于前端实现分页刷新功能,因为返回的食谱名称中包含Html信息,我们将其过滤掉。搜索结果将进入食谱列表界面,界面展示出搜索的结果信息
获取分类食谱
当用户点击某一子分类时,将获取出分类下的食谱列表,主要用到http://api.yi18.net/cook/list接口
我们在MUtils中增加如下方法:
public static ArrayList<Cook> getCooks(final int classId, final int page, final int sortType) { String url = "http://api.yi18.net/cook/list"; String result = HttpUtils.httpGet(url, new HashMap<String, Object>(){{ put("id", classId); put("page", page); put("limit", 20); put("type", sortType == SORT2 ? "count" : "id"); }}); ArrayList<Cook> dataMap = new ArrayList<Cook>(); if(result != null) { try { JSONObject root = new JSONObject(result); if(root.getBoolean("success")) { JSONArray datas = root.getJSONArray("yi18"); for(int i = 0, len = datas.length(); i < len; i++) { JSONObject obj = datas.getJSONObject(i); Cook c = new Cook(); c.id = obj.optInt("id"); c.name = obj.optString("name"); c.count = obj.optInt("count"); c.fcount = obj.optInt("fcount"); c.rcount = obj.optInt("rcount"); c.food = obj.optString("food"); c.img = obj.optString("img"); c.tag = obj.optString("tag"); dataMap.add(c); } } } catch (Exception e) { } } return dataMap; }
同搜索食谱功能一致,这里提供page对象,便于提供分页加载
食谱列表界面
食谱列表界面用于展示匹配相应结果的食谱列表,为了用户更好的了解食谱,在列表中我们提供缩略图展示。当用户搜索食谱或者点击某一分类时,将进入食谱列表界面。
食谱列表界面最终效果图如下所示:
每个列表展示出食谱的缩略图、食谱名称和食谱涉及的材料。列表布局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:background="@drawable/list_item_bg" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="@drawable/title_bg" > <ImageView android:id="@+id/clist_return" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:src="@drawable/arrowl" /> <TextView android:id="@+id/clist_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@android:color/white" android:textSize="18sp" android:textStyle="bold" /> </RelativeLayout> <ListView android:id="@+id/clist" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none" > </ListView> </LinearLayout>
每个Item的布局clist_item.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="150dp" android:background="@drawable/list_selector" > <ImageView android:id="@+id/clist_item_icon" android:layout_width="100dp" android:layout_height="60dp" android:layout_margin="10dp" android:scaleType="fitCenter" android:layout_alignParentLeft="true" android:layout_centerVertical="true" /> <LinearLayout android:layout_centerVertical="true" android:layout_toRightOf="@+id/clist_item_icon" android:layout_toLeftOf="@+id/clist_item_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/clist_item_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="18sp" /> <TextView android:id="@+id/clist_item_detail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:lines="1" android:ellipsize="end" android:textColor="@color/clist_item_tag" android:textSize="14sp" /> </LinearLayout> <ImageView android:id="@+id/clist_item_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="20dp" android:layout_marginLeft="10dp" android:src="@drawable/arrowr" /> </RelativeLayout>
我们利用clist_item.xml渲染自定义的ListAdapter,取名CListApdater,其代码如下所示:
import java.util.List; import android.content.Intent; import android.graphics.Bitmap; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.assist.ImageScaleType; public class CListAdapter extends BaseAdapter { private CListActivity activity; private List<Cook> dataList; public CListAdapter(CListActivity activity, List<Cook> dataList) { this.activity = activity; this.dataList = dataList; //初始化Android-Universal-Image-Loader框架 ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(activity)); } @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { convertView = LayoutInflater.from(activity).inflate(R.layout.clist_item, null); final Cook c = dataList.get(position); ((TextView)convertView.findViewById(R.id.clist_item_title)).setText(c.name); ((TextView)convertView.findViewById(R.id.clist_item_detail)).setText(c.tag); //设置图片显示格式(我们可以设置圆角、缓存等一些列配置) DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.loading) //加载中显示的正在加载中的图片 .showImageOnFail(R.drawable.loading) //为了方便,加载失败也显示加载中的图片 .cacheInMemory(true) //在内存中缓存图片 .cacheOnDisk(true) .bitmapConfig(Bitmap.Config.RGB_565) .imageScaleType(ImageScaleType.EXACTLY) //设置图片以如何的编码方式显示 .build(); //异步加载图片,并渲染到指定的控件上 ImageLoader.getInstance().displayImage(MUtils.PREFIX_IMG + c.img, (ImageView)convertView.findViewById(R.id.clist_item_icon), options); //Item点击后进入食谱详情界面 convertView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(activity, DetailActivity.class); intent.putExtra("id", c.id); intent.putExtra("title", c.name); activity.startActivity(intent); } }); return convertView; } //为Adapter增加新的食谱数据,当分页加载时,用于更新分页信息 public void add(List<Cook> newData) { dataList.addAll(newData); } }
CListAcitivity同时实现了搜索和分类进入的展示功能,并加入下拉分页加载,其代码如下所示:
import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.TextView; public class CListActivity extends Activity implements OnClickListener, OnItemClickListener, Runnable { int lastVisibleIndex = 0; //滚动的最后可见条目 int page = 1; //当前分页页码 int cId; //分类ID String keyword; //搜索关键词 CListAdapter mAdapter; List<Cook> cList; ListView mlistView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.clist); findViewById(R.id.clist_return).setOnClickListener(this); mlistView = (ListView) findViewById(R.id.clist); mlistView.setOnItemClickListener(this); mlistView.setOnScrollListener(scrollListener); cId = getIntent().getIntExtra("id", 0); if(cId == 0) { //食谱搜索 keyword = getIntent().getStringExtra("keyword"); ((TextView)findViewById(R.id.clist_title)).setText(keyword); } else { //分类进入 ((TextView)findViewById(R.id.clist_title)).setText(getIntent().getStringExtra("title")); } cList = new ArrayList<Cook>(); new Thread(this).start(); } @Override public void onItemClick(AdapterView<?> av, View arg1, int arg2, long arg3) { } @Override public void onClick(View v) { switch (v.getId()) { case R.id.clist_return: CListActivity.this.finish(); break; default: break; } } @Override public void run() { loadData(); handler.sendEmptyMessage(0); } public void loadData() { List<Cook> results; if(cId == 0) { results = MUtils.search(keyword, page++); } else { results = MUtils.getCooks(cId, page++, MUtils.SORT1); } //获取成功,则更新数据列表 cList.addAll(results); } Handler handler = new Handler(new Callback() { @Override public boolean handleMessage(Message msg) { //更新列表界面 if(mAdapter == null) { mAdapter = new CListAdapter(CListActivity.this, cList); mlistView.setAdapter(mAdapter); } else { mAdapter.notifyDataSetChanged(); } return false; } }); OnScrollListener scrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { int itemsLastIndex = mAdapter.getCount() - 1; // 数据集最后一项的索引 if ((scrollState == SCROLL_STATE_TOUCH_SCROLL || scrollState == SCROLL_STATE_IDLE) && lastVisibleIndex == itemsLastIndex) { //当滚动最后一项时,加载新的一页数据 new Thread(CListActivity.this).start(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //滚动过程中更新最后可见索引 lastVisibleIndex = firstVisibleItem + visibleItemCount - 1; } }; }
从代码中可以看到,我们通过为ListView定制OnScrollListener来实现滚动的分页加载逻辑
食谱详情
食谱详情用户展示食谱的详细信息,用户可以在详情里面看到食物的具体制作方法。
最终效果图如下所示:
我们首先在MUtils加上获取详情的功能:
public static Cook show(final int id) { String url = "http://api.yi18.net/cook/show"; String result = HttpUtils.httpGet(url, new HashMap<String, Object>(){{ put("id", id); }}); if(result != null) { try { JSONObject root = new JSONObject(result); if(root.getBoolean("success")) { JSONObject obj = root.getJSONObject("yi18"); Cook c = new Cook(); c.id = obj.optInt("id"); c.name = obj.optString("name"); c.count = obj.optInt("count"); c.fcount = obj.optInt("fcount"); c.rcount = obj.optInt("rcount"); c.food = obj.optString("food"); c.img = obj.optString("img"); c.tag = obj.optString("tag"); c.message = obj.optString("message"); return c; } } catch (Exception e) { } } return null; }
详情的布局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="match_parent" android:background="@drawable/list_item_bg" > <RelativeLayout android:id="@+id/detail_layout" android:layout_width="match_parent" android:layout_height="50dp" android:layout_alignParentTop="true" android:background="@drawable/title_bg" > <ImageView android:id="@+id/detail_return" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:src="@drawable/arrowl" /> <TextView android:id="@+id/detail_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@android:color/white" android:textSize="18sp" android:textStyle="bold" /> </RelativeLayout> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/detail_layout" android:scrollbars="none" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" > <ImageView android:id="@+id/detail_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" > </ImageView> <TextView android:id="@+id/detail_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:textColor="@color/clist_item_tag" android:textSize="20sp" /> <TextView android:id="@+id/detail_food" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:textSize="14sp" /> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:background="@drawable/lines" /> <TextView android:id="@+id/detail_text" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="10dp" android:padding="20dp" android:textSize="16sp" /> </LinearLayout> </ScrollView> </RelativeLayout>
DetailActivity的完整实现代码如下所示:
import com.nostra13.universalimageloader.core.ImageLoader; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.Html; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.TextView; public class DetailActivity extends Activity implements OnClickListener, Runnable { private TextView titleView, contentView, foodView, nameView; private ImageView imgView; private int id; private Cook cook; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.detail); titleView = (TextView) findViewById(R.id.detail_title); contentView = (TextView) findViewById(R.id.detail_text); foodView = (TextView) findViewById(R.id.detail_food); nameView = (TextView) findViewById(R.id.detail_name); imgView = (ImageView) findViewById(R.id.detail_img); findViewById(R.id.detail_return).setOnClickListener(this); initData(); } private void initData() { titleView.setText(getIntent().getStringExtra("title")); id = getIntent().getIntExtra("id", 1); new Thread(this).start(); } @Override public void run() { cook = MUtils.show(id); handler.sendEmptyMessage(0); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.detail_return: DetailActivity.this.finish(); break; default: break; } } Handler handler = new Handler() { @Override public void handleMessage(Message msg) { //异步显示图片 ImageLoader.getInstance().displayImage(MUtils.PREFIX_IMG + cook.img, imgView); nameView.setText(cook.name); foodView.setText(cook.food); //内容以Html的方式展示 contentView.setText(Html.fromHtml(cook.message)); } }; }
至此,完整的健康食谱软件开发完成