CrimainalIntent項目的開發(fā)

介紹:CriminalIntent應(yīng)用能記錄陋習(xí)的標(biāo)題,日期以及照片,也支持在聯(lián)系人當(dāng)中查找當(dāng)事人,通過E-mail,Twitter,FaceBook或其他應(yīng)用提出抗議。本博客是在上一個博客的基礎(chǔ)上進行的延伸。

  • 第12章:日期對話框,fragment數(shù)據(jù)傳遞;
  • 第13章:工具欄,菜單,層級式導(dǎo)航;
  • 第14章:SQLite數(shù)據(jù)庫的使用;

項目完成步驟:

  • 添加對話框
  • 創(chuàng)建工具欄菜單
  • 連接數(shù)據(jù)庫

對話框

為應(yīng)用添加對話框,以便用戶修改crime記錄日期。用戶點擊
CrimeFragment中的日期按鈕時,會彈出對話框:

圖12-1 可選擇crime日期的對話框

創(chuàng)建DialogFragment

首先會創(chuàng)建名為DatePickerFragment的DialogFragment子類。然后,在DatePickerFragment中,創(chuàng)建并配置顯示DatePicker組件的AlertDialog實例。DatePickerFragment同樣由CrimePagerActivity托管。圖12-2展示了以上各對象間的關(guān)系。

圖12-2 由CrimePagerActivity托管的兩個fragment對象

要顯示對話框,首先應(yīng)完成以下任務(wù):
? 創(chuàng)建DatePickerFragment類;
? 創(chuàng)建AlertDialog;
? 借助FragmentManager在屏幕上顯示對話框。
稍后,我們還將配置使用DatePicker,并實現(xiàn)在CrimeFragment和DatePickerFragment之間傳遞數(shù)據(jù)。

創(chuàng)建DialogFragment

    public class DatePickerFragment extends DialogFragment {
          @Override
          public Dialog onCreateDialog(Bundle saveInstanceState){
                return new AlertDialog.Builder(getActivity())
                         .setTitle(R.string.date_picker_title)
                         .setPositiveButton(android.R.string.ok, null)
                         .create();
          }
    }

顯示DialogFragment

   public class CrimeFragment extends Fragment { 
    private static final String ARG_CRIME_ID = "crime_id"; 
    private static final String DIALOG_DATE = "DialogDate"; 
    ... 
    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  Bundle savedInstanceState) { 
    ... 
    mDateButton = (Button) v.findViewById(R.id.crime_date); 
    mDateButton.setText(mCrime.getDate().toString()); 
    //mDateButton.setEnabled(false);
    mDateButton.setOnClickListener(new View.OnClickListener() { 
         @Override 
         public void onClick(View v) { 
             FragmentManager manager = getFragmentManager(); 
             DatePickerFragment dialog = new DatePickerFragment(); 
             dialog.show(manager, DIALOG_DATE); 
         } 
     }); 
     mSolvedCheckBox = (CheckBox) v.findViewById(R.id.crime_solved); 
     ... 
     return v; 
     } 
     ...
}

運行CriminalIntent應(yīng)用,點擊日期按鈕彈出對話框:

圖12-3 帶標(biāo)題和OK按鈕的AlertDialog

給AlertDialog添加DatePicker

     @Override 
     public Dialog onCreateDialog(Bundle savedInstanceState) { 
           View v = LayoutInflater.from(getActivity()) 
                .inflate(R.layout.dialog_date, null); 
           return new AlertDialog.Builder(getActivity()) 
                .setView(v) 
                .setTitle(R.string.date_picker_title) 
                .setPositiveButton(android.R.string.ok, null) 
                .create(); 
} 

運行CriminalIntent應(yīng)用,點擊日期按鈕,在對話框中顯示DatePicker視圖:


圖12-4 DatePicker視圖

fragment間的數(shù)據(jù)傳遞

圖12-5CrimeFragment與DatePickerFragment間的對話

傳遞數(shù)據(jù)給DatePickerFragment

public class DatePickerFragment extends DialogFragment {
    public static final String EXTRA_DATE =
            "com.bignerdranch.android.criminalintent.date";

