《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

时间:2021-08-18 17:58:58

  • 通过开发App熟悉Android Studio的用法
  • 开发一款用于管理备忘事项列表的App,核心功能:
    • 创建、删除备忘
    • 将某些备忘标记为重要(左侧带颜色标签突出显示)
  • 涉及:操作栏菜单、上下文菜单、用于持久化的本地数据库、支持多选的设备上的多项选择

启动新项目

File|New|New project命名为Reminders,选择Empty Activity

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

初始化Git仓库

安装、配置Git:https://www.cnblogs.com/hhhqqq/p/12273696.html

  • 创建Git仓库

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

然后选择项目根目录来创建Git仓库

  • 在Version Control工具窗口中右击Unversioned Files选择Add to VCS将这些文件添加到Git索引

  • 提交文件(Ctrl K|将项目的修改记录到Git版本控制系统的过程)

    《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

构建用户界面

visual designer布局

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

可视化设计器

将左侧PaletteLegacy中的ListView拖到编辑区域放置该控件,右侧Attributes修改该控件各项属性

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

编辑布局的原始XML

单击底部Text,编辑原始XML

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

更改背景颜色:

 android:background="#181818" //设置RelativeLayout背景颜色

在XML布局文件中硬编码颜色值并不是最佳方案,更好的方法:在values资源文件夹下定义colors.xml,在里面定义自己的颜色。这样便于编辑而且可以很容易地在整个项目中引用。

将代码修改为:

  android:background="@color/dark_grey"

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

在错误提示中选择第二项(创建颜色资源)

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

修改ListView列表项的显示方式

在reslayout文件夹中新建reminders_row,选择LinearLayout(LinearLayout是布局中的最外层元素)作为根ViewGroup来为单个列表项行创建布局,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
           android:layout_width="match_parent"
           android:layout_height="50dp"
           android:orientation="vertical"
           android:background="@color/dark_grey">

 <LinearLayout
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="48dp">

 <view
     android:layout_width="10dp"
     android:layout_height="match_parent"
     class="android.view.View"
     android:id="@ id/row_tab"
     android:background="@color/green"/>

 <TextView
     android:layout_width="match_parent"
     android:layout_height="50dp"
     android:text="Reminder Text"
     android:id="@ id/row_text"
     android:textColor="@color/white"
     android:textSize="18sp"
     android:gravity="center_vertical"
     android:padding="10dp"
     android:ellipsize="end"
     android:maxLines="1"/>
</LinearLayout>
 <view
     class="android.view.View"
     android:layout_width="fill_parent"
     android:layout_height="1dp"
     android:background="#000"/>
 <view
     class="android.view.View"
     android:layout_width="fill_parent"
     android:layout_height="1dp"
     android:background="#333"/>
</LinearLayout>

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

向ListView添加条目

修改相应的Activity文件(RemindersActivity.java),声明一个ListView成员、修改onCreate()方法

private ListView mListView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_reminders);
     mListView = (ListView)findViewById(R.id.reminders_list_view);
     ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
             this,   //  当前Activity的Context对象
             R.layout.reminders_row, //使用哪个布局
             R.id.row_text,     //布局中的哪个字段来显示数据
             new String[]{"first record","second record"}    //示例条目
     );
     mListView.setAdapter(arrayAdapter);
 }

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

添加操作栏溢出菜单

在res文件夹下新建menu文件夹,右键New|Menu resource file命名为menu_reminders

menu/menu_reminders.xml:

<?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/action_new"
        android:title="New Reminder"
        android:orderInCategory="100"
        app:showAsAction="never"/>
    <item android:id="@ id/action_exit"
        android:title="exit"
        android:orderInCategory="200"
        app:showAsAction="never"/>
</menu>

在RemindersActivity.java中添加创建菜单的方法和菜单的点击事件

