CrimainalIntent项目的开发

2017-10-14  本文已影响0人  老柒老八

介绍:CriminalIntent应用能记录陋习的标题,日期以及照片,也支持在联系人当中查找当事人,通过E-mail,Twitter,FaceBook或其他应用提出抗议。本博客是在上一个博客的基础上进行的延伸。

  • 第12章:日期对话框,fragment数据传递;
  • 第13章:工具栏,菜单,层级式导航;
  • 第14章:SQLite数据库的使用;

项目完成步骤:

  • 添加对话框
  • 创建工具栏菜单
  • 连接数据库

对话框

为应用添加对话框,以便用户修改crime记录日期。用户点击
CrimeFragment中的日期按钮时,会弹出对话框:

图12-1 可选择crime日期的对话框

创建DialogFragment

首先会创建名为DatePickerFragment的DialogFragment子类。然后,在DatePickerFragment中,创建并配置显示DatePicker组件的AlertDialog实例。DatePickerFragment同样由CrimePagerActivity托管。图12-2展示了以上各对象间的关系。

图12-2 由CrimePagerActivity托管的两个fragment对象

要显示对话框,首先应完成以下任务:
 创建DatePickerFragment类;
 创建AlertDialog;
 借助FragmentManager在屏幕上显示对话框。
稍后,我们还将配置使用DatePicker,并实现在CrimeFragment和DatePickerFragment之间传递数据。

创建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应用,点击日期按钮弹出对话框:

图12-3 带标题和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应用,点击日期按钮,在对话框中显示DatePicker视图:


图12-4 DatePicker视图

fragment间的数据传递

图12-5CrimeFragment与DatePickerFragment间的对话

传递数据给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应用创建工具栏菜单,提供新增crime记录的菜单项,并实现向上按钮的导航功能:

图13-1 CriminalIntent应用的工具栏

使用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> 

创建菜单资源

<?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> 

创建菜单

在代码中,Activity类提供了管理菜单的回调函数。需要选项菜单时,Android会调用Activity的onCreateOptionsMenu(Menu)方法。然而,按照CriminalIntent应用的设计,选项菜单相关的回调函数需在fragment而非activity里实现。不用担心,Fragment有一套自己的选项菜单回调函数。稍后,我们会在CrimeListFragment中实现这些方法。以下为创建菜单和响应菜单项选择事件的两个回调方法:
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); 
}

在以上方法中,调用MenuInflater.inflate(int, Menu)方法并传入菜单文件的资源ID,将布局文件中定义的菜单项目填充到Menu实例中。

响应菜单项选择事件

    @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);
        }
    }

实现层级式导航

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

SQLite数据库

创建数据库前,首先要清楚存储什么样的数据。CriminalIntent应用要保存的是一条条crime记录,定义Schema的方式众多,如何选择往往因人而异。处理类似的任务,开发人员都有个共同的目标:“不要重复造轮子。”实际上,这也是人人都应遵守的编程准则:多花时间思考复用代码的编写和调用,避免在应用中到处使用重复代码。
基于上述准则,我们可以使用能统一定义模型层对象(如Crime)的高级ORM(对象关系映射)工具。不过,对于CriminalIntent应用,本章打算直接在Java代码中定义描述表名和数据字段的数据库schema。
首先,我们来创建定义schema的Java类。创建时,命名类为CrimeDbSchema,同时在新建类对话框中输入包名database.CrimeDbSchema。这样,就可以将CrimeDbSchema.java文件放入专门的database包中,实现数据库操作相关代码的组织和归类。

定义CrimeTable内部类

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

定义数据表字段

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";
         }
     } 
} 

创建初始数据库

步骤:
(1) 确认目标数据库是否存在。
(2) 如果不存在,首先创建数据库,然后创建数据库表以及必需的初始化数据。
(3) 如果存在,打开并确认CrimeDbSchema是否是最新版本(后续章节可能会在CriminalIntent
中有增删操作)。
(4) 如果是旧版本,就运行相关代码升级到最新版本。

创建CrimeBaseHelper类编写SQL创建初始代码

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<>(); 
     } 
     ... 
} 

调用getWritableDatabase()方法时,CrimeBaseHelper要做如下工作。
(1)
打开/data/data/com.bignerdranch.android.criminalintent/databases/crimeBase.db数据库;如果不存在,就先创建crimeBase.db数据库文件。
(2) 如果是首次创建数据库,就调用onCreate(SQLiteDatabase)方法,然后保存最新的版本号。
(3) 如果已创建过数据库,首先检查它的版本号。如果CrimeOpenHelper中的版本号更高,就调用onUpgrade(SQLiteDatabase, int, int)方法升级。
最后,再做个总结:onCreate(SQLiteDatabase)方法负责创建初始数据库;onUpgrade (SQLiteDatabase, int, int)方法负责与升级相关的工作。

修改 CrimeLab 类

创建完数据库,接下来是调整CrimeLab类代码,改用mDatabase存储数据。
首先要删除CrimeLab中所有mCrimes相关的代码,代码调整完毕,运行CriminalIntent应用只会看到空列表和空CrimePagerActivity。

写入数据库

  • 使用 ContentValues
  • 插入和更新记录

读取数据库

  • 使用 CursorWrapper
  • 创建模型层对象

总结

编写代码过程中难免遇到单词拼写错误的情况,只要细心检查,错误都是可以解决的。但是遇到手机与电脑连接不起来、虚拟机开启失败等问题,就很难自己解决了。虚拟机时而运行成功时而运行失败,手机是直接安装不了APP,自己也找了很久,还是不能解决问题,也问了老师,但还是没能解决,不知道是哪里出了错。整个项目梳理的还算可以,就是运行不了让人头疼。

上一篇下一篇

猜你喜欢

热点阅读