谢谢大家的厚爱。今天是第二天了,第二个小项目,比第一个要复杂一点点。今天的项目呢是要做一个英语学习小软件,就是英语每日一句。由于是小项目,就不用那么复杂,没有自己去做服务器(自己做也可以,这样最好,可以扩展出很多项目。比如,糗事百科,知乎,或再大点的,美团,淘宝等。可是大项目是要团队做的)。在21世纪,作为一名程序员,不应该推崇单兵做战,之前倒是有很多大神,比如求伯君和WPS,但现在的系统都比较庞大,并且用户需求多元化,并且要求开发周期足够短,所以必须要团队合作。闲话少说,既然没有服务器,那资源从哪里来呢,本文选择金山词霸的每日一句。Let’s go.
一、需求分析
功能性需求:每日显示,一句英语,一张图片,还有翻译,和小编的废话
非功能性需求:要求无网络时程序不会死,网络请求时间要10s之内完成
废话:要想做好需求分析要作为用户去和用户沟通。此,仁者见仁,智者见智。
二、概要设计
内容:金山词霸每日一句http://news.iciba.com/dailysentence/detail-1504.html
数据接口:html
显示内容:图片,英语,汉语,小编
三、详细设计
1、html解析
本文采用JSoup(http://jsoup.org/)进行解析
使用JSoup很简单,首先是导入lib然后build path最后使用如下代码(或参考官方api)进行解析
<span style="font-family:SimSun;">Document doc = Jsoup.connect("http://en.wikipedia.org/").get(); Elements newsHeadlines = doc.select("#mp-itn b a");</span>
2、异步任务
对于异步任务就是实现一个类继承AsyncTask类。
AsyncTask定义了三个泛型类型:Params Progress Result。
· Params 启动任务执行的输入参数
· Progress 后台任务执行的百分比
· Result 后台执行任务最终返回的结果
AsyncTask 一个异步加载数据最少要重写以下这两个方法:
· doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
· onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话还得重写以下这三个方法,但不是必须的:
· onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
· onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
· onCancelled() 用户调用取消时,要做的操作
使用AsyncTask类,以下是几条必须遵守的准则:
· Task的实例必须在UI thread中创建;
· execute方法必须在UI thread中调用;
· 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
· 该task只能被执行一次,否则多次调用时将会出现异常;
具体实现请看四中的代码
3、网络图片下载
这个比较固定,基本如下函数,先建立http网络连接,获取到InputStream,然后通过Bitmap生成Bitmap,此时可以显示到ImageView上或者保存,这取决于你。
/** * 网络请求图片生成Bitmap * * @param path * @return */ private Bitmap getBitmapByUrl(String path) { Bitmap bm = null; URL url; try { url = new URL(path); HttpURLConnection con; con = (HttpURLConnection) url.openConnection(); con.setDoInput(true); con.connect(); InputStream is = con.getInputStream(); bm = BitmapFactory.decodeStream(is); is.close(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return bm; }
4、时间间隔计算
所有的时间都是从1979年开始的。Java提供了方法,可以把日期转换成秒数(相对于1979),然后我们可以把两个日期都转换成秒,然后相减,再然后就是把秒转换成天数,一天24个小时,这不用我说了吧。就知道两个日期相差几天了。
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = df.parse("2015-08-19"); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } long old = date.getTime(); long now = System.currentTimeMillis(); long day = (now - old) / 1000 / 60 / 60 / 12;
四、编码测试
AndroidMenifest.xml 对程序进行配置
<?xml version="1.0" encoding="utf-8"?> <!-- package的包名是App运行的凭证 --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.leo.everyday" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="13" android:targetSdkVersion="21" /> <!-- 同样的网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- theme设置程序主题风格 holo风格 title栏将随着activity最上面颜色的改变而改变 --> <application android:allowBackup="true" android:icon="@drawable/every_day" android:label="@string/app_name" android:theme="@android:style/Theme.Holo.Light.NoActionBar.TranslucentDecor" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
activity_main.xml 布局文件,内容的显示界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/main_bg" android:orientation="vertical" android:paddingBottom="20dip" android:paddingLeft="20dip" android:paddingRight="20dip" android:paddingTop="55dip" > <ImageView android:id="@+id/iv_image_main" android:layout_width="match_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" android:scaleType="centerCrop" android:src="@drawable/test" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/tv_text_main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="EN" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/black" android:textStyle="bold" /> <TextView android:id="@+id/tv_translate_main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CN" android:textColor="@android:color/black" android:layout_margin="6dip" /> <TextView android:id="@+id/tv_editor_main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="EDITOR" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/black" android:textStyle="italic" /> </LinearLayout> </ScrollView> </LinearLayout>
MainActivity.java
package com.leo.everyday; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Bundle; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /** * @time 2015-09-12 * @author Leo * @describe 程序主Activity 负责网络请求数据得到html然后进行解析 最过显示到相应的控件上 * */ public class MainActivity extends Activity { private Context mContext = null;// 自己 private static final String TAG = MainActivity.class.getSimpleName();// Log调试时作为标签 private static final String MAIN_URL = "http://news.iciba.com/dailysentence/detail-";// 网络URL private static final String IMAGE_CLASS = "r_pic";// html中css定义的属性名 此为图片 private static final String TEXT_CLASS = "en";// 英文文本 private static final String TRANSLATE_CLASS = "cn";// 翻译文本 private static final String EDITOR_CLASS = "editor";// 小编 private ImageView imageShow = null;// 显示图片 private TextView textShow = null, translateShow = null, editorShow = null;// 显示文本内容 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);// 设置布局文件 // 游戏中会通过继承SurfaceView来自定义游戏布局 // 会经常使用此方法 findViewsByIds();// 根据ids初始化控件 task.execute(calculateUrlByTime());// 执行异步任务 从4.0开始google限制不能在主线程中执行网络任务 } /** * 取得控件 */ private void findViewsByIds() { imageShow = (ImageView) findViewById(R.id.iv_image_main); textShow = (TextView) findViewById(R.id.tv_text_main); translateShow = (TextView) findViewById(R.id.tv_translate_main); editorShow = (TextView) findViewById(R.id.tv_editor_main); } /** * html解析的异步任务 */ private AsyncTask<String, Integer, Sentence> task = new AsyncTask<String, Integer, Sentence>() { @Override protected Sentence doInBackground(String... arg0) { // TODO Auto-generated method stub Sentence mSentence = null;// bean接收数据 try { Document dom = Jsoup.connect(arg0[0]).get();// Jsoup通过网络连接取得html文档 Element imageElement = dom.getElementsByClass(IMAGE_CLASS) .first(); Element textElement = dom.getElementsByClass(TEXT_CLASS) .first(); Element translateElement = dom.getElementsByClass( TRANSLATE_CLASS).first(); Element editorElement = dom.getElementsByClass(EDITOR_CLASS) .first(); String image = imageElement.getElementsByTag("img").first() .attr("src"); String text = textElement.getElementsByTag("a").first().text(); String translate = translateElement.getElementsByTag("a") .first().text(); String editor = editorElement.text(); mSentence = new Sentence(image, text, translate, editor); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { System.out.println("网络异常"); } return mSentence; } @Override protected void onPostExecute(Sentence result) { // TODO Auto-generated method stub super.onPostExecute(result); if (result != null) { showInformation(result); } else { toast("内容空,请重试"); } } }; /** * 弹出提示 * * @param text */ private void toast(String text) { Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); } /** * 把内容显示到控件上去 * * @param result */ private void showInformation(final Sentence result) { textShow.setText(result.getText()); translateShow.setText(result.getTranslate()); editorShow.setText(result.getEditor()); /** * 偷一回懒 同样用简单异步任务加载图片 */ new AsyncTask<String, Integer, Bitmap>() { @Override protected Bitmap doInBackground(String... arg0) { // TODO Auto-generated method stub Bitmap bmp = getBitmapByUrl(arg0[0]); return bmp; } @Override protected void onPostExecute(Bitmap result) { // TODO Auto-generated method stub super.onPostExecute(result); imageShow.setImageBitmap(result); } }.execute(result.getImage()); } /** * 网络请求图片生成Bitmap * * @param path * @return */ private Bitmap getBitmapByUrl(String path) { Bitmap bm = null; URL url; try { url = new URL(path); HttpURLConnection con; con = (HttpURLConnection) url.openConnection(); con.setDoInput(true); con.connect(); InputStream is = con.getInputStream(); bm = BitmapFactory.decodeStream(is); is.close(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return bm; } /** * 标准阶段1455对应时期为2015-08-19方便计算出以后日期对应的网页连接 比如:2015-08-20对应为1456和1457 */ private static final long OLD_NUMBER = 1455; /** * 计算开始 * * @return */ private static String calculateUrlByTime() { String url = MAIN_URL + 1 + ".html"; SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = df.parse("2015-08-19"); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } long old = date.getTime(); long now = System.currentTimeMillis(); long day = (now - old) / 1000 / 60 / 60 / 12; long number = OLD_NUMBER + day; url = MAIN_URL + number + ".html"; return url; } }
Sentence.java
package com.leo.everyday; /** * @time 2015-09-12 * @author Leo * @describe 内容实体内 java bean * */ public class Sentence { private String image; private String text; private String translate; private String editor; public Sentence() { } public Sentence(String image, String text, String translate, String editor) { super(); this.image = image; this.text = text; this.translate = translate; this.editor = editor; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getTranslate() { return translate; } public void setTranslate(String translate) { this.translate = translate; } public String getEditor() { return editor; } public void setEditor(String editor) { this.editor = editor; } }
项目源码:http://download.csdn.net/detail/zhaicaixiansheng/9101147
五、项目总结
做一个项目最重要的不是编码,而是需求分析和测试。需求做不好,做了也白做;测试没做好,做了还是demo。一个项目要发布交互,首先就是稳定性。再一个就是在实际项目中,就算是大神也会遇到自己不会和地方,这就是知识盲点。或者说,等到了大神的阶段,都不屑于写代码记代码了。所以说,对于一个程序员,重要的是编程的思想和学习的能力。编程思想决定你写出的代码的质量,学习能力决定你开发的速度和发展的前景。对于编程思想,没什么好说的,什么《算法大全》《面向对象分析与设计》《数据结构》《设计模式》《软件工程》等等,这些一定要饿补。学到学习能力,每个人有自己的方法。对于IT技术,我的学习方法是,以IOS为例:首先选择一本书,重点看第一章和下面的目录,然后随便翻翻,这是为了对这个新东西有个全面的了解,知道他是什么,要做什么,能做什么,怎么做;然后就是看每张的最开始的容易的地方,这样是为了掌握基础的东西,可以写出helloworld类的程序。然后就是做项目,遇到什么不会的就百度谷歌api文档,进行学习使用;当你这样达到一定水平后,就是总结和看视频了,总结是系统的全面的理解。看视频是为了知道别人怎么做,并查漏补缺。
好了说了那么多废话,希望对您有所帮助。加油!!!