Android實戰開發——News
1.功能分析介紹
知識點
ViewPager
:頁面的滑動PagerSlidingTabStrip
:第三方的自定義View,使得選單欄和下面的頁面產生聯動的效果。ListView
列表檢視WebView
控制元件:詳情頁面載入網址- 如何獲取網路資料並解析展示
- 資料庫的增刪改查:需要保留自定義的頻道資訊,當下一次進入該應用時候,會顯示上一次保留的頻道資訊。
使用第三方框架
Volley
框架:是網路載入資料的框架Universal-image-loader
圖片載入框架PagerSlidingTabStrip
第三方定義view的使用
邏輯分析
- 首介面為
ViewPager
PagerSlidingTabStrip
,兩個控制元件可以相互影響,點選“+”,可以跳轉到頻道訂閱介面。 - 頻道訂閱介面,能夠選擇首介面顯示的新聞型別,改變上一次選擇內容返回上級頁面時會改變首介面的顯示內容,其中頭條和社會是預設選項,不能改變。
- 點選首介面列表中的每一條目,會跳轉到詳細頁面,顯示新聞的詳細資訊。
2.頁面佈局繪製和介面分析
把 background_tab.xml
匯入drawable中
attrs.xml
是關於自定義View屬性的xml檔案
第三方view
匯入所需要的包
可以巢狀在ViewPager中的Fragment佈局
寫一個能夠將訪問的網址都存放的類
頁面邏輯程式碼
佈局所對應的Activity程式碼的編寫。
在 MainActivity.java
中宣告控制元件:
ViewPager mainVp; //顯示標題,很多個文字組成
PagerSlidingTabStrip tabStrip; //顯示fragment
ImageView addIv;
之後在 OnCreate
中通過 findViewById
找到這些控制元件:
mainVp=findViewById(R.id.main_vp); tabStrip=findViewById(R.id.main_tabstrip); addIv=findViewById(R.id.main_iv_add);
addIv需要實現點選跳轉到下級頁面中,這裡讓整個Activity實現介面 OnClickListener
,然後重寫點選事件。
頁面頻道訂閱邏輯編寫
新建一個activity AddItemActivity
,在佈局 activity_add_item.xml
進行佈局,整體為線性佈局,上面的頻道訂閱顯示為相對佈局,中間一條分割線,下面是一個ListView:
接下來寫對應的Item的佈局,在Layout檔案下建立一個新的佈局 item_add_lv.xml
,這個左右結構,選擇相對佈局。
在 MainActivity
中需要實現點選加號按鈕實現響應事件,之後跳轉到剛才的 AddItemActivity
//實現點選加號從MainActivity跳轉到AddItemActivity介面
public void onClick(View v) {
switch (v.getId()){
case R.id.main_iv_add:
Intent intent = new Intent(MainActivity.this, AddItemActivity.class);
startActivity(intent);
break;
}
}
在 AddItemActivity
中新增控制元件宣告和尋找控制元件:
//宣告控制元件
ImageView backIv;
ListView addLv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_item);
//查詢控制元件
backIv=findViewById(R.id.add_iv_back);
addLv=findViewById(R.id.add_lv);
}
backIv
要實現返回上一級的功能,在這裡依然是實現 OnClickListener
的介面,然後重寫 onClick
方法。首先給 backIv
設定監聽:
backIv.setOnClickListener(this); //新增點選事件的監聽
因為當前的Activity實現了 OnClickListener
這個介面,所以這個Activity的物件就是這個介面的物件,要向backIv中傳入 OnClickListener
的介面物件,就可以直接傳入他的實現類Activity的物件,所以這裡傳 this
即可。
backIv
被點選之後的事件可以在 onClick
方法中執行:
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.add_iv_back:
finish(); //銷燬當前的Activity,返回上一級介面
break;
}
}
此時 backIv
的操作已寫完。
接下來就是寫對 addLv
的操作,它是用來顯示所有的頻道資訊, ListView addLv
中的資料來源應該是之前寫的 TypeBean
, TypeBean
中就封裝了title、URL和是否顯示。
//資料來源
List<TypeBean>mDatas;
由於資訊是會改變的,當這次選中的頻道,我們希望下次進入之後還會保持上一次選中的結果,所以這裡需要本地儲存,這裡選擇的本地儲存為資料庫。
新建一個關於資料庫的包 db
,然後建立一個數據庫的管理類 DBOpenHelper
,使其繼承於 SQLiteOpenHelper
,
public class DBOpenHelper extends SQLiteOpenHelper {
public DBOpenHelper(@Nullable Context context) {
super(context, "info.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql="create table itype(id integer primary key,title varchar(10) unique not null,url text not null,isshow varchar(10) not null)";
db.execSQL(sql);
String inserSql="insert into itype values(?,?,?,?)";
db.execSQL(inserSql,new Object[]{1,"頭條", NewsURL.headline_url,"true"});
db.execSQL(inserSql,new Object[]{2,"社會",NewsURL.society_url,"true"});
db.execSQL(inserSql,new Object[]{3,"國內",NewsURL.home_url,"true"});
db.execSQL(inserSql,new Object[]{4,"國際",NewsURL.entertainment_url,"true"});
db.execSQL(inserSql,new Object[]{5,"娛樂",NewsURL.entertainment_url,"true"});
db.execSQL(inserSql,new Object[]{6,"體育",NewsURL.sport_url,"false"});
db.execSQL(inserSql,new Object[]{7,"軍事",NewsURL.military_url,"false"});
db.execSQL(inserSql,new Object[]{8,"科技",NewsURL.science_url,"false"});
db.execSQL(inserSql,new Object[]{9,"財經",NewsURL.fiance_url,"false"});
db.execSQL(inserSql,new Object[]{10,"時尚",NewsURL.fashion_url,"false"});
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
其中true或false決定了該欄目是顯示還是隱藏。
接下來寫一個獲取資料庫中全部資訊的集合。在資料庫的包 db
中新建一個數據庫的管理類 DBManager
,在這裡寫一個關於資料庫宣告的函式,再新增一個獲取資料庫中全部型別的list集合:
public class DBManager {
public static SQLiteDatabase database;
public static void initDB(Context context){
DBOpenHelper helper=new DBOpenHelper(context);
database=helper.getWritableDatabase();
}
/*獲取資料庫中全部行的內容,儲存到集合當中*/
public static List<TypeBean>getAllTypeList(){
List<TypeBean>list=new ArrayList<>();
Cursor cursor=database.query("itype",null,null,null,null,null,null);
while (cursor.moveToNext()){
int id = cursor.getInt(cursor.getColumnIndex("id"));
String title = cursor.getString(cursor.getColumnIndex("title"));
String url = cursor.getString(cursor.getColumnIndex("url"));
String showstr = cursor.getString(cursor.getColumnIndex("isshow"));
Boolean isshow = Boolean.valueOf(showstr);
TypeBean typeBean=new TypeBean(id,title,url,isshow);
list.add(typeBean);
}
return list;
}
}
將資料庫的宣告 database
放到全域性變數中,在 UniteApp.java
中新增:
DBManager.initDB(this); //宣告全域性的資料庫物件
在 AddItemActivity
需要的就是資料庫中的所有資訊,這裡可以直接呼叫 DBManager
方法來獲取:
mDatas= DBManager.getAllTypeList();
此時資料來源就有了,接下來要建立介面卡物件,寫一下ListView的介面卡物件:新建一個java class AddItemAdapter
,讓它繼承於 BaseAdapter
,重新裡面的四個方法:
public class AddItemAdapter extends BaseAdapter {
Context context;
List<TypeBean>mDatas;
//通過構造方法將上面兩個內容傳遞進來
public AddItemAdapter(Context context, List<TypeBean> mDatas) {
this.context = context;
this.mDatas = mDatas;
}
@Override
public int getCount() {
return mDatas.size(); //返回一共顯示的欄位
}
@Override
public Object getItem(int position) {
return mDatas.get(position); //返回當前位置的資料來源
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView= LayoutInflater.from(context).inflate(R.layout.item_add_lv,null);
//初始化convertView當中的控制元件
TextView nameTv=convertView.findViewById(R.id.item_add_tv);
final ImageView iv=convertView.findViewById(R.id.item_add_iv);
//獲取指定位置的資料
final TypeBean typeBean=mDatas.get(position); //獲取到當前位置的資料來源
nameTv.setText(typeBean.getTitle());
//當isShow()設定為true的時候,對應的後面為對號,當isShow()為false的時候,對應的後面為加號,就是不選中
if (typeBean.isShow()){
iv.setImageResource(R.mipmap.subscribe_checked);
}else {
iv.setImageResource(R.mipmap.subscribe_unchecked);
}
//為了避免所有的選項都沒有選中ViewPager沒有東西可以顯示,預設前兩項是選中的
if (position == 0 || position == 1) {
iv.setVisibility(View.INVISIBLE);
}else {
iv.setVisibility(View.VISIBLE);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
typeBean.setShow(!typeBean.isShow()); //改變選中的狀態
if (typeBean.isShow()) {
iv.setImageResource(R.mipmap.subscribe_checked);
} else {
iv.setImageResource(R.mipmap.subscribe_unchecked);
}
}
});
}
return convertView;
}
}
接下來就在 AddItemActivity
中建立介面卡物件和設定介面卡:
//建立介面卡物件
adapter = new AddItemAdapter(this, mDatas);
//設定介面卡
addLv.setAdapter(adapter);
至此這個介面完成。
每次選中想要訂閱的頻道,想要在下次開啟app的時候還是保留上次選中的頻道,還需要把點選的內容進行提交。
onPause
是Activity中的一個生命週期,表示失去焦點時呼叫的方法,Activity一共有七個生命週期: onCreate
(建立了) 、 onStart
(啟動) 、 onResume
(獲取焦點)、 onPause
(失去焦點)、 onStop
(停止) 、 onDestroy
(銷燬)、 onRestart
(重新啟動)。
當一個Activity跳轉到另一個介面,該Activity就會處於先onPause(失去焦點),再onStop(停止) 的階段,並沒有銷燬,因為它依然在棧當中存在著,當返回到這個Activity介面之後,首先會執行onRestart(重新啟動),不會執行建立,再執行onStart(啟動)
所以 onRestart
(重新啟動)是失去焦點但是並沒有銷燬,重新獲得焦點之後所執行的生命週期。
這些生命週期都不需要我們自己呼叫,Android底層會根據Activity的狀態自動呼叫
所以這裡可以用生命週期的狀態來決定,這裡一旦Activity的失去焦點,說明它已經被銷燬(這裡就是被銷燬掉了,因為沒有做跳轉介面的操作),這裡可以將本次選中的內容進行保留、提交。
所以這裡可以寫一下對於資料修改的方法。
在 DBOpenHelper
中新增:
/*修改資料庫當中資訊的選中記錄*/
public static void updateTypeList(List<TypeBean>typeList){
for (int i = 0; i < typeList.size(); i++) {
TypeBean typeBean = typeList.get(i);
String title = typeBean.getTitle();
ContentValues values = new ContentValues();
values.put("isshow",String.valueOf(typeBean.isShow()));
database.update("itype",values,"title=?",new String[]{title}); //在主執行緒中直接修改資料庫(該資料庫資料量比較少可以這樣做)
}
}
之後在 AddItemActivity
中新增如下程式碼,當該頁面失去焦點時候,修改資料庫:
@Override
protected void onPause() {
super.onPause();
DBManager.updateTypeList(mDatas);
}
執行程式之前先把之前安裝的app解除安裝掉,因為資料庫只有在剛裝的時候才會執行onCreate方法,如果是更新的話就不再執行onCreate方法了,而是執行onUpdate方法。
效果如下:
這裡先不管ViewPager頁數是否改變,可以看到可以正常返回上一級頁面,在下次開啟app的時候還會是保留上次選中的頻道,說明 AddItemActivity
對於資料庫的操作是正確的。
ViewPager頁數的改變是獲取資料庫的資訊,下面介紹。