    private static final String ARG_DATE = "date";
    private DatePicker mDatePicker;

    public static DatePickerFragment newInstance(Date date){
        Bundle args = new Bundle();
        args.putSerializable(ARG_DATE,date);

        DatePickerFragment fragment = new DatePickerFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public Dialog onCreateDialog(Bundle saveInstanceState){
        Date date = (Date) getArguments().getSerializable(ARG_DATE);

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);

        View v = LayoutInflater.from(getActivity())
                .inflate(R.layout.dialog_date,null);

        mDatePicker = (DatePicker) v.findViewById(R.id.dialog_date_picker);
        mDatePicker.init(year,month,day,null);

        return new AlertDialog.Builder(getActivity())
                .setView(v)
                .setTitle(R.string.date_picker_title)
                //.setPositiveButton(android.R.string.ok,null)
                .setPositiveButton(android.R.string.ok,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                int year = mDatePicker.getYear();
                                int month = mDatePicker.getMonth();
                                int day = mDatePicker.getDayOfMonth();
                                Date date = new GregorianCalendar(year,month,day).getTime();
                                sendResult(Activity.RESULT_OK,date);
                            }
                        })
                .create();
    }

    private void sendResult(int resultCode,Date date){
        if(getTargetFragment()==null){
            return;
        }
        Intent intent = new Intent();
        intent.putExtra(EXTRA_DATE,date);

        getTargetFragment()
                .onActivityResult(getTargetRequestCode(),resultCode,intent);
    }

}
public class CrimeFragment extends Fragment {
    public static CrimeFragment newInstance(UUID crimeId) {
        Bundle args = new Bundle();
        args.putSerializable(ARG_CRIME_ID, crimeId);

        CrimeFragment fragment = new CrimeFragment();
        fragment.setArguments(args);
        return fragment;
    }
  ...
    @Override
    public void onActivityResult(int requestCode,int resultCode,Intent data){
        if(resultCode != Activity.RESULT_OK){
            return;
        }
        if(requestCode == REQUEST_DATE){
            Date date = (Date) data.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
            mCrime.setDate(date);
            updateDate();
        }
    }
  ...
    private void updateDate() {
        mDateButton.setText(mCrime.getDate().toString());
    }
}

工具欄

為CriminalIntent應(yīng)用創(chuàng)建工具欄菜單,提供新增crime記錄的菜單項,并實現(xiàn)向上按鈕的導(dǎo)航功能:

圖13-1 CriminalIntent應(yīng)用的工具欄

使用AppCompat庫

? 添加AppCompat依賴項;
? 使用一種AppCompat主題;
? 確保所有的activitiy都是AppCompatActivity子類。

使用AppCompat主題

<resources> 
   <style name="AppTheme"parent="Theme.AppCompat.Light.DarkActionBar"> 
   </style> 
</resources> 

工具欄菜單

菜單及菜單項需用到一些字符串資源。將它們添加到strings.xml文件中。

<resources> 
 ... 
   <string name="date_picker_title">Date of crime:</string> 
   <string name="new_crime">New Crime</string> 
   <string name="show_subtitle">Show Subtitle</string> 
   <string name="hide_subtitle">Hide Subtitle</string> 
   <string name="subtitle_format">%1$s crimes</string> 
</resources> 

創(chuàng)建菜單資源

<?xml version="1.0" encoding="utf-8"?> 
<menu xmlns:android="http://schemas.android.com/apk/res/android" 
   xmlns:app="http://schemas.android.com/apk/res-auto"> 
   <item 
     android:id="@+id/menu_item_new_crime" 
     android:icon="@android:drawable/ic_menu_add" 
     android:title="@string/new_crime" 
     app:showAsAction="ifRoom|withText"/> 
</menu> 

創(chuàng)建菜單