/**
     *创建菜单
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_reminders,menu); //通过getMenuInflater()方法得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了,第一个参数:用于指定我们通过哪一个资源文件来创建菜单;第二个参数:用于指定我们的菜单项将添加到哪一个Menu对象当中。
        return true; // true:允许创建的菜单显示出来,false:创建的菜单将无法显示。
    }

    /**
     *菜单的点击事件
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()){
            case R.id.action_new:
                Log.d(getLocalClassName(),"create new Reminder");
                return true;
            case R.id.action_exit:
                finish();
                return true;
            default:
                return false;
        }
    }

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

持久化备忘录

  • SQLite数据库
  • 数据模型、数据库代理类、CursorAdapter
    • 数据模型:保存从数据库读取以及写入数据库的数据
    • 数据库代理类:适配器类,把来自App的简单调用转换为对SQLite数据库的API调用
    • CursorAdapter:继承以抽象方式处理数据访问的标准Android类

数据模型

创建数据模型

com.example.dell.reminders右键New Java Class命名为Reminder

Reminder类:

public class Reminder {
    private int mId;
    private String mContent;
    private int mImportant;

    public Reminder(int id, String content, int important) {
        mId = id;
        mContent = content;
        mImportant = important;
    }

    public int getId() {
        return mId;
    }

    public void setId(int id) {
        mId = id;
    }

    public String getContent() {
        return mContent;
    }

    public void setContent(String content) {
        mContent = content;
    }

    public int getImportant() {
        return mImportant;
    }

    public void setImportant(int important) {
        mImportant = important;
    }
}

创建数据库代理

com.example.dell.reminders右键New Java Class命名为RemindersDbAdapter

public class RemindersDbAdapter {
    //定义列名
    public static final String COL_ID = "_id";
    public static final String COL_CONTENT = "content";
    public static final String COL_IMPORTANT = "important";
    //定义索引值
    public static final int INDEX_ID = 0;
    public static final int INDEX_CONTENT = INDEX_ID   1;
    public static final int INDEX_IMPORTANT = INDEX_ID   2;
    //用于日志的TAG
    public static final String TAG = "RemindersDbAdapter";

    //两个数据库API对象
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    //数据库名称、主表名称、版本的常量
    public static final String DATABASE_NAME = "dba_remdrs";
    public static final String TABLE_NAME = "tbl_remdrs";
    public static final int DATABASE_VERSION = 1;

    //上下文对象
    private final Context mCtx;

    //用于创建数据库的SQL语句
    private static final String DATABASE_CREATE =
            "CREATE TABLE if not exists "   TABLE_NAME   " ( "  
                    COL_ID   "INTEGER PRIMARY KEY autoincrement, "  
                    COL_CONTENT   " TEXT, "  
                    COL_IMPORTANT   " INTEGER );";
}

SQLite API

DatabaseHelper:用于打开、关闭数据库的SQLite API类,是一个自定义的类,将其实现为RemindersDbAdapter的内部类

private static class DatabaseHelper extends SQLiteOpenHelper{
        //构造函数完成数据库初始化
        DatabaseHelper(Context context){
            //将数据库名和版本号传给超类,由超类完成建立数据库的繁重工作
            super(context,DATABASE_NAME,null,DATABASE_VERSION);
        }

        public void onCreate(SQLiteDatabase db){
            Log.w(TAG,DATABASE_CREATE);
            db.execSQL(DATABASE_CREATE);
        }

        public void onUpgrade(SQLiteDatabase db,int oldVersion, int newVersion){
            Log.w(TAG, "Upgrading database from version "   oldVersion   " to "   newVersion   ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS "   TABLE_NAME);
            onCreate(db);
        }
    }

使用DatabaseHelper打开和关闭数据库,RemindersDbAdapter构造函数保存了Context实例,并传给DatabaseHelper

public RemindersDbAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    public void open() throws SQLException{
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
    }

    public void close(){
        if(mDbHelper != null){
            mDbHelper.close();
        }
    }

数据库的增删改查:

创建方法使用特殊的ContentValues对象,用于将数据值传给数据库对象的insert方法,数据库会将这些对象转换为SQL insert语句并执行

//数据库的增删改查
    public void createReminder(String name, boolean important){
        ContentValues values = new ContentValues();
        values.put(COL_CONTENT, name);
        values.put(COL_IMPORTANT, important?1:0);
        mDb.insert(TABLE_NAME,null,values);
    }
    
    public long createReminder(Reminder reminder){
        ContentValues values = new ContentValues();
        values.put(COL_CONTENT,reminder.getContent());
        values.put(COL_IMPORTANT,reminder.getImportant());
        return mDb.insert(TABLE_NAME,null,values);
    }
    
    public Reminder fetchReminderById(int id){
        Cursor cursor = mDb.query(TABLE_NAME,new String[]{COL_ID,
                COL_CONTENT,COL_IMPORTANT},COL_ID   "=?",
                new String[]{String.valueOf(id)},null,null,null,null);
        if(cursor != null)
            cursor.moveToFirst();
            
            return new Reminder(
              cursor.getInt(INDEX_ID),
              cursor.getString(INDEX_CONTENT),
                    cursor.getInt(INDEX_IMPORTANT)
            );
    }
    
    public Cursor fetchAllReminders(){
        Cursor mCursor = mDb.query(TABLE_NAME,new String[]{COL_ID,
        COL_CONTENT,COL_IMPORTANT},null,null,null,null,null);
        
        if (mCursor != null){
            mCursor.moveToFirst();
        }
        
        return mCursor;
    }
    
    public void updateReminder(Reminder reminder){
        ContentValues values = new ContentValues();
        values.put(COL_CONTENT,reminder.getContent());
        values.put(COL_IMPORTANT,reminder.getImportant());
        mDb.update(TABLE_NAME,values,
                COL_ID "=?",new String[]{String.valueOf(reminder.getId())});
    }
    
    public void deleteReminderById(int nId){
        mDb.delete(TABLE_NAME,COL_ID "=?",new String[]{String.valueOf(nId)});
    }
    
    public void deleteAllReminders(){
        mDb.delete(TABLE_NAME,null,null);
    }

RemindersDbAdapter最终代码

public class RemindersDbAdapter {
    //定义列名
    public static final String COL_ID = "_id";
    public static final String COL_CONTENT = "content";
    public static final String COL_IMPORTANT = "important";
    //定义索引值
    public static final int INDEX_ID = 0;
    public static final int INDEX_CONTENT = INDEX_ID   1;
    public static final int INDEX_IMPORTANT = INDEX_ID   2;
    //用于日志的TAG
    public static final String TAG = "RemindersDbAdapter";

    //两个数据库API对象
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    //数据库名称、主表名称、版本的常量
    public static final String DATABASE_NAME = "dba_remdrs";
    public static final String TABLE_NAME = "tbl_remdrs";
    public static final int DATABASE_VERSION = 1;

    //上下文对象
    private final Context mCtx;

    //用于创建数据库的SQL语句
    private static final String DATABASE_CREATE =
            "CREATE TABLE if not exists "   TABLE_NAME   " ( "  
                    COL_ID   "INTEGER PRIMARY KEY autoincrement, "  
                    COL_CONTENT   " TEXT, "  
                    COL_IMPORTANT   " INTEGER );";

    public RemindersDbAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    public void open() throws SQLException{
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
    }

    public void close(){
        if(mDbHelper != null){
            mDbHelper.close();
        }
    }

    //数据库的增删改查
    public void createReminder(String name, boolean important){
        ContentValues values = new ContentValues();
        values.put(COL_CONTENT, name);
        values.put(COL_IMPORTANT, important?1:0);
        mDb.insert(TABLE_NAME,null,values);
    }

    public long createReminder(Reminder reminder){
        ContentValues values = new ContentValues();
        values.put(COL_CONTENT,reminder.getContent());
        values.put(COL_IMPORTANT,reminder.getImportant());
        return mDb.insert(TABLE_NAME,null,values);
    }

    public Reminder fetchReminderById(int id){
        Cursor cursor = mDb.query(TABLE_NAME,new String[]{COL_ID,
                COL_CONTENT,COL_IMPORTANT},COL_ID   "=?",
                new String[]{String.valueOf(id)},null,null,null,null);
        if(cursor != null)
            cursor.moveToFirst();

            return new Reminder(
              cursor.getInt(INDEX_ID),
              cursor.getString(INDEX_CONTENT),
                    cursor.getInt(INDEX_IMPORTANT)
            );
    }

    public Cursor fetchAllReminders(){
        Cursor mCursor = mDb.query(TABLE_NAME,new String[]{COL_ID,
        COL_CONTENT,COL_IMPORTANT},null,null,null,null,null);

        if (mCursor != null){
            mCursor.moveToFirst();
        }

        return mCursor;
    }

    public void updateReminder(Reminder reminder){
        ContentValues values = new ContentValues();
        values.put(COL_CONTENT,reminder.getContent());
        values.put(COL_IMPORTANT,reminder.getImportant());
        mDb.update(TABLE_NAME,values,
                COL_ID "=?",new String[]{String.valueOf(reminder.getId())});
    }

    public void deleteReminderById(int nId){
        mDb.delete(TABLE_NAME,COL_ID "=?",new String[]{String.valueOf(nId)});
    }

    public void deleteAllReminders(){
        mDb.delete(TABLE_NAME,null,null);
    }
    
    private static class DatabaseHelper extends SQLiteOpenHelper{
        //构造函数完成数据库初始化
        DatabaseHelper(Context context){
            //将数据库名和版本号传给超类,由超类完成建立数据库的繁重工作
            super(context,DATABASE_NAME,null,DATABASE_VERSION);
        }

        public void onCreate(SQLiteDatabase db){
            Log.w(TAG,DATABASE_CREATE);
            db.execSQL(DATABASE_CREATE);
        }

        public void onUpgrade(SQLiteDatabase db,int oldVersion, int newVersion){
            Log.w(TAG, "Upgrading database from version "   oldVersion   " to "   newVersion   ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS "   TABLE_NAME);
            onCreate(db);
        }
    }
}

CursorAdapter

最后,需要一种从数据库获取备忘并加入到ListView中的方法,新建java类RemindersSimpleCursorAdapter

public class RemindersSimpleCursorAdapter extends SimpleCursorAdapter {
    public RemindersSimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
        super(context, layout, c, from, to, flags);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        return super.newView(context, cursor, parent);
    }


    //ListView会利用屏幕上的单个View对象反复调用此方法,Adapter的职责就是使用列表项来填充这些视图
    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        super.bindView(view, context, cursor);//调用超类方法,通过游标cursor获取到的值映射到View中的元素

        ViewHolder holder = (ViewHolder)view.getTag();
        if(holder == null){ //检查holder是否绑定到了标签
            holder = new ViewHolder();
            holder.colImp = cursor.getColumnIndexOrThrow(RemindersDbAdapter.COL_IMPORTANT);
            holder.listTab = view.findViewById(R.id.row_tab);
            view.setTag(holder);
        }

        //使用当前备忘COL_IMPORTANT常量对应的值来决定颜色1:重要 0:次要
        if(cursor.getInt(holder.colImp) > 0){
            holder.listTab.setBackgroundColor(context.getResources().getColor(R.color.orange));
        }
        else{
            holder.listTab.setBackgroundColor(context.getResources().getColor(R.color.green));
        }
    }

    //静态内部类
    static class ViewHolder{
        int colImp; //Important表列的索引
        View listTab;   //在布局中定义的row_tab视图
    }
}

调整ReminderActivity

public class RemindersActivity extends AppCompatActivity {

    private ListView mListView;
    private RemindersDbAdapter mDbAdapter;
    private RemindersSimpleCursorAdapter mCursorAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reminders);
        mListView = (ListView)findViewById(R.id.reminders_list_view);
        mListView.setDivider(null);
        mDbAdapter = new RemindersDbAdapter(this);
        mDbAdapter.open();

        Cursor cursor = mDbAdapter.fetchAllReminders();

        String[] from = new String[]{RemindersDbAdapter.COL_CONTENT};

        int[] to = new int[]{R.id.row_text};

        mCursorAdapter = new RemindersSimpleCursorAdapter(
                RemindersActivity.this,
                R.layout.reminders_row,
                cursor,
                from,
                to,
                0
        );
        mListView.setAdapter(mCursorAdapter);
    }
}

调整完运行app,将不会在列表中看到任何内容,因为最后的修改插入的是SQLite功能而非示例数据

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)

Ctrl K提交备忘录实验1的最后一次修改

《Android Studio实战 快速、高效地构建Android应用》--五、备忘录实验(1/2)