1. 程式人生 > 實用技巧 >Android實戰開發——News

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 中的資料來源應該是之前寫的 TypeBeanTypeBean 中就封裝了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頁數的改變是獲取資料庫的資訊,下面介紹。

頁面詳細資訊邏輯編寫