在代碼中,Activity類提供了管理菜單的回調(diào)函數(shù)。需要選項菜單時,Android會調(diào)用Activity的onCreateOptionsMenu(Menu)方法。然而,按照CriminalIntent應(yīng)用的設(shè)計,選項菜單相關(guān)的回調(diào)函數(shù)需在fragment而非activity里實現(xiàn)。不用擔(dān)心,F(xiàn)ragment有一套自己的選項菜單回調(diào)函數(shù)。稍后,我們會在CrimeListFragment中實現(xiàn)這些方法。以下為創(chuàng)建菜單和響應(yīng)菜單項選擇事件的兩個回調(diào)方法:
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
public boolean onOptionsItemSelected(MenuItem item)
在CrimeListFragment.java中,覆蓋onCreateOptionsMenu(Menu, MenuInflater)方法,實例化fragment_crime_list.xml中定義的菜單。

實例化選項菜單

@Override 
public void onResume() { 
 super.onResume(); 
 updateUI(); 
} 
@Override 
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
 super.onCreateOptionsMenu(menu, inflater); 
 inflater.inflate(R.menu.fragment_crime_list, menu); 
}

在以上方法中,調(diào)用MenuInflater.inflate(int, Menu)方法并傳入菜單文件的資源ID,將布局文件中定義的菜單項目填充到Menu實例中。

響應(yīng)菜單項選擇事件

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
        super.onCreateOptionsMenu(menu,inflater);
        inflater.inflate(R.menu.fragment_crime_list,menu);
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item){
        switch (item.getItemId()){
            case R.id.new_crime:
                Crime crime = new Crime();
                CrimeLab.get(getActivity()).addCrime(crime);
                Intent intent = CrimePagerActivity
                        .newIntent(getActivity(),crime.getId());
                startActivity(intent);
                return true;
             default:
                return super.onOptionsItemSelected(item);
        }
    }

實現(xiàn)層級式導(dǎo)航

... 
<activity 
   android:name=".CrimePagerActivity" 
   android:label="@string/app_name" 
   android:parentActivityName=".CrimeListActivity"> 
</activity> 
... 
圖13-2 CrimePagerActivity界面上的向上按鈕

SQLite數(shù)據(jù)庫

創(chuàng)建數(shù)據(jù)庫前,首先要清楚存儲什么樣的數(shù)據(jù)。CriminalIntent應(yīng)用要保存的是一條條crime記錄,定義Schema的方式眾多,如何選擇往往因人而異。處理類似的任務(wù),開發(fā)人員都有個共同的目標(biāo):“不要重復(fù)造輪子?!睂嶋H上,這也是人人都應(yīng)遵守的編程準(zhǔn)則:多花時間思考復(fù)用代碼的編寫和調(diào)用,避免在應(yīng)用中到處使用重復(fù)代碼。
基于上述準(zhǔn)則,我們可以使用能統(tǒng)一定義模型層對象(如Crime)的高級ORM(對象關(guān)系映射)工具。不過,對于CriminalIntent應(yīng)用,本章打算直接在Java代碼中定義描述表名和數(shù)據(jù)字段的數(shù)據(jù)庫schema。
首先,我們來創(chuàng)建定義schema的Java類。創(chuàng)建時,命名類為CrimeDbSchema,同時在新建類對話框中輸入包名database.CrimeDbSchema。這樣,就可以將CrimeDbSchema.java文件放入專門的database包中,實現(xiàn)數(shù)據(jù)庫操作相關(guān)代碼的組織和歸類。

定義CrimeTable內(nèi)部類

public class CrimeDbSchema { 
   public static final class CrimeTable { 
       public static final String NAME = "crimes"; 
   } 
} 

定義數(shù)據(jù)表字段

public class CrimeDbSchema { 
     public static final class CrimeTable { 
         public static final String NAME = "crimes"; 
         public static final class Cols {
             public static final String UUID = "uuid";
             public static final String TITLE = "title";
             public static final String DATE = "date";
             public static final String SOLVED = "solved";
         }
     } 
} 

創(chuàng)建初始數(shù)據(jù)庫

步驟:
(1) 確認(rèn)目標(biāo)數(shù)據(jù)庫是否存在。
(2) 如果不存在,首先創(chuàng)建數(shù)據(jù)庫,然后創(chuàng)建數(shù)據(jù)庫表以及必需的初始化數(shù)據(jù)。
(3) 如果存在,打開并確認(rèn)CrimeDbSchema是否是最新版本(后續(xù)章節(jié)可能會在CriminalIntent
中有增刪操作)。
(4) 如果是舊版本,就運行相關(guān)代碼升級到最新版本。

