基于Android的小巫新闻客户端开发--主界面业务逻辑实现
上一篇介绍了主界面的UI设计,现在直接进入主题,业务逻辑的实现,由于项目的开发总是在不断的完善的,最初实现的效果,总会随项目的进度而做出相应的改变,小巫也不可能从新开发整个客户端,然后再一步一步记录,那没有必要,学习东西,只需要知道关键点在哪里就行了,关于细节方面,遇到再去解决。就是这么简单。
主界面的最终实现效果如下;
下面是MainActivity.java的代码
package com.xiaowu.news; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.json.JSONArray; import org.json.JSONObject; import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.AsyncTask; import android.os.Bundle; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.GridView; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.SimpleAdapter; import android.widget.TextView; import android.widget.Toast; import com.xiaowu.news.custom.ConstomSimpleAdapter; import com.xiaowu.news.model.Category; import com.xiaowu.news.service.SyncHttp; import com.xiaowu.news.update.UpdateManager; import com.xiaowu.news.util.DensityUtil; import com.xiaowu.news.util.StringUtil; /** * * @author wwj * */ public class MainActivity extends Activity { private final int COLUMNWIDTH_PX = 56; // GridView每个单元格的宽度(像素) private final int FLINGVELOCITY_PX = 800; // ViewFilper滑动的距离(像素) private final int NEWSCOUNT = 5; // 显示新闻的条数 private final int SUCCESS = 0; // 加载新闻成功 private final int NONEWS = 1; // 没有新闻 private final int NOMORENEWS = 2; // 没有更多新闻 private final int LOADERROR = 3; // 加载失败 private long exitTime; //按返回键退出的时间 private int mColumnWidth_dip; private int mFlingVelocity_dip; private int mCid; // 新闻编号 private String mCategoryTitle; // 新闻分类标题 private ListView mNewslist; // 新闻列表 private SimpleAdapter mNewslistAdapter; // 为新闻内容提供需要显示的列表 private ArrayList<HashMap<String, Object>> mNewsData; // 存储新闻信息的数据集合 private LayoutInflater mInflater; // 用来动态载入没有loadmore_layout界面 private Button category_Button = null; // 新闻分类标题栏的向右查看的按钮 private HorizontalScrollView categoryScrollView = null;// 水平滚动图 private Button mTitleBarRefresh; // 标题栏的刷新按钮 private ProgressBar mTitleBarProgress; // 进度条 private Button mLoadmoreButton; // 加载更多按钮 private LoadNewsAsyncTack mLoadNewsAsyncTack; // 声明LoadNewsAsyncTack引用 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_home_layout); //通过id来获取按钮的引用 mTitleBarRefresh = (Button) findViewById(R.id.titlebar_refresh); mTitleBarProgress = (ProgressBar) findViewById(R.id.titlebar_progress); mTitleBarRefresh.setOnClickListener(loadmoreListener); // 将px转换为dip mColumnWidth_dip = DensityUtil.px2dip(this, COLUMNWIDTH_PX); mFlingVelocity_dip = DensityUtil.px2dip(this, FLINGVELOCITY_PX); //初始化新闻分类的编号 mCid = 1; mCategoryTitle = "焦点"; mInflater = getLayoutInflater(); //存储新闻信息的数据集合 mNewsData = new ArrayList<HashMap<String, Object>>(); // 获取数组资源 String[] categoryArray = getResources().getStringArray( R.array.categories); // 定义一个List数组,用来存放HashMap对象 final List<HashMap<String, Category>> categories = new ArrayList<HashMap<String, Category>>(); // 分割新闻字符串 for (int i = 0; i < categoryArray.length; i++) { String temp[] = categoryArray[i].split("[|]"); if (temp.length == 2) { int cid = StringUtil.string2Int(temp[0]); String title = temp[1]; Category type = new Category(cid, title); // 定义一个HashMap对象,用来存放键值对 HashMap<String, Category> hashMap = new HashMap<String, Category>(); hashMap.put("category_title", type); categories.add(hashMap); } } ConstomSimpleAdapter categoryAdapter = new ConstomSimpleAdapter(this, categories, R.layout.category_item_layout, new String[] { "category_title" }, new int[] { R.id.category_title }); // 创建一个网格视图, 用于实现新闻标题的布局 GridView category = new GridView(this); // 设置单元格的背景色为透明,这样选择分类时就不会显示黄色背景了 category.setSelector(new ColorDrawable(Color.TRANSPARENT)); // 设置每一个新闻标题的宽度 category.setColumnWidth(mColumnWidth_dip); // 设置网格视图的列数 category.setNumColumns(GridView.AUTO_FIT); // 设置对齐方式 category.setGravity(Gravity.CENTER); // 根据单元格的宽度和数目计算网格视图的宽度 int width = mColumnWidth_dip * categories.size(); // 获取布局参数 LayoutParams params = new LayoutParams(width, LayoutParams.MATCH_PARENT); // 设置参数 category.setLayoutParams(params); // 设置Adapter category.setAdapter(categoryAdapter); // 通过ID获取LinearLayout布局对象 LinearLayout categoryLayout = (LinearLayout) findViewById(R.id.category_layout); // 将网格视图组件添加到LinearLayout布局当中 categoryLayout.addView(category); // 添加单元格点击事件 category.setOnItemClickListener(new OnItemClickListener() { TextView categoryTitle; @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub for (int i = 0; i < parent.getCount(); i++) { categoryTitle = (TextView) parent.getChildAt(i); categoryTitle.setTextColor(0XFFADB2AD); categoryTitle.setBackgroundDrawable(null); } categoryTitle = (TextView) view; categoryTitle.setTextColor(0xFFFFFFFF); categoryTitle .setBackgroundResource(R.drawable.image_categorybar_item_selected_background); Toast.makeText(MainActivity.this, categoryTitle.getText(), Toast.LENGTH_SHORT).show(); //获取新闻分类编号 mCid = categories.get(position).get("category_title").getCid(); mCategoryTitle = categories.get(position).get("category_title").getTitle(); mLoadNewsAsyncTack = new LoadNewsAsyncTack(); mLoadNewsAsyncTack.execute(0, true); } }); //第一次获取新闻列表 getSpecCatNews(mCid, mNewsData, 0, true); // 箭头 categoryScrollView = (HorizontalScrollView) findViewById(R.id.categorybar_scrollView); category_Button = (Button) findViewById(R.id.category_arrow_right); category_Button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub categoryScrollView.fling(mFlingVelocity_dip); } }); mNewslistAdapter = new SimpleAdapter(this, mNewsData, R.layout.newslist_item_layout, new String[] { "newslist_item_title", "newslist_item_digest", "newslist_item_source", "newslist_item_ptime" }, new int[] { R.id.newslist_item_title, R.id.newslist_item_digest, R.id.newslist_item_source, R.id.newslist_item_ptime }); mNewslist = (ListView) findViewById(R.id.news_list); View footerView = mInflater.inflate(R.layout.loadmore_layout, null); //在LiseView下面添加“加载更多” mNewslist.addFooterView(footerView); //显示列表 mNewslist.setAdapter(mNewslistAdapter); mNewslist.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub Intent intent = new Intent(MainActivity.this, NewsDetailActivity.class); intent.putExtra("categoryTitle", mCategoryTitle); intent.putExtra("newsData", mNewsData); intent.putExtra("position", position); startActivity(intent); } }); mLoadmoreButton = (Button) findViewById(R.id.loadmore_btn); mLoadmoreButton.setOnClickListener(loadmoreListener); } /** * 获取指定类型的新闻列表 * * @param cid * @return */ private int getSpecCatNews(int cid, List<HashMap<String, Object>> newsList, int startnid, boolean firstTime) { // 如果是第一次加载的话 if (firstTime) { newsList.clear(); } //本机:http://10.0.2.2:8080/web/getSpecifyCategoryNews //wifi局域网:192.168.220.1 String url = "http://10.0.2.2:8080/web/getSpecifyCategoryNews"; String params = "startnid=" + startnid + "&count=" + NEWSCOUNT + "&cid=" + cid; SyncHttp syncHttp = new SyncHttp(); try { // 通过Http协议发送Get请求,返回字符串 String retStr = syncHttp.httpGet(url, params); JSONObject jsonObject = new JSONObject(retStr); int retCode = jsonObject.getInt("ret"); if (retCode == 0) { JSONObject dataObj = jsonObject.getJSONObject("data"); // 获取返回数目 int totalNum = dataObj.getInt("totalnum"); if (totalNum > 0) { // 获取返回新闻集合 JSONArray newslistArray = dataObj.getJSONArray("newslist"); // 将用JSON格式解析的数据添加到数据集合当中 for (int i = 0; i < newslistArray.length(); i++) { JSONObject newsObject = (JSONObject) newslistArray .opt(i); HashMap<String, Object> hashMap = new HashMap<String, Object>(); hashMap.put("nid", newsObject.getInt("nid")); hashMap.put("newslist_item_title", newsObject.getString("title")); hashMap.put("newslist_item_digest", newsObject.getString("digest")); hashMap.put("newslist_item_source", newsObject.getString("source")); hashMap.put("newslist_item_ptime", newsObject.getString("ptime")); hashMap.put("newslist_item_comments", newsObject.getInt("commentcount")); newsList.add(hashMap); } return SUCCESS; } else { //第一次加载新闻列表 if (firstTime) { return NONEWS; //没有新闻 } else { return NOMORENEWS; //没有更多新闻 } } } else { return LOADERROR; //加载新闻失败 } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return LOADERROR; //加载新闻失败 } } /** * 为“加载更多”按钮定义匿名内部类 */ private OnClickListener loadmoreListener = new OnClickListener() { @Override public void onClick(View v) { mLoadNewsAsyncTack = new LoadNewsAsyncTack(); switch (v.getId()) { //点击加载更多 case R.id.loadmore_btn: mLoadNewsAsyncTack.execute(mNewsData.size(), false); //不是第一次加载新闻里列表 break; //点击刷新按钮 case R.id.titlebar_refresh: mLoadNewsAsyncTack.execute(0, true); break; } } }; /** * 异步更新UI * @author wwj * */ private class LoadNewsAsyncTack extends AsyncTask<Object, Integer, Integer> { //准备运行 @Override protected void onPreExecute() { mTitleBarRefresh.setVisibility(View.GONE); mTitleBarProgress.setVisibility(View.VISIBLE); mLoadmoreButton.setText(R.string.loadmore_text); } //在后台运行 @Override protected Integer doInBackground(Object... params) { return getSpecCatNews(mCid, mNewsData, (Integer) params[0], (Boolean) params[1]); } //完成后台任务 @Override protected void onPostExecute(Integer result) { switch (result) { //该栏目没有新闻 case NONEWS: Toast.makeText(MainActivity.this, R.string.nonews, Toast.LENGTH_SHORT) .show(); break; //该栏目没有更多新闻 case NOMORENEWS: Toast.makeText(MainActivity.this, R.string.nomorenews, Toast.LENGTH_SHORT).show(); break; //加载失败 case LOADERROR: Toast.makeText(MainActivity.this, R.string.loadnewserror, Toast.LENGTH_SHORT) .show(); break; } mTitleBarRefresh.setVisibility(View.VISIBLE); //刷新按钮设置为可见 mTitleBarProgress.setVisibility(View.GONE); //进度条设置为不可见 mLoadmoreButton.setText(R.string.loadmore_btn); //按钮信息替换为“加载更多” mNewslistAdapter.notifyDataSetChanged(); //通知ListView更新数据 } } /** * 添加菜单 */ @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub menu.add(1, 1, 1, "更新"); menu.add(1, 2, 2, "退出"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case 1: UpdateManager updateManager = new UpdateManager(MainActivity.this); //检测更新 updateManager.checkUpdate(); break; case 2: finish(); break; } return true; } /** * 按键触发的事件 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){ if((System.currentTimeMillis() - exitTime > 2000)){ Toast.makeText(getApplicationContext(), R.string.backcancel , Toast.LENGTH_LONG).show(); exitTime = System.currentTimeMillis(); } else{ finish(); System.exit(0); } return true; } return super.onKeyDown(keyCode, event); } }
主界面的业务逻辑实现,要一步就实现是非常困难的,因为项目总是从简单到复杂,所以小巫只把关键点说一下就行了:
这里主要有三个关键点:
1.分类栏的实现?
首先创建一个GridView视图,通过GridView来填充数据,把每一类新闻分类显示到GridView视图中去,最后通过获取到界面布局中的LinearLayout对象,把GridView添加到LinearLayout布局当中去,最终实现效果。
2.获取新闻分类列表(对JSON格式数据的解析)?
JSON数据的解析并不算太难,主要把JSON数据的数据结构搞清楚,解析起来还是挺方便的。
进行解析虽然方便,但前提是要把数据得到,因为数据是要在服务器端得到,需要利用Android的Http通信来实现。
这里需要利用到httpGet还有httpPost方法,这个代码很需要贴一贴滴。自定义的SyncHttp类
package com.xiaowu.news.service; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import com.xiaowu.news.model.Parameter; public class SyncHttp { /** * 通过Get方式发送请求 * @param url * @param params * @return * @throws Exception */ public String httpGet(String url, String params) throws Exception { String response = null; //返回信息 //拼接请求URl if(null != params && !params.equals("")) { url += "?" + params; } int timeOutConnection = 3000; int timeOutSocket = 5000; HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, timeOutConnection); HttpConnectionParams.setSoTimeout(httpParams, timeOutSocket); //构造HttpClient实例 HttpClient httpClient = new DefaultHttpClient(); //创建GET方法实例 HttpGet httpGet = new HttpGet(url); try { HttpResponse httpResponse = httpClient.execute(httpGet); int statusCode = httpResponse.getStatusLine().getStatusCode(); if(statusCode == HttpStatus.SC_OK) { //获得返回结果 response = EntityUtils.toString(httpResponse.getEntity()); } else{ response = "返回码:" + statusCode; } } catch (Exception e) { // TODO: handle exception throw new Exception(e); } return response; } /** * 通过post方式发送请求 * @param url * @param params * @return * @throws Exception */ public String httpPost(String url, List<Parameter> params) throws Exception { String response = null; int timeOutConnection = 3000; int timeOutSocket = 5000; HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, timeOutConnection); HttpConnectionParams.setSoTimeout(httpParams, timeOutSocket); //构造HttpClient实例 HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(url); if(params.size() > 0) { //设置post请求参数 httpPost.setEntity(new UrlEncodedFormEntity(buildNameValuePair(params), HTTP.UTF_8)); } //使用execute方法发送Http Post 请求,并返回HttpResponse对象 HttpResponse httpResponse = httpClient.execute(httpPost); int statusCode = httpResponse.getStatusLine().getStatusCode(); if(statusCode == HttpStatus.SC_OK) { //获得返回结果 response = EntityUtils.toString(httpResponse.getEntity()); } else { response = "返回码:" + statusCode; } return response; } /** * 把Paramster类型集合转换为NameValuePair类型集合 * @param params * @return */ private List<BasicNameValuePair> buildNameValuePair (List<Parameter> params) { List<BasicNameValuePair> result = new ArrayList<BasicNameValuePair>(); for(Parameter param : params) { BasicNameValuePair pair = new BasicNameValuePair(param.getName(), param.getValue()); result.add(pair); } return result; } }
定义好了SyncHttp类之后,就可以通过调用httpGet方法来获取数据,在Activity的getSpecCatNews方法有详细实现,看一下就可以知道了。
3.异步更新UI的实现?
关于异步更新UI也算是一个比较难理解的东西,在Activity里定义了一个继承AsyncTask类的内部类,并实现三个方法,比较灵活。具体实现看代码。
以上三点是小巫认为比较核心的地方,具体的需要动手之后才知道。