美文网首页
CrimainalIntent项目的开发

CrimainalIntent项目的开发

作者: 老柒老八 | 来源:发表于2017-10-14 15:35 被阅读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,自己也找了很久,还是不能解决问题,也问了老师,但还是没能解决,不知道是哪里出了错。整个项目梳理的还算可以,就是运行不了让人头疼。

    相关文章

      网友评论

          本文标题:CrimainalIntent项目的开发

          本文链接:https://www.haomeiwen.com/subject/qdbxuxtx.html