創(chuàng)建CrimeBaseHelper類編寫SQL創(chuàng)建初始代碼

public class CrimeBaseHelper extends SQLiteOpenHelper {
    private static final int VERSION = 1;
    private static final String DATABASE_NAME = "crimeBase.db";

    public CrimeBaseHelper(Context context){
        super(context,DATABASE_NAME,null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table " + CrimeDBSchema.CrimeTable.NAME); 
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

打開SQLiteDatabase

public class CrimeLab { 
     private static CrimeLab sCrimeLab; 
     private List<Crime> mCrimes; 
     private Context mContext; 
     private SQLiteDatabase mDatabase; 
     ... 
     private CrimeLab(Context context) { 
         mContext = context.getApplicationContext();
         mDatabase = new CrimeBaseHelper(mContext)
             .getWritableDatabase();
         mCrimes = new ArrayList<>(); 
     } 
     ... 
} 

調(diào)用getWritableDatabase()方法時,CrimeBaseHelper要做如下工作。
(1)
打開/data/data/com.bignerdranch.android.criminalintent/databases/crimeBase.db數(shù)據(jù)庫;如果不存在,就先創(chuàng)建crimeBase.db數(shù)據(jù)庫文件。
(2) 如果是首次創(chuàng)建數(shù)據(jù)庫,就調(diào)用onCreate(SQLiteDatabase)方法,然后保存最新的版本號。
(3) 如果已創(chuàng)建過數(shù)據(jù)庫,首先檢查它的版本號。如果CrimeOpenHelper中的版本號更高,就調(diào)用onUpgrade(SQLiteDatabase, int, int)方法升級。
最后,再做個總結(jié):onCreate(SQLiteDatabase)方法負(fù)責(zé)創(chuàng)建初始數(shù)據(jù)庫;onUpgrade (SQLiteDatabase, int, int)方法負(fù)責(zé)與升級相關(guān)的工作。

修改 CrimeLab 類

創(chuàng)建完數(shù)據(jù)庫,接下來是調(diào)整CrimeLab類代碼,改用mDatabase存儲數(shù)據(jù)。
首先要刪除CrimeLab中所有mCrimes相關(guān)的代碼,代碼調(diào)整完畢,運行CriminalIntent應(yīng)用只會看到空列表和空CrimePagerActivity。

寫入數(shù)據(jù)庫

  • 使用 ContentValues
  • 插入和更新記錄

讀取數(shù)據(jù)庫

  • 使用 CursorWrapper
  • 創(chuàng)建模型層對象

總結(jié)

編寫代碼過程中難免遇到單詞拼寫錯誤的情況,只要細(xì)心檢查,錯誤都是可以解決的。但是遇到手機與電腦連接不起來、虛擬機開啟失敗等問題,就很難自己解決了。虛擬機時而運行成功時而運行失敗,手機是直接安裝不了APP,自己也找了很久,還是不能解決問題,也問了老師,但還是沒能解決,不知道是哪里出了錯。整個項目梳理的還算可以,就是運行不了讓人頭疼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,213評論 4 61
  • 介紹:CriminalIntent應(yīng)用能記錄陋習(xí)的標(biāo)題,日期以及照片,也支持在聯(lián)系人當(dāng)中查找當(dāng)事人,通過E-mai...
    19377b7fb478閱讀 362評論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,983評論 25 709
  • 1慢吧慢吧 拖著那回憶的時光 讓這三行 無限的長 2在我的心上 那貧瘠的土壤 那曾是你深深扎根的地方 3散吧 散吧...
    思嘉閱讀 349評論 0 1
  • 測試,好像是現(xiàn)在年輕人的一種潮流,聰明的人想測智商,懂人情世故的人想測情商,單身的人想測戀愛值期盼自己的另...
    鈺子閱讀 1,139評論 0 1

友情鏈接更多精彩內(nèi)容